#import win32 "sys/windows.odin";
#import "fmt.odin";
#import "os.odin";
#import "mem.odin";

CANVAS_WIDTH  :: 128;
CANVAS_HEIGHT :: 128;
CANVAS_SCALE  :: 3;
FRAME_TIME    :: 1.0/30.0;
WINDOW_TITLE  :: "Punity\x00";

_ := compile_assert(CANVAS_WIDTH % 16 == 0);

WINDOW_WIDTH  :: CANVAS_WIDTH  * CANVAS_SCALE;
WINDOW_HEIGHT :: CANVAS_HEIGHT * CANVAS_SCALE;


STACK_CAPACITY   :: 1<<20;
STORAGE_CAPACITY :: 1<<20;

DRAW_LIST_RESERVE :: 128;

MAX_KEYS :: 256;

Core :: struct {
	stack:   ^Bank,
	storage: ^Bank,

	running:       bool,
	key_modifiers: u32,
	key_states:    [MAX_KEYS]byte,
	key_deltas:    [MAX_KEYS]byte,

	perf_frame,
	perf_frame_inner,
	perf_step,
	perf_audio,
	perf_blit,
	perf_blit_cvt,
	perf_blit_gdi: Perf_Span,

	frame: i64,

	canvas:    Canvas,
	draw_list: ^Draw_List,
}

Perf_Span :: struct {
	stamp: f64,
	delta: f32,
}

Bank :: struct {
	memory: []byte,
	cursor: int,
}

Bank_State :: struct {
	state: Bank,
	bank: ^Bank,
}


Color :: raw_union {
	using channels: struct{a, b, g, r: byte},
	rgba: u32,
}

Palette :: struct {
	colors: [256]Color,
	colors_count: byte,
}


Rect :: raw_union {
	using minmax: struct {min_x, min_y, max_x, max_y: int},
	using pos: struct {left, top, right, bottom: int},
	e: [4]int,
}

Bitmap :: struct {
	pixels: []byte,
	width:  int,
	height: int,
}

Font :: struct {
	using bitmap: Bitmap,
	char_width:   int,
	char_height:  int,
}

Canvas :: struct {
	using bitmap: ^Bitmap,
	palette:      Palette,
	translate_x:  int,
	translate_y:  int,
	clip:         Rect,
	font:         ^Font,
}

DrawFlag :: enum {
	NONE   = 0,
	FLIP_H = 1<<0,
	FLIP_V = 1<<1,
	MASK   = 1<<2,
}

Draw_Item :: struct {}
Draw_List :: struct {
	items: []Draw_Item,
}

Key :: enum {
	MOD_SHIFT   = 0x0001,
	MOD_CONTROL = 0x0002,
	MOD_ALT     = 0x0004,
	MOD_SUPER   = 0x0008,

	UNKNOWN            =-1,
	INVALID            =-2,

	LBUTTON            = 1,
	RBUTTON            = 2,
	CANCEL             = 3,
	MBUTTON            = 4,

	BACK               = 8,
	TAB                = 9,
	CLEAR              = 12,
	RETURN             = 13,
	SHIFT              = 16,
	CONTROL            = 17,
	MENU               = 18,
	PAUSE              = 19,
	CAPITAL            = 20,
	KANA               = 0x15,
	HANGEUL            = 0x15,
	HANGUL             = 0x15,
	JUNJA              = 0x17,
	FINAL              = 0x18,
	HANJA              = 0x19,
	KANJI              = 0x19,
	ESCAPE             = 0x1B,
	CONVERT            = 0x1C,
	NONCONVERT         = 0x1D,
	ACCEPT             = 0x1E,
	MODECHANGE         = 0x1F,
	SPACE              = 32,
	PRIOR              = 33,
	NEXT               = 34,
	END                = 35,
	HOME               = 36,
	LEFT               = 37,
	UP                 = 38,
	RIGHT              = 39,
	DOWN               = 40,
	SELECT             = 41,
	PRINT              = 42,
	EXEC               = 43,
	SNAPSHOT           = 44,
	INSERT             = 45,
	DELETE             = 46,
	HELP               = 47,
	LWIN               = 0x5B,
	RWIN               = 0x5C,
	APPS               = 0x5D,
	SLEEP              = 0x5F,
	NUMPAD0            = 0x60,
	NUMPAD1            = 0x61,
	NUMPAD2            = 0x62,
	NUMPAD3            = 0x63,
	NUMPAD4            = 0x64,
	NUMPAD5            = 0x65,
	NUMPAD6            = 0x66,
	NUMPAD7            = 0x67,
	NUMPAD8            = 0x68,
	NUMPAD9            = 0x69,
	MULTIPLY           = 0x6A,
	ADD                = 0x6B,
	SEPARATOR          = 0x6C,
	SUBTRACT           = 0x6D,
	DECIMAL            = 0x6E,
	DIVIDE             = 0x6F,
	F1                 = 0x70,
	F2                 = 0x71,
	F3                 = 0x72,
	F4                 = 0x73,
	F5                 = 0x74,
	F6                 = 0x75,
	F7                 = 0x76,
	F8                 = 0x77,
	F9                 = 0x78,
	F10                = 0x79,
	F11                = 0x7A,
	F12                = 0x7B,
	F13                = 0x7C,
	F14                = 0x7D,
	F15                = 0x7E,
	F16                = 0x7F,
	F17                = 0x80,
	F18                = 0x81,
	F19                = 0x82,
	F20                = 0x83,
	F21                = 0x84,
	F22                = 0x85,
	F23                = 0x86,
	F24                = 0x87,
	NUMLOCK            = 0x90,
	SCROLL             = 0x91,
	LSHIFT             = 0xA0,
	RSHIFT             = 0xA1,
	LCONTROL           = 0xA2,
	RCONTROL           = 0xA3,
	LMENU              = 0xA4,
	RMENU              = 0xA5,

	APOSTROPHE         = 39,  /* ' */
	COMMA              = 44,  /* , */
	MINUS              = 45,  /* - */
	PERIOD             = 46,  /* . */
	SLASH              = 47,  /* / */
	NUM0               = 48,
	NUM1               = 49,
	NUM2               = 50,
	NUM3               = 51,
	NUM4               = 52,
	NUM5               = 53,
	NUM6               = 54,
	NUM7               = 55,
	NUM8               = 56,
	NUM9               = 57,
	SEMICOLON          = 59,  /* ; */
	EQUAL              = 61,  /* = */
	A                  = 65,
	B                  = 66,
	C                  = 67,
	D                  = 68,
	E                  = 69,
	F                  = 70,
	G                  = 71,
	H                  = 72,
	I                  = 73,
	J                  = 74,
	K                  = 75,
	L                  = 76,
	M                  = 77,
	N                  = 78,
	O                  = 79,
	P                  = 80,
	Q                  = 81,
	R                  = 82,
	S                  = 83,
	T                  = 84,
	U                  = 85,
	V                  = 86,
	W                  = 87,
	X                  = 88,
	Y                  = 89,
	Z                  = 90,
	LEFT_BRACKET       = 91,  /* [ */
	BACKSLASH          = 92,  /* \ */
	RIGHT_BRACKET      = 93,  /* ] */
	GRAVE_ACCENT       = 96,  /* ` */
};


key_down :: proc(k: Key) -> bool {
	return _core.key_states[k] != 0;
}

key_pressed :: proc(k: Key) -> bool {
	return (_core.key_deltas[k] != 0) && key_down(k);
}




win32_perf_count_freq := win32.GetQueryPerformanceFrequency();
time_now :: proc() -> f64 {
	assert(win32_perf_count_freq != 0);

	counter: i64;
	win32.QueryPerformanceCounter(^counter);
	result := cast(f64)counter / cast(f64)win32_perf_count_freq;
	return result;
}

_core: Core;

run :: proc(user_init, user_step: proc(c: ^Core)) {
	using win32;

	_core.running = true;

	win32_proc :: proc(hwnd: win32.HWND, msg: u32, wparam: win32.WPARAM, lparam: win32.LPARAM) -> win32.LRESULT #no_inline #cc_c {
		win32_app_key_mods :: proc() -> u32 {
			mods: u32 = 0;

			if is_key_down(Key_Code.SHIFT) {
				mods |= cast(u32)Key.MOD_SHIFT;
			}
			if is_key_down(Key_Code.CONTROL) {
				mods |= cast(u32)Key.MOD_CONTROL;
			}
			if is_key_down(Key_Code.MENU) {
				mods |= cast(u32)Key.MOD_ALT;
			}
			if is_key_down(Key_Code.LWIN) || is_key_down(Key_Code.RWIN) {
				mods |= cast(u32)Key.MOD_SUPER;
			}

			return mods;
		}

		match msg {
		case WM_KEYDOWN:
			_core.key_modifiers = win32_app_key_mods();
			if wparam < MAX_KEYS {
				_core.key_states[wparam] = 1;
				_core.key_deltas[wparam] = 1;
			}
			return 0;

		case WM_KEYUP:
			_core.key_modifiers = win32_app_key_mods();
			if wparam < MAX_KEYS {
				_core.key_states[wparam] = 0;
				_core.key_deltas[wparam] = 1;
			}
			return 0;

		case WM_CLOSE:
			PostQuitMessage(0);
			_core.running = false;
			return 0;
		}

		return DefWindowProcA(hwnd, msg, wparam, lparam);
	}


	window_class := WNDCLASSEXA{
		class_name = (cast(string)"Punity\x00").data, // C-style string
		size       = size_of(WNDCLASSEXA),
		style      = CS_HREDRAW | CS_VREDRAW | CS_OWNDC,
		instance   = cast(HINSTANCE)GetModuleHandleA(nil),
		wnd_proc   = win32_proc,
		// wnd_proc   = DefWindowProcA,
		background = cast(HBRUSH)GetStockObject(BLACK_BRUSH),
	};

	if RegisterClassExA(^window_class) == 0 {
		fmt.fprintln(os.stderr, "RegisterClassExA failed");
		return;
	}

	screen_width  := GetSystemMetrics(SM_CXSCREEN);
	screen_height := GetSystemMetrics(SM_CYSCREEN);

	rc: RECT;
	rc.left   = (screen_width - WINDOW_WIDTH)   / 2;
	rc.top    = (screen_height - WINDOW_HEIGHT) / 2;
	rc.right  = rc.left + WINDOW_WIDTH;
	rc.bottom = rc.top + WINDOW_HEIGHT;

	style: u32 = WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX;
	assert(AdjustWindowRect(^rc, style, 0) != 0);

	wt := WINDOW_TITLE;

	win32_window := CreateWindowExA(0,
	                                window_class.class_name,
	                                wt.data,
	                                style,
	                                rc.left, rc.top,
	                                rc.right-rc.left, rc.bottom-rc.top,
	                                nil, nil, window_class.instance,
	                                nil);

	if win32_window == nil {
		fmt.fprintln(os.stderr, "CreateWindowExA failed");
		return;
	}


	window_bmi: BITMAPINFO;
	window_bmi.size        = size_of(BITMAPINFOHEADER);
	window_bmi.width       = CANVAS_WIDTH;
	window_bmi.height      = CANVAS_HEIGHT;
	window_bmi.planes      = 1;
	window_bmi.bit_count   = 32;
	window_bmi.compression = BI_RGB;


	user_init(^_core);

	ShowWindow(win32_window, SW_SHOW);

	window_buffer := new_slice(u32, CANVAS_WIDTH * CANVAS_HEIGHT);
	defer free(window_buffer);


	for i := 0; i < window_buffer.count; i += 1 {
		window_buffer[i] = 0xff00ff;
	}


	dt: f64;
	prev_time := time_now();
	curr_time := time_now();
	total_time : f64 = 0;
	offset_x := 0;
	offset_y := 0;

	message: MSG;
	for _core.running {
		curr_time = time_now();
		dt = curr_time - prev_time;
		prev_time = curr_time;
		total_time += dt;

		offset_x += 1;
		offset_y += 2;

		{
			data: [128]byte;
			buf: fmt.Buffer;
			buf.data = data[:];
			fmt.bprintf(^buf, "Punity: %.4f ms\x00", dt*1000);
			win32.SetWindowTextA(win32_window, ^buf[0]);
		}


		for y := 0; y < CANVAS_HEIGHT; y += 1 {
			for x := 0; x < CANVAS_WIDTH; x += 1 {
				g := (x % 32) * 8;
				b := (y % 32) * 8;
				window_buffer[x + y*CANVAS_WIDTH] = cast(u32)(g << 8 | b);
			}
		}

		mem.zero(^_core.key_deltas[0], size_of_val(_core.key_deltas));

		for PeekMessageA(^message, nil, 0, 0, PM_REMOVE) != 0 {
			if message.message == WM_QUIT {
				_core.running = false;
			}
			TranslateMessage(^message);
			DispatchMessageA(^message);
		}

		user_step(^_core);

		dc := GetDC(win32_window);
		StretchDIBits(dc,
		              0, 0, CANVAS_WIDTH * CANVAS_SCALE, CANVAS_HEIGHT * CANVAS_SCALE,
		              0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
		              window_buffer.data,
		              ^window_bmi,
		              DIB_RGB_COLORS,
		              SRCCOPY);
		ReleaseDC(win32_window, dc);


		{
			delta := time_now() - prev_time;
			ms := cast(i32)((FRAME_TIME - delta) * 1000);
			if ms > 0 {
				win32.Sleep(ms);
			}
		}

		_core.frame += 1;
	}
}


main :: proc() {
	user_init :: proc(c: ^Core) {

	}

	user_step :: proc(c: ^Core) {

	}

	run(user_init, user_step);
}
