#+build windows
package console

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 {
	GetConsoleScreenBufferInfo :: proc(hConsoleOutput: windows.HANDLE, lpConsoleScreenBufferInfo: ^CONSOLE_SCREEN_BUFFER_INFO) -> windows.BOOL ---
	GetConsoleWindow :: proc() -> windows.HWND ---

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

ENABLE_ECHO_INPUT :: 0x0004
ENABLE_INSERT_MODE :: 0x0020
ENABLE_LINE_INPUT :: 0x000
ENABLE_MOUSE_INPUT :: 0x0010
ENABLE_PROCESSED_INPUT :: 0x0001
ENABLE_QUICK_EDIT_MODE :: 0x0040
ENABLE_WINDOW_INPUT :: 0x0008
ENABLE_VIRTUAL_TERMINAL_INPUT :: 0x0200

ENABLE_PROCESSED_OUTPUT :: 0x0001
ENABLE_WRAP_AT_EOL_OUTPUT :: 0x0002
ENABLE_VIRTUAL_TERMINAL_PROCESSING :: 0x0004

ATTACH_PARENT_PROCESS: i32 : -1

CODEPAGE_UTF8 :: 65001

Console :: struct {
	prev_output_mode: windows.DWORD,
	prev_input_mode:  windows.DWORD,
	output_handle:    windows.HANDLE,
	input_handle:     windows.HANDLE,
	width:            int,
	height:           int,
	destroy:          proc(con: ^Console),
	clear:            proc(con: ^Console),
	write:            proc(con: ^Console, msg: string),
	write_at:         proc(con: ^Console, msg: string, x, y: int),
	putch:            proc(con: ^Console, c: rune, x, y: int),
	putch_s:          proc(con: ^Console, c: rune, x, y: int) -> string,
	wait_for_input:   proc(con: ^Console) -> Keys,
	get_size:         proc(con: ^Console) -> (int, int),
}

create :: proc() -> 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 := Console {
		output_handle  = windows.GetStdHandle(windows.STD_OUTPUT_HANDLE),
		input_handle   = windows.GetStdHandle(windows.STD_INPUT_HANDLE),
		destroy        = con_destroy,
		clear          = con_clear,
		write          = con_write,
		write_at       = con_write_at,
		putch          = con_putch,
		putch_s        = con_putch_s,
		wait_for_input = con_wait_for_input,
		get_size       = con_get_size,
	}

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

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

	con->write(CURSOR_HIDE)

	con.width, con.height = con->get_size()

	return con
}

@(private)
con_destroy :: proc(con: ^Console) {
	con->write(CURSOR_SHOW)

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

	FreeConsole()
}

@(private)
con_clear :: proc(con: ^Console) {
	con->write(CLEAR)
}

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

@(private)
con_write_at :: proc(con: ^Console, msg: string, x, y: int) {
	con->write(fmt.tprintf(CURSOR_POSITION + "%s", y, x, msg))
}

@(private)
con_putch :: proc(con: ^Console, c: rune, x, y: int) {
	con->write(fmt.tprintf(CURSOR_POSITION + "%c", y, x, c))
}

@(private)
con_putch_s :: proc(con: ^Console, c: rune, x, y: int) -> string {
	return fmt.tprintf(CURSOR_POSITION + "%c", y, x, c)
}

@(private)
con_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
}

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