package main

import rl "libraries/raylib"
import ui "libraries/snths_ui"
import "core:fmt"

UiContainerType :: enum {
	Normal,
	Matrix,
	Scroll,
}

UiElementData_Container :: struct {
	type: UiContainerType,
	thickness: f32,
	scroll: f32,
}

UiElementData_Slider :: struct {
	min, max: f32,
	value:    ^f32,
}

UiElementData_FloatInput :: struct {
	value: ^f32,
	value_string: cstring,
}

UiElementData_Dropdown :: struct {
	value: ^i32,
}

UiButtonType :: enum {
	Button,
	Dropdown,
}

UiElementData_Button :: struct {
	type: UiButtonType,
	options: cstring,
}

MatrixTypes :: enum {
	Identity,
	Translation,
	Scale,
	Rotation,
}

@rodata matrix_add_hotkeys := [MatrixTypes]rl.KeyboardKey {
	.Identity = rl.KeyboardKey.I,
	.Translation = rl.KeyboardKey.T,
	.Scale = rl.KeyboardKey.S,
	.Rotation = rl.KeyboardKey.R,
}

FontStyle :: struct {
	font: rl.Font,
	size, spacing: f32
}

ProgramState :: enum {
	Normal,
	Message,
	Dialog,
}

MouseState :: enum {
	Hover,
	DraggingGrid,
	HoverMatrices,
}

ProgramDialogType :: enum {
	CreateTranslationMatrix,
	CreateScaleMatrix,
	CreateRotationMatrix,
}

@rodata program_dialog_messages := [ProgramDialogType]cstring {
	.CreateTranslationMatrix = "Create tranlation matrix",
	.CreateScaleMatrix = "Create scale matrix",
	.CreateRotationMatrix = "Create rotation matrix",
}

ProgramMessageType :: enum {
	ChangeMatrixTypeClearData,
}

@rodata program_messages := [ProgramMessageType]cstring {
	.ChangeMatrixTypeClearData = "Changing the matrix type will delete all current matrices.\nAre you sure you want to do this?",
}

program: Program

InputFloatField :: [32]byte
MATRIX_POOL_SIZE :: 4096

Program :: struct {
	ui_context: ui.Context,
	ui_element_focus: ui.ElementId,
	ui_overlay_context: ui.Context,
	ui_overlay_element_focus: ui.ElementId,
	matrix_float_pool: []f32,
	matrix_value_string_pool: []InputFloatField,
	matrix_create_dropdown_value: MatrixTypes,
	matrix_count: int,
	matrix_scroll: f32,
	matrix_scroll_speed: f32,
	mouse_state: MouseState,
	grid: Grid,
	font_style: FontStyle,
	state: ProgramState,
	message_type: ProgramMessageType,
	dialog_type: ProgramDialogType,
}

Grid :: struct {
	area:        rect2,
	inner_area:  rect2,
	zoom:        f32,
}

matrix_pool_max :: proc() -> int {
	return len(program.matrix_float_pool) / 9
}

matrix_pool_get_mat3 :: proc() -> ^mat3 {
	assert(program.matrix_count + 1 < matrix_pool_max(), "Max matrix count exceeded")
	mat := cast(^mat3)&program.matrix_float_pool[program.matrix_count * 9]
	program.matrix_count += 1
	return mat
}

program_show_dialog :: proc(program: ^Program, dialog_type: ProgramDialogType) {
	program.state = .Dialog
	program.dialog_type = dialog_type
}

program_show_mesasge :: proc(program: ^Program, message: ProgramMessageType) {
	program.state = .Message
	program.message_type = message
}

program_init :: proc(allocator := context.allocator) {
	program.matrix_float_pool = make(type_of(program.matrix_float_pool), MATRIX_POOL_SIZE)
	program.matrix_value_string_pool = make(type_of(program.matrix_value_string_pool), MATRIX_POOL_SIZE)
	program.matrix_scroll_speed = 32.0
	program.font_style = {
		font = rl.GetFontDefault(),
		size = 16.0,
		spacing = 1.0
	}
	program.grid = {
		area = {0, 0, WINDOW_WIDTH, WINDOW_HEIGHT},
		inner_area = {0, 0, WINDOW_WIDTH, WINDOW_HEIGHT},
		zoom = 1.0,
	}

	ui.init(&program.ui_context, 4000, ui_measure_text, &program.font_style, allocator)
	program.ui_element_focus = ui.element_id_invalid()
	ui.init(&program.ui_overlay_context, 200, ui_measure_text, &program.font_style, allocator)
	program.ui_overlay_element_focus = ui.element_id_invalid()
}

program_update :: proc() {
	if rl.IsKeyPressed(.ENTER) {
		matrix_pool_get_mat3()
	}

	ui_elements, matrix_container := program_build_ui()

	switch program.state {
	case .Normal:
		program.mouse_state = program_handle_mouse_workspace(program.mouse_state, matrix_container)
		if rl.IsKeyPressed(.TAB) {
			program.ui_element_focus = ui.focus_next_element(program.ui_element_focus, ui_elements, {.InputFloat})
		}
	case .Message:
	case .Dialog:
	}

	grid := program.grid
	grid_inner_position := rect_get_tl(grid.inner_area)
	draw_grid(grid.area, {32.0, 32.0}, rl.GRAY)

	program_render_ui(&program.ui_context, ui_elements)
}

program_handle_mouse_workspace :: proc(mouse_state: MouseState, matrix_container: ^ui.Element) -> MouseState {
	mouse := rl.GetMousePosition()
	switch mouse_state {
	case .Hover:
		if rl.IsMouseButtonPressed(.LEFT) {
			return .DraggingGrid
		} else if rect_has_point(rect2(matrix_container.area), mouse) {
			return .HoverMatrices
		}
	case .DraggingGrid:
		program.grid.inner_area = rect_translate(program.grid.inner_area, rl.GetMouseDelta())
		if !rl.IsMouseButtonDown(.LEFT) {
			return .Hover
		}
	case .HoverMatrices:
		program.matrix_scroll = max(0, program.matrix_scroll - rl.GetMouseWheelMove() * program.matrix_scroll_speed)
		if !rect_has_point(rect2(matrix_container.area), mouse) {
			return .Hover
		}
	}
	return mouse_state
}

program_build_ui :: proc() -> (elements: []ui.Element, matrix_container: ^ui.Element) {
	ui.start(&program.ui_context, {0, 0}, {WINDOW_WIDTH, WINDOW_HEIGHT})
	top_bar_container_config := ui.ElementConfiguration {margin = V2_ONE * 4.0}
	top_bar_element_margin := V2_ONE * 4.0
	top_bar_container := ui.container_begin({ui.Size_Percentage(1.0), ui.Size_Pixels(48.0)}, top_bar_container_config)
	top_bar_inner_height := ui.get_element_inner_area(top_bar_container^).height
	top_bar_button_dimensions := ui.Dimensions {ui.Size_Pixels(top_bar_inner_height), ui.Size_Pixels(top_bar_inner_height)}
	top_bar_button_config := ui.ElementConfiguration {margin = top_bar_element_margin}
	top_bar_dropdown_dimensions := ui.Dimensions {ui.Size_Pixels(160.0), ui.Size_Percentage(1.0)}
	top_bar_dropdown_config := ui.ElementConfiguration {margin = top_bar_element_margin}
	{
		button_config := top_bar_button_config
		button_config.label = "+"
		ui.element(.Button, top_bar_button_dimensions, button_config)
		button_config.label = "-"
		ui.same_line()
		ui.element(.Button, top_bar_button_dimensions, button_config)

		ui.same_line()
		matrix_dropdown_config := top_bar_dropdown_config
		matrix_dropdown_config.label = enum_to_raygui_options_string_hotkey(MatrixTypes, &matrix_add_hotkeys, context.temp_allocator)
		matrix_dropdown_data := new(UiElementData_Dropdown, context.temp_allocator)
		matrix_dropdown_data^ = {value = cast(^i32)&program.matrix_create_dropdown_value}
		ui.element(.Dropdown, top_bar_dropdown_dimensions, matrix_dropdown_config, matrix_dropdown_data)
	}
	ui.container_end()

	side_bar_container_config := ui.ElementConfiguration {margin = V2_ONE * 4.0}
	side_bar_container_data := new(UiElementData_Container, context.temp_allocator)
	side_bar_container_data^ = {type = .Scroll, scroll = program.matrix_scroll}
	matrix_container = ui.container_begin({ui.Size_Percentage(0.2), ui.Size_PercentageRemainder(1.0)}, side_bar_container_config, side_bar_container_data)
	for i in 0..<program.matrix_count {
		container_config := ui.ElementConfiguration{margin = V2_ONE * 8.0, label = fmt.ctprintf("Matrix %d", i)}
		container_dimensions := ui.Dimensions {ui.Size_Percentage(1.0), ui.Size_Pixels(112.0)}
		mat := program.matrix_float_pool[i * 9 : i * 9 + 9]
		value_strings := program.matrix_value_string_pool[i * 9 : i * 9 + 9]
		ui_mat3(mat, value_strings, container_dimensions, container_config)
	}
	ui.container_end()
	elements = ui.end()
	return
}

program_render_ui :: proc(ctx: ^ui.Context, elements: []ui.Element) {
	scissor_container: ^ui.Element
	render_above: ui.Element
	render_above.id = ui.element_id_invalid()

	for &element in elements {
		offset: v2
		if scissor_container != nil {
			if element.id.container_index < scissor_container.id.container_index {
				rl.EndScissorMode()
				scissor_container = nil
			} else {
				offset.y = -(cast(^UiElementData_Container)scissor_container.data).scroll
			}
		}

		area := rect_translate(rl.Rectangle(element.area), offset)
		#partial switch element.type {
		case .Container:
			dim := rl.Color {0, 0, 0, 192}
			if element.data != nil {
				data := cast(^UiElementData_Container)element.data
				switch data.type {
				case .Normal:
					rl.DrawRectangleRounded(area, 0.1, 4.0, dim)
					rl.GuiGroupBox(area, element.label)
				case .Matrix:
					matrix_area := ui.rect_grow(ui.rect2(area), element.margin / 2.0)
					tl, tr, br, bl := ui.rect_get_corners(matrix_area)
					col := rl.WHITE
					hook_length: f32 = 4.0
					rl.DrawLineEx(tl, bl, data.thickness, col)
					rl.DrawLineEx(tl, tl + V2_RIGHT * hook_length, data.thickness, col)
					rl.DrawLineEx(bl, bl + V2_RIGHT * hook_length, data.thickness, col)
					rl.DrawLineEx(tr, br, data.thickness, col)
					rl.DrawLineEx(tr, tr + V2_LEFT * hook_length, data.thickness, col)
					rl.DrawLineEx(br, br + V2_LEFT * hook_length, data.thickness, col)
				case .Scroll:
					rl.DrawRectangleRounded(area, 0.1, 4.0, dim)
					rl.GuiGroupBox(area, element.label)
					scissor_container = &element
					rl.BeginScissorMode(**rect2_to_irect2(area))
				}
			} else {
				rl.DrawRectangleRounded(area, 0.1, 4.0, dim)
				rl.GuiGroupBox(area, element.label)
			}
		case .Button:
			rl.GuiButton(area, element.label)
		case .Slider:
			data := cast(^UiElementData_Slider)element.data
			rl.GuiSlider(area, element.label, "", data.value, data.min, data.max)
		case .InputFloat:
			data := cast(^UiElementData_FloatInput)element.data
			if rl.GuiValueBoxFloat(area, element.label, data.value_string, data.value, program.ui_element_focus == element.id) == 1 {
				program.ui_element_focus = element.id
			}
		case .Dropdown:
			assert(element.data != nil, "Element data cannot be nil when creating a dropdown")
			data := cast(^UiElementData_Dropdown)element.data
			if program.ui_element_focus == element.id {
				render_above = element
			} else {
				if rl.GuiDropdownBox(area, element.label, data.value, program.ui_element_focus == element.id) {
					if program.ui_element_focus == element.id {
						program.ui_element_focus = ui.element_id_invalid()
					} else {
						program.ui_element_focus = element.id
					}
				}
			}
		case:
			rl.DrawRectangleRec(area, rl.MAGENTA)
		}
	}

	if scissor_container != nil {
		rl.EndScissorMode()
		scissor_container = nil
	}

	if render_above.id != ui.element_id_invalid() {
		#partial switch render_above.type {
		case .Dropdown:
			data := cast(^UiElementData_Dropdown)render_above.data
			if rl.GuiDropdownBox(rl.Rectangle(render_above.area), render_above.label, data.value, program.ui_element_focus == render_above.id) {
				if program.ui_element_focus == render_above.id {
					program.ui_element_focus = ui.element_id_invalid()
				} else {
					program.ui_element_focus = render_above.id
				}
			}
		}
	}
}

ui_measure_text :: proc(str: cstring, font_data: rawptr) -> ui.v2 {
	style := cast(^FontStyle)font_data
	return rl.MeasureTextEx(style.font, str, style.size, style.spacing)
}

ui_mat3 :: proc(mat: []f32, value_strings: []InputFloatField, dimensions: ui.Dimensions, config: ui.ElementConfiguration, allocator := context.temp_allocator) {
	container_data := new(UiElementData_Container, allocator)
	container_data^ = {type = .Matrix, thickness = 2.0}
	ui.container_begin(dimensions, config, container_data)
		matrix_input_config := ui.ElementConfiguration {margin = V2_ONE * 4.0}
		matrix_input_dimensions := ui.Dimensions {ui.Size_Percentage(1.0 / 3.0), ui.Size_Percentage(1.0 / 3.0)}
		#unroll for i in 0..<3 {
			#unroll for j in 0..<3 {
				data := new(UiElementData_FloatInput, allocator)
				index := i * 3 + j
				data^ = {value = &mat[index], value_string = cstring(&value_strings[index][0])}
				ui.element(.InputFloat, matrix_input_dimensions, matrix_input_config, data)
				ui.same_line()
			}
			program.ui_context.same_line = false
		}
	ui.container_end()
}

draw_grid :: proc(area: rect2, line_interval: v2, color: rl.Color) {
	br := rect_get_br(area)
	for x := area.x; x < br.x; x += line_interval.x {
		rl.DrawLineV({x, area.y}, {x, br.y}, color)
	}
	for y := area.y; y < br.y; y += line_interval.y {
		rl.DrawLineV({area.x, y}, {br.x, y}, color)
	}
}
