package linter

import (
	"fmt"
	"io"
	"log"
	"os"
	"sort"

	"alpinelinux.org/go/atools/apkbuild"
)

type Severity string

const (
	Serious   Severity = "S"
	Important Severity = "I"
	Minor     Severity = "M"
	Style     Severity = "T"
)

type Certainty string

const (
	Certain  Certainty = "C"
	Possible Certainty = "P"
)

type LintingViolation struct {
	Code        string
	Severity    Severity
	Certainty   Certainty
	Description string
	Skip        string
	Details     []string
	Filename    string
	Line        uint
	Column      uint
}

type Linter = func(*apkbuild.Apkbuild, func(string) string) []LintingViolation

type linterEntry struct {
	lintingViolation *LintingViolation
	check            Linter
}

var linters []*linterEntry

func Check(reader io.Reader, filename string) (violations []LintingViolation) {
	apkbuild, err := apkbuild.Parse(reader, filename)

	if err != nil {
		log.Fatal(err)
	}

	for _, linter := range linters {
		var skip bool = false

		skipVars := []string{
			fmt.Sprintf("SKIP_%s", linter.lintingViolation.Code),
			fmt.Sprintf("SKIP_%s", linter.lintingViolation.Skip),
		}

		for _, entry := range skipVars {
			if os.Getenv(entry) != "" {
				skip = true
				break
			}
		}
		if !skip {
			violations = append(violations, linter.check(apkbuild, os.Getenv)...)
		}
	}

	return
}

func Register(violation *LintingViolation, check Linter) {
	linters = append(linters, &linterEntry{
		lintingViolation: violation,
		check:            check,
	})
}

func (lv LintingViolation) Instantiate(filename string, line uint, column uint, details ...string) (instantiated LintingViolation) {
	instantiated = lv
	instantiated.Filename = filename
	instantiated.Line = line
	instantiated.Column = column
	instantiated.Details = details
	return
}

func (lv *LintingViolation) Is(other LintingViolation) bool {
	return lv.Code == other.Code
}

func (lv LintingViolation) String() string {
	details := make([]interface{}, len(lv.Details))
	for i, d := range lv.Details {
		details[i] = d
	}
	return fmt.Sprintf(
		"%s%s[%s]:%s:%d:%d:%s",
		lv.Severity,
		lv.Certainty,
		lv.Code,
		lv.Filename,
		lv.Line,
		lv.Column,
		fmt.Sprintf(lv.Description, details...),
	)
}

func SortViolationsByLine(vl []LintingViolation) []LintingViolation {
	sort.Slice(vl, func(i, j int) bool { return vl[i].Line < vl[j].Line })

	return vl
}
