package main

import (
	"aed-api-server/internal/pkg/utils"
	"bytes"
	"fmt"
	"github.com/fsnotify/fsnotify"
	log "github.com/sirupsen/logrus"
	"io/ioutil"
	"path"
	"path/filepath"
	"strings"
)

func ScanDir(dir, moduleName, moduleDir string) ([]*Pkg, error) {
	pkgs := make([]*Pkg, 0)
	pkg, err := scanDir(dir, moduleName, moduleDir)
	if err != nil {
		return nil, err
	}
	if pkg != nil {
		pkgs = append(pkgs, pkg)
	}
	childrenPkgs, err := scanChildrenDir(dir, moduleName, moduleDir)
	if err != nil {
		return nil, err
	}
	pkgs = append(pkgs, childrenPkgs...)
	return pkgs, nil
}

func scanDir(dir, moduleName, moduleDir string) (*Pkg, error) {
	defer utils.TimeStat("scanDir:" + dir)()
	dirs, _ := ioutil.ReadDir(dir)

	var pkg Pkg
	for i := range dirs {
		if !dirs[i].IsDir() {
			filename := path.Join(dir, dirs[i].Name())
			if strings.HasSuffix(filename, ".go") {
				rst, err := goFileParse(filename)
				if err != nil {
					return nil, err
				}
				if rst != nil {
					pkg.ID = rst.Path
					pkg.Name = rst.PkgName
					rel, err := filepath.Rel(moduleDir, dir)
					if err != nil {
						return nil, err
					}
					pkg.PkgPath = filepath.Join(moduleName, rel)
					pkg.FuncNames = append(pkg.FuncNames, rst.InjectNames...)
				}
			}
		}
	}
	if pkg.FuncNames == nil {
		return nil, nil
	}
	return &pkg, nil
}

func scanChildrenDir(dir, moduleName, moduleDir string) ([]*Pkg, error) {
	pkgs := make([]*Pkg, 0)

	dirs, _ := ioutil.ReadDir(dir)
	for i := range dirs {
		if dirs[i].IsDir() {
			list, err := ScanDir(path.Join(dir, dirs[i].Name()), moduleName, moduleDir)
			if err != nil {
				return nil, err
			}
			pkgs = append(pkgs, list...)
		}
	}

	return pkgs, nil
}

type Pkg struct {
	ID        string //包的绝对路径
	Name      string
	PkgPath   string
	FuncNames []string
}

func (p *Pkg) generateFuncContent() string {
	if len(p.FuncNames) > 0 {
		strList := make([]string, 0)
		for i := range p.FuncNames {
			funcName := p.FuncNames[i]
			strList = append(strList,
				fmt.Sprintf("    component.Load(%s.%s())", p.Name, funcName),
			)
		}
		return strings.Join(strList, "\n")
	}
	return ""
}

func (p *Pkg) genImportContent() string {
	if path.Base(p.PkgPath) != p.Name {
		return fmt.Sprintf("    %s \"%s\"", p.Name, p.PkgPath)
	} else {
		return fmt.Sprintf("    \"%s\"", p.PkgPath)
	}
}

func genFileContent(pkgs []*Pkg, funcName string, pkgName string) string {
	imports := make([]string, 0)
	funcContents := make([]string, 0)

	for i := range pkgs {
		pkg := pkgs[i]
		funcContents = append(funcContents, pkg.generateFuncContent())
		if len(funcContents) > 0 {
			imports = append(imports, pkg.genImportContent())
		}
	}

	imports = append(imports, "    \"gitlab.openviewtech.com/openview-pub/gopkg/inject\"")

	var b bytes.Buffer

	b.WriteString("// Code generated by inject-component autoloader; DO NOT EDIT.\n")
	b.WriteString(fmt.Sprintf("package %s\n", pkgName))
	b.WriteString("import (\n")
	b.WriteString(strings.Join(imports, "\n"))
	b.WriteString("\n)\n\n")

	b.WriteString(fmt.Sprintf("func %s(component *inject.Component){\n", funcName))
	b.WriteString(strings.Join(funcContents, "\n"))
	b.WriteString("\n}\n")
	return b.String()
}

type autoload struct {
	scanDir      []string
	packageName  string
	functionName string
	outputFile   string

	moduleName string
	moduleDir  string

	pkgsMap map[string]*Pkg
}

func (loader *autoload) generate(outputFile string) {
	l := len(loader.pkgsMap)
	pkgs := make([]*Pkg, 0, l)

	for _, v := range loader.pkgsMap {
		pkgs = append(pkgs, v)
	}
	content := genFileContent(pkgs, loader.functionName, loader.packageName)
	err := ioutil.WriteFile(outputFile, []byte(content), 0666)
	if err != nil {
		log.Error("write file error:", err)
	}
}

func (loader *autoload) fillModuleInfo() error {
	info, absPath, err := goModuleInfo(loader.scanDir[0])
	if err != nil {
		return err
	}
	loader.moduleName = info
	loader.moduleDir = absPath
	return nil
}

func (loader *autoload) firstGenerate() error {
	pkgs := make([]*Pkg, 0)
	for _, dir := range loader.scanDir {
		pkgList, err := ScanDir(dir, loader.moduleName, loader.moduleDir)
		if err != nil {
			return err
		}
		pkgs = append(pkgs, pkgList...)
	}

	loader.pkgsMap = make(map[string]*Pkg)
	for _, pkg := range pkgs {
		loader.pkgsMap[pkg.ID] = pkg
	}

	loader.generate(loader.outputFile)
	return nil
}

func (loader *autoload) reGenerate(dir string, filename string, op fsnotify.Op) {
	log.Infof("%v %s in %s", op, filename, dir)
	pkg, err := scanDir(dir, loader.moduleName, loader.moduleDir)
	if err != nil {
		log.Errorf("scan %s err:%v", dir, err)
		return
	}
	if pkg != nil {
		loader.pkgsMap[pkg.ID] = pkg
	} else {
		delete(loader.pkgsMap, dir)
	}
	loader.generate(loader.outputFile)
}
