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.

Have the Write manage itself.
if
*/

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

	/*
		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
		}
	*/

	// DATA RACE.  Because WriterClosed is getting changed by
	// the closeChannel ... this isn't synchronized/safe.

	// DATA RACE.  If I just close the writerChannel, there's
	// a data race on the channel.  (writing to and closing)

	// DATA RACE.  Sending a "" to the channel for a "close"
	// signal doesn't work either ... (called from go routine)
	// The channel <- is synced, the access to the bool isn't. :(
	//if !d.WriterClosed {
	// Apparently, when I get to the next line, there's
	// already a data race here...
	/*
		if !d.WriterIsClosed() {
			d.writerChannel <- output
		}
	*/

	defer func() {
		if err := recover(); err != nil {
			log.Println("Write failed.", err)
		}
	}()

	//if !d.WriterIsClosed() {
	d.writerChannel <- output
	//}
	/*
		d.writerMutex.Lock()
		defer d.writerMutex.Unlock()
		if d.WriterClosed {
			return
		}
		d.writerChannel <- output
	*/
}