package main

import (
	"encoding/binary"
	"fmt"
	"os"
	"red-green/door"
	"regexp"
	"strings"
)

// Best used with | less -RS

// Attempt to fix broken fonts.
// This verifies that the character offsets are proceeded
// by a null character.
func FontFixup(offsets []uint16, data *[]byte) bool {
	fixed := false
	for _, offset := range offsets {
		if offset == 65535 {
			continue
		}
		if offset == 0 {
			continue
		}
		if (*data)[offset-1] != 0 {
			(*data)[offset-1] = 0
			fixed = true
		}
	}
	return fixed
}

func StripANSIColors(text string) string {
	var re *regexp.Regexp

	re, _ = regexp.Compile("\x1b\\[[0-9;]*m")
	return re.ReplaceAllString(text, "")
}

func Show(parts []string) {
	var reset string = "\x1b[0m"

	if len(parts) > 0 {
		for _, line := range parts {
			fmt.Printf("%s%s\n", door.CP437_to_Unicode(string(line)), reset)
			// fmt.Printf("%s%s\n", line, reset)
		}
		fmt.Println("")
	}
}

// Examine character indexes, are there lower case letter? are they unique?
// What are the available characters in this font?
func FontInfo(characters []int) (lower bool, lowerUnique bool, available string) {
	// Does it have lowercase?
	// Is lowercase unique?
	// Available characters in font

	// 33 -126
	lower = characters[int(rune('a')-33)] != -1
	lowerUnique = characters[int(rune('A')-33)] != characters[int(rune('a')-33)]

	for idx, offset := range characters {
		char := idx + 33
		if offset != -1 {
			available += string(rune(char))
		}
	}
	return
}

func ShowBlockFontSize(bf *door.BlockFont, text string, width int) {
	var output []string
	output, _ = bf.Output(text)
	if width == 0 || len(output[0]) < width {
		Show(output)
		return
	}

	for text != "" {
	nextPart:
		for pos := 1; pos < len(text); pos++ {
			output, _ = bf.Output(text[:pos])

			if len(output[0]) >= width {
				// use previous
				output, _ = bf.Output(text[:pos-1])
				Show(output)
				text = text[pos-1:]
				goto nextPart
			}
		}
		output, _ = bf.Output(text)
		Show(output)
		text = ""
	}
}

func ShowBlockFont(name string, bf *door.BlockFont, width int) {
	var low, uniq bool
	var avail string
	low, uniq, avail = FontInfo(bf.Characters)
	fmt.Printf("Font: %s (LowerCase %t, Unique Lower %t, [%s]\n", name, low, uniq, avail)

	ShowBlockFontSize(bf, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", width)
	/*
		var output []string
		output, _ = bf.Output("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
		Show(output)
	*/
	if low && uniq {
		ShowBlockFontSize(bf, "abcdefghijklmnopqrstuvwxyz", width)
		/*
			output, _ = bf.Output("abcdefghijklmnopqrstuvwxyz")
			Show(output)
		*/
	}

	var leftovers string = avail
	var reg *regexp.Regexp
	reg, _ = regexp.Compile("[a-zA-Z]+")
	var left string = reg.ReplaceAllString(leftovers, "")
	// output, _ = bf.Output("abcdef")
	// Show(output)
	if len(left) > 0 {
		ShowBlockFontSize(bf, left, width)
		/*
			output, _ = bf.Output(left)
			Show(output)
		*/
	}
}

func ShowColorFontSize(bf *door.ColorFont, text string, width int) {
	var output []string
	output, _ = bf.Output(text)
	if width == 0 || len(output[0]) < width {
		Show(output)
		return
	}

	// Guessing doesn't work very well

	for text != "" {
	nextPart:
		// fmt.Printf("TEXT: [%s]\n", text)
		for pos := 1; pos < len(text); pos++ {
			output, _ = bf.Output(text[:pos])
			// fmt.Println(pos, len(StripANSIColors(output[0])))

			if len(StripANSIColors(output[0])) >= width {
				// use previous
				output, _ = bf.Output(text[:pos-1])
				// fmt.Println("OUT:", text[:pos-1])
				Show(output)
				text = text[pos-1:]
				// fmt.Println("TEXT:", text)
				goto nextPart
			}
		}
		output, _ = bf.Output(text)
		Show(output)
		text = ""
	}

	/*
		var guess int = len(StripANSIColors(output[0])) / len(text)
		guess = (width / guess)

		for text != "" {
			var part string
			if guess >= len(text) {
				part = text
			} else {
				part = text[0:guess]
			}
			output, _ = bf.Output(part)
			Show(output)
			if guess >= len(text) {
				text = ""
			} else {
				text = text[guess:]
			}
		}
	*/

}
func ShowColorFont(name string, cf *door.ColorFont, width int) {
	var low, uniq bool
	var avail string
	low, uniq, avail = FontInfo(cf.Characters)
	fmt.Printf("Font: %s (LowerCase %t, Unique Lower %t, [%s]\n", name, low, uniq, avail)

	ShowColorFontSize(cf, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", width)
	/*
		// fmt.Printf("Font: %s\n", name)
		var output []string
		output, _ = cf.Output("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
		Show(output)
	*/
	if low && uniq {
		ShowColorFontSize(cf, "abcdefghijklmnopqrstuvwxyz", width)
		/*
			output, _ = cf.Output("abcdefghijklmnopqrstuvwxyz")
			Show(output)
		*/
	}

	var leftovers string = avail
	var reg *regexp.Regexp
	reg, _ = regexp.Compile("[a-zA-Z]+")
	var left string = reg.ReplaceAllString(leftovers, "")
	// output, _ = bf.Output("abcdef")
	// Show(output)
	if len(left) > 0 {
		ShowColorFontSize(cf, left, width)
		/*
			output, _ = cf.Output(left)
			Show(output)
		*/
	}
}

// Example using FontOutput interface
/*
func ShowFont(name string, f FontOutput) {
	var low, uniq bool
	var avail string

	v, ok := f.(BlockFont)
	if ok {
		low, uniq, avail := FontInfo(v.characters)
	}

	v, ok = f.(ColorFont)
	if ok {
		low, uniq, avail := FontInfo(v.characters)
	}

	// low, uniq, avail := FontInfo((*f).characters)
	fmt.Printf("Font: %s (LowerCase %t, Unique Lower %t, [%s]\n", name, low, uniq, avail)

	// fmt.Printf("Font: %s\n", name)
	output, _ := f.Output("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
	Show(output)

	if low && uniq {
		output, _ = f.Output("abcdefghijklmnopqrstuvwxyz")
		Show(output)
	}

	leftovers := avail
	reg, _ := regexp.Compile("[a-zA-Z]+")
	left := reg.ReplaceAllString(leftovers, "")
	//  output, _ = bf.Output("abcdef")
	// Show(output)

	// left := "0123456789!@#$%^&*()-=_+[]{}~"

	if len(left) > 0 {
		output, _ = f.Output(left)
		Show(output)
	}
}
*/

// displays fonts
func DisplayFonts(filename string, fonts []string, width int) {
	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)

		// The problem isn't that the offsets are > BlockSize
		// Detect "truncated" fonts...
		broken := false
		for idx, i := range letterOffsets {
			if i != 65535 {
				if i >= uint16(BlockSize) {
					broken = true
					// Mark character offset as not used
					letterOffsets[idx] = 65535
					// fmt.Printf("offset %d / %x is out of range %d / %x\n", i, i, BlockSize, BlockSize)
				}
			}
		}

		if broken {
			fmt.Println("FONT is corrupted/truncated.  FIX attempted.")
		}

		if FontFixup(letterOffsets, &data) {
			fmt.Printf("Attempting to *FIX* Font %s\n", Name)
		}

		// Special case where they are asking for all fonts
		if len(fonts) == 1 && fonts[0] == "*" {

			switch FontType {
			case 1:
				bf := ExtractBlock(Name, letterOffsets, data)
				if len(bf.Characters) == 0 {
					fmt.Printf("%s : BLOCK FONT FAIL\n", Name)
				} else {
					// ShowFont(Name, &bf)
					ShowBlockFont(Name, &bf, width)
				}
			case 2:
				cf := ExtractColor(Name, letterOffsets, data)
				if len(cf.Characters) == 0 {
					fmt.Printf("%s : COLOR FONT FAIL\n", Name)
				} else {
					// ShowFont(Name, &cf)
					ShowColorFont(Name, &cf, width)
				}
			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:
						bf := ExtractBlock(Name, letterOffsets, data)
						if len(bf.Characters) == 0 {
							fmt.Printf("%s : BLOCK FONT FAIL\n", Name)
						} else {
							ShowBlockFont(Name, &bf, width)
						}
					case 2:
						cf := ExtractColor(Name, letterOffsets, data)
						if len(cf.Characters) == 0 {
							fmt.Printf("%s : COLOR FONT FAIL\n", Name)
						} else {
							ShowColorFont(Name, &cf, width)
						}
					default:
						fmt.Printf("Sorry, I can't handle Font: %s Type %d!\n", Name, FontType)
					}
					break
				}
			}
		}
	}
}

/*
func main() {
	fmt.Println("Font-Show - A TDF (TheDraw Font) Viewer.")
	var fonts string
	var listFonts bool
	var allFonts bool
	door.Unicode = true

	flag.StringVar(&fonts, "f", "", "Font(s) to show")
	flag.BoolVar(&allFonts, "a", false, "Show All Fonts")
	flag.BoolVar(&listFonts, "l", false, "List Fonts")
	flag.Parse()

	if flag.NArg() == 0 {
		fmt.Println("I need a TDF filename.")
		flag.PrintDefaults()
		os.Exit(2)
	}

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

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

	for _, fontfile := range flag.Args() {
		if listFonts {
			ListFonts(fontfile)
		}

		if len(fontList) > 0 {
			DisplayFonts(fontfile, fontList)
		}
	}
}
*/