package door

import (
	"log"
	"strconv"
	"strings"
	"time"
	"unicode"
)

// This is neat, would be neater if it WORKED! go:generate stringer -type=Extended
type Extended int8

const (
	NOP Extended = iota
	UP_ARROW
	DOWN_ARROW
	RIGHT_ARROW
	LEFT_ARROW
	HOME
	END
	PAGE_UP
	PAGE_DOWN
	INSERT
	DELETE
	F1
	F2
	F3
	F4
	F5
	F6
	F7
	F8
	F9
	F10
	F11
	F12
	MOUSE
	CURSOR
	UNKNOWN
)

// To rebuild this, copy the above into it's own file and generate.
// Then, copy the generated code to below:

const _Extended_name = "NOPUP_ARROWDOWN_ARROWRIGHT_ARROWLEFT_ARROWHOMEENDPAGE_UPPAGE_DOWNINSERTDELETEF1F2F3F4F5F6F7F8F9F10F11F12MOUSECURSORUNKNOWN"

var _Extended_index = [...]uint8{0, 3, 11, 21, 32, 42, 46, 49, 56, 65, 71, 77, 79, 81, 83, 85, 87, 89, 91, 93, 95, 98, 101, 104, 109, 115, 122}

func (i Extended) String() string {
	if i < 0 || i >= Extended(len(_Extended_index)-1) {
		return "Extended(" + strconv.FormatInt(int64(i), 10) + ")"
	}
	return _Extended_name[_Extended_index[i]:_Extended_index[i+1]]
}

// This is the current list of Extended keys we support:
const (
	XKEY_UP_ARROW    = 0x1001
	XKEY_DOWN_ARROW  = 0x1002
	XKEY_RIGHT_ARROW = 0x1003
	XKEY_LEFT_ARROW  = 0x1004
	XKEY_HOME        = 0x1010
	XKEY_END         = 0x1011
	XKEY_PGUP        = 0x1012
	XKEY_PGDN        = 0x1023
	XKEY_INSERT      = 0x1024
	XKEY_DELETE      = 0x7f
	XKEY_F1          = 0x1021
	XKEY_F2          = 0x1022
	XKEY_F3          = 0x1023
	XKEY_F4          = 0x1024
	XKEY_F5          = 0x1025
	XKEY_F6          = 0x1026
	XKEY_F7          = 0x1027
	XKEY_F8          = 0x1028
	XKEY_F9          = 0x1029
	XKEY_F10         = 0x102a
	XKEY_F11         = 0x102b
	XKEY_F12         = 0x102c
	XKEY_MOUSE       = 0x1100
	XKEY_UNKNOWN     = 0x1111
)

var extendedKeys map[string]int = map[string]int{
	"[A":   XKEY_UP_ARROW,
	"[B":   XKEY_DOWN_ARROW,
	"[C":   XKEY_RIGHT_ARROW,
	"[D":   XKEY_LEFT_ARROW,
	"[H":   XKEY_HOME,
	"[F":   XKEY_END, // terminal
	"[K":   XKEY_END,
	"[V":   XKEY_PGUP,
	"[U":   XKEY_PGDN,
	"[@":   XKEY_INSERT,
	"[2~":  XKEY_INSERT, // terminal
	"[3~":  XKEY_DELETE, // terminal
	"[5~":  XKEY_PGUP,   // terminal
	"[6~":  XKEY_PGDN,   // terminal
	"[15~": XKEY_F5,     // terminal
	"[17~": XKEY_F6,     // terminal
	"[18~": XKEY_F7,     // terminal
	"[19~": XKEY_F8,     // terminal
	"[20~": XKEY_F9,     // terminal
	"[21~": XKEY_F10,    // terminal
	"[23~": XKEY_F11,
	"[24~": XKEY_F12, // terminal
	"OP":   XKEY_F1,
	"OQ":   XKEY_F2,
	"OR":   XKEY_F3,
	"OS":   XKEY_F4,
	"Ot":   XKEY_F5, // syncterm
}

// "[1":   XKEY_UNKNOWN, // Syncterm is lost, could be F1..F5?

// Low level read key function.
// This gets the raw keys from the client, it doesn't handle extended keys,
// functions, arrows.
// Return key, or -1 (Timeout/No key available), -2 hangup
func (d *Door) getch() int {
	select {
	case res, ok := <-d.readerChannel:
		if ok {
			return int(res)
		} else {
			d.Disconnected = true
			// atomic.StoreInt32(&d.Disconnected, 1)
			return -2
		}
	case <-time.After(time.Duration(100) * time.Millisecond):
		return -1
	}
}

func (d *Door) getkey_or_pushback() int {
	if !d.Pushback.Empty() {
		return d.Pushback.Pop()
	}

	if false {
		var key int = d.getch()
		log.Printf("%d / %X\n", key, key)
		return key
	} else {
		return d.getch()
	}
}

// Return key received, or XKEY_* values.
// -1 timeout/no key
// -2 hangup
// -3 out of time
func (d *Door) GetKey() int {
	var c, c2 int

	if d.Disconnect() {
		return -2
	}

	if d.TimeLeft() < 0 {
		return -3
	}
	c = d.getkey_or_pushback()

	if c < 0 {
		return c
	}

	// We get 0x0d 0x00, or 0x0d 0x0a from syncterm.
	if c == 0x0d {
		c2 = d.getkey_or_pushback()
		if c2 > 0 {
			// wasn't an error
			if c2 != 0x00 && c2 != 0x0a {
				// wasn't 0x00 or 0x0a
				d.Pushback.Push(c2)
				// log.Printf("Push 0x0d trailer %d / %x\n", c2, c2)
			}
		}
		return c
	}

	if c == 0 {
		// possibly doorway mode
		var tries int = 0
		c2 = d.getkey_or_pushback()
		for c2 < 0 {
			if tries > 7 {
				return c
			}
			c2 = d.getkey_or_pushback()
			tries++
		}

		switch c2 {
		case 0x50:
			return XKEY_DOWN_ARROW
		case 0x48:
			return XKEY_UP_ARROW
		case 0x4b:
			return XKEY_LEFT_ARROW
		case 0x4d:
			return XKEY_RIGHT_ARROW
		case 0x47:
			return XKEY_HOME
		case 0x4f:
			return XKEY_END
		case 0x49:
			return XKEY_PGUP
		case 0x51:
			return XKEY_PGDN
		case 0x3b:
			return XKEY_F1
		case 0x3c:
			return XKEY_F2
		case 0x3d:
			return XKEY_F3
		case 0x3e:
			return XKEY_F4
		case 0x3f:
			return XKEY_F5
		case 0x40:
			return XKEY_F6
		case 0x41:
			return XKEY_F7
		case 0x42:
			return XKEY_F8
		case 0x43:
			return XKEY_F9
		case 0x44:
			return XKEY_F10
		/*
			case 0x45:
				return XKEY_F11
			case 0x46:
				return XKEY_F12
		*/
		case 0x52:
			return XKEY_INSERT
		case 0x53:
			return XKEY_DELETE
		default:
			log.Printf("ERROR Doorway mode: 0x00 %x\n", c2)
			return XKEY_UNKNOWN
		}
	}

	if c == 0x1b {
		// Escape key?
		c2 = d.getkey_or_pushback()
		if c2 < 0 {
			// Just escape key
			return c
		}
		var extended []byte = make([]byte, 1)
		extended[0] = byte(c2) // string = string(byte(c2))

		c2 = d.getkey_or_pushback()
		for c2 > 0 {
			if c2 == 0x1b {
				d.Pushback.Push(c2)
				break
			}
			extended = append(extended, byte(c2)) // += string(byte(c2))

			var has bool
			c2, has = extendedKeys[string(extended)]
			if has {
				// break out here if \x1b[ + letter or @
				// break out if \x1b[ + digits + ~
				// break out if \x1bO + letter
				return c2
			}
			c2 = d.getkey_or_pushback()
		}

		if strings.HasPrefix(string(extended), "[M") && len(extended) == 5 {
			// log.Printf("MOUSE Extended %#v\n", extended)
			var mouse Mouse = Mouse{Button: int8(extended[2]) - ' ' + 1,
				X: int8(extended[3]) - '!' + 1,
				Y: int8(extended[4]) - '!' + 1}
			d.AddMouse(mouse)
			log.Printf("MOUSE %d (%d,%d)\n", mouse.Button, mouse.X, mouse.Y)
			return XKEY_MOUSE
		}

		log.Printf("ERROR Extended %#v\n", extended)
		return XKEY_UNKNOWN
	}

	return c
}

func (d *Door) Key() int {
	return d.WaitKey(Inactivity, 0)
}

// usecs = Microseconds
func (d *Door) WaitKey(secs int64, usecs int64) int {
	if d.Disconnect() {
		return -2
	}

	if !d.Pushback.Empty() {
		return d.GetKey()
	}

	var timeout time.Duration = time.Duration(secs)*time.Second + time.Duration(usecs)*time.Microsecond

	select {
	case res, ok := <-d.readerChannel:
		if ok {
			d.Pushback.Push(int(res))
			return d.GetKey()
		} else {
			// Reader Closed

			d.Disconnected = true

			// If I wrap this with if !d.WriterClosed .. races ?

			// Why can't I do this?  This isn't a go routine...
			if !d.WriterClosed {
				d.writerChannel <- ""
			}

			// d.closeChannel <- struct{}{}

			// atomic.StoreInt32(&d.Disconnected, 1)
			return -2
		}
	case <-time.After(timeout):
		return -1
	}
}

// Outputs spaces and backspaces
// If you have set a background color, this shows the input area.
func DisplayInput(max int) string {
	return strings.Repeat(" ", max) + strings.Repeat("\x08", max)
}

// Input a string of max length.
// This displays the input area if a bg color was set.
// This handles timeout, input, backspace, and enter.
func (d *Door) Input(max int) string {
	var line string

	// draw input area
	d.Write(DisplayInput(max))

	var c int

	for {
		c = d.WaitKey(Inactivity, 0)
		if c < 0 {
			// timeout/hangup
			return ""
		}

		if c > 1000 {
			continue
		}

		if strconv.IsPrint(rune(c)) {
			if len(line) < max {
				d.Write(string(byte(c)))
				line += string(byte(c))
			} else {
				d.Write("\x07")
			}
		} else {
			// Non-print
			switch c {
			case 0x7f, 0x08:
				if len(line) > 0 {
					d.Write("\x08 \x08")
					line = line[:len(line)-1]
				}
			case 0x0d:
				return line
			}
		}
	}
}

func (d *Door) GetOneOf(possible string) int {
	var c int

	for {
		c = d.WaitKey(Inactivity, 0)
		if c < 0 {
			return c
		}
		r := unicode.ToUpper(rune(c))
		if strings.ContainsRune(possible, r) {
			// return upper case rune
			return int(r)
		}
		/*
			c = strings.IndexRune(possible, r)
			if c != -1 {
				return c
			}
		*/
	}
}