package analyzer

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

	"golang.org/x/tools/go/analysis/passes/inspect"
	"golang.org/x/tools/go/ast/inspector"

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

// Analyzer that checks if all struct's fields are initialized
var Analyzer = &analysis.Analyzer{
	Name:     "exhaustivestruct",
	Doc:      "Checks if all struct's fields are initialized",
	Run:      run,
	Requires: []*analysis.Analyzer{inspect.Analyzer},
}

func run(pass *analysis.Pass) (interface{}, error) {
	inspector := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)

	nodeFilter := []ast.Node{
		(*ast.CompositeLit)(nil),
	}

	inspector.Preorder(nodeFilter, func(node ast.Node) {
		var name string

		compositeLit := node.(*ast.CompositeLit)

		i, ok := compositeLit.Type.(*ast.Ident)

		if ok {
			name = i.Name
		} else {
			s, ok := compositeLit.Type.(*ast.SelectorExpr)

			if !ok {
				return
			}

			name = s.Sel.Name
		}

		if compositeLit.Type == nil {
			return
		}

		t := pass.TypesInfo.TypeOf(compositeLit.Type)

		if t == nil {
			return
		}

		str, ok := t.Underlying().(*types.Struct)

		if !ok {
			return
		}

		if len(compositeLit.Elts) == 0 {
			return
		}

		for i := 0; i < str.NumFields(); i++ {
			fieldName := str.Field(i).Name()
			exists := false

			for _, e := range compositeLit.Elts {
				if k, ok := e.(*ast.KeyValueExpr); ok {
					if i, ok := k.Key.(*ast.Ident); ok {
						if i.Name == fieldName {
							exists = true
							break
						}
					}
				}
			}

			if !exists {
				pass.Reportf(node.Pos(), "%s is missing in %s", fieldName, name)
			}
		}
	})

	return nil, nil
}
