package st1011

import (
	"fmt"
	"go/ast"
	"go/token"
	"strings"

	"honnef.co/go/tools/analysis/code"
	"honnef.co/go/tools/analysis/lint"
	"honnef.co/go/tools/analysis/report"
	"honnef.co/go/tools/go/types/typeutil"

	"golang.org/x/tools/go/analysis"
	"golang.org/x/tools/go/analysis/passes/inspect"
)

var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
	Analyzer: &analysis.Analyzer{
		Name:     "ST1011",
		Run:      run,
		Requires: []*analysis.Analyzer{inspect.Analyzer},
	},
	Doc: &lint.RawDocumentation{
		Title: `Poorly chosen name for variable of type \'time.Duration\'`,
		Text: `\'time.Duration\' values represent an amount of time, which is represented
as a count of nanoseconds. An expression like \'5 * time.Microsecond\'
yields the value \'5000\'. It is therefore not appropriate to suffix a
variable of type \'time.Duration\' with any time unit, such as \'Msec\' or
\'Milli\'.`,
		Since:   `2019.1`,
		MergeIf: lint.MergeIfAny,
	},
})

var Analyzer = SCAnalyzer.Analyzer

func run(pass *analysis.Pass) (interface{}, error) {
	suffixes := []string{
		"Sec", "Secs", "Seconds",
		"Msec", "Msecs",
		"Milli", "Millis", "Milliseconds",
		"Usec", "Usecs", "Microseconds",
		"MS", "Ms",
	}
	fn := func(names []*ast.Ident) {
		for _, name := range names {
			if _, ok := pass.TypesInfo.Defs[name]; !ok {
				continue
			}
			T := pass.TypesInfo.TypeOf(name)
			if !typeutil.IsTypeWithName(T, "time.Duration") && !typeutil.IsPointerToTypeWithName(T, "time.Duration") {
				continue
			}
			for _, suffix := range suffixes {
				if strings.HasSuffix(name.Name, suffix) {
					report.Report(pass, name, fmt.Sprintf("var %s is of type %v; don't use unit-specific suffix %q", name.Name, T, suffix))
					break
				}
			}
		}
	}

	fn2 := func(node ast.Node) {
		switch node := node.(type) {
		case *ast.ValueSpec:
			fn(node.Names)
		case *ast.FieldList:
			for _, field := range node.List {
				fn(field.Names)
			}
		case *ast.AssignStmt:
			if node.Tok != token.DEFINE {
				break
			}
			var names []*ast.Ident
			for _, lhs := range node.Lhs {
				if lhs, ok := lhs.(*ast.Ident); ok {
					names = append(names, lhs)
				}
			}
			fn(names)
		}
	}

	code.Preorder(pass, fn2, (*ast.ValueSpec)(nil), (*ast.FieldList)(nil), (*ast.AssignStmt)(nil))
	return nil, nil
}
