// +build windows

package winconsole

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"strconv"
	"strings"
	"sync"
	"syscall"
	"unsafe"

	"github.com/sirupsen/logrus"
)

const (
	// Consts for Get/SetConsoleMode function
	// -- See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx
	ENABLE_PROCESSED_INPUT = 0x0001
	ENABLE_LINE_INPUT      = 0x0002
	ENABLE_ECHO_INPUT      = 0x0004
	ENABLE_WINDOW_INPUT    = 0x0008
	ENABLE_MOUSE_INPUT     = 0x0010
	ENABLE_INSERT_MODE     = 0x0020
	ENABLE_QUICK_EDIT_MODE = 0x0040
	ENABLE_EXTENDED_FLAGS  = 0x0080

	// If parameter is a screen buffer handle, additional values
	ENABLE_PROCESSED_OUTPUT   = 0x0001
	ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002

	//http://msdn.microsoft.com/en-us/library/windows/desktop/ms682088(v=vs.85).aspx#_win32_character_attributes
	FOREGROUND_BLUE       = 1
	FOREGROUND_GREEN      = 2
	FOREGROUND_RED        = 4
	FOREGROUND_INTENSITY  = 8
	FOREGROUND_MASK_SET   = 0x000F
	FOREGROUND_MASK_UNSET = 0xFFF0

	BACKGROUND_BLUE       = 16
	BACKGROUND_GREEN      = 32
	BACKGROUND_RED        = 64
	BACKGROUND_INTENSITY  = 128
	BACKGROUND_MASK_SET   = 0x00F0
	BACKGROUND_MASK_UNSET = 0xFF0F

	COMMON_LVB_REVERSE_VIDEO = 0x4000
	COMMON_LVB_UNDERSCORE    = 0x8000

	// http://man7.org/linux/man-pages/man4/console_codes.4.html
	// ECMA-48 Set Graphics Rendition
	ANSI_ATTR_RESET     = 0
	ANSI_ATTR_BOLD      = 1
	ANSI_ATTR_DIM       = 2
	ANSI_ATTR_UNDERLINE = 4
	ANSI_ATTR_BLINK     = 5
	ANSI_ATTR_REVERSE   = 7
	ANSI_ATTR_INVISIBLE = 8

	ANSI_ATTR_UNDERLINE_OFF = 24
	ANSI_ATTR_BLINK_OFF     = 25
	ANSI_ATTR_REVERSE_OFF   = 27
	ANSI_ATTR_INVISIBLE_OFF = 8

	ANSI_FOREGROUND_BLACK   = 30
	ANSI_FOREGROUND_RED     = 31
	ANSI_FOREGROUND_GREEN   = 32
	ANSI_FOREGROUND_YELLOW  = 33
	ANSI_FOREGROUND_BLUE    = 34
	ANSI_FOREGROUND_MAGENTA = 35
	ANSI_FOREGROUND_CYAN    = 36
	ANSI_FOREGROUND_WHITE   = 37
	ANSI_FOREGROUND_DEFAULT = 39

	ANSI_BACKGROUND_BLACK   = 40
	ANSI_BACKGROUND_RED     = 41
	ANSI_BACKGROUND_GREEN   = 42
	ANSI_BACKGROUND_YELLOW  = 43
	ANSI_BACKGROUND_BLUE    = 44
	ANSI_BACKGROUND_MAGENTA = 45
	ANSI_BACKGROUND_CYAN    = 46
	ANSI_BACKGROUND_WHITE   = 47
	ANSI_BACKGROUND_DEFAULT = 49

	ANSI_MAX_CMD_LENGTH = 256

	MAX_INPUT_EVENTS = 128
	MAX_INPUT_BUFFER = 1024
	DEFAULT_WIDTH    = 80
	DEFAULT_HEIGHT   = 24
)

// http://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
const (
	VK_PRIOR    = 0x21 // PAGE UP key
	VK_NEXT     = 0x22 // PAGE DOWN key
	VK_END      = 0x23 // END key
	VK_HOME     = 0x24 // HOME key
	VK_LEFT     = 0x25 // LEFT ARROW key
	VK_UP       = 0x26 // UP ARROW key
	VK_RIGHT    = 0x27 // RIGHT ARROW key
	VK_DOWN     = 0x28 // DOWN ARROW key
	VK_SELECT   = 0x29 // SELECT key
	VK_PRINT    = 0x2A // PRINT key
	VK_EXECUTE  = 0x2B // EXECUTE key
	VK_SNAPSHOT = 0x2C // PRINT SCREEN key
	VK_INSERT   = 0x2D // INS key
	VK_DELETE   = 0x2E // DEL key
	VK_HELP     = 0x2F // HELP key
	VK_F1       = 0x70 // F1 key
	VK_F2       = 0x71 // F2 key
	VK_F3       = 0x72 // F3 key
	VK_F4       = 0x73 // F4 key
	VK_F5       = 0x74 // F5 key
	VK_F6       = 0x75 // F6 key
	VK_F7       = 0x76 // F7 key
	VK_F8       = 0x77 // F8 key
	VK_F9       = 0x78 // F9 key
	VK_F10      = 0x79 // F10 key
	VK_F11      = 0x7A // F11 key
	VK_F12      = 0x7B // F12 key
)

var kernel32DLL = syscall.NewLazyDLL("kernel32.dll")

var (
	setConsoleModeProc                = kernel32DLL.NewProc("SetConsoleMode")
	getConsoleScreenBufferInfoProc    = kernel32DLL.NewProc("GetConsoleScreenBufferInfo")
	setConsoleCursorPositionProc      = kernel32DLL.NewProc("SetConsoleCursorPosition")
	setConsoleTextAttributeProc       = kernel32DLL.NewProc("SetConsoleTextAttribute")
	fillConsoleOutputCharacterProc    = kernel32DLL.NewProc("FillConsoleOutputCharacterW")
	writeConsoleOutputProc            = kernel32DLL.NewProc("WriteConsoleOutputW")
	readConsoleInputProc              = kernel32DLL.NewProc("ReadConsoleInputW")
	getNumberOfConsoleInputEventsProc = kernel32DLL.NewProc("GetNumberOfConsoleInputEvents")
	getConsoleCursorInfoProc          = kernel32DLL.NewProc("GetConsoleCursorInfo")
	setConsoleCursorInfoProc          = kernel32DLL.NewProc("SetConsoleCursorInfo")
	setConsoleWindowInfoProc          = kernel32DLL.NewProc("SetConsoleWindowInfo")
	setConsoleScreenBufferSizeProc    = kernel32DLL.NewProc("SetConsoleScreenBufferSize")
)

// types for calling various windows API
// see http://msdn.microsoft.com/en-us/library/windows/desktop/ms682093(v=vs.85).aspx
type (
	SHORT int16
	BOOL  int32
	WORD  uint16
	WCHAR uint16
	DWORD uint32

	SMALL_RECT struct {
		Left   SHORT
		Top    SHORT
		Right  SHORT
		Bottom SHORT
	}

	COORD struct {
		X SHORT
		Y SHORT
	}

	CONSOLE_SCREEN_BUFFER_INFO struct {
		Size              COORD
		CursorPosition    COORD
		Attributes        WORD
		Window            SMALL_RECT
		MaximumWindowSize COORD
	}

	CONSOLE_CURSOR_INFO struct {
		Size    DWORD
		Visible BOOL
	}

	// http://msdn.microsoft.com/en-us/library/windows/desktop/ms684166(v=vs.85).aspx
	KEY_EVENT_RECORD struct {
		KeyDown         BOOL
		RepeatCount     WORD
		VirtualKeyCode  WORD
		VirtualScanCode WORD
		UnicodeChar     WCHAR
		ControlKeyState DWORD
	}

	INPUT_RECORD struct {
		EventType WORD
		KeyEvent  KEY_EVENT_RECORD
	}

	CHAR_INFO struct {
		UnicodeChar WCHAR
		Attributes  WORD
	}
)

// TODO(azlinux): Basic type clean-up
// -- Convert all uses of uintptr to syscall.Handle to be consistent with Windows syscall
// -- Convert, as appropriate, types to use defined Windows types (e.g., DWORD instead of uint32)

// Implements the TerminalEmulator interface
type WindowsTerminal struct {
	outMutex            sync.Mutex
	inMutex             sync.Mutex
	inputBuffer         []byte
	inputSize           int
	inputEvents         []INPUT_RECORD
	screenBufferInfo    *CONSOLE_SCREEN_BUFFER_INFO
	inputEscapeSequence []byte
}

func getStdHandle(stdhandle int) uintptr {
	handle, err := syscall.GetStdHandle(stdhandle)
	if err != nil {
		panic(fmt.Errorf("could not get standard io handle %d", stdhandle))
	}
	return uintptr(handle)
}

func WinConsoleStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
	handler := &WindowsTerminal{
		inputBuffer:         make([]byte, MAX_INPUT_BUFFER),
		inputEscapeSequence: []byte(KEY_ESC_CSI),
		inputEvents:         make([]INPUT_RECORD, MAX_INPUT_EVENTS),
	}

	if IsConsole(os.Stdin.Fd()) {
		stdIn = &terminalReader{
			wrappedReader: os.Stdin,
			emulator:      handler,
			command:       make([]byte, 0, ANSI_MAX_CMD_LENGTH),
			fd:            getStdHandle(syscall.STD_INPUT_HANDLE),
		}
	} else {
		stdIn = os.Stdin
	}

	if IsConsole(os.Stdout.Fd()) {
		stdoutHandle := getStdHandle(syscall.STD_OUTPUT_HANDLE)

		// Save current screen buffer info
		screenBufferInfo, err := GetConsoleScreenBufferInfo(stdoutHandle)
		if err != nil {
			// If GetConsoleScreenBufferInfo returns a nil error, it usually means that stdout is not a TTY.
			// However, this is in the branch where stdout is a TTY, hence the panic.
			panic("could not get console screen buffer info")
		}
		handler.screenBufferInfo = screenBufferInfo

		buffer = make([]CHAR_INFO, screenBufferInfo.MaximumWindowSize.X*screenBufferInfo.MaximumWindowSize.Y)

		stdOut = &terminalWriter{
			wrappedWriter: os.Stdout,
			emulator:      handler,
			command:       make([]byte, 0, ANSI_MAX_CMD_LENGTH),
			fd:            stdoutHandle,
		}
	} else {
		stdOut = os.Stdout
	}

	if IsConsole(os.Stderr.Fd()) {
		stdErr = &terminalWriter{
			wrappedWriter: os.Stderr,
			emulator:      handler,
			command:       make([]byte, 0, ANSI_MAX_CMD_LENGTH),
			fd:            getStdHandle(syscall.STD_ERROR_HANDLE),
		}
	} else {
		stdErr = os.Stderr
	}

	return stdIn, stdOut, stdErr
}

// GetHandleInfo returns file descriptor and bool indicating whether the file is a console.
func GetHandleInfo(in interface{}) (uintptr, bool) {
	var inFd uintptr
	var isTerminalIn bool

	switch t := in.(type) {
	case *terminalReader:
		in = t.wrappedReader
	case *terminalWriter:
		in = t.wrappedWriter
	}

	if file, ok := in.(*os.File); ok {
		inFd = file.Fd()
		isTerminalIn = IsConsole(inFd)
	}
	return inFd, isTerminalIn
}

func getError(r1, r2 uintptr, lastErr error) error {
	// If the function fails, the return value is zero.
	if r1 == 0 {
		if lastErr != nil {
			return lastErr
		}
		return syscall.EINVAL
	}
	return nil
}

// GetConsoleMode gets the console mode for given file descriptor
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx
func GetConsoleMode(handle uintptr) (uint32, error) {
	var mode uint32
	err := syscall.GetConsoleMode(syscall.Handle(handle), &mode)
	return mode, err
}

// SetConsoleMode sets the console mode for given file descriptor
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx
func SetConsoleMode(handle uintptr, mode uint32) error {
	return getError(setConsoleModeProc.Call(handle, uintptr(mode), 0))
}

// SetCursorVisible sets the cursor visbility
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms686019(v=vs.85).aspx
func SetCursorVisible(handle uintptr, isVisible BOOL) (bool, error) {
	var cursorInfo *CONSOLE_CURSOR_INFO = &CONSOLE_CURSOR_INFO{}
	if err := getError(getConsoleCursorInfoProc.Call(handle, uintptr(unsafe.Pointer(cursorInfo)), 0)); err != nil {
		return false, err
	}
	cursorInfo.Visible = isVisible
	if err := getError(setConsoleCursorInfoProc.Call(handle, uintptr(unsafe.Pointer(cursorInfo)), 0)); err != nil {
		return false, err
	}
	return true, nil
}

// SetWindowSize sets the size of the console window.
func SetWindowSize(handle uintptr, width, height, max SHORT) (bool, error) {
	window := SMALL_RECT{Left: 0, Top: 0, Right: width - 1, Bottom: height - 1}
	coord := COORD{X: width - 1, Y: max}
	if err := getError(setConsoleWindowInfoProc.Call(handle, uintptr(1), uintptr(unsafe.Pointer(&window)))); err != nil {
		return false, err
	}
	if err := getError(setConsoleScreenBufferSizeProc.Call(handle, marshal(coord))); err != nil {
		return false, err
	}
	return true, nil
}

// GetConsoleScreenBufferInfo retrieves information about the specified console screen buffer.
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms683171(v=vs.85).aspx
func GetConsoleScreenBufferInfo(handle uintptr) (*CONSOLE_SCREEN_BUFFER_INFO, error) {
	var info CONSOLE_SCREEN_BUFFER_INFO
	if err := getError(getConsoleScreenBufferInfoProc.Call(handle, uintptr(unsafe.Pointer(&info)), 0)); err != nil {
		return nil, err
	}
	return &info, nil
}

// setConsoleTextAttribute sets the attributes of characters written to the
// console screen buffer by the WriteFile or WriteConsole function,
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms686047(v=vs.85).aspx
func setConsoleTextAttribute(handle uintptr, attribute WORD) error {
	return getError(setConsoleTextAttributeProc.Call(handle, uintptr(attribute), 0))
}

func writeConsoleOutput(handle uintptr, buffer []CHAR_INFO, bufferSize COORD, bufferCoord COORD, writeRegion *SMALL_RECT) (bool, error) {
	if err := getError(writeConsoleOutputProc.Call(handle, uintptr(unsafe.Pointer(&buffer[0])), marshal(bufferSize), marshal(bufferCoord), uintptr(unsafe.Pointer(writeRegion)))); err != nil {
		return false, err
	}
	return true, nil
}

// http://msdn.microsoft.com/en-us/library/windows/desktop/ms682663(v=vs.85).aspx
func fillConsoleOutputCharacter(handle uintptr, fillChar byte, length uint32, writeCord COORD) (bool, error) {
	out := int64(0)
	if err := getError(fillConsoleOutputCharacterProc.Call(handle, uintptr(fillChar), uintptr(length), marshal(writeCord), uintptr(unsafe.Pointer(&out)))); err != nil {
		return false, err
	}
	return true, nil
}

// Gets the number of space characters to write for "clearing" the section of terminal
func getNumberOfChars(fromCoord COORD, toCoord COORD, screenSize COORD) uint32 {
	// must be valid cursor position
	if fromCoord.X < 0 || fromCoord.Y < 0 || toCoord.X < 0 || toCoord.Y < 0 {
		return 0
	}
	if fromCoord.X >= screenSize.X || fromCoord.Y >= screenSize.Y || toCoord.X >= screenSize.X || toCoord.Y >= screenSize.Y {
		return 0
	}
	// can't be backwards
	if fromCoord.Y > toCoord.Y {
		return 0
	}
	// same line
	if fromCoord.Y == toCoord.Y {
		return uint32(toCoord.X-fromCoord.X) + 1
	}
	// spans more than one line
	if fromCoord.Y < toCoord.Y {
		// from start till end of line for first line +  from start of line till end
		retValue := uint32(screenSize.X-fromCoord.X) + uint32(toCoord.X) + 1
		// don't count first and last line
		linesBetween := toCoord.Y - fromCoord.Y - 1
		if linesBetween > 0 {
			retValue = retValue + uint32(linesBetween*screenSize.X)
		}
		return retValue
	}
	return 0
}

var buffer []CHAR_INFO

func clearDisplayRect(handle uintptr, attributes WORD, fromCoord COORD, toCoord COORD) (uint32, error) {
	var writeRegion SMALL_RECT
	writeRegion.Left = fromCoord.X
	writeRegion.Top = fromCoord.Y
	writeRegion.Right = toCoord.X
	writeRegion.Bottom = toCoord.Y

	// allocate and initialize buffer
	width := toCoord.X - fromCoord.X + 1
	height := toCoord.Y - fromCoord.Y + 1
	size := uint32(width) * uint32(height)
	if size > 0 {
		buffer := make([]CHAR_INFO, size)
		for i := range buffer {
			buffer[i] = CHAR_INFO{WCHAR(' '), attributes}
		}

		// Write to buffer
		r, err := writeConsoleOutput(handle, buffer, COORD{X: width, Y: height}, COORD{X: 0, Y: 0}, &writeRegion)
		if !r {
			if err != nil {
				return 0, err
			}
			return 0, syscall.EINVAL
		}
	}
	return uint32(size), nil
}

func clearDisplayRange(handle uintptr, attributes WORD, fromCoord COORD, toCoord COORD) (uint32, error) {
	nw := uint32(0)
	// start and end on same line
	if fromCoord.Y == toCoord.Y {
		return clearDisplayRect(handle, attributes, fromCoord, toCoord)
	}
	// TODO(azlinux): if full screen, optimize

	// spans more than one line
	if fromCoord.Y < toCoord.Y {
		// from start position till end of line for first line
		n, err := clearDisplayRect(handle, attributes, fromCoord, COORD{X: toCoord.X, Y: fromCoord.Y})
		if err != nil {
			return nw, err
		}
		nw += n
		// lines between
		linesBetween := toCoord.Y - fromCoord.Y - 1
		if linesBetween > 0 {
			n, err = clearDisplayRect(handle, attributes, COORD{X: 0, Y: fromCoord.Y + 1}, COORD{X: toCoord.X, Y: toCoord.Y - 1})
			if err != nil {
				return nw, err
			}
			nw += n
		}
		// lines at end
		n, err = clearDisplayRect(handle, attributes, COORD{X: 0, Y: toCoord.Y}, toCoord)
		if err != nil {
			return nw, err
		}
		nw += n
	}
	return nw, nil
}

// setConsoleCursorPosition sets the console cursor position
// Note The X and Y are zero based
// If relative is true then the new position is relative to current one
func setConsoleCursorPosition(handle uintptr, isRelative bool, column int16, line int16) error {
	screenBufferInfo, err := GetConsoleScreenBufferInfo(handle)
	if err != nil {
		return err
	}
	var position COORD
	if isRelative {
		position.X = screenBufferInfo.CursorPosition.X + SHORT(column)
		position.Y = screenBufferInfo.CursorPosition.Y + SHORT(line)
	} else {
		position.X = SHORT(column)
		position.Y = SHORT(line)
	}
	return getError(setConsoleCursorPositionProc.Call(handle, marshal(position), 0))
}

// http://msdn.microsoft.com/en-us/library/windows/desktop/ms683207(v=vs.85).aspx
func getNumberOfConsoleInputEvents(handle uintptr) (uint16, error) {
	var n DWORD
	if err := getError(getNumberOfConsoleInputEventsProc.Call(handle, uintptr(unsafe.Pointer(&n)))); err != nil {
		return 0, err
	}
	return uint16(n), nil
}

//http://msdn.microsoft.com/en-us/library/windows/desktop/ms684961(v=vs.85).aspx
func readConsoleInputKey(handle uintptr, inputBuffer []INPUT_RECORD) (int, error) {
	var nr DWORD
	if err := getError(readConsoleInputProc.Call(handle, uintptr(unsafe.Pointer(&inputBuffer[0])), uintptr(len(inputBuffer)), uintptr(unsafe.Pointer(&nr)))); err != nil {
		return 0, err
	}
	return int(nr), nil
}

func getWindowsTextAttributeForAnsiValue(originalFlag WORD, defaultValue WORD, ansiValue int16) (WORD, error) {
	flag := WORD(originalFlag)
	if flag == 0 {
		flag = defaultValue
	}
	switch ansiValue {
	case ANSI_ATTR_RESET:
		flag &^= COMMON_LVB_UNDERSCORE
		flag &^= BACKGROUND_INTENSITY
		flag = flag | FOREGROUND_INTENSITY
	case ANSI_ATTR_INVISIBLE:
		// TODO: how do you reset reverse?
	case ANSI_ATTR_UNDERLINE:
		flag = flag | COMMON_LVB_UNDERSCORE
	case ANSI_ATTR_BLINK:
		// seems like background intenisty is blink
		flag = flag | BACKGROUND_INTENSITY
	case ANSI_ATTR_UNDERLINE_OFF:
		flag &^= COMMON_LVB_UNDERSCORE
	case ANSI_ATTR_BLINK_OFF:
		// seems like background intenisty is blink
		flag &^= BACKGROUND_INTENSITY
	case ANSI_ATTR_BOLD:
		flag = flag | FOREGROUND_INTENSITY
	case ANSI_ATTR_DIM:
		flag &^= FOREGROUND_INTENSITY
	case ANSI_ATTR_REVERSE, ANSI_ATTR_REVERSE_OFF:
		// swap forground and background bits
		foreground := flag & FOREGROUND_MASK_SET
		background := flag & BACKGROUND_MASK_SET
		flag = (flag & BACKGROUND_MASK_UNSET & FOREGROUND_MASK_UNSET) | (foreground << 4) | (background >> 4)

	// FOREGROUND
	case ANSI_FOREGROUND_DEFAULT:
		flag = (flag & FOREGROUND_MASK_UNSET) | (defaultValue & FOREGROUND_MASK_SET)
	case ANSI_FOREGROUND_BLACK:
		flag = flag ^ (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE)
	case ANSI_FOREGROUND_RED:
		flag = (flag & FOREGROUND_MASK_UNSET) | FOREGROUND_RED
	case ANSI_FOREGROUND_GREEN:
		flag = (flag & FOREGROUND_MASK_UNSET) | FOREGROUND_GREEN
	case ANSI_FOREGROUND_YELLOW:
		flag = (flag & FOREGROUND_MASK_UNSET) | FOREGROUND_RED | FOREGROUND_GREEN
	case ANSI_FOREGROUND_BLUE:
		flag = (flag & FOREGROUND_MASK_UNSET) | FOREGROUND_BLUE
	case ANSI_FOREGROUND_MAGENTA:
		flag = (flag & FOREGROUND_MASK_UNSET) | FOREGROUND_RED | FOREGROUND_BLUE
	case ANSI_FOREGROUND_CYAN:
		flag = (flag & FOREGROUND_MASK_UNSET) | FOREGROUND_GREEN | FOREGROUND_BLUE
	case ANSI_FOREGROUND_WHITE:
		flag = (flag & FOREGROUND_MASK_UNSET) | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE

	// Background
	case ANSI_BACKGROUND_DEFAULT:
		// Black with no intensity
		flag = (flag & BACKGROUND_MASK_UNSET) | (defaultValue & BACKGROUND_MASK_SET)
	case ANSI_BACKGROUND_BLACK:
		flag = (flag & BACKGROUND_MASK_UNSET)
	case ANSI_BACKGROUND_RED:
		flag = (flag & BACKGROUND_MASK_UNSET) | BACKGROUND_RED
	case ANSI_BACKGROUND_GREEN:
		flag = (flag & BACKGROUND_MASK_UNSET) | BACKGROUND_GREEN
	case ANSI_BACKGROUND_YELLOW:
		flag = (flag & BACKGROUND_MASK_UNSET) | BACKGROUND_RED | BACKGROUND_GREEN
	case ANSI_BACKGROUND_BLUE:
		flag = (flag & BACKGROUND_MASK_UNSET) | BACKGROUND_BLUE
	case ANSI_BACKGROUND_MAGENTA:
		flag = (flag & BACKGROUND_MASK_UNSET) | BACKGROUND_RED | BACKGROUND_BLUE
	case ANSI_BACKGROUND_CYAN:
		flag = (flag & BACKGROUND_MASK_UNSET) | BACKGROUND_GREEN | BACKGROUND_BLUE
	case ANSI_BACKGROUND_WHITE:
		flag = (flag & BACKGROUND_MASK_UNSET) | BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE
	}
	return flag, nil
}

// HandleOutputCommand interpretes the Ansi commands and then makes appropriate Win32 calls
func (term *WindowsTerminal) HandleOutputCommand(handle uintptr, command []byte) (n int, err error) {
	// always consider all the bytes in command, processed
	n = len(command)

	parsedCommand := parseAnsiCommand(command)
	logrus.Debugf("[windows] HandleOutputCommand: %v", parsedCommand)

	// console settings changes need to happen in atomic way
	term.outMutex.Lock()
	defer term.outMutex.Unlock()

	switch parsedCommand.Command {
	case "m":
		// [Value;...;Valuem
		// Set Graphics Mode:
		// Calls the graphics functions specified by the following values.
		// These specified functions remain active until the next occurrence of this escape sequence.
		// Graphics mode changes the colors and attributes of text (such as bold and underline) displayed on the screen.
		screenBufferInfo, err := GetConsoleScreenBufferInfo(handle)
		if err != nil {
			return n, err
		}
		flag := screenBufferInfo.Attributes
		for _, e := range parsedCommand.Parameters {
			value, _ := strconv.ParseInt(e, 10, 16) // base 10, 16 bit
			if value == ANSI_ATTR_RESET {
				flag = term.screenBufferInfo.Attributes // reset
			} else {
				flag, err = getWindowsTextAttributeForAnsiValue(flag, term.screenBufferInfo.Attributes, int16(value))
				if err != nil {
					return n, err
				}
			}
		}
		if err := setConsoleTextAttribute(handle, flag); err != nil {
			return n, err
		}
	case "H", "f":
		// [line;columnH
		// [line;columnf
		// Moves the cursor to the specified position (coordinates).
		// If you do not specify a position, the cursor moves to the home position at the upper-left corner of the screen (line 0, column 0).
		screenBufferInfo, err := GetConsoleScreenBufferInfo(handle)
		if err != nil {
			return n, err
		}
		line, err := parseInt16OrDefault(parsedCommand.getParam(0), 1)
		if err != nil {
			return n, err
		}
		if line > int16(screenBufferInfo.Window.Bottom) {
			line = int16(screenBufferInfo.Window.Bottom) + 1
		}
		column, err := parseInt16OrDefault(parsedCommand.getParam(1), 1)
		if err != nil {
			return n, err
		}
		if column > int16(screenBufferInfo.Window.Right) {
			column = int16(screenBufferInfo.Window.Right) + 1
		}
		// The numbers are not 0 based, but 1 based
		logrus.Debugf("[windows] HandleOutputCommmand: Moving cursor to (%v,%v)", column-1, line-1)
		if err := setConsoleCursorPosition(handle, false, column-1, line-1); err != nil {
			return n, err
		}

	case "A":
		// [valueA
		// Moves the cursor up by the specified number of lines without changing columns.
		// If the cursor is already on the top line, ignores this sequence.
		value, err := parseInt16OrDefault(parsedCommand.getParam(0), 1)
		if err != nil {
			return len(command), err
		}
		if err := setConsoleCursorPosition(handle, true, 0, -value); err != nil {
			return n, err
		}
	case "B":
		// [valueB
		// Moves the cursor down by the specified number of lines without changing columns.
		// If the cursor is already on the bottom line, ignores this sequence.
		value, err := parseInt16OrDefault(parsedCommand.getParam(0), 1)
		if err != nil {
			return n, err
		}
		if err := setConsoleCursorPosition(handle, true, 0, value); err != nil {
			return n, err
		}
	case "C":
		// [valueC
		// Moves the cursor forward by the specified number of columns without changing lines.
		// If the cursor is already in the rightmost column, ignores this sequence.
		value, err := parseInt16OrDefault(parsedCommand.getParam(0), 1)
		if err != nil {
			return n, err
		}
		if err := setConsoleCursorPosition(handle, true, value, 0); err != nil {
			return n, err
		}
	case "D":
		// [valueD
		// Moves the cursor back by the specified number of columns without changing lines.
		// If the cursor is already in the leftmost column, ignores this sequence.
		value, err := parseInt16OrDefault(parsedCommand.getParam(0), 1)
		if err != nil {
			return n, err
		}
		if err := setConsoleCursorPosition(handle, true, -value, 0); err != nil {
			return n, err
		}
	case "J":
		// [J   Erases from the cursor to the end of the screen, including the cursor position.
		// [1J  Erases from the beginning of the screen to the cursor, including the cursor position.
		// [2J  Erases the complete display. The cursor does not move.
		// Clears the screen and moves the cursor to the home position (line 0, column 0).
		value, err := parseInt16OrDefault(parsedCommand.getParam(0), 0)
		if err != nil {
			return n, err
		}
		var start COORD
		var cursor COORD
		var end COORD
		screenBufferInfo, err := GetConsoleScreenBufferInfo(handle)
		if err != nil {
			return n, err
		}
		switch value {
		case 0:
			start = screenBufferInfo.CursorPosition
			// end of the buffer
			end.X = screenBufferInfo.Size.X - 1
			end.Y = screenBufferInfo.Size.Y - 1
			// cursor
			cursor = screenBufferInfo.CursorPosition
		case 1:

			// start of the screen
			start.X = 0
			start.Y = 0
			// end of the screen
			end = screenBufferInfo.CursorPosition
			// cursor
			cursor = screenBufferInfo.CursorPosition
		case 2:
			// start of the screen
			start.X = 0
			start.Y = 0
			// end of the buffer
			end.X = screenBufferInfo.Size.X - 1
			end.Y = screenBufferInfo.Size.Y - 1
			// cursor
			cursor.X = 0
			cursor.Y = 0
		}
		if _, err := clearDisplayRange(uintptr(handle), term.screenBufferInfo.Attributes, start, end); err != nil {
			return n, err
		}
		// remember the the cursor position is 1 based
		if err := setConsoleCursorPosition(handle, false, int16(cursor.X), int16(cursor.Y)); err != nil {
			return n, err
		}

	case "K":
		// [K
		// Clears all characters from the cursor position to the end of the line (including the character at the cursor position).
		// [K  Erases from the cursor to the end of the line, including the cursor position.
		// [1K  Erases from the beginning of the line to the cursor, including the cursor position.
		// [2K  Erases the complete line.
		value, err := parseInt16OrDefault(parsedCommand.getParam(0), 0)
		var start COORD
		var cursor COORD
		var end COORD
		screenBufferInfo, err := GetConsoleScreenBufferInfo(uintptr(handle))
		if err != nil {
			return n, err
		}
		switch value {
		case 0:
			// start is where cursor is
			start = screenBufferInfo.CursorPosition
			// end of line
			end.X = screenBufferInfo.Size.X - 1
			end.Y = screenBufferInfo.CursorPosition.Y
			// cursor remains the same
			cursor = screenBufferInfo.CursorPosition

		case 1:
			// beginning of line
			start.X = 0
			start.Y = screenBufferInfo.CursorPosition.Y
			// until cursor
			end = screenBufferInfo.CursorPosition
			// cursor remains the same
			cursor = screenBufferInfo.CursorPosition
		case 2:
			// start of the line
			start.X = 0
			start.Y = screenBufferInfo.CursorPosition.Y - 1
			// end of the line
			end.X = screenBufferInfo.Size.X - 1
			end.Y = screenBufferInfo.CursorPosition.Y - 1
			// cursor
			cursor.X = 0
			cursor.Y = screenBufferInfo.CursorPosition.Y - 1
		}
		if _, err := clearDisplayRange(uintptr(handle), term.screenBufferInfo.Attributes, start, end); err != nil {
			return n, err
		}
		// remember the the cursor position is 1 based
		if err := setConsoleCursorPosition(uintptr(handle), false, int16(cursor.X), int16(cursor.Y)); err != nil {
			return n, err
		}

	case "l":
		for _, value := range parsedCommand.Parameters {
			switch value {
			case "?25", "25":
				SetCursorVisible(uintptr(handle), BOOL(0))
			case "?1049", "1049":
				// TODO (azlinux):  Restore terminal
			case "?1", "1":
				// If the DECCKM function is reset, then the arrow keys send ANSI cursor sequences to the host.
				term.inputEscapeSequence = []byte(KEY_ESC_CSI)
			}
		}
	case "h":
		for _, value := range parsedCommand.Parameters {
			switch value {
			case "?25", "25":
				SetCursorVisible(uintptr(handle), BOOL(1))
			case "?1049", "1049":
				// TODO (azlinux): Save terminal
			case "?1", "1":
				// If the DECCKM function is set, then the arrow keys send application sequences to the host.
				// DECCKM (default off): When set, the cursor keys send an ESC O prefix, rather than ESC [.
				term.inputEscapeSequence = []byte(KEY_ESC_O)
			}
		}

	case "]":
		/*
			TODO (azlinux):
				Linux Console Private CSI Sequences

			       The following sequences are neither ECMA-48 nor native VT102.  They are
			       native  to the Linux console driver.  Colors are in SGR parameters: 0 =
			       black, 1 = red, 2 = green, 3 = brown, 4 = blue, 5 = magenta, 6 =  cyan,
			       7 = white.

			       ESC [ 1 ; n ]       Set color n as the underline color
			       ESC [ 2 ; n ]       Set color n as the dim color
			       ESC [ 8 ]           Make the current color pair the default attributes.
			       ESC [ 9 ; n ]       Set screen blank timeout to n minutes.
			       ESC [ 10 ; n ]      Set bell frequency in Hz.
			       ESC [ 11 ; n ]      Set bell duration in msec.
			       ESC [ 12 ; n ]      Bring specified console to the front.
			       ESC [ 13 ]          Unblank the screen.
			       ESC [ 14 ; n ]      Set the VESA powerdown interval in minutes.

		*/
	}
	return n, nil
}

// WriteChars writes the bytes to given writer.
func (term *WindowsTerminal) WriteChars(fd uintptr, w io.Writer, p []byte) (n int, err error) {
	if len(p) == 0 {
		return 0, nil
	}
	return w.Write(p)
}

const (
	CAPSLOCK_ON        = 0x0080 //The CAPS LOCK light is on.
	ENHANCED_KEY       = 0x0100 //The key is enhanced.
	LEFT_ALT_PRESSED   = 0x0002 //The left ALT key is pressed.
	LEFT_CTRL_PRESSED  = 0x0008 //The left CTRL key is pressed.
	NUMLOCK_ON         = 0x0020 //The NUM LOCK light is on.
	RIGHT_ALT_PRESSED  = 0x0001 //The right ALT key is pressed.
	RIGHT_CTRL_PRESSED = 0x0004 //The right CTRL key is pressed.
	SCROLLLOCK_ON      = 0x0040 //The SCROLL LOCK light is on.
	SHIFT_PRESSED      = 0x0010 // The SHIFT key is pressed.
)

const (
	KEY_CONTROL_PARAM_2 = ";2"
	KEY_CONTROL_PARAM_3 = ";3"
	KEY_CONTROL_PARAM_4 = ";4"
	KEY_CONTROL_PARAM_5 = ";5"
	KEY_CONTROL_PARAM_6 = ";6"
	KEY_CONTROL_PARAM_7 = ";7"
	KEY_CONTROL_PARAM_8 = ";8"
	KEY_ESC_CSI         = "\x1B["
	KEY_ESC_N           = "\x1BN"
	KEY_ESC_O           = "\x1BO"
)

var keyMapPrefix = map[WORD]string{
	VK_UP:     "\x1B[%sA",
	VK_DOWN:   "\x1B[%sB",
	VK_RIGHT:  "\x1B[%sC",
	VK_LEFT:   "\x1B[%sD",
	VK_HOME:   "\x1B[1%s~", // showkey shows ^[[1
	VK_END:    "\x1B[4%s~", // showkey shows ^[[4
	VK_INSERT: "\x1B[2%s~",
	VK_DELETE: "\x1B[3%s~",
	VK_PRIOR:  "\x1B[5%s~",
	VK_NEXT:   "\x1B[6%s~",
	VK_F1:     "",
	VK_F2:     "",
	VK_F3:     "\x1B[13%s~",
	VK_F4:     "\x1B[14%s~",
	VK_F5:     "\x1B[15%s~",
	VK_F6:     "\x1B[17%s~",
	VK_F7:     "\x1B[18%s~",
	VK_F8:     "\x1B[19%s~",
	VK_F9:     "\x1B[20%s~",
	VK_F10:    "\x1B[21%s~",
	VK_F11:    "\x1B[23%s~",
	VK_F12:    "\x1B[24%s~",
}

var arrowKeyMapPrefix = map[WORD]string{
	VK_UP:    "%s%sA",
	VK_DOWN:  "%s%sB",
	VK_RIGHT: "%s%sC",
	VK_LEFT:  "%s%sD",
}

func getControlStateParameter(shift, alt, control, meta bool) string {
	if shift && alt && control {
		return KEY_CONTROL_PARAM_8
	}
	if alt && control {
		return KEY_CONTROL_PARAM_7
	}
	if shift && control {
		return KEY_CONTROL_PARAM_6
	}
	if control {
		return KEY_CONTROL_PARAM_5
	}
	if shift && alt {
		return KEY_CONTROL_PARAM_4
	}
	if alt {
		return KEY_CONTROL_PARAM_3
	}
	if shift {
		return KEY_CONTROL_PARAM_2
	}
	return ""
}

func getControlKeys(controlState DWORD) (shift, alt, control bool) {
	shift = 0 != (controlState & SHIFT_PRESSED)
	alt = 0 != (controlState & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED))
	control = 0 != (controlState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED))
	return shift, alt, control
}

func charSequenceForKeys(key WORD, controlState DWORD, escapeSequence []byte) string {
	i, ok := arrowKeyMapPrefix[key]
	if ok {
		shift, alt, control := getControlKeys(controlState)
		modifier := getControlStateParameter(shift, alt, control, false)
		return fmt.Sprintf(i, escapeSequence, modifier)
	}

	i, ok = keyMapPrefix[key]
	if ok {
		shift, alt, control := getControlKeys(controlState)
		modifier := getControlStateParameter(shift, alt, control, false)
		return fmt.Sprintf(i, modifier)
	}

	return ""
}

// mapKeystokeToTerminalString maps the given input event record to string
func mapKeystokeToTerminalString(keyEvent *KEY_EVENT_RECORD, escapeSequence []byte) string {
	_, alt, control := getControlKeys(keyEvent.ControlKeyState)
	if keyEvent.UnicodeChar == 0 {
		return charSequenceForKeys(keyEvent.VirtualKeyCode, keyEvent.ControlKeyState, escapeSequence)
	}
	if control {
		// TODO(azlinux): Implement following control sequences
		// <Ctrl>-D  Signals the end of input from the keyboard; also exits current shell.
		// <Ctrl>-H  Deletes the first character to the left of the cursor. Also called the ERASE key.
		// <Ctrl>-Q  Restarts printing after it has been stopped with <Ctrl>-s.
		// <Ctrl>-S  Suspends printing on the screen (does not stop the program).
		// <Ctrl>-U  Deletes all characters on the current line. Also called the KILL key.
		// <Ctrl>-E  Quits current command and creates a core

	}
	// <Alt>+Key generates ESC N Key
	if !control && alt {
		return KEY_ESC_N + strings.ToLower(string(keyEvent.UnicodeChar))
	}
	return string(keyEvent.UnicodeChar)
}

// getAvailableInputEvents polls the console for availble events
// The function does not return until at least one input record has been read.
func getAvailableInputEvents(handle uintptr, inputEvents []INPUT_RECORD) (n int, err error) {
	// TODO(azlinux): Why is there a for loop? Seems to me, that `n` cannot be negative. - tibor
	for {
		// Read number of console events available
		n, err = readConsoleInputKey(handle, inputEvents)
		if err != nil || n >= 0 {
			return n, err
		}
	}
}

// getTranslatedKeyCodes converts the input events into the string of characters
// The ansi escape sequence are used to map key strokes to the strings
func getTranslatedKeyCodes(inputEvents []INPUT_RECORD, escapeSequence []byte) string {
	var buf bytes.Buffer
	for i := 0; i < len(inputEvents); i++ {
		input := inputEvents[i]
		if input.EventType == KEY_EVENT && input.KeyEvent.KeyDown != 0 {
			keyString := mapKeystokeToTerminalString(&input.KeyEvent, escapeSequence)
			buf.WriteString(keyString)
		}
	}
	return buf.String()
}

// ReadChars reads the characters from the given reader
func (term *WindowsTerminal) ReadChars(fd uintptr, r io.Reader, p []byte) (n int, err error) {
	for term.inputSize == 0 {
		nr, err := getAvailableInputEvents(fd, term.inputEvents)
		if nr == 0 && nil != err {
			return n, err
		}
		if nr > 0 {
			keyCodes := getTranslatedKeyCodes(term.inputEvents[:nr], term.inputEscapeSequence)
			term.inputSize = copy(term.inputBuffer, keyCodes)
		}
	}
	n = copy(p, term.inputBuffer[:term.inputSize])
	term.inputSize -= n
	return n, nil
}

// HandleInputSequence interprets the input sequence command
func (term *WindowsTerminal) HandleInputSequence(fd uintptr, command []byte) (n int, err error) {
	return 0, nil
}

func marshal(c COORD) uintptr {
	return uintptr(*((*DWORD)(unsafe.Pointer(&c))))
}

// IsConsole returns true if the given file descriptor is a terminal.
// -- The code assumes that GetConsoleMode will return an error for file descriptors that are not a console.
func IsConsole(fd uintptr) bool {
	_, e := GetConsoleMode(fd)
	return e == nil
}
