package tea

import (
	"errors"
	"fmt"
	"io"
	"unicode/utf8"
)

// KeyMsg contains information about a keypress. KeyMsgs are always sent to
// the program's update function. There are a couple general patterns you could
// use to check for keypresses:
//
//     // Switch on the string representation of the key (shorter)
//     switch msg := msg.(type) {
//     case KeyMsg:
//         switch msg.String() {
//         case "enter":
//             fmt.Println("you pressed enter!")
//         case "a":
//             fmt.Println("you pressed a!")
//         }
//     }
//
//     // Switch on the key type (more foolproof)
//     switch msg := msg.(type) {
//     case KeyMsg:
//         switch msg.Type {
//         case KeyEnter:
//             fmt.Println("you pressed enter!")
//         case KeyRunes:
//             switch string(msg.Runes) {
//             case "a":
//                 fmt.Println("you pressed a!")
//             }
//         }
//     }
//
// Note that Key.Runes will always contain at least one character, so you can
// always safely call Key.Runes[0]. In most cases Key.Runes will only contain
// one character, though certain input method editors (most notably Chinese
// IMEs) can input multiple runes at once.
type KeyMsg Key

// String returns a string representation for a key message. It's safe (and
// encouraged) for use in key comparison.
func (k KeyMsg) String() (str string) {
	return Key(k).String()
}

// Key contains information about a keypress.
type Key struct {
	Type  KeyType
	Runes []rune
	Alt   bool
}

// String returns a friendly string representation for a key. It's safe (and
// encouraged) for use in key comparison.
//
//     k := Key{Type: KeyEnter}
//     fmt.Println(k)
//     // Output: enter
//
func (k Key) String() (str string) {
	if k.Alt {
		str += "alt+"
	}
	if k.Type == KeyRunes {
		str += string(k.Runes)
		return str
	} else if s, ok := keyNames[k.Type]; ok {
		str += s
		return str
	}
	return ""
}

// KeyType indicates the key pressed, such as KeyEnter or KeyBreak or KeyCtrlC.
// All other keys will be type KeyRunes. To get the rune value, check the Rune
// method on a Key struct, or use the Key.String() method:
//
//     k := Key{Type: KeyRunes, Runes: []rune{'a'}, Alt: true}
//     if k.Type == KeyRunes {
//
//         fmt.Println(k.Runes)
//         // Output: a
//
//         fmt.Println(k.String())
//         // Output: alt+a
//
//     }
type KeyType int

func (k KeyType) String() (str string) {
	if s, ok := keyNames[k]; ok {
		return s
	}
	return ""
}

// Control keys. We could do this with an iota, but the values are very
// specific, so we set the values explicitly to avoid any confusion.
//
// See also:
// https://en.wikipedia.org/wiki/C0_and_C1_control_codes
const (
	keyNUL KeyType = 0   // null, \0
	keySOH KeyType = 1   // start of heading
	keySTX KeyType = 2   // start of text
	keyETX KeyType = 3   // break, ctrl+c
	keyEOT KeyType = 4   // end of transmission
	keyENQ KeyType = 5   // enquiry
	keyACK KeyType = 6   // acknowledge
	keyBEL KeyType = 7   // bell, \a
	keyBS  KeyType = 8   // backspace
	keyHT  KeyType = 9   // horizontal tabulation, \t
	keyLF  KeyType = 10  // line feed, \n
	keyVT  KeyType = 11  // vertical tabulation \v
	keyFF  KeyType = 12  // form feed \f
	keyCR  KeyType = 13  // carriage return, \r
	keySO  KeyType = 14  // shift out
	keySI  KeyType = 15  // shift in
	keyDLE KeyType = 16  // data link escape
	keyDC1 KeyType = 17  // device control one
	keyDC2 KeyType = 18  // device control two
	keyDC3 KeyType = 19  // device control three
	keyDC4 KeyType = 20  // device control four
	keyNAK KeyType = 21  // negative acknowledge
	keySYN KeyType = 22  // synchronous idle
	keyETB KeyType = 23  // end of transmission block
	keyCAN KeyType = 24  // cancel
	keyEM  KeyType = 25  // end of medium
	keySUB KeyType = 26  // substitution
	keyESC KeyType = 27  // escape, \e
	keyFS  KeyType = 28  // file separator
	keyGS  KeyType = 29  // group separator
	keyRS  KeyType = 30  // record separator
	keyUS  KeyType = 31  // unit separator
	keySP  KeyType = 32  // space
	keyDEL KeyType = 127 // delete. on most systems this is mapped to backspace, I hear
)

// Control key aliases.
const (
	KeyNull      KeyType = keyNUL
	KeyBreak     KeyType = keyETX
	KeyEnter     KeyType = keyCR
	KeyBackspace KeyType = keyDEL
	KeyTab       KeyType = keyHT
	KeySpace     KeyType = keySP
	KeyEsc       KeyType = keyESC
	KeyEscape    KeyType = keyESC

	KeyCtrlAt           KeyType = keyNUL // ctrl+@
	KeyCtrlA            KeyType = keySOH
	KeyCtrlB            KeyType = keySTX
	KeyCtrlC            KeyType = keyETX
	KeyCtrlD            KeyType = keyEOT
	KeyCtrlE            KeyType = keyENQ
	KeyCtrlF            KeyType = keyACK
	KeyCtrlG            KeyType = keyBEL
	KeyCtrlH            KeyType = keyBS
	KeyCtrlI            KeyType = keyHT
	KeyCtrlJ            KeyType = keyLF
	KeyCtrlK            KeyType = keyVT
	KeyCtrlL            KeyType = keyFF
	KeyCtrlM            KeyType = keyCR
	KeyCtrlN            KeyType = keySO
	KeyCtrlO            KeyType = keySI
	KeyCtrlP            KeyType = keyDLE
	KeyCtrlQ            KeyType = keyDC1
	KeyCtrlR            KeyType = keyDC2
	KeyCtrlS            KeyType = keyDC3
	KeyCtrlT            KeyType = keyDC4
	KeyCtrlU            KeyType = keyNAK
	KeyCtrlV            KeyType = keySYN
	KeyCtrlW            KeyType = keyETB
	KeyCtrlX            KeyType = keyCAN
	KeyCtrlY            KeyType = keyEM
	KeyCtrlZ            KeyType = keySUB
	KeyCtrlOpenBracket  KeyType = keyESC // ctrl+[
	KeyCtrlBackslash    KeyType = keyFS  // ctrl+\
	KeyCtrlCloseBracket KeyType = keyGS  // ctrl+]
	KeyCtrlCaret        KeyType = keyRS  // ctrl+^
	KeyCtrlUnderscore   KeyType = keyUS  // ctrl+_
	KeyCtrlQuestionMark KeyType = keyDEL // ctrl+?
)

// Other keys.
const (
	KeyRunes KeyType = -(iota + 1)
	KeyUp
	KeyDown
	KeyRight
	KeyLeft
	KeyShiftTab
	KeyHome
	KeyEnd
	KeyPgUp
	KeyPgDown
	KeyDelete
)

// Mapping for control keys to friendly consts.
var keyNames = map[KeyType]string{
	keyNUL: "ctrl+@", // also ctrl+`
	keySOH: "ctrl+a",
	keySTX: "ctrl+b",
	keyETX: "ctrl+c",
	keyEOT: "ctrl+d",
	keyENQ: "ctrl+e",
	keyACK: "ctrl+f",
	keyBEL: "ctrl+g",
	keyBS:  "ctrl+h",
	keyHT:  "tab", // also ctrl+i
	keyLF:  "ctrl+j",
	keyVT:  "ctrl+k",
	keyFF:  "ctrl+l",
	keyCR:  "enter",
	keySO:  "ctrl+n",
	keySI:  "ctrl+o",
	keyDLE: "ctrl+p",
	keyDC1: "ctrl+q",
	keyDC2: "ctrl+r",
	keyDC3: "ctrl+s",
	keyDC4: "ctrl+t",
	keyNAK: "ctrl+u",
	keySYN: "ctrl+v",
	keyETB: "ctrl+w",
	keyCAN: "ctrl+x",
	keyEM:  "ctrl+y",
	keySUB: "ctrl+z",
	keyESC: "esc",
	keyFS:  "ctrl+\\",
	keyGS:  "ctrl+]",
	keyRS:  "ctrl+^",
	keyUS:  "ctrl+_",
	keySP:  "space",
	keyDEL: "backspace",

	KeyRunes:    "runes",
	KeyUp:       "up",
	KeyDown:     "down",
	KeyRight:    "right",
	KeyLeft:     "left",
	KeyShiftTab: "shift+tab",
	KeyHome:     "home",
	KeyEnd:      "end",
	KeyPgUp:     "pgup",
	KeyPgDown:   "pgdown",
}

// Mapping for sequences to consts.
var sequences = map[string]KeyType{
	"\x1b[A": KeyUp,
	"\x1b[B": KeyDown,
	"\x1b[C": KeyRight,
	"\x1b[D": KeyLeft,
}

// Mapping for hex codes to consts. Unclear why these won't register as
// sequences.
var hexes = map[string]Key{
	"1b5b5a":       {Type: KeyShiftTab},
	"1b5b337e":     {Type: KeyDelete},
	"1b0d":         {Type: KeyEnter, Alt: true},
	"1b7f":         {Type: KeyBackspace, Alt: true},
	"1b5b48":       {Type: KeyHome},
	"1b5b377e":     {Type: KeyHome}, // urxvt
	"1b5b313b3348": {Type: KeyHome, Alt: true},
	"1b1b5b377e":   {Type: KeyHome, Alt: true}, // urxvt
	"1b5b46":       {Type: KeyEnd},
	"1b5b387e":     {Type: KeyEnd}, // urxvt
	"1b5b313b3346": {Type: KeyEnd, Alt: true},
	"1b1b5b387e":   {Type: KeyEnd, Alt: true}, // urxvt
	"1b5b357e":     {Type: KeyPgUp},
	"1b5b353b337e": {Type: KeyPgUp, Alt: true},
	"1b1b5b357e":   {Type: KeyPgUp, Alt: true}, // urxvt
	"1b5b367e":     {Type: KeyPgDown},
	"1b5b363b337e": {Type: KeyPgDown, Alt: true},
	"1b1b5b367e":   {Type: KeyPgDown, Alt: true}, // urxvt
	"1b5b313b3341": {Type: KeyUp, Alt: true},
	"1b5b313b3342": {Type: KeyDown, Alt: true},
	"1b5b313b3343": {Type: KeyRight, Alt: true},
	"1b5b313b3344": {Type: KeyLeft, Alt: true},

	// Powershell
	"1b4f41": {Type: KeyUp, Alt: false},
	"1b4f42": {Type: KeyDown, Alt: false},
	"1b4f43": {Type: KeyRight, Alt: false},
	"1b4f44": {Type: KeyLeft, Alt: false},
}

// readInput reads keypress and mouse input from a TTY and returns a message
// containing information about the key or mouse event accordingly.
func readInput(input io.Reader) (Msg, error) {
	var buf [256]byte

	// Read and block
	numBytes, err := input.Read(buf[:])
	if err != nil {
		return nil, err
	}

	// See if it's a mouse event. For now we're parsing X10-type mouse events
	// only.
	mouseEvent, err := parseX10MouseEvent(buf[:numBytes])
	if err == nil {
		return MouseMsg(mouseEvent), nil
	}

	// Is it a special sequence, like an arrow key?
	if k, ok := sequences[string(buf[:numBytes])]; ok {
		return KeyMsg(Key{Type: k}), nil
	}

	// Some of these need special handling
	hex := fmt.Sprintf("%x", buf[:numBytes])
	if k, ok := hexes[hex]; ok {
		return KeyMsg(k), nil
	}

	// Is the alt key pressed? The buffer will be prefixed with an escape
	// sequence if so.
	if numBytes > 1 && buf[0] == 0x1b {
		// Now remove the initial escape sequence and re-process to get the
		// character being pressed in combination with alt.
		c, _ := utf8.DecodeRune(buf[1:])
		if c == utf8.RuneError {
			return nil, errors.New("could not decode rune after removing initial escape")
		}
		return KeyMsg(Key{Alt: true, Type: KeyRunes, Runes: []rune{c}}), nil
	}

	var runes []rune
	b := buf[:numBytes]

	// Translate input into runes. In most cases we'll receive exactly one
	// rune, but there are cases, particularly when an input method editor is
	// used, where we can receive multiple runes at once.
	for i, w := 0, 0; i < len(b); i += w {
		r, width := utf8.DecodeRune(b[i:])
		if r == utf8.RuneError {
			return nil, errors.New("could not decode rune")
		}
		runes = append(runes, r)
		w = width
	}

	if len(runes) == 0 {
		return nil, errors.New("received 0 runes from input")
	} else if len(runes) > 1 {
		// We received multiple runes, so we know this isn't a control
		// character, sequence, and so on.
		return KeyMsg(Key{Type: KeyRunes, Runes: runes}), nil
	}

	// Is the first rune a control character?
	r := KeyType(runes[0])
	if numBytes == 1 && r <= keyUS || r == keyDEL {
		return KeyMsg(Key{Type: r}), nil
	}

	// Welp, it's just a regular, ol' single rune
	return KeyMsg(Key{Type: KeyRunes, Runes: runes}), nil
}
