package door

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

// See door.go for DEBUG_INPUT const

var ErrInactivity error = fmt.Errorf("Inactivity")
var ErrTimeout error = fmt.Errorf("Timeout")
var ErrDisconnected error = fmt.Errorf("Disconnected")

var DefaultTimeout time.Duration = time.Duration(60) * time.Second

// put the common input routines here
var readerBuffer []rune

// Translate these extended codes into these Extended values.
var extendedKeys map[string]Extended = map[string]Extended{
	"[A":   UP_ARROW,
	"[B":   DOWN_ARROW,
	"[C":   RIGHT_ARROW,
	"[D":   LEFT_ARROW,
	"[H":   HOME,
	"[F":   END, // terminal
	"[K":   END,
	"[V":   PAGE_UP,
	"[U":   PAGE_DOWN,
	"[@":   INSERT,
	"[2~":  INSERT,    // terminal
	"[3~":  DELETE,    // terminal
	"[5~":  PAGE_UP,   // terminal
	"[6~":  PAGE_DOWN, // terminal
	"[15~": F5,        // terminal
	"[17~": F6,        // terminal
	"[18~": F7,        // terminal
	"[19~": F8,        // terminal
	"[20~": F9,        // terminal
	"[21~": F10,       // terminal
	"[23~": F11,
	"[24~": F12, // terminal
	"OP":   F1,
	"OQ":   F2,
	"OR":   F3,
	"OS":   F4, // syncterm "[1" = F1,F2,F3, and F4)
	"Ot":   F5, // syncterm
}

func process(d *Door, newRune bool) {
	// This is like GetKey
	var rlen int
	var r, r2 rune
	var has2 bool
	var done bool = false

	for !done {
		rlen = len(readerBuffer)
		if rlen == 0 {
			// Nothing to do here
			return
		}

		if DEBUG_INPUT {
			log.Println("rlen:", rlen, "readerBuffer:", readerBuffer, "newRune:", newRune)
		}

		r = readerBuffer[0]
		if rlen >= 2 {
			r2 = readerBuffer[1]
			has2 = true
		} else {
			r2 = unicode.ReplacementChar
			has2 = false
		}

		// if !has2 and !newRune, then we're "done" (received everything,
		// and there's nothing else coming...)

		// fyneterm CR
		if r == '\x0a' {
			if !has2 && !newRune {
				ArrayPop(&readerBuffer, 1)
				d.readerChannel <- ReaderData{R: '\x0d', Ex: NOP, Err: nil}
				return
			}

			if has2 {
				ArrayPop(&readerBuffer, 1)
				if r2 == '\x00' || r2 == '\x0a' {
					ArrayPop(&readerBuffer, 1)
				}
				d.readerChannel <- ReaderData{R: '\x0d', Ex: NOP, Err: nil}
			} else {
				// We don't have a 2nd rune, and we haven't timed out.
				return
			}

			continue
		}

		// We get 0x0d, 0x00, or 0x0d 0x0a from syncterm
		if r == '\x0d' {
			if !has2 && !newRune {
				ArrayPop(&readerBuffer, 1)
				d.readerChannel <- ReaderData{R: r, Ex: NOP, Err: nil}
				return
			}

			if has2 {
				ArrayPop(&readerBuffer, 1)
				if r2 == '\x00' || r2 == '\x0a' {
					ArrayPop(&readerBuffer, 1)
				}
				d.readerChannel <- ReaderData{R: r, Ex: NOP, Err: nil}
			} else {
				return
			}

			continue
		}

		if r == '\x00' {
			// Possibly doorway mode - deprecated?
			// syncterm does support this.

			if !has2 && !newRune {
				// timeout
				ArrayPop(&readerBuffer, 1)
				d.readerChannel <- ReaderData{R: r, Ex: NOP, Err: nil}
				return
			}

			if has2 {
				ArrayPop(&readerBuffer, 2)
				switch r2 {
				case '\x50':
					d.readerChannel <- ReaderData{0, DOWN_ARROW, nil}
				case '\x48':
					d.readerChannel <- ReaderData{0, UP_ARROW, nil}
				case '\x4b':
					d.readerChannel <- ReaderData{0, LEFT_ARROW, nil}
				case 0x4d:
					d.readerChannel <- ReaderData{0, RIGHT_ARROW, nil}
				case 0x47:
					d.readerChannel <- ReaderData{0, HOME, nil}
				case 0x4f:
					d.readerChannel <- ReaderData{0, END, nil}
				case 0x49:
					d.readerChannel <- ReaderData{0, PAGE_UP, nil}
				case 0x51:
					d.readerChannel <- ReaderData{0, PAGE_DOWN, nil}
				case 0x3b:
					d.readerChannel <- ReaderData{0, F1, nil}
				case 0x3c:
					d.readerChannel <- ReaderData{0, F2, nil}
				case 0x3d:
					d.readerChannel <- ReaderData{0, F3, nil}
				case 0x3e:
					d.readerChannel <- ReaderData{0, F4, nil}
				case 0x3f:
					d.readerChannel <- ReaderData{0, F5, nil}
				case 0x40:
					d.readerChannel <- ReaderData{0, F6, nil}
				case 0x41:
					d.readerChannel <- ReaderData{0, F7, nil}
				case 0x42:
					d.readerChannel <- ReaderData{0, F8, nil}
				case 0x43:
					d.readerChannel <- ReaderData{0, F9, nil}
				case 0x44:
					d.readerChannel <- ReaderData{0, F10, nil}

				case 0x45:
					d.readerChannel <- ReaderData{0, F11, nil}
				case 0x46:
					d.readerChannel <- ReaderData{0, F12, nil}

				case 0x52:
					d.readerChannel <- ReaderData{0, INSERT, nil}
				case 0x53:
					d.readerChannel <- ReaderData{0, DELETE, nil}
				default:
					log.Printf("ERROR Doorway mode: 0x00 %x\n", r2)
					d.readerChannel <- ReaderData{0, UNKNOWN, nil}

				}
			} else {
				return
			}
			continue
		} // end doorway mode

		if r == '\x1b' {
			// Escape key, or ?
			// This is a little harder, since we don't know how many we need.
			if !has2 && !newRune {
				ArrayPop(&readerBuffer, 1)
				d.readerChannel <- ReaderData{r, NOP, nil}
				return
			}

			if has2 {
				// We at least have the 2nd one...
				if r2 == '\x1b' {
					ArrayPop(&readerBuffer, 1)
					d.readerChannel <- ReaderData{r, NOP, nil}
					continue
				}

				// Can't distinguish between \x1bO and \x1bOP (F1)
				/*
					if unicode.IsLetter(r2) {
						// ALT-KEY
						if unicode.IsLower(r2) {
							ArrayPop(&readerBuffer, 2)
							ex := Extended(int(ALT_a) + int(r2-'a'))
							d.readerChannel <- ReaderData{0, ex, nil}
						} else {
							// Must be upper
							ArrayPop(&readerBuffer, 2)
							ex := Extended(int(ALT_A) + int(r2-'A'))
							d.readerChannel <- ReaderData{0, ex, nil}
						}
						continue
					}
				*/

				var extended []rune = make([]rune, 0, 10)
				extended = append(extended, r2)
				var extlen = 2 // Length of codes to remove on successful match.
				var pos = 2    // Where we get the next rune from
				var isMouse bool = false
				var found bool = false

				if DEBUG_INPUT {
					log.Println(pos, readerBuffer)
				}

				for pos < rlen {
					r2 = readerBuffer[pos]

					pos++
					extlen++

					extended = append(extended, r2)

					if DEBUG_INPUT {
						log.Println("0x1b LOOP pos:", pos, "extlen:", extlen, "extended:", string(extended))
					}

					ext, has := extendedKeys[string(extended)]
					if has {
						// Found it!
						if DEBUG_INPUT {
							log.Println("Found Extended Match:", ext.String())
						}
						ArrayPop(&readerBuffer, extlen)
						d.readerChannel <- ReaderData{0, ext, nil}
						found = true
						break
					}

					// Mouse codes can also contain letters.
					if !isMouse && unicode.IsLetter(r2) {
						// end of extended code, unless mouse!
						if DEBUG_INPUT {
							log.Println("not mouse, is letter...")
						}
						if string(extended) == "[M" {
							isMouse = true
						} else {
							break
						}
					}

					if isMouse && len(extended) == 5 {
						break
					}
				}

				if found {
					continue
				}

				if DEBUG_INPUT {
					log.Println("POS:", pos, "EXLEN:", extlen, "Extended:", extended, "readerBuffer:", readerBuffer)
					log.Printf("(possible) Extended Code: [%s]", extended_output(extended))
				}

				var exString string = string(extended)
				if strings.HasPrefix(exString, "[M") && len(extended) == 5 {
					// Yes, "valid"
					ArrayPop(&readerBuffer, extlen)
					// Mouse Extended - input zero based (I add +1 to X, Y, and button)
					var mouse Mouse = Mouse{Button: RuneToInt8(extended[2]) - ' ' + 1,
						X: RuneToInt8(extended[3]) - '!' + 1,
						Y: RuneToInt8(extended[4]) - '!' + 1}
					d.AddMouse(mouse)
					log.Println("Mouse:", mouse)
					d.readerChannel <- ReaderData{0, MOUSE, nil}
					continue
				}

				if strings.HasSuffix(exString, "R") {
					// yes, "valid"
					ArrayPop(&readerBuffer, extlen)
					// Cursor Position information (or Shift-F3)
					var cursor CursorPos
					// ^[[1;1R^[[2;3r^[[41;173R
					// Y;X
					exString = exString[1 : len(exString)-1] // Remove [ and R
					pos := SplitToInt(exString, ";")
					if len(pos) == 2 {
						cursor.X = pos[1]
						cursor.Y = pos[0]
						d.AddCursorPos(cursor)
						/*
							d.mcMutex.Lock()
							d.LastCursor = append(d.LastCursor, cursor)
							d.mcMutex.Unlock()
						*/
						log.Println("Cursor Pos:", cursor)
						d.readerChannel <- ReaderData{0, CURSOR, nil}
						continue
					} else {
						log.Println("ERROR Cursor Pos:", extended)
						d.readerChannel <- ReaderData{0, UNKNOWN, nil}
						continue
					}
				}

				// Ok, this LOOKS like something invalid...
				if !newRune {
					// Yes, this is invalid.
					ArrayPop(&readerBuffer, extlen)
					log.Println("ERROR Extended:", extended)
					d.readerChannel <- ReaderData{0, UNKNOWN, nil}
				} else {
					if DEBUG_INPUT {
						log.Println("(Possibly) invalid extended:", extended)
					}
					return
				}

			} else {
				return
			}
			continue
		}

		ArrayPop(&readerBuffer, 1)
		d.readerChannel <- ReaderData{r, NOP, nil}
		continue
	}
}

func (d *Door) WaitKey(timeout time.Duration) (rune, Extended, error) {
	/*
		d.readerMutex.Lock()
		if d.ReaderClosed {
			d.readerMutex.Unlock()
			return 0, NOP, ErrDisconnected
		}
		d.readerMutex.Unlock()
	*/

	// Probably faster to just read from closed channel and get ok = false.

	select {
	case r, ok := <-d.readerChannel:
		if ok {
			if DEBUG_INPUT {
				log.Println("WaitKey:", r)
			}
			// return bio.GetKey()
			return r.R, r.Ex, r.Err
		} else {
			log.Println("WaitKey: Disconnected")
			// Reader has closed.
			// Disconnected = true
			return 0, NOP, ErrDisconnected
		}
	case <-time.After(timeout):
		return 0, NOP, ErrTimeout
	}
}

// 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 []rune = make([]rune, 0, max)
	var length int

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

	var r rune
	var ex Extended
	var err error

	for {
		r, ex, err = d.WaitKey(DefaultTimeout)
		if err != nil {
			// timeout/hangup
			return ""
		}

		if ex != NOP {
			continue
		}

		uw := UnicodeWidth(r)

		if strconv.IsPrint(r) {
			if length+uw <= max {
				d.Write(string(r))
				line = append(line, r)
				length += uw
			} else {
				d.Write("\x07")
			}
		} else {
			// Non-print
			switch r {
			case 0x7f, 0x08:
				if len(line) > 0 {
					d.Write("\x08 \x08")
					rlen := len(line)
					if UnicodeWidth(line[rlen-1]) == 2 {
						d.Write("\x08 \x08")
						length -= 2
					} else {
						length--
					}
					line = line[0 : rlen-1]
				}
			case 0x0d:
				return string(line)
			}
		}
	}
}

func (d *Door) GetOneOf(possible string) rune {
	var r rune
	var err error

	for {
		r, _, err = d.WaitKey(DefaultTimeout)
		if err != nil {
			return rune(0)
		}
		r := unicode.ToUpper(r)
		if strings.ContainsRune(possible, r) {
			// return upper case rune
			return r
		}
	}
}