#+private
#+build linux, darwin
package vtty

import "base:intrinsics"
import "core:fmt"
import "core:io"
import "core:os"

_Console :: struct {
	input_stream:     io.Reader,
	prev_input_state: Termios,
}

when ODIN_OS == .Linux {
	IOC_NRBITS :: 8
	IOC_TYPEBITS :: 8
	IOC_SIZEBITS :: 14
	IOC_DIRBITS :: 2

	IOC_NRSHIFT :: 0
	IOC_TYPESHIFT :: IOC_NRSHIFT + IOC_NRBITS
	IOC_SIZESHIFT :: IOC_TYPESHIFT + IOC_TYPEBITS
	IOC_DIRSHIFT :: IOC_SIZESHIFT + IOC_SIZEBITS

	IOC_NONE: uint : 0
	IOC_WRITE: uint : 1
	IOC_READ: uint : 2

	// TODO: Fix these definitions
	TCGETS :: 0x5401
	TCSETS :: 0x5402
	TIOCWINSZ :: 0x5413
}

when ODIN_OS == .Darwin {
	IOCPARM_MASK :: 0x1fff

	IOC_VOID: u32 : 0x20000000
	IOC_OUT: u32 : 0x40000000
	IOC_IN: u32 : 0x80000000
	IOC_INOUT :: (IOC_IN | IOC_OUT)

	_IOC :: proc(inout: u32, group: u8, num: u32, len: u32) -> uintptr {
		return uintptr(inout | ((len & IOCPARM_MASK) << 16) | (u32(group) << 8) | num)
	}
	_IOR :: proc(group: u8, num: u32, type: typeid) -> uintptr {
		return _IOC(IOC_OUT, group, num, size_of(type))
	}
	_IOW :: proc(group: u8, num: u32, type: typeid) -> uintptr {
		return _IOC(IOC_IN, group, num, size_of(type))
	}
	_IOWR :: proc(group: u8, num: u32, type: typeid) -> uintptr {
		return _IOC(IOC_INOUT, group, num, size_of(type))
	}
	// TIOCGETA
	// TCGETS :: uintptr(IOC_OUT | ((size_of(Termios) & IOCPARM_MASK) << 16) | ('t' << 8) | 19)
	TCGETS := _IOR('t', 19, Termios)
	// TIOCSETA
	// TCSETS :: uintptr(IOC_IN | ((size_of(Termios) & IOCPARM_MASK) << 16) | ('t' << 8) | 20)
	TCSETS := _IOW('t', 20, Termios)
	// TIOCGWINSZ
	//TIOCWINSZ :: uintptr(IOC_OUT | ((size_of(Win_Size) & IOCPARM_MASK) << 16) | ('t' << 8) | 104)
	TIOCWINSZ := _IOR('t', 104, Win_Size)

	/*
#define _IOC(inout, group, num, len) \
	(inout | ((len & IOCPARM_MASK) << 16) | ((group) << 8) | (num))
#define _IO(g, n)        _IOC(IOC_VOID,	(g), (n), 0)
#define _IOR(g, n, t)     _IOC(IOC_OUT,	(g), (n), sizeof(t))
#define _IOW(g, n, t)     _IOC(IOC_IN,	(g), (n), sizeof(t))
/* this should be _IORW, but stdio got there first */
#define _IOWR(g, n, t)    _IOC(IOC_INOUT,	(g), (n), sizeof(t))
*/
	// #define TIOCGETA        _IOR('t', 19, struct termios) /* get termios struct */
	// #define TIOCSETA        _IOW('t', 20, struct termios) /* set termios struct */
	// #define TIOCGWINSZ      _IOR('t', 104, struct winsize)  /* get window size */
}

ECHOKE :: (1 << 0)
ECHOE :: (1 << 1)
ECHOK :: (1 << 2)
ECHO :: (1 << 3)
ECHONL :: (1 << 4)
ECHOPRT :: (1 << 5)
ECHOCTL :: (1 << 6)
ISIG :: (1 << 7)
ICANON :: (1 << 8)
ALTWERASE :: (1 << 9)
IEXTEN :: (1 << 10)
EXTPROC :: (1 << 11)
TOSTOP :: (1 << 22)
FLUSHO :: (1 << 23)
XCASE :: (1 << 24)
NOKERNINFO :: (1 << 25)
PENDIN :: (1 << 29)
NOFLSH :: (1 << 31)

Win_Size :: struct {
	ws_row:    u16,
	ws_col:    u16,
	ws_xpixel: u16,
	ws_ypixel: u16,
}

Termios :: struct {
	c_iflag:  u32,
	c_oflag:  u32,
	c_cflag:  u32,
	c_lflag:  u32,
	c_cc:     [20]byte,
	c_ispeed: i32,
	c_ospeed: i32,
}

_create :: proc(con: ^Console) {
	con.input_stream = os.to_reader(os.stdin)
	_write(con, CURSOR_HIDE)

	term: Termios
	intrinsics.syscall(ioctl, uintptr(os.stdin), TCGETS, uintptr(&term))
	con.prev_input_state = term
	term.c_lflag = ISIG | EXTPROC
	intrinsics.syscall(ioctl, uintptr(os.stdin), TCSETS, uintptr(&term))

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

_destroy :: proc(con: ^Console) {
	intrinsics.syscall(ioctl, uintptr(os.stdin), TCSETS, uintptr(&con.prev_input_state))

	_write(con, CURSOR_SHOW)
}

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

_write :: proc(con: ^Console, msg: string) {
	fmt.print(msg)
}

_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
	to_read := 10
	io.read(con.input_stream, buffer[:], &to_read)

	if (buffer[0] == 27 && buffer[1] == 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]}
		if (input == [?]u8{27, 91, 68}) {
			return .back
		} else if (input == [?]u8{27, 91, 67}) {
			return .forward
		}
	}
	return .unk
}

_get_size :: proc(con: ^Console) -> (width: int, height: int) {
	size: Win_Size
	intrinsics.syscall(ioctl, uintptr(os.stdin), TIOCWINSZ, uintptr(&size))
	width = int(size.ws_col)
	height = int(size.ws_row)
	return
}