#+build linux
package console

import "base:intrinsics"
import "core:fmt"
import "core:io"
import "core:os"
import "core:sys/unix"

Console :: struct {
	input_stream:     io.Reader,
	prev_input_state: termios,
	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,
	read_input:       proc(con: ^Console) -> Keys,
	get_size:         proc(con: ^Console) -> (int, int),
}

@(private)
TCGETS :: 0x5401
@(private)
TCSETS :: 0x5402
@(private)
TIOCWINSZ :: 0x5413

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)

@(private)
winsize :: struct {
	ws_row:    u16,
	ws_col:    u16,
	ws_xpixel: u16,
	ws_ypixel: u16,
}

@(private)
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() -> Console {
	con := Console {
		input_stream   = io.Reader(os.stream_from_handle(os.stdin)),
		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,
	}

	con->write(CURSOR_HIDE)

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

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

	return con
}

@(private)
con_destroy :: proc(con: ^Console) {
	intrinsics.syscall(unix.SYS_ioctl, uintptr(os.stdin), TCSETS, uintptr(&con.prev_input_state))

	con->write(CURSOR_SHOW)
}

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

@(private)
con_write :: proc(con: ^Console, msg: string) {
	fmt.print(msg)
}

@(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
	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
}

@(private)
con_get_size :: proc(con: ^Console) -> (width: int, height: int) {
	size: winsize
	intrinsics.syscall(unix.SYS_ioctl, uintptr(os.stdin), TIOCWINSZ, uintptr(&size))
	width = int(size.ws_col)
	height = int(size.ws_row)
	return
}