package main

import (
	"encoding/binary"
	"flag"
	"fmt"
	"os"
	"strconv"
	"strings"
)

// http://www.roysac.com/blog/2014/04/thedraw-fonts-file-tdf-specifications/

var Conversion [][2]int

// copied from tdfont.go, and modified

func thedraw_to_ansi(c int) int {
	trans := []int{0, 4, 2, 6, 1, 5, 3, 7}
	//             0, 1, 2, 3, 4, 5, 6, 7
	return trans[c]
}

func MatchStyle(color byte, look byte) int {
	var match int = 0
	if ((color >> 4) & 0x07) == look {
		// Top
		match |= 1
	}
	if (color & 0x07) == look {
		// Bottom
		match |= 2
	}
	return match
}

func PatchColor(color byte, new_color byte, style int) byte {
	var c byte = color
	if style&1 == 1 {
		c = (c & 0x8f) | new_color<<4
	}
	if style&2 == 2 {
		c = (c & 0xf8) | new_color
	}
	return c
}

type ColorMap map[[2]int][][2]int

func Scan(block [][][]byte, find_color int) ColorMap {
	var Targets ColorMap = make(ColorMap, 0)
	// Scan the font looking for the given color FG/BG
	// Covert color code to TheDraw Color
	actual := byte(thedraw_to_ansi(find_color))

	for charIndex := range block {
		for lineIndex := range block[charIndex] {
			var found bool = false
			var patches [][2]int = make([][2]int, 0)
			for offset := 1; offset < len(block[charIndex][lineIndex]); offset += 2 {
				color := block[charIndex][lineIndex][offset]
				style := MatchStyle(color, actual)
				if style != 0 {
					// log.Printf("color: %x actual %x style: %d\n", color, actual, style)
					patches = append(patches, [2]int{offset, style})
					found = true
				}
			}
			if found {
				pos := [2]int{charIndex, lineIndex}
				Targets[pos] = make([][2]int, len(patches))
				copy(Targets[pos], patches)
				/*
					for i := range patches {
						Targets[pos][i] = patches[i]
					}
				*/
				// Targets[pos] = patches
			}
		}
	}
	return Targets
}

func Modify(block [][][]byte, new_color int, Targets ColorMap) {
	// Covert color code to TheDraw Color
	actual := byte(thedraw_to_ansi(new_color))
	for pos, patch := range Targets {
		for _, p := range patch {
			block[pos[0]][pos[1]][p[0]] = PatchColor(block[pos[0]][pos[1]][p[0]], actual, p[1])
		}
	}
}

// Use ExtractColor to get the font -- then output that?

func OutputColor(writer *os.File, name string, offsets []uint16, data []byte) {
	// fmt.Printf("Extract Color Font: %s\n", name)
	var indexes []int
	var blocks [][][]byte
	var current [][]byte
	var line []byte

	pos := 0
	for pos < len(data) {
		indexes = append(indexes, pos)
		current = make([][]byte, 0)
		line = make([]byte, 0)

		// We don't use these.
		// w = data[pos]
		// h = data[pos+1]
		pos += 2

		// process this character
		for pos < len(data) {
			ch := data[pos]
			pos++
			if ch == 0x00 {
				// end of character
				current = append(current, line)
				blocks = append(blocks, current)
				// current = make([][]byte, 0)
				// line = make([]byte, 0)
				break
			}
			if ch == 0x0d {
				// end of this character line
				current = append(current, line)
				line = make([]byte, 0)
				continue
			}
			if ch == 0x26 {
				// & descender mark
				continue
			}
			line = append(line, ch)
			color := data[pos]
			pos++
			line = append(line, color)
		}
	}

	// offset optimization:
	var single []int
	for _, o := range offsets {
		if o == 65535 {
			single = append(single, -1)
			continue
		}
		for idx, i := range indexes {
			if o == uint16(i) {
				single = append(single, idx)
				break
			}
		}
	}

	/*
		// Handle Names with spaces
		filename := fmt.Sprintf("%s_font.go", strings.Replace(name, " ", "", -1))

		fp, err := os.Create(filename)
		if err != nil {
			panic(err)
		}
		fmt.Printf("Writing: %s\n", filename)

		defer fp.Close()
		writer := bufio.NewWriter(fp)
	*/
	// writer := bufio.NewWriter(os.Stdout)

	writer.WriteString("\n// " + name + "\n\n")

	// Name := strings.ToUpper(name)
	Name := strings.Replace(name, " ", "", -1)

	writer.WriteString("func Font" + Name + "() door.ColorFont {\n")
	var output string
	output = "\treturn door.ColorFont{Characters: []int{"
	for _, s := range single {
		output += strconv.Itoa(s) + ", "
	}
	output = output[:len(output)-2] + "},\n"
	writer.WriteString(output)
	// writer.Flush()

	if len(Conversion) > 0 {
		// Color Convert time!
		var Maps []map[[2]int][][2]int = make([]map[[2]int][][2]int, len(Conversion))
		for idx, codes := range Conversion {
			Maps[idx] = Scan(blocks, codes[0])
		}
		for idx, codes := range Conversion {
			Modify(blocks, codes[1], Maps[idx])
		}
	}

	output = "\t\tData: [][][]byte{"
	for _, blk := range blocks {
		output += "{"
		if len(blk) == 0 {
			output += "{},"
		} else {
			for _, inner := range blk {
				// output += text_to_hextext(b) + ","
				output += "{" + byte_to_text(inner) + "}, "
			}
			output = output[:len(output)-2]
		}

		output += "},\n"
		writer.WriteString(output)
		output = "\t\t\t"
	}
	writer.WriteString("\t\t}}\n")
	writer.WriteString("}\n")
	// writer.Flush()
}

// Use ExtractBlock to get the Font, and then output that?

func OutputBlock(writer *os.File, name string, offsets []uint16, data []byte) {
	// fmt.Printf("Extract Block Font: %s\n", name)
	var indexes []int
	var blocks [][][]byte
	var current [][]byte
	var line []byte

	pos := 0
	for pos < len(data) {
		indexes = append(indexes, pos)
		current = make([][]byte, 0)
		line = make([]byte, 0)

		// We don't use these
		// w = data[pos]
		// h = data[pos+1]
		pos += 2

		// process this character
		for pos < len(data) {
			ch := data[pos]
			pos++
			if ch == 0x00 {
				// end of character
				current = append(current, line)
				blocks = append(blocks, current)
				// current = make([][]byte, 0)
				// line = make([]byte, 0)
				break
			}
			if ch == 0x0d {
				// end of this character line
				current = append(current, line)
				line = make([]byte, 0)
				continue
			}
			if ch == 0x26 {
				// & descender mark
				continue
			}
			line = append(line, ch)
		}
	}

	// offset optimization:
	var single []int
	for _, o := range offsets {
		if o == 65535 {
			single = append(single, -1)
			continue
		}
		for idx, i := range indexes {
			if o == uint16(i) {
				single = append(single, idx)
				break
			}
		}
	}

	/*
		// Handle Names with spaces
		filename := fmt.Sprintf("%s_font.go", strings.Replace(name, " ", "", -1))

		fp, err := os.Create(filename)
		if err != nil {
			panic(err)
		}
		fmt.Printf("Writing: %s\n", filename)

		defer fp.Close()
		writer := bufio.NewWriter(fp)
	*/
	// writer := bufio.NewWriter(os.Stdout)

	// Should this output routine be part of the BlockFont?
	// I think so!

	// writer.WriteString("package main\n")
	writer.WriteString("// " + name + "\n\n")

	// Name := strings.ToUpper(name)
	Name := strings.Replace(name, " ", "", -1)

	writer.WriteString("func Font" + Name + "() door.BlockFont {\n")
	var output string
	output = "  return door.BlockFont{Characters: []int{"
	for _, s := range single {
		output += strconv.Itoa(s) + ", "
	}
	output = output[:len(output)-2] + "},\n"
	writer.WriteString(output)
	// writer.Flush()

	output = "    Data: [][][]byte{"
	for _, blk := range blocks {
		output += "{"
		if len(blk) == 0 {
			output += "{},"
		} else {
			for _, inner := range blk {
				output += "{" + byte_to_text(inner) + "},"
			}
			output = output[:len(output)-1]
		}

		output += "},\n"

		// output = output[:len(output)-1]
		// output += "},\n"
		writer.WriteString(output)
		output = "      "
	}
	writer.WriteString("    }}\n")
	writer.WriteString("}\n")
	// writer.Flush()
}

// outputs fonts
func OutputFonts(writer *os.File, filename string, fonts []string) {
	f, err := os.Open(filename)
	if err != nil {
		fmt.Printf("Open(%s): %s\n", filename, err)
		panic(err)
	}
	defer f.Close()
	tdfonts := make([]byte, 20)
	f.Read(tdfonts)
	for {
		fontdef := make([]byte, 4)
		read, _ := f.Read(fontdef)
		if read != 4 {
			break
		}
		fontname := make([]byte, 13)
		f.Read(fontname)
		Name := strings.Trim(string(fontname[1:]), "\x00")
		// fmt.Printf("Font: %s\n", Name)
		f.Read(fontdef)
		single := make([]byte, 1)

		var FontType int8
		binary.Read(f, binary.LittleEndian, &FontType)

		// fmt.Printf("Font: %s (type %d)\n", Name, FontType)
		f.Read(single) // Spacing

		var BlockSize int16
		binary.Read(f, binary.LittleEndian, &BlockSize)

		letterOffsets := make([]uint16, 94)
		binary.Read(f, binary.LittleEndian, &letterOffsets)

		if false {
			for idx, i := range letterOffsets {
				fmt.Printf(" %04X", i)
				if (idx+1)%10 == 0 {
					fmt.Println("")
				}
			}
			fmt.Println("")
		}

		data := make([]byte, BlockSize)
		binary.Read(f, binary.LittleEndian, &data)

		// Special case where they are asking for all fonts
		if len(fonts) == 1 && fonts[0] == "*" {
			switch FontType {
			case 1:
				OutputBlock(writer, Name, letterOffsets, data)
			case 2:
				OutputColor(writer, Name, letterOffsets, data)
			default:
				fmt.Printf("// Sorry, I can't handle Font: %s Type %d!\n", Name, FontType)
			}
		} else {
			for _, f := range fonts {
				if Name == f {
					switch FontType {
					case 1:
						OutputBlock(writer, Name, letterOffsets, data)
					case 2:
						OutputColor(writer, Name, letterOffsets, data)
					default:
						fmt.Printf("// Sorry, I can't handle Font: %s Type %d!\n", Name, FontType)
					}
					break
				}
			}
		}
	}
}

// Created so that multiple inputs can be accepted
type arrayFlags []string

func (i *arrayFlags) String() string {
	// change this, this example is just to satisfy the interface
	result := ""
	for _, str := range *i {
		if result != "" {
			result += ", "
		}
		result += str
	}
	return result
}

func (i *arrayFlags) Set(value string) error {
	*i = append(*i, strings.TrimSpace(value))
	return nil
}

func ParseColorConvert(convert arrayFlags) {
	Conversion = make([][2]int, 0)
	if len(convert) > 0 {
		// Something to do
		for _, color := range convert {
			split := strings.Split(color, ",")
			v1, _ := strconv.Atoi(split[0])
			v2, _ := strconv.Atoi(split[1])
			Conversion = append(Conversion, [2]int{v1, v2})
		}
	}
}

/*
I could envision something like this:

https://blog.ralch.com/articles/golang-subcommands/
https://stackoverflow.com/questions/23725924/can-gos-flag-package-print-usage

list   (font files)
show -a -f (font files)
extract -all -font -color -package -output (font files)

show - under windows ?  :cat_scream:

*/

func main() {
	var usage func() = func() {
		fmt.Println("Usage: font-util <command> [<args>] fontfile.tdf...")
		fmt.Println("  list    - List available fonts")
		fmt.Println("  show    - Show fonts")
		fmt.Println("  extract - Extract to source.go file")
	}

	if len(os.Args) == 1 {
		usage()
		return
	}

	var fonts string
	var defaultPackage string = "main"
	var allFonts bool
	var convert arrayFlags
	var output string
	var width int

	var listCommand *flag.FlagSet = flag.NewFlagSet("list", flag.ExitOnError)
	var showCommand *flag.FlagSet = flag.NewFlagSet("show", flag.ExitOnError)
	showCommand.BoolVar(&allFonts, "a", false, "Show All Fonts")
	showCommand.StringVar(&fonts, "f", "", "Fonts to Show font1,font2,font3")
	showCommand.IntVar(&width, "w", 0, "Width to Show fonts")

	var extractCommand *flag.FlagSet = flag.NewFlagSet("extract", flag.ExitOnError)
	extractCommand.BoolVar(&allFonts, "a", false, "Extract All Fonts")
	extractCommand.StringVar(&fonts, "f", "", "Fonts to Extract font1,font2,font3")
	extractCommand.StringVar(&defaultPackage, "p", "main", "Package name to use")
	extractCommand.Var(&convert, "c", "Convert Color to Color n,n")
	extractCommand.StringVar(&output, "o", "", "Output to file")

	switch os.Args[1] {
	case "list":
		listCommand.Parse(os.Args[2:])
		if listCommand.Parsed() {
			if len(listCommand.Args()) == 0 {
				listCommand.Usage()
				fmt.Println("No TDF Font files given.")
				os.Exit(2)
			}

			for _, fontfile := range listCommand.Args() {
				ListFonts(fontfile)
			}
		}
		os.Exit(0)

	case "show":
		showCommand.Parse(os.Args[2:])

	case "extract":
		extractCommand.Parse(os.Args[2:])

	default:
		usage()
		fmt.Printf("%q is not a valid command.\n", os.Args[1])
		os.Exit(2)
	}

	var fontList []string
	if len(fonts) > 0 {
		fontList = strings.Split(fonts, ",")
	}

	if allFonts {
		fontList = make([]string, 0)
		fontList = append(fontList, "*")
	}

	var err error

	if showCommand.Parsed() {
		// Show Fonts
		var exit bool
		if len(fontList) == 0 {
			showCommand.Usage()
			fmt.Println("No Fonts selected.")
			exit = true
		}

		if len(showCommand.Args()) == 0 {
			if !exit {
				showCommand.Usage()
			}
			fmt.Println("No TDF Fonts files given.")
			exit = true
		}

		if exit {
			os.Exit(2)
		}
		for _, fontfile := range showCommand.Args() {
			fmt.Println("FILE:", fontfile)
			DisplayFonts(fontfile, fontList, width)
		}
	}

	if extractCommand.Parsed() {
		// Extract Fonts
		var exit bool

		if len(fontList) == 0 {
			extractCommand.Usage()
			fmt.Println("No Fonts selected.")
			exit = true

		}

		if len(extractCommand.Args()) == 0 {
			if !exit {
				extractCommand.Usage()
			}
			fmt.Println("No TDF Fonts files given.")
			exit = true
		}

		if exit {
			os.Exit(2)
		}

		var saveTo *os.File

		ParseColorConvert(convert)

		// Setup saveTo
		if output == "" {
			saveTo = os.Stdout
		} else {
			saveTo, err = os.Create(output)
			if err != nil {
				fmt.Println("Create", output, "error:", err)
				os.Exit(2)
			}
		}

		fmt.Fprintf(saveTo, "package %s\n\n", defaultPackage)
		fmt.Fprintf(saveTo, "import (\n\t\"red-green/door\"\n)\n")

		for _, fontfile := range extractCommand.Args() {
			OutputFonts(saveTo, fontfile, fontList)
		}
	}
}