package olpfile

import (
	"bufio"
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"os"
	"os/exec"
	"regexp"
	"strings"

	"github.com/fsnotify/fsnotify"
	"gitlab.com/olanguage/olang/color"
	"gopkg.in/src-d/go-git.v4"
)

type OlpParsed struct {
	Name      string        `json:"package"`
	Publisher Publisher     `json:"publisher"`
	Sources   string        `json:"sources"`
	Main      string        `json:"main"`
	Requires  []Require     `json:"requires"`
	Compiled  string        `json:"compiled"`
	Install   []InstallTask `json:"install"`
	WatchList FileWatcher   `json:"watch"`
	Jobs      []Job         `json:"jobs"`
	Type      string        `json:"type"`
}

type Require struct {
	Name     string `json:"name"`
	Src      string `json:"src"`
	FileName string `json:"filename"`
}

type Job struct {
	Name    string `json:"name"`
	Command string `json:"command"`
}
type InstallTask map[string]string

type Publisher struct {
	Name   string `json:"name"`
	Email  string `json:"email"`
	Github string `json:"github"`
}

/* Add File Watcher */
type FileWatcher struct {
	Folder string `json:"folder"`
	Output string `json:"output"`
}

type Olpfile struct {
	filename string
	path     string
	libPath  string
	Error    string
	Parsed   OlpParsed
}

type CompiledFile struct {
	FileName string
}

var watcher *fsnotify.Watcher
var watchedFiles []string

func New(filename string, libPath string) *Olpfile {
	wd, _ := os.Getwd()
	olpfile := &Olpfile{filename: filename, path: wd, libPath: libPath}
	if Exists(wd + "/" + filename) {
		olpfile.Error = "[!] Olpfile not found."
	}
	return olpfile
}

func Create(filename string) *Olpfile {
	olpfile := &Olpfile{filename: filename}
	olpfile.path, _ = os.Getwd()

	if Exists(filename) {
		fmt.Println("[!] library.olp already exists.")
		return olpfile
	}

	f, _ := os.Create(olpfile.path + "/" + olpfile.filename)
	defer f.Close()

	olpfileParsed := OlpParsed{
		Name:      "",
		Publisher: Publisher{},
		Sources:   "",
		Main:      "",
		Requires:  []Require{},
		Compiled:  "",
		Install:   []InstallTask{},
		WatchList: FileWatcher{},
		Jobs:      []Job{},
		Type:      "",
	}
	olpfileParsed.Name = Ask("[>] Package Name (example): ", "")
	olpfileParsed.Main = Ask("[>] Package Main File (main.ola): ", "main.ola")
	olpfileParsed.Sources = Ask("[>] Package Source Folder (src): ", "src")
	olpfileParsed.Type = Ask("[>] Package Type (library, project): ", "project")
	olpfileParsed.Publisher.Name = Ask("[>] Publisher Name: ", "")
	olpfileParsed.Publisher.Email = Ask("[>] Publisher Email: ", "")
	olpfileParsed.Publisher.Github = Ask("[>] Publisher Github: ", "")

	json, _ := json.Marshal(olpfileParsed)
	f.WriteString(jsonPrettyPrint(string(json)))
	f.Sync()

	return olpfile
}

func Ask(question string, defaultText string) string {
	reader := bufio.NewReader(os.Stdin)
	fmt.Print(question)
	text, _ := reader.ReadString('\n')
	text = strings.TrimSpace(text)
	if text == "" {
		return defaultText
	}
	return text
}

func Exists(name string) bool {
	if _, err := os.Stat(name); err != nil {
		if os.IsNotExist(err) {
			return false
		}
	}
	return true
}

func (olp *Olpfile) Parse() *Olpfile {
	raw, err := os.ReadFile(olp.path + "/" + olp.filename)
	if err != nil {
		olp.Error = err.Error()
	}

	var parsed OlpParsed

	json.Unmarshal(raw, &parsed)

	olp.Parsed = parsed

	return olp
}

func (olp *Olpfile) Install() bool {
	if !Exists(olp.filename) {
		return false
	}

	//autoload := ""

	if !Exists(olp.path) {
		color.Print(color.Blue("[!] INFO: \"" + olp.path + "\" path not found. Path creating..."))
		os.Mkdir(olp.path, 0777)
		color.Print(color.Grey("[ready]\n"))
	}
	parseOlp := olp.Parse()
	for _, r := range parseOlp.Parsed.Requires {
		olp.Clone(r.Name, r.Src)

		/*

			libName := olp.filename
			if r.FileName != "" {
				libName = r.FileName
			}

			if Exists(olp.path + "/" + r.Name + "/" + libName) {
				subOlp := New(olp.path+"/"+r.Name+"/"+libName, olp.path+"/"+r.Name)
				parseOlp := subOlp.Parse()

				res := subOlp.CompileVendors(subOlp.path+"/"+parseOlp.Main, subOlp.path+"/"+parseOlp.Compiled, parseOlp.Sources, parseOlp.Name)

				if res {
					color.Print(color.Grey("[done]\n"))
					file, err := os.Open(subOlp.path + "/" + parseOlp.Compiled + "/" + parseOlp.Name + ".ola")
					if err != nil {
						log.Fatal(err)
					}
					scanner := bufio.NewScanner(file)
					//env := object.NewEnvironment()

					for scanner.Scan() {
						line := scanner.Text()
						autoload += line + "\n"
					}
				} else {
					color.Print(color.Grey("[fail]"))
				}
			}
		*/
	}
	//color.Printf("[>] Writing Autoload... [vendor/ondle.ola]")
	//olp.WriteAutoLoad(autoload, "vendor/ondle.ola")
	//color.Print(color.Grey("[done]\n"))
	//color.Print(color.Green("[X] Fetch Completed."))
	fmt.Printf("\n")
	return true
}

func (olp *Olpfile) Clone(name string, url string) {
	if name == "" {
		color.Printf("[>] Error [" + color.Red("Name Null") + "]\t\n")
		return
	}

	if url == "" {
		color.Printf("[>] Error [" + color.Red("Url Null") + "]\t\n")
		return
	}
	color.Printf("[>] Fetching\t["+color.Blue("%s")+"]", name)
	if Exists(olp.libPath + "/" + name) {
		r, errorPlain := git.PlainOpen(olp.libPath + "/" + name)
		w, errorWorktree := r.Worktree()
		if errorWorktree != nil {
			color.Printf(""+color.Red("[fail]")+" [%s]", errorWorktree)
			return
		}
		if errorPlain != nil {
			color.Printf(""+color.Red("[fail]")+" [%s]", errorWorktree)
			return
		}
		w.Pull(&git.PullOptions{RemoteName: "origin"})
		color.Print(color.Green("[updated]"))

	} else {
		_, err := git.PlainClone(olp.libPath+"/"+name, false, &git.CloneOptions{
			URL:               url,
			RecurseSubmodules: git.DefaultSubmoduleRecursionDepth,
		})

		if err == nil {
			color.Print(color.Grey("[done]"))
		} else {
			color.Printf(""+color.Red("[fail]")+" [%s]", err)
		}
	}

	fmt.Printf("\n")
}

func NewProject(url string, name string, fileName string, libPath string) *Olpfile {
	olp := New(name+"/"+fileName, libPath)
	if name == "" {
		color.Printf("[>] Error [" + color.Red("Name Null") + "]\t\n")
		return olp
	}

	if url == "" {
		color.Printf("[>] Error [" + color.Red("Url Null") + "]\t\n")
		return olp
	}

	color.Printf("[>] Fetching\t["+color.Blue("%s")+"]", name)

	_, err := git.PlainClone(name, false, &git.CloneOptions{
		URL:               url,
		RecurseSubmodules: git.DefaultSubmoduleRecursionDepth,
	})

	if err == nil {
		color.Print(color.Grey("[done]\n"))

		color.Printf("[>] Clean\t["+color.Blue("%s")+"]\n", name)

		color.Printf("[>] Installing Depends\t["+color.Blue("%s")+"]\n", name)

		os.RemoveAll(name + "/.git")
		os.Remove(name + "/.gitignore")
		os.Remove(name + "/.gitmodules")
		os.Remove(name + "/README")
		os.Remove(name + "/README.md")
		os.Remove(name + "/LICENSE")

		olp.Install()
	} else {
		color.Printf(""+color.Red("[fail]")+" [%s]", err)
	}

	return olp
}

/*
func (olp *Olpfile) WriteAutoLoad(content string, autoload string) {
	f, _ := os.Create(autoload)
	defer f.Close()
	f.WriteString(content)
	f.Sync()
}
*/

/*
func (olp *Olpfile) CompileVendors(filename string, out string, src string, packt string) bool {
	color.Printf("[>] Compiling\t["+color.Blue("%s")+"]", filename)
	result := FileParse(filename, src, packt)
	os.Mkdir(out, 0777)

	f, _ := os.Create(out + "/" + packt + ".ola")
	f.WriteString(result + "\n")
	f.Sync()

	return true
}

func (olp *Olpfile) CompileProject(filename string, out string, packt string) bool {
	color.Printf("[>] Compiling\t["+color.Blue("%s")+"]", filename)

	result := FileParse(filename, out, packt)

	f, _ := os.Create(out + "/" + packt + ".ola")
	f.WriteString(result + "\n")
	f.Sync()
	os.Chmod(out+"/"+packt+".ola", 0777)

	return true
}
*/

func (olp *Olpfile) WriteOlpFile(parser OlpParsed) bool {
	f, _ := os.Create(olp.filename)

	marshallJson, _ := json.Marshal(parser)

	f.WriteString(jsonPrettyPrint(string(marshallJson)))

	f.Sync()

	return true
}

func FileParse(filename string, src string, packt string) string {
	content, _ := os.ReadFile(filename)

	contstr := string(content)

	if len(contstr) > 0 {
		var re = regexp.MustCompile(`(?m)\#[\s\S]*?.*|\#\![\s\S]*?.*|\/\*[\s\S]*?\*\/|([^:]|^)\/\/.*$`)
		// \#[\s\S]*?.*|\/\*[\s\S]*?\*\/|([^:]|^)\/\/.*$

		var rel = regexp.MustCompile(`(?mi)(?:load)+\s\S+`)
		var loadRemove = regexp.MustCompile(`(?mi)(?:load)\W`)

		loads := rel.FindAllString(contstr, -2)

		loaded := ""
		for _, load := range loads {
			load = loadRemove.ReplaceAllString(load, "$1W")
			load = strings.Replace(load, "\"", "", -2)
			curDir, _ := os.Getwd()
			loadedContent, _ := os.ReadFile(curDir + "/vendor/" + packt + "/" + load)
			fmt.Printf("\n")
			color.Printf("[>] Compiling\t["+color.Blue("%s")+"]", curDir+"/vendor/"+packt+"/"+load)
			loaded += string(loadedContent)
		}

		contstr = rel.ReplaceAllString(contstr, loaded)
		contstr = re.ReplaceAllString(contstr, "$1W")
		contstr = strings.Replace(contstr, "\n", "", -2)
		contstr = strings.Replace(contstr, "\t", "", -2)
		contstr = strings.Replace(contstr, "  ", " ", -2)

	}
	return contstr
}

func (olp *Olpfile) JobRun(jobname string) {
	parseOlp := olp.Parse()
	for _, j := range parseOlp.Parsed.Jobs {
		if j.Name == jobname {
			split := strings.Split(j.Command, " ")
			first := split[0]
			_, split = split[0], split[1:]
			args := strings.Join(split, " ")
			log.Println("Opkg Running... [" + j.Name + "][" + j.Command + "]")
			cmd := exec.Command(first, args)
			cmd.Run()
		}
	}
}

func jsonPrettyPrint(in string) string {
	var out bytes.Buffer
	err := json.Indent(&out, []byte(in), "", "\t")
	if err != nil {
		return in
	}
	return out.String()
}
