package pkglint

import (
	"netbsd.org/pkglint/textproc"
	"strings"
)

func CheckFileAlternatives(filename CurrPath, pkg *Package) {
	lines := Load(filename, NotEmpty|LogErrors)
	if lines == nil {
		return
	}

	var ck AlternativesChecker
	ck.Check(lines, pkg)
}

type AlternativesChecker struct{}

func (ck *AlternativesChecker) Check(lines *Lines, pkg *Package) {
	var plistFiles map[RelPath]*PlistLine
	if pkg != nil {
		plistFiles = pkg.Plist.Files
	}

	for _, line := range lines.Lines {
		ck.checkLine(line, plistFiles, pkg)
	}
}

// checkLine checks a single line for the following format:
//  wrapper alternative [arguments]
func (ck *AlternativesChecker) checkLine(line *Line, plistFiles map[RelPath]*PlistLine, pkg *Package) {
	m, wrapper, space, alternative := match3(line.Text, `^([^\t ]+)([ \t]+)([^\t ]+).*$`)
	if !m {
		line.Errorf("Invalid line %q.", line.Text)
		line.Explain(
			sprintf("Run %q for more information.", bmakeHelp("alternatives")))
		return
	}

	if wrapper := NewPath(wrapper); wrapper.IsAbs() {
		line.Errorf("Alternative wrapper %q must be relative to PREFIX.", wrapper.String())
	} else {
		wrapper := NewRelPath(wrapper)
		if plistFiles[wrapper] != nil {
			line.Errorf("Alternative wrapper %q must not appear in the PLIST.", wrapper)
		}
		if !wrapper.HasPrefixText("bin/") &&
			!wrapper.HasPrefixText("sbin/") {
			line.Errorf("Alternative wrapper %q must be in "+
				"\"bin\" or \"sbin\".", wrapper)
		}
	}

	if plistFiles != nil {
		ck.checkAlternativePlist(line, alternative, plistFiles, pkg)
	}

	ck.checkAlternativeAbs(alternative, line, space)

	LineChecker{line}.CheckTrailingWhitespace()
}

func (ck *AlternativesChecker) checkAlternativeAbs(alternative string, line *Line, space string) {
	lex := textproc.NewLexer(alternative)

	if lex.SkipByte('/') || lex.SkipByte('@') {
		return
	}

	fix := line.Autofix()
	fix.Errorf("Alternative implementation %q must be an absolute path.", alternative)
	fix.Explain(
		"It usually starts with @PREFIX@/... to refer to a path inside the installation prefix.")
	if lex.TestByteSet(textproc.Alnum) {
		fix.ReplaceAfter(space, alternative, "@PREFIX@/"+alternative)
	}
	fix.Apply()
}

func (ck *AlternativesChecker) checkAlternativePlist(line *Line, alternative string,
	plistFiles map[RelPath]*PlistLine, pkg *Package) {

	relImplementation := strings.Replace(alternative, "@PREFIX@/", "", 1)
	plistName := replaceAll(relImplementation, `@(\w+)@`, "${$1}")
	if NewPath(plistName).IsAbs() {
		// It's possible but unusual to refer to a fixed absolute path.
		// These cannot be mentioned in the PLIST since they are not part of the package.
		return
	}

	rel := NewRelPathString(plistName)
	if plistFiles[rel] != nil || pkg.vars.IsDefined("ALTERNATIVES_SRC") {
		return
	}

	if plistName == alternative {
		line.Errorf("Alternative implementation %q must appear in the PLIST.",
			alternative)
	} else {
		line.Errorf("Alternative implementation %q must appear in the PLIST as %q.",
			alternative, plistName)
	}
}
