// This file is part of arduino-cli.
//
// Copyright 2020 ARDUINO SA (http://www.arduino.cc/)
//
// This software is released under the GNU General Public License version 3,
// which covers the main part of arduino-cli.
// The terms of this license can be found at:
// https://www.gnu.org/licenses/gpl-3.0.en.html
//
// You can be released from the requirements of the above licenses by purchasing
// a commercial license. Buying such a license is mandatory if you want to
// modify or otherwise use the software for commercial activities involving the
// Arduino software without disclosing the source code of your own applications.
// To purchase a commercial license, send an email to license@arduino.cc.

package builder

import (
	"bufio"
	"strconv"
	"strings"

	"github.com/arduino/go-paths-helper"

	"github.com/arduino/arduino-cli/legacy/builder/types"
	"github.com/arduino/arduino-cli/legacy/builder/utils"
)

type FilterSketchSource struct {
	Source            *string
	RemoveLineMarkers bool
}

func (s *FilterSketchSource) Run(ctx *types.Context) error {
	fileNames := paths.NewPathList()
	fileNames.Add(ctx.Sketch.MainFile.Name)
	for _, file := range ctx.Sketch.OtherSketchFiles {
		fileNames = append(fileNames, file.Name)
	}

	inSketch := false
	filtered := ""

	scanner := bufio.NewScanner(strings.NewReader(*s.Source))
	for scanner.Scan() {
		line := scanner.Text()
		if filename := parseLineMarker(line); filename != nil {
			inSketch = fileNames.Contains(filename)
			if inSketch && s.RemoveLineMarkers {
				continue
			}
		}

		if inSketch {
			filtered += line + "\n"
		}
	}

	*s.Source = filtered
	return nil
}

// Parses the given line as a gcc line marker and returns the contained
// filename.
func parseLineMarker(line string) *paths.Path {
	// A line marker contains the line number and filename and looks like:
	// # 123 /path/to/file.cpp
	// It can be followed by zero or more flag number that indicate the
	// preprocessor state and can be ignored.
	// For exact details on this format, see:
	// https://github.com/gcc-mirror/gcc/blob/edd716b6b1caa1a5cb320a8cd7f626f30198e098/gcc/c-family/c-ppoutput.c#L413-L415

	split := strings.SplitN(line, " ", 3)
	if len(split) < 3 || len(split[0]) == 0 || split[0][0] != '#' {
		return nil
	}

	_, err := strconv.Atoi(split[1])
	if err != nil {
		return nil
	}

	// If we get here, we found a # followed by a line number, so
	// assume this is a line marker and see if the rest of the line
	// starts with a string containing the filename
	str, rest, ok := utils.ParseCppString(split[2])

	if ok && (rest == "" || rest[0] == ' ') {
		return paths.New(str)
	}
	return nil
}
