#+build windows
package main

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

DEBUG_DRAW_TIMINGS :: #config(DEBUG_DRAW_TIMINGS, false)

global_is_running := true

window_proc :: proc "std" (
	hwnd: windows.HWND,
	msg: u32,
	wparam: windows.WPARAM,
	lparam: windows.LPARAM,
) -> windows.LRESULT {result: windows.LRESULT
	switch msg {
	case windows.WM_DESTROY:
		global_is_running = false
		msg := cstring("WM_DESTROY\n")
		windows.OutputDebugStringW(([^]u16)(&msg))

	case windows.WM_CLOSE:
		global_is_running = false
		msg := cstring("WM_CLOSE\n")
		windows.OutputDebugStringW(([^]u16)(&msg))

	case:
		result = windows.DefWindowProcW(hwnd, msg, wparam, lparam)
	}

	return result
}

Backbuffer :: struct {
	memory:      rawptr,
	bitmap_info: windows.BITMAPINFO,
	width:       u32,
	height:      u32,
	bps:         u32,
	pitch:       u32,
}

resize_backbuffer :: proc(backbuffer: ^Backbuffer, width: u32, height: u32) {
	if backbuffer.memory != nil {
		windows.VirtualFree(backbuffer.memory, 0, windows.MEM_RELEASE)
	}

	backbuffer.width = width
	backbuffer.height = height
	backbuffer.bps = 4
	backbuffer.pitch = width * height

	backbuffer.bitmap_info.bmiHeader = {
		biSize        = size_of(backbuffer.bitmap_info.bmiHeader),
		biWidth       = i32(width),
		biHeight      = -i32(height),
		biPlanes      = 1,
		biBitCount    = 32,
		biCompression = windows.BI_RGB,
	}

	backbuffer.memory = windows.VirtualAlloc(
		nil,
		uint(backbuffer.bps * backbuffer.pitch),
		windows.MEM_RESERVE | windows.MEM_COMMIT,
		windows.PAGE_READWRITE,
	)
}

get_clock_value :: #force_inline proc() -> i64 {
	result: windows.LARGE_INTEGER
	windows.QueryPerformanceCounter(&result)
	return i64(result)
}

global_perf_count_frequency: i64

get_seconds_elapsed :: #force_inline proc(start, end: i64) -> f32 {
	return f32(end - start) / f32(global_perf_count_frequency)
}

blit_buffer_in_window :: proc(backbuffer: ^Backbuffer, dc: windows.HDC, width, height: i32) {
	ratio: f32 : 16.0 / 9.0
	fixed_width := i32(f32(height) * ratio)
	offset_x := (width - fixed_width) / 2

	if fixed_width != width {
		windows.PatBlt(dc, 0, 0, offset_x, height, windows.BLACKNESS)
		windows.PatBlt(dc, width - offset_x, 0, offset_x, height, windows.BLACKNESS)
	}
	windows.StretchDIBits(
		dc,
		offset_x,
		0,
		fixed_width,
		height,
		0,
		0,
		i32(backbuffer.width),
		i32(backbuffer.height),
		backbuffer.memory,
		&backbuffer.bitmap_info,
		windows.DIB_RGB_COLORS,
		windows.SRCCOPY,
	)
}

TIMERR_BASE :: 96
TIMERR_NOCANDO :: TIMERR_BASE + 1
TIMERR_NOERROR :: 0

input_key_translation := map[uint]AppInputKey {
	windows.VK_ESCAPE = .Escape,
	windows.VK_0      = .Num0,
	windows.VK_1      = .Num1,
	windows.VK_2      = .Num2,
	windows.VK_3      = .Num3,
	windows.VK_4      = .Num4,
	windows.VK_5      = .Num5,
	windows.VK_6      = .Num6,
	windows.VK_7      = .Num7,
	windows.VK_8      = .Num8,
	windows.VK_9      = .Num9,
	windows.VK_LEFT   = .ArrowLeft,
	windows.VK_RIGHT  = .ArrowRight,
}

main :: proc() {
	fmt.println("Hellope!")

	hinstance := windows.GetModuleHandleW(nil)
	fmt.printf("hinstance %x\n", hinstance)

	windows.QueryPerformanceFrequency(((^windows.LARGE_INTEGER)(&global_perf_count_frequency)))
	fmt.printf("perf_count_frequency: %d\n", global_perf_count_frequency)

	sleep_is_granular := (windows.timeBeginPeriod(1) == TIMERR_NOERROR)
	if sleep_is_granular {
		fmt.printf("-=sleep is granular=-\n")
	}

	class_name := windows.utf8_to_wstring("sandboxwindowsplatform_odin")
	window_class := windows.WNDCLASSW {
		style         = windows.CS_OWNDC | windows.CS_HREDRAW | windows.CS_VREDRAW,
		lpfnWndProc   = window_proc,
		hInstance     = windows.HINSTANCE(hinstance),
		hCursor       = windows.LoadCursorW(nil, ([^]u16)(windows._IDC_ARROW)),
		lpszClassName = class_name,
	}
	fmt.printf("window_class %v\n", window_class)

	if windows.RegisterClassW(&window_class) == 0 {
		msg := cstring("could not register class\n")
		windows.OutputDebugStringW(([^]u16)(&msg))
	}

	wsize := windows.RECT {
		right  = 1280,
		bottom = 720,
	}
	windows.AdjustWindowRect(&wsize, windows.WS_OVERLAPPEDWINDOW, false)
	fmt.printf("adjusted wndrect %v\n", wsize)

	window_title := windows.utf8_to_wstring("odin-lang hello winapi")
	window := windows.CreateWindowW(
		class_name,
		window_title,
		windows.WS_OVERLAPPEDWINDOW | windows.WS_VISIBLE,
		windows.CW_USEDEFAULT,
		windows.CW_USEDEFAULT,
		wsize.right - wsize.left,
		wsize.bottom - wsize.top,
		nil,
		nil,
		windows.HINSTANCE(hinstance),
		nil,
	)

	if window == nil {
		msg := cstring("could not create window\n")
		windows.OutputDebugStringW(([^]u16)(&msg))
		return
	}
	defer windows.DestroyWindow(window)

	backbuffer_width :: 1280 / 2
	backbuffer_height :: 720 / 2
	backbuffer_index: u32
	backbuffers: [2]Backbuffer
	resize_backbuffer(&backbuffers[0], backbuffer_width, backbuffer_height)
	resize_backbuffer(&backbuffers[1], backbuffer_width, backbuffer_height)
	defer windows.VirtualFree(backbuffers[0].memory, 0, windows.MEM_RELEASE)
	defer windows.VirtualFree(backbuffers[1].memory, 0, windows.MEM_RELEASE)
	fmt.printf("backbuffer[0]: %+v\n", backbuffers[0])
	fmt.printf("backbuffer[1]: %+v\n", backbuffers[1])

	game_update_hz := f32(30)
	target_seconds_per_frame := 1.0 / game_update_hz
	last_counter := get_clock_value()

	when DEBUG_DRAW_TIMINGS {
		debug_frame_timings: [256]f32
		debug_render_timings: [256]f32
		debug_highest_timing := f32(1)
	}

	input: [2]App_Input
	curr_input := &input[0]
	prev_input := &input[1]
	dc := windows.GetDC(window)
	defer windows.ReleaseDC(window, dc)
	for global_is_running {
		message: windows.MSG
		for windows.PeekMessageW(&message, window, 0, 0, windows.PM_REMOVE) {
			switch message.message {
			case windows.WM_QUIT:
				global_is_running = false
				msg := cstring("WM_QUIT\n")
				windows.OutputDebugStringW(([^]u16)(&msg))

			case windows.WM_SYSKEYDOWN, windows.WM_SYSKEYUP, windows.WM_KEYDOWN, windows.WM_KEYUP:
				msg := cstring("WM_SYS/KEY\n")
				windows.OutputDebugStringW(([^]u16)(&msg))
				key_code := uint(message.wParam)
				if key_code == windows.VK_ESCAPE {
					global_is_running = false
				}

				if translated_key, ok := input_key_translation[key_code]; ok {
					curr_input.keyboard[translated_key].down = ((message.lParam & (1 << 31)) == 0)
				}

			case:
				windows.TranslateMessage(&message)
				windows.DispatchMessageW(&message)
			}
		}

		mouse_point: windows.POINT
		windows.GetCursorPos(&mouse_point)
		windows.ScreenToClient(window, &mouse_point)
		curr_input.mouse_x = mouse_point.x / 2
		curr_input.mouse_y = mouse_point.y / 2

		curr_input.mouse_button[0].down = ((int(windows.GetKeyState(windows.VK_LBUTTON)) & (1 << 15)) != 0)
		curr_input.mouse_button[0].transition = (curr_input.mouse_button[0].down != prev_input.mouse_button[0].down)

		backbuffer := &backbuffers[backbuffer_index]
		backbuffer_index = (backbuffer_index + 1) % 2
		screen_buffer := Bitmap {
			buffer = ([^]u32)(backbuffer.memory)[0:backbuffer.pitch],
			width  = backbuffer.width,
			height = backbuffer.height,
		}

		when DEBUG_DRAW_TIMINGS {
			update_and_render_timing_start := get_clock_value()
		}
		app_update_and_render(&screen_buffer, curr_input)
		when DEBUG_DRAW_TIMINGS {
			update_and_render_timing_stop := get_clock_value()
		}

		curr_input, prev_input = prev_input, curr_input

		when DEBUG_DRAW_TIMINGS {
			pix_step := 128 / debug_highest_timing
			target_ms := 1000 * target_seconds_per_frame
			target_ms_line := int(((target_ms * (target_ms / debug_highest_timing)) * pix_step) + 0.5)
			for x := 0; x < 256; x += 1 {
				frame_height: int
				render_height: int
				{
					scaling := debug_frame_timings[x] / debug_highest_timing
					frame_height = int(((debug_frame_timings[x] * scaling) * pix_step) + 0.5)
				}
				{
					scaling := debug_render_timings[x] / debug_highest_timing
					render_height = int(((debug_render_timings[x] * scaling) * pix_step) + 0.5)
				}

				for y := 0; y < 128; y += 1 {
					i := ((127 - y) * int(screen_buffer.width)) + x

					c: u32 = 0xFF000000
					if y == target_ms_line {
						c = 0xFFFF00FF
					} else if y <= render_height {
						if render_height > target_ms_line {
							c = 0xFFFF0000
						} else {
							c = 0xFF00AA00
						}
					} else if y <= frame_height {
						c = 0xFF0000AA
					}
					screen_buffer.buffer[i] = c
				}
			}
		}

		elapsed := get_seconds_elapsed(last_counter, get_clock_value())
		if elapsed < target_seconds_per_frame {
			if sleep_is_granular {
				sleep_ms := u32(1000 * (target_seconds_per_frame - elapsed))
				if sleep_ms > 0 {
					windows.Sleep(sleep_ms)
				}
			}

			for elapsed < target_seconds_per_frame {
				elapsed = get_seconds_elapsed(last_counter, get_clock_value())
			}
		} else {
			fmt.printf("missed sleep\n")
		}

		client_rect: windows.RECT
		windows.GetClientRect(window, &client_rect)
		dim_width := client_rect.right - client_rect.left
		dim_height := client_rect.bottom - client_rect.top
		blit_buffer_in_window(backbuffer, dc, dim_width, dim_height)

		when DEBUG_DRAW_TIMINGS {
			ms_per_frame := 1000.0 * elapsed
			frame_render_ms :=
				1000.0 * get_seconds_elapsed(update_and_render_timing_start, update_and_render_timing_stop)

			debug_highest_timing = 0
			for i := 0; i < 255; i += 1 {
				debug_frame_timings[i] = debug_frame_timings[i + 1]
				if debug_frame_timings[i] > debug_highest_timing {
					debug_highest_timing = debug_frame_timings[i]
				}
				debug_render_timings[i] = debug_render_timings[i + 1]
				if debug_render_timings[i] > debug_highest_timing {
					debug_highest_timing = debug_render_timings[i]
				}
			}
			debug_frame_timings[255] = ms_per_frame
			if ms_per_frame > debug_highest_timing {
				debug_highest_timing = ms_per_frame
			}
			debug_render_timings[255] = frame_render_ms
			if frame_render_ms > debug_highest_timing {
				debug_highest_timing = frame_render_ms
			}
		}

		last_counter = get_clock_value()
	}
}