package common

import (
	"fmt"
	"os/exec"
	"regexp"
	"strings"
)

type Command struct {
	Id          string `json:"id"`
	Cmd         string `json:"cmd"`
	Description string `json:"description"`
	IgnoreErros bool   `json:"ignore.errors"`
}

type BuildSet struct {
	Modes   map[string]*BuildMode `json:"modes"`
	Cmd     string                `json:"cmd"`
	Workdir string                `json:"workdir"`
	Steps   []*Command            `json:"steps"`
	Before  []*Command            `json:"beforeCompile"`
	After   []*Command            `json:"afterCompile"`
	Options *BuildOptions         `json:"options"`
}

func (b *BuildSet) Commands() []*Command {
	var (
		mode       *BuildMode
		ok, ignore bool
		steps      = []*Command{}
		modeKey    = b.Options.Mode
	)

	if modeKey == "" {
		modeKey = "default"
	}

	if mode, ok = b.Modes[modeKey]; !ok {
		panic(fmt.Sprintf("Build mode '%s' not defined", modeKey))
	}

	if (len(mode.Ignore) == 0) &&
		len(mode.ExecuteOnly) == 0 {
		return b.Steps
	}

	ignore = len(mode.Ignore) > 0

	if ignore && len(mode.ExecuteOnly) > 0 {
		panic("Ignore and ExecuteOnly are mutually exclusive")
	}

	ids := map[string]bool{}

	for _, v := range append(mode.Ignore, mode.ExecuteOnly...) {
		ids[v] = true
	}

	if ignore {
		for _, step := range b.Steps {
			if _, ok := ids[step.Id]; !ok {
				steps = append(steps, step)
			}
		}
	} else {
		for _, step := range b.Steps {
			if _, ok := ids[step.Id]; ok {
				steps = append(steps, step)
			}
		}
	}

	return steps
}

type BuildMode struct {
	Ignore      []string `json:"ignore"`
	ExecuteOnly []string `json:"executeOnly"`
}

var (
	expressionRegexp = regexp.MustCompile(`\{([\w\.]+)\}`)
)

func NewBuildOptions() *BuildOptions {
	return &BuildOptions{
		Mode:               "default",
		ProjectDotNotation: map[string]interface{}{},
	}
}

func runBuildCommads(project *Project, client *Client, buildOptions *BuildOptions) (err error) {
	var (
		instance         *exec.Cmd
		translateOptions *BuildSet
		step             int
		cmd              string
		out              []byte
		// variables        = map[string]interface{}{}
		buildfile = fmt.Sprintf("build/%s", client.Id)
		mode      = project.Mode
		buildSet  = &BuildSet{
			Steps: []*Command{},
			Modes: map[string]*BuildMode{
				"default": {},
			},
		}
	)

	if buildOptions == nil {
		buildOptions = NewBuildOptions()
	}

	buildOptions.Parse()

	if err = JsonParseMode(buildfile, mode, &buildSet); err != nil {
		err = nil
		fmt.Printf("[warning] running '%s' without build file", client.Id)
		buildOptions.Mode = "default"
	}

	buildSet.Options = buildOptions

	if bo, found := project.TranslatorBuildOptionsMap[client.Id]; found {
		translateOptions, err = bo(project, buildOptions)
		if translateOptions.Before != nil {
			buildSet.Steps = append(translateOptions.Before, buildSet.Steps...)
		}
		if translateOptions.After != nil {
			buildSet.Steps = append(buildSet.Steps, translateOptions.After...)
		}
	}
	if buildSet.After != nil {
		buildSet.Steps = append(buildSet.Steps, buildSet.After...)
	}

	fmt.Println("Running build commands... on mode ", mode)
	fmt.Println("RUN translate ", client.Id)

	cmds := buildSet.Commands()
	total := len(cmds)

	for k, command := range cmds {

		if cmd, err = parseCommand(command, buildOptions.ProjectDotNotation); err != nil {
			return
		}
		step = k + 1

		// goterm.MoveCursor(1, 1)

		if buildOptions.IgnoreStep(step) {
			// goterm.Printf("\tStep (%d / %d) - [ Skiped ] %s\n", step, total, cmd)
			fmt.Printf("\tStep (%d / %d) - [ Skiped ] %s\n", step, total, cmd)
			continue
		}

		// goterm.Printf("\tStep (%d / %d) - %s\n", step, total, cmd)
		fmt.Printf("\tStep (%d / %d) - %s\n", step, total, cmd)

		instance = exec.Command("sh", "-c", cmd)
		instance.Dir = buildSet.Workdir

		// goterm.Flush() // Call it every time at the end of rendering
		if out, err = instance.CombinedOutput(); err != nil {
			fmt.Println(string(out))
			return
		}
	}

	return
}

func parseCommand(c *Command, variables map[string]interface{}) (string, error) {
	var (
		expression, replace, dotPathName string
		cmd                              = c.Cmd
		matches                          = expressionRegexp.FindAllStringSubmatch(cmd, -1)
	)

	for _, match := range matches {
		dotPathName = match[1]
		if value, exists := variables[dotPathName]; !exists || value == nil {
			return "", fmt.Errorf("Value for expression '%s' not defined", dotPathName)
		}
		expression = match[0]
		replace = fmt.Sprintf("%v", variables[dotPathName])
		cmd = strings.ReplaceAll(cmd, expression, replace)
	}

	return cmd, nil
}
