// Package parser provides the ability to parse the data that is bound in Wails projects.
// Using this, it can also generate a Javascript module that represents the DTOs used, as
// well as providing wrappers for bound methods.
package parser

import (
	"go/token"

	"github.com/pkg/errors"
	"golang.org/x/tools/go/packages"
)

// Parser is the Wails project parser
type Parser struct {

	// Placeholders for Go's parser
	fileSet *token.FileSet

	// The packages we parse
	// The map key is the package ID
	packages map[string]*Package
}

// NewParser creates a new Wails project parser
func NewParser() *Parser {
	return &Parser{
		fileSet:  token.NewFileSet(),
		packages: make(map[string]*Package),
	}
}

// ParseProject will parse the Wails project in the given directory
func (p *Parser) ParseProject(dir string) error {

	var err error

	err = p.loadPackages(dir)
	if err != nil {
		return err
	}

	// Find all the bound structs
	for _, pkg := range p.packages {
		err = p.findBoundStructs(pkg)
		if err != nil {
			return err
		}
	}

	// Parse the structs
	for _, pkg := range p.packages {
		err = p.parseBoundStructs(pkg)
		if err != nil {
			return err
		}
	}

	// Resolve package names
	// We do this because some packages may have the same name
	p.resolvePackageNames()

	return nil
}

func (p *Parser) loadPackages(projectPath string) error {
	mode := packages.NeedName |
		packages.NeedFiles |
		packages.NeedSyntax |
		packages.NeedTypes |
		packages.NeedImports |
		packages.NeedTypesInfo |
		packages.NeedModule

	cfg := &packages.Config{Fset: p.fileSet, Mode: mode, Dir: projectPath}
	pkgs, err := packages.Load(cfg, "./...")
	if err != nil {
		return errors.Wrap(err, "Problem loading packages")
	}
	// Check for errors
	var parseError error
	for _, pkg := range pkgs {
		for _, err := range pkg.Errors {
			if parseError == nil {
				parseError = errors.New(err.Error())
			} else {
				parseError = errors.Wrap(parseError, err.Error())
			}
		}
	}

	if parseError != nil {
		return parseError
	}

	// Create a map of packages
	for _, pkg := range pkgs {
		p.packages[pkg.ID] = newPackage(pkg)
	}

	return nil
}

func (p *Parser) getPackageByID(id string) *Package {
	return p.packages[id]
}

func (p *Parser) packagesToGenerate() []*Package {

	var result []*Package

	for _, pkg := range p.packages {
		if pkg.ShouldGenerate() {
			result = append(result, pkg)
		}
	}

	return result
}

type ParserReport struct {
	Packages []*Package
}
