package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"os"
	"path/filepath"
	"strings"
)

func embStructsSubCommand(args []string) {
	cmdName := "emb-structs"
	cmd := flag.NewFlagSet(cmdName, flag.ExitOnError)
	cmd.Usage = func() {
		_, _ = fmt.Fprintf(cmd.Output(), `Usage of %s:

  Embed Go structs as compact format in the binary.
  JSON file format:
    {
      "prefix": "html5entities", // prefix for the generated const/var
      "types": { // struct field types
          "Name": "string",         // struct fields
          "CodePoints": "[]int",
          "Characters": "[]byte"
      },

      // array of struct data
      // all data must be a string
      "data": [ 
        {"Name": "AElig", "CodePoints": ["198"], "Characters": ["0xc3", "0x86"]},
        {"Name": "AMP", "CodePoints": ["38"], "Characters": ["0x26"]},
      ]
    }

`, cmdName)
		cmd.PrintDefaults()
	}

	inputJSONPath := cmd.String("i", "", "source JSON file path(required)")
	outputPath := cmd.String("o", "", "output file path(required)")

	if err := cmd.Parse(args); err != nil ||
		len(*inputJSONPath) == 0 ||
		len(*outputPath) == 0 {
		usage(cmd.Usage, err)
	}
	var source map[string]any
	inputJSONSource, err := os.ReadFile(*inputJSONPath)
	if err != nil {
		panic(err)
	}
	if err := json.Unmarshal(inputJSONSource, &source); err != nil {
		panic(err)
	}

	f, err := os.Create(*outputPath)
	if err != nil {
		fmt.Printf("Failed to open %s: %v\n", *outputPath, err)
		os.Exit(1)
	}
	defer f.Close()

	abs, _ := filepath.Abs(*outputPath)
	pkg := filepath.Base(filepath.Dir(abs))

	write := func(ft string, v ...any) {
		if len(v) == 0 {
			_, _ = f.WriteString(ft)
			return
		}
		_, _ = f.WriteString(fmt.Sprintf(ft, v...))
	}
	writeln := func(ft string, v ...any) {
		write(ft+"\n", v...)
	}

	writeln("// Code generated by _tools; DO NOT EDIT.")
	writeln("package " + pkg)

	prefix := source["prefix"].(string)
	types := source["types"].(map[string]any)
	data := source["data"].([]any)

	writeln("const _%sLength = %d", prefix, len(data))

	for prop, _typ := range types {
		typ := _typ.(string)
		if typ == "string" {
			write("const _%s%s string = \"", prefix, prop)
			for _, _d := range data {
				d := _d.(map[string]any)
				v := d[prop].(string)
				write(v)
			}
			writeln(`"`)
			write("const _%s%sIndex  = \"", prefix, prop)
			for _, _d := range data {
				d := _d.(map[string]any)
				v := d[prop].(string)
				write("\\x%02x", len(v))
			}
			writeln(`"`)
			continue
		}

		if strings.HasPrefix(typ, "[]") {
			elmTyp := typ[2:]
			write("var _%s%s = [...]%s{", prefix, prop, elmTyp)
			for i, _d := range data {
				d := _d.(map[string]any)
				arr := d[prop].([]any)
				for j, a := range arr {
					v := a.(string)
					if typ == "[]string" {
						write(`"` + v + `"`)
					} else {
						write(v)
					}
					if i != len(data)-1 || j != len(arr)-1 {
						write(", ")
					}
				}
			}
			writeln("}")
			write("var _%s%sIndex  = \"", prefix, prop)
			for _, _d := range data {
				d := _d.(map[string]any)
				arr := d[prop].([]any)
				write("\\x%02x", len(arr))
			}
			writeln(`"`)
			continue
		}

		write("var _%s%s = [...]%s{", prefix, prop, typ)
		for i, _d := range data {
			d := _d.(map[string]any)
			v := d[prop].(string)
			write(v)
			if i != len(data)-1 {
				write(", ")
			}
		}
		writeln(`}`)
	}
}
