package main

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

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))
				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])
		}
	}
}

func ListFonts(filename 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 true {
		fontdef := make([]byte, 4)
		read, _ := f.Read(fontdef)
		if read != 4 {
			fmt.Println("*END*")
			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)

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

		// blocksize := make([]byte, 2)
		// f.Read(blocksize)

		var BlockSize int16
		binary.Read(f, binary.LittleEndian, &BlockSize)
		// fmt.Printf("Size: %d / %x\n", BlockSize, BlockSize)

		letterOffsets := make([]int16, 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)
	}
}

func byte_to_text(line []byte) string {
	var output string

	for _, ch := range line {
		output += fmt.Sprintf("0x%02x,", ch)
	}
	if len(output) > 0 {
		output = output[:len(output)-1]
	}
	return output
}

func text_to_hextext(line string) string {
	var output string
	// output = "\""
	for _, ch := range []byte(line) {
		// output += fmt.Sprintf("\\x%02x", ch)
		output += fmt.Sprintf("0x%02x,", ch)
	}
	if len(output) > 1 {
		output = output[:len(output)-1]
	}
	// output += "\""
	return output
}

func ExtractColor(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("package main\n")
	writer.WriteString("// " + name + "\n\n")

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

	writer.WriteString("func Font" + Name + "() door.ColorFont {\n")
	var output string
	output = "  return 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 = "    Data: [][][]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)-1]
		}

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

func ExtractBlock(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()
}

func ExtractFonts(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 true {
		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:
				ExtractBlock(Name, letterOffsets, data)
			case 2:
				ExtractColor(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:
						ExtractBlock(Name, letterOffsets, data)
					case 2:
						ExtractColor(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 accecpted
type arrayFlags []string

func (i *arrayFlags) String() string {
	// change this, this is just can example 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})
		}
	}
}

func main() {

	var fonts string
	var defaultPackage string = "main"
	var listFonts bool
	var allFonts bool
	var convert arrayFlags

	flag.StringVar(&fonts, "f", "", "Font(s) to extract")
	flag.BoolVar(&allFonts, "a", false, "Extract All Fonts")
	flag.StringVar(&defaultPackage, "p", "main", "Package name to use")
	flag.BoolVar(&listFonts, "l", false, "List Fonts")
	flag.Var(&convert, "c", "Convert Color to Color n,n")
	flag.Parse()

	ParseColorConvert(convert)

	// fmt.Printf("Convert: %#v\n", convert)
	// fmt.Printf("Conversion: %#v\n", Conversion)

	if flag.NArg() == 0 {
		fmt.Println("Font-Out - A TDF (TheDraw Font) file processor.")
		fmt.Println("No TDF filenames given.")
		flag.PrintDefaults()
		os.Exit(2)
	}

	if !listFonts {
		fmt.Printf("package %s\n\n", defaultPackage)
		fmt.Println("import (")
		fmt.Println("  \"red-green/door\"")
		fmt.Println(")")
		fmt.Println("")
	} else {
		fmt.Println("Font-Out - A TDF (TheDraw Font) file processor.")
	}

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

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

	if !listFonts && len(fontList) == 0 {
		fmt.Println("Default to -l (List Fonts)")
		listFonts = true
	}

	for _, fontfile := range flag.Args() {
		if listFonts {
			ListFonts(fontfile)
		} else {
			if len(fontList) > 0 {
				ExtractFonts(fontfile, fontList)
			}
		}
	}
}