//+private
//+build windows
package vtty

import "core:fmt"
import "core:sys/windows"

// https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences
// https://github.com/odin-lang/Odin/blob/master/core/sys/windows/kernel32.odin

SMALL_RECT :: struct {
	Left:   windows.SHORT,
	Top:    windows.SHORT,
	Right:  windows.SHORT,
	Bottom: windows.SHORT,
}

COORD :: struct {
	X: windows.SHORT,
	Y: windows.SHORT,
}

CONSOLE_SCREEN_BUFFER_INFO :: struct {
	dwSize:              COORD,
	dwCursorPosition:    COORD,
	wAttributes:         windows.WORD,
	srWindow:            SMALL_RECT,
	dwMaximumWindowSize: COORD,
}

foreign import kernel32 "system:Kernel32.lib"
@(default_calling_convention = "stdcall")
foreign kernel32 {
	GetConsoleWindow :: proc() -> windows.HWND ---

	AllocConsole :: proc() -> windows.BOOL ---
	AttachConsole :: proc(dwProcessId: windows.DWORD) -> windows.BOOL ---
	SetConsoleCP :: proc(wCodePageID: windows.UINT) -> windows.BOOL ---
}

ATTACH_PARENT_PROCESS: i32 : -1

_Console :: struct {
	prev_output_mode: windows.DWORD,
	prev_input_mode:  windows.DWORD,
	output_handle:    windows.HANDLE,
	input_handle:     windows.HANDLE,
}

_create :: proc(con: ^Console) {
	if !AttachConsole(transmute(windows.DWORD)ATTACH_PARENT_PROCESS) == windows.TRUE {
		AllocConsole()
		cx := windows.GetSystemMetrics(windows.SM_CXSCREEN) / 2
		cy := windows.GetSystemMetrics(windows.SM_CYSCREEN) / 2
		windows.MoveWindow(GetConsoleWindow(), cx - 640, cy - 360, 1280, 720, windows.TRUE)
	}

	con.output_handle = windows.GetStdHandle(windows.STD_OUTPUT_HANDLE)
	con.input_handle = windows.GetStdHandle(windows.STD_INPUT_HANDLE)

	windows.GetConsoleMode(con.output_handle, &con.prev_output_mode)
	windows.SetConsoleMode(
		con.output_handle,
		windows.ENABLE_PROCESSED_OUTPUT | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING,
	)
	windows.SetConsoleOutputCP(windows.CP_UTF8)

	windows.GetConsoleMode(con.input_handle, &con.prev_input_mode)
	windows.SetConsoleMode(
		con.input_handle,
		windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_VIRTUAL_TERMINAL_INPUT,
	)
	SetConsoleCP(windows.CP_UTF8)

	_write(con, CURSOR_HIDE)

	con.width, con.height = _get_size(con)
}

_destroy :: proc(con: ^Console) {
	_write(con, CURSOR_SHOW)

	windows.SetConsoleMode(con.output_handle, con.prev_output_mode)
	windows.SetConsoleMode(con.input_handle, con.prev_input_mode)

	windows.FreeConsole()
}

_clear :: proc(con: ^Console) {
	_write(con, CLEAR)
}

_write :: proc(con: ^Console, msg: string) {
	windows.WriteConsoleW(
		con.output_handle,
		windows.utf8_to_wstring(msg),
		windows.DWORD(len(msg)),
		nil,
		nil,
	)
}

_write_at :: proc(con: ^Console, msg: string, x, y: int) {
	_write(con, fmt.tprintf(CURSOR_POSITION + "%s", y, x, msg))
}

_putch :: proc(con: ^Console, c: rune, x, y: int) {
	_write(con, fmt.tprintf(CURSOR_POSITION + "%c", y, x, c))
}

_wait_for_input :: proc(con: ^Console) -> Keys {
	buffer: [256]u8
	num_read: u32
	foo := windows.ReadConsoleW(con.input_handle, &buffer, len(buffer), &num_read, nil)
	_ = foo
	// NOTE: Check two bytes due to W.
	if (buffer[0] == 27 && buffer[2] == 0) || buffer[0] == 3 || buffer[0] == 113 { 	// 27 = Escape, 3 = CTRL+C, 113 = q
		return .quit
	} else if buffer[0] == 32 { 	// 32 = Space
		return .forward
	} else {
		input := [?]u8{buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5]}
		if (input == [?]u8{27, 0, 91, 0, 68, 0}) {
			return .back
		} else if (input == [?]u8{27, 0, 91, 0, 67, 0}) {
			return .forward
		}
	}
	return .unk
}

_get_size :: proc(con: ^Console) -> (width: int, height: int) {
	screen_buffer_info: windows.CONSOLE_SCREEN_BUFFER_INFO
	windows.GetConsoleScreenBufferInfo(con.output_handle, &screen_buffer_info)
	width = int(screen_buffer_info.dwSize.X)
	height = int(screen_buffer_info.dwSize.Y)
	return
}