package main

import (
	"io/ioutil"
	"fmt"
	"encoding/json"
	"os"
	"path/filepath"
	"strings"
	"sort"
	"hash/crc64"
	//"encoding/base64"
)

func main() {
	// Show Usage
	if len(os.Args) < 3 {
		fmt.Println("Usage: ./keymap-gen src-dir out-dir")
		fmt.Println("Outputs c files in out-dir")
		fmt.Println("Make sure the dirs exist.")
		return
	}

	files, err := filepath.Glob(os.Args[1] + "/*.json")
	if err != nil {
		fmt.Printf("Could not open src-dir: %v\n", err)
		return
	}

	for _, fname := range(files) {
		fmt.Println("Processing: ", fname)
		// Read the source
		data, err := ioutil.ReadFile(fname)
		if err != nil {
			panic(err)
		}

		// Unbundle Data
		var FullDict map[string]Entry
		json.Unmarshal(data, &FullDict)

		// Loop over entries and store
		var output []string
		for i,v := range FullDict {
			if i == "0" {
				continue
			}
			
			// Special handling for numbermap
			var entry string
			if strings.Contains(fname, "num") {
				entry = v.toKeymap("NUM|")
			} else {
				entry = v.toKeymap("")
			}

			if entry != "" {
				output = append(output, entry)
			}
		}

		// Sort by length, then amount of whitespace lol
		sort.Slice(output, func (i,j int) bool {
			var maxLen int
			if len(output[i]) > len(output[j]) { 
				maxLen = len(output[i])
			} else {
				maxLen = len(output[j])
			}

			return maxLen-strings.Count(output[i][:40], " ") < maxLen-strings.Count(output[j][:40], " ")
		})

		// Whack a disclaimer
		output = append([]string{"// This file is automatically generated. Do not edit it!\n\n"}, output...)
		
		// Write all data out
		outName := filepath.Base(fname)
		outName = os.Args[2] + outName[:len(outName)-5]+".def"
		fmt.Println("Saving: ", outName)
		ioutil.WriteFile(outName, []byte(strings.Join(output, "")), 0755)
	}
}

func (e Entry) toKeymap(prefix string) (string) {
	// storage for parts
	var command, chord, arg string
	wordInfo := parseWords(e)

	// Handle prefix
	if prefix != "" {
		chord = prefix
	}

	// Format the chord
	keys := []string{"AA", "AS", "AE", "AT", "AN", "AI", "AO", "AP"}
	for i, v := range e.Input { 
		chord += keys[v-1]

		if i != len(e.Input)-1 {
			chord += "|"
		}
	}

	// Handle specials/base first
	var ok bool
	var v  []string

	if e.Special != "" {
		v, ok = QMKLookup[e.Special] 
	}
	if !ok && e.Base != "" {
		v, ok = QMKLookup[e.Base] 
	}

	if ok {
		// Determine way to send key
		if len(v) == 1 {
			command = "PRES("
		} else {
			command = "KEYS("
		}
	}

	if ok {
		if len(v) > 1 {
			arg += "{"
		}

		// String together args
		for ii, vv := range(v) {
			arg += vv
			if ii == len(v)-1 && len(v) > 1 {
				arg += ", COMBO_END}"
			} else if ii != len(v)-1 {
				arg += ", "
			}
		}

		
		hash := crc64.Checksum([]byte(fmt.Sprintf("%v%v", arg, chord)), crc64.MakeTable(crc64.ECMA))
		hashStr := fmt.Sprintf("cmb_%x", hash)
		wordSpacer := strings.Repeat(" ", 42-len(arg))
		if command == "KEYS(" {
			arg = fmt.Sprintf("%v, %v %v", hashStr, wordSpacer, arg)
		} else {
			arg = fmt.Sprintf("%65v", arg)
		}
			
		goto Found
	}

	// Parse out word info
	if wordInfo.LRank == 0 && wordInfo.RRank == 0 {
		goto Blank
	}

	if wordInfo.LRank != 0 || wordInfo.RRank != 0 {
		if wordInfo.LRank != 0 && wordInfo.RRank != 0 {
			// Just blank the structure and recall
			left, right := e, e
			left.Trw = nil
			right.Tlw = nil
			return fmt.Sprintf("%v%v", left.toKeymap("LFT|"), right.toKeymap("RGT|"))
		}

		var word string
		if wordInfo.LRank > wordInfo.RRank {
			word = wordInfo.LWord
		} else {
			word = wordInfo.RWord
		}

		// Add in thumb
		chord = "AR|" + chord


		// generate function name
		hash := crc64.Checksum([]byte(word), crc64.MakeTable(crc64.ECMA))
		hashStr := fmt.Sprintf("str_%016X", hash)
		command = "SUBS("
		wordSpacer := strings.Repeat(" ", 40-len(word))
		arg = fmt.Sprintf("%v, %v \"%v \"", hashStr, wordSpacer, word)
		goto Found
	}

	panic(e.String())

	Found:
	chord += ","
	return fmt.Sprintf("%v%-35v%v)\n", command, chord, arg)

	Blank:
	return ""
}

type Entry struct {
	Input 	[]int
	Base		string
	Tlw			[]interface{}
	Trw			[]interface{}
	Special string
}
type Word struct {
	LWord 		string 
	LRank			float64
	RWord 		string 
	RRank			float64
}

func parseWords(e Entry) (ret Word) {
	if len(e.Tlw) > 0 { 
		ret.LWord = e.Tlw[0].(string)
		ret.LRank=  e.Tlw[1].(float64)
	}
	if len(e.Trw) > 0 { 
		ret.RWord = e.Trw[0].(string)
		ret.RRank=  e.Trw[1].(float64)
	}
	return ret
}
func (e Entry) String() (ret string) {
	ret = fmt.Sprintln("Input: ", e.Input)
	ret += fmt.Sprintln("Base: ",  e.Base)
	ret += fmt.Sprintln("Left: ", e.Tlw)
	ret += fmt.Sprintln("Right: ", e.Trw)
	ret += fmt.Sprintln("Special: ", e.Special)

	return ret
}

var QMKLookup = map[string][]string {
"!":[]string{"KC_LSFT", "KC_1"},
"'":[]string{"KC_QUOT"},
"(":[]string{"KC_LSFT", "KC_9"},
")":[]string{"KC_LSFT", "KC_0"},
",":[]string{"KC_COMM"},
"-":[]string{"KC_MINS"},
".":[]string{"KC_DOT"},
";":[]string{"KC_SCLN"},
"?":[]string{"KC_QUOT"},
"a":[]string{"KC_A"},
"b":[]string{"KC_B"},
"c":[]string{"KC_C"},
"d":[]string{"KC_D"},
"e":[]string{"KC_E"},
"f":[]string{"KC_F"},
"g":[]string{"KC_G"},
"h":[]string{"KC_H"},
"i":[]string{"KC_I"},
"j":[]string{"KC_J"},
"k":[]string{"KC_K"},
"l":[]string{"KC_L"},
"m":[]string{"KC_M"},
"n":[]string{"KC_N"},
"o":[]string{"KC_O"},
"p":[]string{"KC_P"},
"q":[]string{"KC_Q"},
"r":[]string{"KC_R"},
"s":[]string{"KC_S"},
"t":[]string{"KC_T"},
"u":[]string{"KC_U"},
"v":[]string{"KC_V"},
"w":[]string{"KC_W"},
"x":[]string{"KC_X"},
"y":[]string{"KC_Y"},
"z":[]string{"KC_Z"},

//specials
"bksp":[]string{"KC_BSPC"},
"enter":[]string{"KC_ENT"},
//"numsym":[]string{"NUM)"}, //TODO: Sticky
//"LETTERS":[]string{"KC_SPC"},

//symbols
"[":[]string{"KC_LBRC"},
"]":[]string{"KC_RBRC"},
" ":[]string{"KC_SPC"},
"1":[]string{"KC_1"},
"2":[]string{"KC_2"},
"3":[]string{"KC_3"},
"4":[]string{"KC_4"},
"5":[]string{"KC_5"},
"6":[]string{"KC_6"},
"7":[]string{"KC_7"},
"8":[]string{"KC_8"},
"9":[]string{"KC_9"},
"0":[]string{"KC_0"},
"=":[]string{"KC_EQL"},
"Fn":[]string{"KC_NO"},
"SPACE":[]string{"KC_SPC"},
"Home":[]string{"KC_HOME"},
"End":[]string{"KC_END"},
"    ":[]string{"KC_TAB"},
"   ":[]string{"KC_TAB"},
"\t":[]string{"KC_TAB"},
"`":[]string{"KC_GRV"},
}
