package door

import (
	"fmt"
	"strconv"
	"strings"
	"syscall"
)

// 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_UNKNOWN     = 0x1111
)

// from: https://github.com/yubo/dea_ng
// https://github.com/yubo/dea_ng/blob/master/go/src/directoryserver/streaming.go

func set(fdSetPtr *syscall.FdSet, fd int) {
	(*fdSetPtr).Bits[fd/64] |= 1 << uint64(fd%64)
}

func isSet(fdSetPtr *syscall.FdSet, fd int) bool {
	return ((*fdSetPtr).Bits[fd/64] & (1 << uint64(fd%64))) != 0
}

func clearAll(fdSetPtr *syscall.FdSet) {
	for index, _ := range (*fdSetPtr).Bits {
		(*fdSetPtr).Bits[index] = 0
	}
}

// Is there a key waiting?
// Returns true on key, or if hungup/time out
func (d *Door) HasKey() bool {
	if d.Disconnected {
		return true
	}

	if d.TimeLeft() < 0 {
		return true
	}

	var fdsetRead = syscall.FdSet{}
	clearAll(&fdsetRead)
	set(&fdsetRead, d.READFD)
	timeout := syscall.Timeval{Sec: 0, Usec: 1}
	v, _ := syscall.Select(d.READFD+1, &fdsetRead, nil, nil, &timeout)
	if v == -1 {
		return false
	}
	if v == 0 {
		return false
	}
	return true
}

// 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 {
	var fdsetRead syscall.FdSet
	clearAll(&fdsetRead)
	set(&fdsetRead, d.READFD)

	// 100 Usec seems like a good value, works with QModem in dosbox.
	timeout := syscall.Timeval{Sec: 0, Usec: 100}
	v, err := syscall.Select(d.READFD+1, &fdsetRead, nil, nil, &timeout)
	if v == -1 {
		// hangup
		return -2
	}
	if v == 0 {
		// timeout
		return -1
	}

	buffer := make([]byte, 1)
	r, err := syscall.Read(d.READFD, buffer)
	if r != 1 {
		// I'm getting write error here... (when disconnected)
		fmt.Printf("Read said ready, but didn't read a character %d %v.", r, err)
		// hangup
		d.Disconnected = true
		return -2
	}
	return int(buffer[0])
}

func (d *Door) getkey_or_pushback() int {
	if d.pushback.Len() != 0 {
		e := d.pushback.Front()
		d.pushback.Remove(e)
		return e.Value.(int)
	}
	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.Disconnected {
		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.PushFront(c2)
			}
		}
		return c
	}

	if c == 0 {
		// possibly doorway mode
		tries := 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:
			fmt.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 string = string(c2)

		c2 = d.getkey_or_pushback()
		for c2 > 0 {
			if c2 == 0x1b {
				d.pushback.PushBack(c2)
				break
			}
			extended += string(c2)
			c2 = d.getkey_or_pushback()
		}

		switch extended {
		case "[A":
			return XKEY_UP_ARROW
		case "[B":
			return XKEY_DOWN_ARROW
		case "[C":
			return XKEY_RIGHT_ARROW
		case "[D":
			return XKEY_LEFT_ARROW
		case "[H":
			return XKEY_HOME
		case "[F":
			return XKEY_END // terminal
		case "[K":
			return XKEY_END
		case "[V":
			return XKEY_PGUP
		case "[U":
			return XKEY_PGDN
		case "[@":
			return XKEY_INSERT
		case "[1":
			// Syncterm is lost, could be F1..F5?
			fmt.Printf("ERROR (Syncterm) Extended %#v\n", extended)
			return XKEY_UNKNOWN
		case "[2~":
			return XKEY_INSERT // terminal
		case "[3~":
			return XKEY_DELETE // terminal
		case "[5~":
			return XKEY_PGUP // terminal
		case "[6~":
			return XKEY_PGDN // terminal
		case "[15~":
			return XKEY_F5 // terminal
		case "[17~":
			return XKEY_F6 // terminal
		case "[18~":
			return XKEY_F7 // terminal
		case "[19~":
			return XKEY_F8 // terminal
		case "[20~":
			return XKEY_F9 // terminal
		case "[21~":
			return XKEY_F10 // terminal
		case "[23~":
			return XKEY_F11
		case "[24~":
			return XKEY_F12 // terminal
		case "OP":
			return XKEY_F1
		case "OQ":
			return XKEY_F2
		case "OR":
			return XKEY_F3
		case "OS":
			return XKEY_F4
		case "Ot":
			return XKEY_F5 // syncterm
		default:
			fmt.Printf("ERROR Extended %#v\n", extended)
			return XKEY_UNKNOWN
		}

	}

	return c
}

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

func (d *Door) WaitKey(secs int64, usecs int64) int {
	if d.Disconnected {
		return -2
	}

	var fdsetRead syscall.FdSet

retry:
	clearAll(&fdsetRead)
	set(&fdsetRead, d.READFD)

	timeout := syscall.Timeval{Sec: secs, Usec: usecs}
	v, err := syscall.Select(d.READFD+1, &fdsetRead, nil, nil, &timeout)

	if v == -1 {
		errno, ok := err.(syscall.Errno)
		if ok && errno == syscall.EINTR {
			// Should I retry my syscall.Select here?  Yes! -1 is not return value.
			fmt.Printf("WaitKey: EINTR\n")
			goto retry
			// return -1
		}

		/*
			Interrupted system call ???  What does this mean?
			This isn't hangup.  I'm still connected.
			-1 :  interrupted system call
			This seemed to happen when I called WaitKey many times with usecs.
		*/

		fmt.Println("-1 : ", err)
		// hangup ?!
		return -2
	}
	if v == 0 {
		// timeout
		return -1
	}

	return d.GetKey()

	// var buffer []byte   -- 0 byte buffer.  doh!
	buffer := make([]byte, 1)
	r, err := syscall.Read(d.READFD, buffer)
	if r != 1 {
		// I'm getting write error here... (when disconnected)
		fmt.Printf("Read said ready, but didn't read a character %d %v.", r, err)
		// hangup
		d.Disconnected = true
		return -2
	}
	return int(buffer[0])
}

// Outputs spaces and backspaces
// If you have set a background color, this shows the input area.
func (d *Door) DisplayInput(max int) {
	d.Write(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.DisplayInput(max)

	var c int

	for true {
		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(c))
				line += string(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
			}
		}
	}
	// this is never reached
	return line
}