package main

// import "core:c"
import "core:math"
import "core:math/linalg"
import "core:mem"
import "core:os"

import "core:fmt"
import mu "vendor:microui"
import stbtt "vendor:stb/truetype"

DEBUG_DRAW_UI_CALLS :: #config(DEBUG_DRAW_UI_CALLS, false)

color_u32 :: proc {
	color_u8_to_u32,
	color_mu_color_to_u32,
}

color_u8_to_u32 :: #force_inline proc(r, g, b, a: u8) -> u32 {
	return (u32(a) << 24) + (u32(r) << 16) + (u32(g) << 8) + u32(b)
}

color_mu_color_to_u32 :: #force_inline proc(c: mu.Color) -> u32 {
	return (u32(c.a) << 24) + (u32(c.r) << 16) + (u32(c.g) << 8) + u32(c.b)
}

round_i32 :: #force_inline proc(v: f32) -> i32 {
	return i32(v + 0.5)
}

abs :: proc(a: i32) -> i32 {
	v := a >> 31
	return (a + v) ~ v
}

clamp :: proc(a, min, max: i32) -> i32 {
	t := (a < min ? min : a)
	return t > max ? max : t
}

// h: 0-360, s: 0-1, v: 0-1
rgb_to_hsv :: proc(h: u32, s: f32, v: f32) -> u32 {
	c := s * v
	x := c * (1 - linalg.abs(math.mod(f32(h) / 60, 2) - 1))
	m := v - c

	r1, g1, b1: f32
	if h >= 0 && h < 60 {
		r1, g1, b1 = c, x, 0
	} else if h >= 60 && h < 120 {
		r1, g1, b1 = x, c, 0
	} else if h >= 120 && h < 180 {
		r1, g1, b1 = 0, c, x
	} else if h >= 180 && h < 240 {
		r1, g1, b1 = 0, x, c
	} else if h >= 240 && h < 300 {
		r1, g1, b1 = x, 0, c
	} else {
		r1, g1, b1 = c, 0, x
	}

	r := u8((r1 + m) * 255)
	g := u8((g1 + m) * 255)
	b := u8((b1 + m) * 255)

	return color_u32(r, g, b, 0xFF)
}

draw_xor :: proc(bitmap: ^Bitmap) {
	@(static) offset: u32 = 0

	i: u32
	for y: u32; y < bitmap.height; y += 1 {
		for x: u32; x < bitmap.width; x += 1 {
			c := u8(((x + offset) ~ (y + offset)) % 256)
			bitmap.buffer[i] = color_u32(c, c, c, 0xFF)
			i += 1
		}
	}

	offset += 2
}

Fixed_Width :: 1280
Fixed_Height :: 720

draw_fast_circles :: proc(bitmap: ^Bitmap) {
	@(static) offset := 0
	@(static) scale: f32 = 512
	@(static) palette: [256]u32
	if palette[0] == 0x0 {
		for i := 0; i < 256; i += 1 {
			palette[i] = rgb_to_hsv(u32((f32(i) * 1.4) + 0.5), 1, 1)
		}
	}
	@(static) buffer: [Fixed_Width * Fixed_Height]u8
	if buffer[0] == 0 {
		i: u32
		for y: u32; y < Fixed_Height; y += 1 {
			for x: u32; x < Fixed_Width; x += 1 {
				c := u8(128 + (math.sin(f32((x * x) + (y * y)) / scale) * 127))
				buffer[i] = c
				i += 1
			}
		}
	}

	for p, i in buffer {
		bitmap.buffer[i] = palette[(offset + int(p)) % 256]
	}

	offset = (offset + 1) % 256
}

draw_plasma :: proc(bitmap: ^Bitmap) {
	@(static) offset := 0
	@(static) palette: [256]u32
	if palette[0] == 0x0 {
		for i := 0; i < 256; i += 1 {
			palette[i] = rgb_to_hsv(u32((f32(i) * 1.4) + 0.5), 1, 1)
		}
	}
	@(static) buffer: [Fixed_Width * Fixed_Height]u8
	if buffer[0] == 0 {
		i: u32
		for y: u32; y < Fixed_Height; y += 1 {
			for x: u32; x < Fixed_Width; x += 1 {
				c := u8((128 + (math.sin(f32(x) / 16) * 128) + 128 + (math.sin(f32(y) / 16) * 128)) / 2)
				buffer[i] = c
				i += 1
			}
		}
	}

	for p, i in buffer {
		bitmap.buffer[i] = palette[(offset + int(p)) % 256]
	}

	/** Debug palette drawing */
	palette_start := int(bitmap.width * (bitmap.height - 11))
	for x := 0; x <= 256; x += 1 {
		bitmap.buffer[palette_start + x] = 0xFF000000
	}
	palette_start += int(bitmap.width)
	for y := 0; y < 10; y += 1 {
		y_offset := y * int(bitmap.width)
		for c, i in palette {
			bitmap.buffer[palette_start + y_offset + i] = c
		}
		bitmap.buffer[palette_start + y_offset + 256] = 0xFF000000
	}

	offset = (offset + 1) % 256
}

draw_line_tests :: proc(screen_buffer: ^Bitmap) {
	draw_line(screen_buffer, 220, 360, 420, 360, 0xFFFFFFFF)
	draw_line(screen_buffer, 320, 260, 320, 460, 0xFFFFFFFF)
	draw_line(screen_buffer, 220, 260, 420, 460, 0xFFFFFFFF)
	draw_line(screen_buffer, 220, 460, 420, 260, 0xFFFFFFFF)
	draw_line(screen_buffer, 220, 260, 420, 260, 0xFFFFFFFF)
	draw_line(screen_buffer, 220, 460, 420, 460, 0xFFFFFFFF)
	draw_line(screen_buffer, 220, 260, 220, 460, 0xFFFFFFFF)
	draw_line(screen_buffer, 420, 260, 420, 460, 0xFFFFFFFF)

	for α := 0; α < 180; α += 1 {
		θ := f32(α) / f32(math.DEG_PER_RAD)
		x := i32((math.cos(θ) * 100) + 0.5)
		y := -i32((math.sin(θ) * 100) + 0.5)
		draw_line(screen_buffer, 960 - x, 360 - y, 960 + x, 360 + y, 0xFFFFFFFF)
	}

	px: i32 = 50
	py: i32 = 0
	for α := 0; α <= 360; α += 45 {
		θ := f32(α) / f32(math.DEG_PER_RAD)
		x := i32((math.cos(θ) * 50) + 0.5)
		y := -i32((math.sin(θ) * 50) + 0.5)
		draw_line(screen_buffer, 640 + px, 360 + py, 640 + x, 360 + y, 0xFFFFFFFF)
		px = x
		py = y
	}
}

// TODO: Basic 24-bit .bmp-support. (can/should use stb libs in the future)
// TODO: Basic bitmap functions. (sub-copy, alpha, etc)
// TODO: Advanced bitmap functions. (line, rectangle, triangle, filled, etc)
// TODO: Controller support, win/lin.
// TODO: "Piano" scene. (after audio)

scene_funcs := []proc(_: ^Bitmap){draw_xor, draw_fast_circles, draw_plasma, draw_line_tests}

FONT_HEIGHT :: 20
font_bitmap: Bitmap
glyph_data: [96]stbtt.bakedchar
ctx: mu.Context
mu_has_init := false

init_mu :: proc() {
	{
		when ODIN_OS == .Windows {
			data, err := os.read_entire_file_from_path("C:\\Windows\\Fonts\\arial.ttf")
		}
		when ODIN_OS == .Linux {
			data, err := os.read_entire_file_from_path(
				"/usr/share/fonts/TTF/JetBrainsMonoNL-Regular.ttf",
				context.allocator,
			)
		}
		if err != nil {
			fmt.println("fail reading font")
			return
		}

		font: stbtt.fontinfo
		stbtt.InitFont(&font, &data[0], stbtt.GetFontOffsetForIndex(&data[0], 0))
		temp_bitmap: [256 * 256]byte
		stbtt.BakeFontBitmap(&data[0], 0, FONT_HEIGHT, &temp_bitmap[0], 256, 256, 32, 96, &glyph_data[0])

		fmt.printf("%+v\n", glyph_data)

		font_bitmap.buffer = make([]u32, 256 * 256)
		font_bitmap.width = 256
		font_bitmap.height = 256
		for i in 0 ..< len(temp_bitmap) {
			font_bitmap.buffer[i] = color_u32(temp_bitmap[i], 0, 0, 255)
		}
	}

	mu.init(&ctx)
	ctx.text_width = proc(font: mu.Font, str: string) -> i32 {
		// fmt.printf("checking text_width: %s\n", str)
		width: i32
		for r in str {
			gd := &glyph_data[r - 32]
			width += i32(gd.xadvance + 0.5)
		}
		return width
	}
	ctx.text_height = proc(font: mu.Font) -> i32 {
		return FONT_HEIGHT
	}
}

// TODO: Text drawing.
// TODO: VirtualAlloc'ed/shm memory, hooked into Odin's context.
app_update_and_render :: proc(screen_buffer: ^Bitmap, input: ^AppInput) {
	if !mu_has_init {
		mu_has_init = true
		init_mu()
	}

	@(static) scene_num := 0
	@(static) mouse_x: i32
	@(static) mouse_y: i32

	if input.keyboard[AppInputKey.Num1].down {
		scene_num = 0
		mem.zero_slice(screen_buffer.buffer)
	} else if input.keyboard[AppInputKey.Num2].down {
		scene_num = 1
		mem.zero_slice(screen_buffer.buffer)
	} else if input.keyboard[AppInputKey.Num3].down {
		scene_num = 2
		mem.zero_slice(screen_buffer.buffer)
	} else if input.keyboard[AppInputKey.Num4].down {
		scene_num = 3
		mem.zero_slice(screen_buffer.buffer)
	}

	scene_funcs[scene_num](screen_buffer)

	draw_bitmap(screen_buffer, &font_bitmap, 400, 100)

	mx: i32
	my: i32
	if input.mouse_x > 0 && input.mouse_x < i32(screen_buffer.width) - 10 {
		mx = input.mouse_x
	}
	if input.mouse_y > 0 && input.mouse_y < i32(screen_buffer.height) - 10 {
		my = input.mouse_y
	}
	if mx != mouse_x || my != mouse_y {
		mu.input_mouse_move(&ctx, mx, my)
	}

	if input.mouse_button[0].transition {
		if input.mouse_button[0].down {
			mu.input_mouse_down(&ctx, mx, my, .LEFT)
		} else {
			mu.input_mouse_up(&ctx, mx, my, .LEFT)
		}
	}
	mouse_x, mouse_y = mx, my


	/*
  input_scroll :: proc(ctx: ^Context, x, y: i32)
  input_key_down :: proc(ctx: ^Context, key: Key)
  input_key_up :: proc(ctx: ^Context, key: Key)
  input_text :: proc(ctx: ^Context, text: string)
  */

	draw_rect(
		screen_buffer,
		mouse_x,
		mouse_y,
		mouse_x + 10,
		mouse_y + 10,
		0xFFFF00FF if input.mouse_button[0].down else 0xFFFF0000,
	)

	mu.begin(&ctx)
	if mu.begin_window(&ctx, "Test Window", {x = 10, y = 10, w = 200, h = 200}) {
		mu.label(&ctx, "Test label")
		mu.button(&ctx, "Button!")
		mu.end_window(&ctx)
	}
	if mu.begin_window(&ctx, "Test Slider", {x = 100, y = 20, w = 200, h = 200}) {
		value := f32(650)
		mu.slider(&ctx, &value, 42, 1337, 10)
		mu.end_window(&ctx)
	}
	mu.end(&ctx)


	clip := [?]i32{0, 0, i32(screen_buffer.width), i32(screen_buffer.height)}
	cmd: ^mu.Command = nil
	for mu.next_command(&ctx, &cmd) {
		switch c in cmd.variant {
		case ^mu.Command_Jump:
			fmt.println("Command_Jump")

		case ^mu.Command_Text:
			x := f32(c.pos[0])
			y := f32(c.pos[1] + 16)
			for r in c.str {
				quad: stbtt.aligned_quad
				stbtt.GetBakedQuad(&glyph_data[0], 256, 256, i32(r - 32), &x, &y, &quad, true)

				if i32(quad.x0) < clip[0] || i32(quad.x1) > clip[2] {
					continue
				}
				if i32(quad.y0) < clip[1] || i32(quad.y1) > clip[3] {
					continue
				}

				sx1 := u32(quad.s0 * 256.0)
				sy1 := u32(quad.t0 * 256.0)
				sx2 := u32(quad.s1 * 256.0)
				sy2 := u32(quad.t1 * 256.0)
				// fmt.printf("%c #%+v -> %d, %d : %d, %d\n", r, quad, sx1, sy1, sx2, sy2)
				draw_sub_bitmap(screen_buffer, &font_bitmap, i32(quad.x0), i32(quad.y0), sx1, sy1, sx2, sy2)

				/*
        when DEBUG_DRAW_UI_CALLS {
          draw_rect(
            screen_buffer,
            i32(quad.x0 + 0.5),
            i32(quad.y0 + 0.5),
            i32(quad.x1 + 0.5),
            i32(quad.y1 + 0.5),
            0xFFFF00FF,
          )
        }
        */
			}

		case ^mu.Command_Rect:
			x1, x2 := c.rect.x, c.rect.x + c.rect.w
			y1, y2 := c.rect.y, c.rect.y + c.rect.h
			for y in y1 ..< y2 {
				if y < clip[1] || y >= clip[3] {
					continue
				}
				for x in x1 ..< x2 {
					if x < clip[0] || x >= clip[2] {
						continue
					}
					screen_buffer.buffer[(y * i32(screen_buffer.width)) + x] = color_u32(c.color)
				}
			}
			when DEBUG_DRAW_UI_CALLS {
				draw_rect(screen_buffer, x1, y1, x2, y2, 0xFFFF0000)
			}

		case ^mu.Command_Icon:
			when DEBUG_DRAW_UI_CALLS {
				x1, x2 := c.rect.x, c.rect.x + c.rect.w
				y1, y2 := c.rect.y, c.rect.y + c.rect.h
				draw_rect(screen_buffer, x1, y1, x2, y2, 0xFF0000FF)
			}

		case ^mu.Command_Clip:
			clip[0] = c.rect.x
			clip[1] = c.rect.y
			clip[2] = c.rect.x + c.rect.w
			clip[3] = c.rect.y + c.rect.h
			when DEBUG_DRAW_UI_CALLS {
				draw_rect(screen_buffer, clip[0], clip[1], clip[2], clip[3], 0xFFFFAA00)
			}
		}
	}
}

glyph :: struct {
	bitmap: [^]byte,
	width:  int,
	height: int,
}

/*
@(init)
test_stbtt :: proc() {
	when ODIN_OS == .Windows {
		data, ok := os.read_entire_file_from_filename("C:\\Windows\\Fonts\\arialbd.ttf")
	}
	when ODIN_OS == .Linux {
		data, ok := os.read_entire_file_from_filename("/usr/share/fonts/TTF/OpenSans-Regular.ttf")
	}
	if !ok {
		fmt.println("fail reading font")
		return
	}

	font: stbtt.fontinfo
	stbtt.InitFont(&font, &data[0], stbtt.GetFontOffsetForIndex(&data[0], 0))

	max_h: int
	msg := "Hello stb_truetype!"
	glyphs := make(map[rune]glyph)
	defer delete(glyphs)
	for r in msg {
		if !(r in glyphs) {
			w, h: c.int
			bitmap := stbtt.GetCodepointBitmap(&font, 0, stbtt.ScaleForPixelHeight(&font, 16), r, &w, &h, nil, nil)
			glyphs[r] = {
				bitmap = bitmap,
				width  = int(w),
				height = int(h),
			}
			if int(h) > max_h {
				max_h = int(h)
			}
		}
	}
	defer {
		for r in glyphs {
			defer stbtt.FreeBitmap(glyphs[r].bitmap, nil)
		}
	}

	fmt.printf("%+v\nmax_h %d\n", glyphs, max_h)

	chars := [?]rune{' ', '.', ':', 'i', 'o', 'V', 'M', '@'}
	for y in 0 ..< max_h {
		for r in msg {
			g := glyphs[r]

			if g.bitmap == nil {
				for _ in 0 ..< 4 {
					fmt.printf(" ")
				}
				fmt.printf(" ")
				continue
			}

			offset := max_h - g.height
			if y < offset || y >= max_h - 1 {
				for x in 0 ..< g.width {
					fmt.printf(" ")
				}
				fmt.printf(" ")
				continue
			}

			i := (y - offset) * g.width
			for x in 0 ..< g.width {
				fmt.printf("%c", chars[g.bitmap[i + x] >> 5])
			}
			fmt.printf(" ")
		}
		fmt.println()
	}
	fmt.println()
}
*/