package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strings"
	"text/template"
)

// Flags
var (
	ability   = flag.String("a", "", "the path to the directory containing the ability")
	abilities = flag.String("as", "", "the path to the directory containing abilities")
)

type Data struct {
	Package   string
	Static    map[string][]byte
	Templates map[string][]byte
}

const s = `// Generated by cmd/operatable
// DO NOT EDIT
package {{ .Package }}

import (
	{{ if gt (len .Static) 0 }}"net/http"

	{{ end }}"github.com/asticode/go-astibob"
	"github.com/asticode/go-astikit"
)

func newBaseOperatable(l astikit.SeverityLogger) (o *astibob.BaseOperatable) {
	// Create operatable
	o = astibob.NewBaseOperatable()
{{ if gt (len .Static) 0 }}
	// Add static
	{{ range $k, $v := .Static }}o.AddRoute("{{ $k }}", http.MethodGet, astibob.ContentHandle("{{ $k }}", []byte{ {{ range $_, $b := $v }}{{ printf "%#x," $b }}{{ end }} }, l))
	{{ end }}
{{ end }}{{ if gt (len .Templates) 0 }}	// Add templates
	{{ range $k, $v := .Templates }}o.AddTemplate("{{ $k }}", []byte{ {{ range $_, $b := $v }}{{ printf "%#x," $b }}{{ end }} })
	{{ end }}{{ end }}return
}
`

func main() {
	// Set logger
	flag.Parse()
	log.SetFlags(0)

	// Read dir
	var fs []os.FileInfo
	var baseDir string
	var err error
	if *abilities != "" {
		baseDir = *abilities
		if fs, err = ioutil.ReadDir(*abilities); err != nil {
			log.Fatal(fmt.Errorf("main: reading dir %s failed: %w", *abilities, err))
		}
	} else if *ability != "" {
		baseDir = filepath.Dir(*ability)
		f, err := os.Stat(*ability)
		if err != nil {
			log.Fatal(fmt.Errorf("main: stating %s failed: %w", *ability, err))
		}
		fs = append(fs, f)
	} else {
		log.Fatal("main: no input path provided")
	}

	// Parse template
	r := template.New("root")
	t, err := r.Parse(s)
	if err != nil {
		log.Fatal(fmt.Errorf("main: parsing template failed: %w", err))
	}

	// Loop through files
	for _, f := range fs {
		// Not a dir
		if !f.IsDir() {
			continue
		}

		// Create data
		d := Data{
			Package:   f.Name(),
			Static:    make(map[string][]byte),
			Templates: make(map[string][]byte),
		}

		// Stat resources folder
		rp := filepath.Join(baseDir, f.Name(), "resources")
		if _, err = os.Stat(rp); err != nil && !os.IsNotExist(err) {
			log.Fatal(fmt.Errorf("main: stating %s failed: %w", rp, err))
		} else if os.IsNotExist(err) {
			continue
		}

		// Process statics
		if err = processStatics(filepath.Join(baseDir, f.Name()), &d); err != nil {
			log.Fatal(fmt.Errorf("main: processing statics failed: %w", err))
		}

		// Process templates
		if err = processTemplates(filepath.Join(baseDir, f.Name()), &d); err != nil {
			log.Fatal(fmt.Errorf("main: processing templates failed: %w", err))
		}

		// Create destination
		dp := filepath.Join(baseDir, f.Name(), "operatable.go")
		f, err := os.Create(dp)
		if err != nil {
			log.Fatal(fmt.Errorf("main: creating %s failed: %w", dp, err))
		}
		defer f.Close()

		// Execute template
		if err = t.Execute(f, d); err != nil {
			log.Fatal(fmt.Errorf("main: executing template for %s failed: %w", rp, err))
		}
	}
}

func processStatics(basePath string, d *Data) (err error) {
	// Stat static folder
	sp := filepath.Join(basePath, "resources", "static")
	if _, err = os.Stat(sp); err != nil && !os.IsNotExist(err) {
		err = fmt.Errorf("main: stating %s failed: %w", sp, err)
		return
	}

	// Loop through static
	if err == nil {
		if err = filepath.Walk(sp, func(path string, info os.FileInfo, e error) (err error) {
			// Check input error
			if e != nil {
				err = fmt.Errorf("main: walking templates has an input error for path %s: %w", path, e)
				return
			}

			// Only process files
			if info.IsDir() {
				return
			}

			// Read file
			var b []byte
			if b, err = ioutil.ReadFile(path); err != nil {
				err = fmt.Errorf("main: reading %s failed: %w", path, err)
				return
			}

			// Add to data
			d.Static["/static"+filepath.ToSlash(strings.TrimPrefix(path, sp))] = b
			return
		}); err != nil {
			err = fmt.Errorf("main: looping through static in %s failed: %w", sp, err)
			return
		}
	}
	return
}

func processTemplates(basePath string, d *Data) (err error) {
	// Stat templates folder
	tp := filepath.Join(basePath, "resources", "templates")
	if _, err = os.Stat(tp); err != nil && !os.IsNotExist(err) {
		err = fmt.Errorf("main: stating %s failed: %w", tp, err)
		return
	}

	// Loop through templates
	if err == nil {
		if err = filepath.Walk(tp, func(path string, info os.FileInfo, e error) (err error) {
			// Check input error
			if e != nil {
				err = fmt.Errorf("main: walking templates has an input error for path %s: %w", path, e)
				return
			}

			// Only process files
			if info.IsDir() {
				return
			}

			// Check extension
			if filepath.Ext(path) != ".html" {
				return
			}

			// Read file
			var b []byte
			if b, err = ioutil.ReadFile(path); err != nil {
				err = fmt.Errorf("main: reading %s failed: %w", path, err)
				return
			}

			// Add to data
			d.Templates[filepath.ToSlash(strings.TrimSuffix(strings.TrimPrefix(path, tp), ".html"))] = b
			return
		}); err != nil {
			err = fmt.Errorf("main: looping through templates in %s failed: %w", tp, err)
			return
		}
	}
	return
}
