package door

import (
	"log"
	"regexp"
	"strconv"
	"strings"
)

var FindANSIColor *regexp.Regexp

func init() {
	FindANSIColor = regexp.MustCompile("\x1b\\[([0-9;]*)m")
}

/*
find \x1b[0;1;37;40m codes.
return array of start/end positions in the given string.
*/
func find_ansicolor(text string) [][]int {
	var color_codes [][]int

	// word, _ := regexp.Compile("\x1b\\[([0-9;]+)m")
	var colors [][]int = FindANSIColor.FindAllStringIndex(text, -1)
	// regexp seems to be ignoring the capture groups.
	// colors := word.FindAllSubmatchIndex([]byte(text), len(text))
	for _, pos := range colors {
		var txt string = text[pos[0]+2 : pos[1]-1]
		if txt == "" {
			txt = "0"
		}
		// log.Printf("Text: [%s]\n", txt)
		var codes []string = strings.Split(txt, ";")
		// log.Printf("Codes: [%#v]\n", codes)
		var code []int = make([]int, len(codes))
		for idx, c := range codes {
			var err error
			code[idx], err = strconv.Atoi(c)
			if err != nil {
				log.Printf("Atoi: %#v [%s]\n", err, c)
			}
			color_codes = append(color_codes, code)
		}

	}
	return color_codes
}

type ANSIColorParts struct {
	Bold  bool
	Blink bool
	FG    int
	BG    int
}

// Parse an array of codes into their ANSIColorParts.
func ParseColorArray(colorarray []int) ANSIColorParts {
	var acp ANSIColorParts
	acp.FG = -1
	acp.BG = -1

	for _, c := range colorarray {
		switch c {
		case 0:
			acp.FG = 7
			acp.BG = 0
		case 1:
			acp.Bold = true
		case 5:
			acp.Blink = true
		case 30, 31, 32, 33, 34, 35, 36, 37:
			acp.FG = c - 30
		case 40, 41, 42, 43, 44, 45, 46, 47:
			acp.BG = c - 40
		}
	}
	return acp
}

// Update the LastColor with the latest codes.
func (d *Door) UpdateLastColor(output string, LastColor *[]int) {
	// use the last color information + whatever is in the string to
	// track the last color set
	var updated ANSIColorParts = ParseColorArray(*LastColor)
	var colors [][]int = find_ansicolor(output)
	for _, codes := range colors {
		if codes[0] == 0 {
			updated = ParseColorArray(codes)
		} else {
			newCode := ParseColorArray(codes)
			if newCode.Bold {
				updated.Bold = true
			}
			if newCode.Blink {
				updated.Blink = true
			}
			if (newCode.FG != -1) && (newCode.FG != updated.FG) {
				updated.FG = newCode.FG
			}
			if (newCode.BG != -1) && (newCode.BG != updated.BG) {
				updated.BG = newCode.BG
			}
		}
	}

	*LastColor = make([]int, 1)
	(*LastColor)[0] = 0
	if updated.Blink {
		*LastColor = append(*LastColor, 5)
	}
	if updated.Bold {
		*LastColor = append(*LastColor, 1)
	}
	if updated.FG != -1 {
		*LastColor = append(*LastColor, updated.FG+30)
	}
	if updated.BG != -1 {
		*LastColor = append(*LastColor, updated.BG+40)
	}
}

/*
reading from a closed channel is easy to detect.

res, ok := <-channel
ok == false

writing to a closed channel is a panic.
*/

// Write string to client.
func (d *Door) Write(output string) {
	if d.Disconnect() {
		return
	}

	defer func() {
		if err := recover(); err != nil {
			log.Println("Write FAILURE:", err)
			// Display error to user
			// This displays stack trace stderr
			// debug.PrintStack()
		}
	}()

	if d.Disconnect() {
		return
	}

	// Is it possible that the channel would get closed here?
	d.writerChannel <- output
	return
}