package setval

import (
	"go/ast"
	"go/token"
	"go/types"

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

var Analyzer = &analysis.Analyzer{
	Name: "setval",
	Doc:  "find Cmder types that are missing a SetVal method",

	Run: func(pass *analysis.Pass) (interface{}, error) {
		cmderTypes := make(map[string]token.Pos)
		typesWithSetValMethod := make(map[string]bool)

		for _, file := range pass.Files {
			for _, decl := range file.Decls {
				funcName, receiverType := parseFuncDecl(decl, pass.TypesInfo)

				switch funcName {
				case "Result":
					cmderTypes[receiverType] = decl.Pos()
				case "SetVal":
					typesWithSetValMethod[receiverType] = true
				}
			}
		}

		for cmder, pos := range cmderTypes {
			if !typesWithSetValMethod[cmder] {
				pass.Reportf(pos, "%s is missing a SetVal method", cmder)
			}
		}

		return nil, nil
	},
}

func parseFuncDecl(decl ast.Decl, typesInfo *types.Info) (funcName, receiverType string) {
	funcDecl, ok := decl.(*ast.FuncDecl)
	if !ok {
		return "", "" // Not a function declaration.
	}

	if funcDecl.Recv == nil {
		return "", "" // Not a method.
	}

	if len(funcDecl.Recv.List) != 1 {
		return "", "" // Unexpected number of receiver arguments. (Can this happen?)
	}

	receiverTypeObj := typesInfo.TypeOf(funcDecl.Recv.List[0].Type)
	if receiverTypeObj == nil {
		return "", "" // Unable to determine the receiver type.
	}

	return funcDecl.Name.Name, receiverTypeObj.String()
}
