package door

import (
	"bytes"
	"fmt"
	"strconv"
	"strings"
)

type BlockFont struct {
	Characters []int
	Data       [][][]byte
}

/*
func BytesHexed(output []byte) {
	fmt.Printf("BH: ")
	for _, ch := range output {
		fmt.Printf("%02x ", ch)
	}
	fmt.Println("")
}

func BytesArrayHexed(output *[][]byte) {
	for _, out := range *output {
		BytesHexed(out)
	}
}
*/

// Make []strings all the same length
func normalizeBlock(block *[][]byte) {
	var max int
	// StringO(block)
	for _, line := range *block {
		var l int = len(line)
		if max < l {
			max = l
		}
	}
	// fmt.Printf("max = %d\n", max)
	for idx := range *block {
		// l := len(line)
		for len((*block)[idx]) < max {
			//(*block)[idx] += byte(0x20) //append((*block)[idx], byte{0x20})
			(*block)[idx] = append((*block)[idx], byte(0x20))
		}
	}
	// StringO(block)
	// fmt.Println("== normalized ==")
}

// Get Character information from BlockFont return Normalize
func (bf *BlockFont) GetCharacter(c int) [][]byte {
	var result [][]byte

	if c == 32 {
		// Space character
		result = append(result, []byte{0x20, 0x20})
		return result
	}

	// 33-126 are the possible characters.
	if c >= 33 && c <= 126 {
		c -= 33
		var idx int = bf.Characters[c]
		if idx == -1 {
			// This character is not supported by this font.
			return result
		}
		result = bf.Data[bf.Characters[c]]
		// fmt.Println("normalizeCharBlock")
		normalizeBlock(&result)
		// fmt.Println("normalizeChar done")
		return result
	} else {
		return result
	}
}

func expandBlock(block *[][]byte) {
	// l := len((*block)[0])
	var l int = len((*block)[0])
	var blank []byte = []byte{0x20}
	*block = append(*block, bytes.Repeat(blank, l))
}

// Given text, translate to thedraw font.
func (bf *BlockFont) Output(input string) ([]string, int) {
	var out [][]byte
	var max int

	for _, ch := range input {
		// fmt.Printf("%d %x\n", ch, ch)
		block := bf.GetCharacter(int(ch))
		// fmt.Println("ok")
		var l int = len(block)
		max += l
		if l != 0 {
			if len(out) == 0 {
				// First time
				out = append(out, block...)
				/*
					for _, b := range block {
						out = append(out, b)
					}
				*/
			} else {
				if len(out) != 0 {
					for l > len(out) {
						// We need to expand the out
						// fmt.Println("expandBlock")
						expandBlock(&out)
					}
				}
				// fmt.Println("Append block to out")

				// Ok, we have something!
				for idx, b := range block {
					out[idx] = append(out[idx], byte(0x20))
					out[idx] = append(out[idx], b...)

					/*
						for _, inner := range b {
							out[idx] = append(out[idx], inner)
						}
					*/

					// out[idx] += byte(0x20) + b
					// fmt.Printf("%s\n", CP437_to_Unicode(b))
				}
			}
		}
		// fmt.Println("normalizeBlock")
		normalizeBlock(&out)
		// fmt.Println("normalizeBlock done")
	}

	var output []string
	for idx := range out {
		if Unicode {
			output = append(output, CP437_to_Unicode(string(out[idx])))
		} else {
			output = append(output, string(out[idx]))
		}
	}
	return output, len(out[0])
}

type ColorFont struct {
	Characters []int
	Data       [][][]byte
}

func normalizeColor(block *[][]byte) int {
	var max int
	for _, line := range *block {
		l := len(line)
		if max < l {
			max = l
		}
	}
	for idx := range *block {
		// l := len(line)
		var blank []byte = []byte{0x20, 0x0f}
		for len((*block)[idx]) < max {
			(*block)[idx] = append((*block)[idx], blank...)

			/*
				for _, b := range blank {
					(*block)[idx] = append((*block)[idx], b)
				}
			*/
			// (*block)[idx] += strings.Repeat(" \x0f", max-l/2)
		}
	}
	return max
}

func (cf *ColorFont) GetCharacter(c int) ([][]byte, int) {
	var result [][]byte

	if c == 32 {
		var blank []byte = []byte{0x20, 0x0f, 0x20, 0x0f}
		result = append(result, blank) // " \x0f \x0f")
		return result, len(result[0]) / 2
	}

	if c >= 33 && c <= 126 {
		c -= 33
		var idx int = cf.Characters[c]
		if idx == -1 {
			return result, 0
		}
		result = cf.Data[cf.Characters[c]]
		// fmt.Println("Character:")
		// BytesArrayHexed(&result)
		// fmt.Println("normalizing...")
		var max int = normalizeColor(&result)
		// BytesArrayHexed(&result)
		// StringHexO(&result)
		return result, max
	} else {
		return result, 0
	}
}

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

/*
// Before color output was optimized
func colorout(c int) string {
	bg := thedraw_to_ansi(c / 16)
	fg := c % 16
	bold := (fg & 0x8) == 0x8
	fg = thedraw_to_ansi(fg & 0x7)
	if bold {
		return fmt.Sprintf("\x1b[0;1;%d;%dm", fg+30, bg+40)
	} else {
		return fmt.Sprintf("\x1b[0;%d;%dm", fg+30, bg+40)
	}
}
*/

type ColorParts struct {
	Background int
	Foreground int
	Bold       bool
}

func ColorSplit(color int) ColorParts {
	return ColorParts{Background: thedraw_to_ansi(color / 16), Foreground: thedraw_to_ansi(color & 7), Bold: (color%16)&0x08 == 0x08}
}

func ColorOutput(previous int, color int) string {
	var prev ColorParts = ColorSplit(previous)
	var c ColorParts = ColorSplit(color)
	var codes []int8

	if c.Bold {
		if !prev.Bold {
			codes = append(codes, 1)
		}
	} else {
		if prev.Bold {
			// bold was set previously, there's only one way to reset it
			codes = append(codes, 0)
			prev.Background = 0
			prev.Foreground = 7
		}
	}

	if prev.Background == 0 && prev.Foreground == 0 {
		// output everything
		codes = append(codes, int8(c.Foreground)+30)
		codes = append(codes, int8(c.Background)+40)
	} else {
		if c.Foreground != prev.Foreground {
			codes = append(codes, int8(c.Foreground)+30)
		}
		if c.Background != prev.Background {
			codes = append(codes, int8(c.Background)+40)
		}
	}

	if len(codes) == 0 {
		// Everything matched
		return ""
	}
	var text []string
	for _, code := range codes {
		text = append(text, strconv.Itoa(int(code)))
	}

	return "\x1b[" + strings.Join(text, ";") + "m"
}

func Colorize(input []byte) []byte {
	var result []byte
	// runes := []rune(input)
	var previous int

	// BytesHexed(input)

	for pos := 0; pos < len(input); pos += 2 {
		var ch byte = input[pos]
		var color int = int(input[pos+1])
		// fmt.Printf("%d : CH %d / %x, Color %d / %x\n", pos, ch, ch, color, color)
		var colorstring string = ColorOutput(previous, color)
		result = append(result, []byte(colorstring)...)
		/*
			for _, c := range []byte(colorstring) {
				result = append(result, c)
			}
		*/

		// result = append(result, []byte(colorstring))

		// result += []byte(ColorOutput(previous, color))
		// result += ColorOutput(previous, color) + string(ch)
		result = append(result, ch)
		// result += string(ch)
		previous = color
		// result += colorout(color) + string(ch)
	}
	return result
}

/*
func __expandColor(block *[]string, need int) {
	*block = append(*block, strings.Repeat(" \x0f", need))
}
*/

func expandColor(block *[][]byte, need int) {
	var blank []byte = []byte{0x20, 0x0f}
	// *block = append(*block, strings.Repeat(" \x0f", need))
	*block = append(*block, bytes.Repeat(blank, need))
}

func (bf *ColorFont) Output(input string) ([]string, int) {
	var out [][]byte
	var max int

	for _, ch := range input {
		// fmt.Printf("%d %x\n", ch, ch)
		var block [][]byte
		var blklen int
		block, blklen = bf.GetCharacter(int(ch))
		if blklen == 0 {
			continue
		}

		var l int = len(block)
		max += blklen
		if l != 0 {
			if len(out) == 0 {
				// First time
				out = append(out, block...)
				/*
					for _, b := range block {
						out = append(out, b)
					}
				*/

			} else {
				if len(out) != 0 {
					for l > len(out) {
						// We need to expand the out
						expandColor(&out, len(out[0])/2)
						// ExpandColor(&out)
					}
				}

				for len(out) > len(block) {
					// We need to expand the block out
					expandColor(&block, len(block)/2)
				}

				// Normalizing the character blocks
				normalizeColor(&block)

				if len(out) != len(block) {
					panic(fmt.Sprintf("len(out) %d != len(block) %d", len(out), len(block)))
				}

				var blank []byte = []byte{0x20, 0x0f}
				// Ok, we have something!
				for idx, b := range block {
					/*
						out[idx] = append(out[idx], byte(0x20))
						out[idx] = append(out[idx], byte(0x0f))
					*/
					out[idx] = append(out[idx], blank...)
					out[idx] = append(out[idx], b...)
					/*
						for _, inner := range b {
							out[idx] = append(out[idx], inner)
						}
					*/
					//out[idx] += " \x0f" + b
					// fmt.Printf("%s\n", CP437_to_Unicode(b))
				}
			}
			// NormalizeColor(&out)
		}

	}

	if len(out) == 0 {
		return []string{}, 0
	}

	// StringHexO(&out)
	max = len(out[0]) / 2

	for idx := range out {
		out[idx] = Colorize(out[idx])
	}

	var output []string
	for idx := range out {
		if Unicode {
			output = append(output, CP437_to_Unicode(string(out[idx])))
		} else {
			output = append(output, string(out[idx]))
		}
	}

	return output, max
}

// Given a color to look for, see if it is in the color byte.
// 0x01 = Upper match
// 0x02 = Lower match
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
}

// Update a color byte with the new color information.
// Style 0x01 = Upper, 0x02 = Lower, 0x03 = Both.
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

// Scan a ColorFont for a specific color.
// This returns a map key of character Index + line Index
// with an array of [2]int (Index, Style) to change.
func (cf *ColorFont) Scan(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
	var actual byte = byte(thedraw_to_ansi(find_color))

	for charIndex := range cf.Data {
		for lineIndex := range cf.Data[charIndex] {
			var found bool = false
			var patches [][2]int = make([][2]int, 0)
			for offset := 1; offset < len(cf.Data[charIndex][lineIndex]); offset += 2 {
				var color byte = cf.Data[charIndex][lineIndex][offset]
				var style int = 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 {
				var pos [2]int = [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
}

// This modifies the font color, using the Targets found with
// Scan.
func (cf *ColorFont) Modify(new_color int, Targets ColorMap) {
	// Covert color code to TheDraw Color
	var actual byte = byte(thedraw_to_ansi(new_color))
	for pos, patch := range Targets {
		for _, p := range patch {
			cf.Data[pos[0]][pos[1]][p[0]] = PatchColor(cf.Data[pos[0]][pos[1]][p[0]], actual, p[1])
		}
	}
}