import dlib;
import dlib.util : HTPush = Push;
import dlib.math : Clamp;

import vulkan;
import vulkan : RendererGetExtent = GetExtent;
import buffer;
import parsing;
import editor;
import views;

import std.stdio;
import std.math.traits : isNaN;
import std.math.rounding : ceil, floor;
import std.math.exponential : pow;
import std.math.remainder : fmod;
import std.algorithm.comparison : clamp;
import std.format : sformat;
import core.stdc.string : memset;
import core.stdc.math : fabsf, cbrtf;
import std.conv;
import core.stdc.stdio : sprintf;

/****** IMPORTANT NOTES *********

	- Display string should be separate from key string
	- If key is 0 then its transient and will be discarded next frame
	- Events should be created then processed instead of directly from inputs
// Little mode ui thing in the corner thats colored and it slides the colour out (left to right) when mode is changed

*********************************/

enum Vec4 BORDER_COL    = SRGBVec4(0.254, 0.254, 0.266, 1.0);
enum Vec4 BORDER_HL_COL = SRGBVec4(0.035, 0.549, 0.824, 1.0);
enum Vec4 TEXT_COL      = SRGBVec4(1.0);
enum Vec4 TEXT_HL_COL   = SRGBVec4(0.0, 0.0, 0.0, 1.0);
enum Vec4 BG_COL         = Vec4(0.18, 0.18, 0.18, 1.0);
enum Vec4 BG_HL_COL      = Vec4(0.160, 0.533, 0.803, 1.0);
enum Vec4 HL_BORDER_COL  = Vec4(0.172, 0.643, 0.988, 1.0);
enum Vec4 CMD_COL        = Vec4(0.22, 0.22, 0.22, 1.0);
enum Vec4 CMD_BORDER_COL = Vec4(0.48, 0.48, 0.48, 1.0);
enum Vec4 CMD_INPUT_COL  = Vec4(0.130, 0.420, 0.640, 1.0);
enum Vec4 GREY           = Vec4(0.80, 0.80, 0.80, 1.0);
enum Vec4 WHITE          = HexCol(0xFFFFFF);
enum Vec4 BLUE           = HexCol(0x4EA9FF);
enum Vec4 RED            = HexCol(0xFF3268);
enum Vec4 YELLOW         = HexCol(0xF7C443);
enum Vec4 OPT_COL        = Vec4(0.22, 0.22, 0.22, 1.0);
enum Vec4 OPT_ALT_COL    = Vec4(0.31, 0.31, 0.31, 1.0);

const u64  VERTEX_MAX_COUNT = 10000;
const Vec2 CLICK_BUFFER     = Vec2(3.0, 3.0);
const u32  FONT_SIZES       = 24;
const u32  ATLAS_DIMENSION  = 512;

// TODO: add setting
const f32 SCROLL_SPEED       = 48.0;
const f32 LINE_COUNT_PADDING = 4.0;

__gshared UICtx g_ui_ctx;

//u8[] FONT_BYTES     = Arr!(u8)(import("pc-9800.ttf"));
//u8[] FONT_BYTES     = Arr!(u8)(import("NuberNextCondensed-DemiBold.otf"));
u8[] FONT_BYTES     = cast(u8[])import("jetbrains-mono/JetBrainsMono-Regular.ttf");
u8[] VERTEX_BYTES   = cast(u8[])import("gui.vert.spv");
u8[] FRAGMENT_BYTES = cast(u8[])import("gui.frag.spv");

Vec4      g_corner_radius_default     = 0.0;
f32       g_edge_softness_default     = 0.8;
f32       g_border_thickness_default  = 0.0;
Vec4      g_border_col_default        = BORDER_COL;
Vec4      g_border_hl_col_default     = BORDER_HL_COL;
Vec4      g_text_col_default          = TEXT_COL;
Vec4      g_text_hl_col_default       = TEXT_HL_COL;
UISize[2] g_size_info_default         = [UISize(ST.Percentage, 1.0), UISize(ST.Percentage, 1.0)];
Axis2D    g_layout_axis_default       = A2D.X;
Vec2      g_padding_default           = Vec2(0.0);
u32       g_text_size_default         = 18;
Vec2      g_scroll_target_default     = Vec2(0.0);
Vec2      g_scroll_clamp_default      = Vec2(0.0);
string    g_display_string_default    = null;
UISH      g_syntax_highlight_default  = UISH.None;
u8[]      g_syntax_tokens_default     = [];
Vec2      g_fixed_pos_default         = Vec2(0.0);
alias     g_parent_default            = g_UI_NIL;

const UI_COUNT = 5000;

__gshared const UIItem         g_ui_nil_item;
__gshared       UIItem*        g_UI_NIL;
__gshared const UIInput        g_ui_nil_input;
__gshared       UIInput*       g_UI_NIL_INPUT;

enum Axis2D
{
	X,
	Y,
	Max,
} alias A2D = Axis2D;

enum UIFlags : u64
{
	None   = 0,
	Parent = 1<<0,
	Panel  = 1<<1,
	Window = 1<<2,
} alias UIF = UIFlags;

enum UISignal
{
	None     = 0,
	Clicked  = 1<<0,
	Dragged  = 1<<1,
	Hovered  = 1<<2,
} alias UIS = UISignal;

enum UIEvent
{
	None        = 0,
	Click       = 1<<0,
	DragRelease = 1<<1,
	Drag        = 1<<2,
	DragStart   = 1<<3,
	Press       = 1<<4,
	Scroll      = 1<<5,
} alias UIE = UIEvent;

struct UIInput
{
	UIEvent  type;
	UIInput* next, prev;
	Modifier md;
	union
	{
		struct
		{
			Input    key;
			string   text;
		};
		IVec2 rel;
		IVec2 pos;
		i32   scroll;
	}
}

enum UISyntaxHighlight
{
	None,
	D,
	Max,
} alias UISH = UISyntaxHighlight;

enum ItemStateFlags
{
	None      = 0,
	Hot       = 1<<0,
	Ready     = 1<<1,
	Active    = 1<<2,
	SetHot    = 1<<3,
	SetReady  = 1<<4,
	SetActive = 1<<5,
} alias ISF = ItemStateFlags;

struct PushConst
{
	Mat4 projection;
	u32  atlas_index;
}

struct UICtx
{
	enum FO = FRAME_OVERLAP;
	enum FS = FONT_SIZES;

	HashTable!(UIHash, UIItem*) items;
	UIItem*                     free_items;
	UIItem*                     transient_items;

	Arena                       arena;
	Arena                       temp_arena;
	Arena                       stack_arena;

	Inputs*                     inputs;
	u64                         frame;
	u64 f_idx;

	LinkedList!(UIInput)        events;
	IVec2                       mouse_pos;

	PlatformWindow*             window;
	Renderer                    rd;
	Descriptor[FS][FO]          font_descs;
	Descriptor                  default_tex;
	Descriptor                  sampler;
	Pipeline                    pipeline;
	DescSetLayout               desc_set_layout;
	DescSet[FO]                 desc_sets;
	PipelineLayout              pipeline_layout;
	PushConst                   pc;
	Vec2                        res;

	FontFace                    font;
	FontGlyphs[FS]              glyph_sets;
	u32                         glyph_sets_used;
	bool[FO]                    glyphs_to_load;
	bool                        prev_has_tex;
	u32                         prev_atlas_index;           

	UIBuffer[FO]                buffers;

	u32                         tab_width;
	Vec4[TS.max][UISH.max]      syntax_colors;

	UIItem*                     root_first;
	UIItem*                     root_last;
	UIKey                       drag_key;
	UIKey                       hover_key;
	u64                         last_hover_frame;

	f32                         scroll_rate;
	f32                         animation_rate;
	f32                         fade_rate;

	Stack!(UIItem*)*            key_item_stack;
	Stack!(UIItem*)*            parent_stack;
}

struct FontGlyphs
{
	FontAtlasBuf        abuf;
	u32                 size;
	u32                 index;
	bool[FRAME_OVERLAP] loaded;
}

struct Stack(T)
{
	Stack!(T)* next;
	T          value;
	bool       auto_pop;
}

struct StackTop(T)
{
	Stack!(T)* top;
	Stack!(T)* free;
}

struct UIItem
{
	IVec2    dragged;
	UIKey    key;
	UIItem*  next, prev, first, last, parent;

	Rect     rect;
	Vec2     size;

	Vec4     bg_col;
	Vec4     border_col;
	Vec4     corner_radius;
	Vec2     scroll_offset;
	Vec2     scroll_size;
	UIFlags  flags;
	u64      last_frame;
	Axis2D   layout_axis;
	f32      edge_softness;
	f32      border_thickness;
	f32      panel_pct;
	f32      ready_t;  // Item visible
	f32      hot_t;    // Item to be interacted with (e.g. hover)
	f32      active_t; // Item active (retained focus)
}

enum SizeType
{
	Pixels,
	Percentage,
	ChildrenSum,
	TextSize,
	Pct = ST.Percentage,
	Px  = ST.Pixels,
}

alias ST = SizeType;

struct UISize
{
	SizeType type;
	f32      value;
}

struct UIBuffer
{
	MappedBuffer!(Vertex) m_vtx;
	MappedBuffer!(u32)    m_idx;
	Vertex[]              vtx;
	u32[]                 idx;
	u32                   count;
	u32                   vtx_offset;
}

struct VPos
{
	Vec2 start;
	Vec2 end;
}

struct Vertex
{
	Vec4    cols;
	Vec4    corner_radius;
	union
	{
		VPos[2] pos;
		struct
		{
			Vec2 dst_start;
			Vec2 dst_end;
			Vec2 src_start;
			Vec2 src_end;
		};
	};
	f32 border_thickness;
	f32 edge_softness;
	f32 raised;
	u32 has_texture;
}

enum SettingType
{
	Value,
	Slider,
	Dropdown,
	Toggle,
}

Attribute[10] attributes = [
	{ binding: 0, location: 0, format: FMT.RGBA_F32, offset: Vertex.cols.offsetof             },
	{ binding: 0, location: 1, format: FMT.RGBA_F32, offset: Vertex.corner_radius.offsetof    },
	{ binding: 0, location: 2, format: FMT.RG_F32,   offset: Vertex.dst_start.offsetof        },
	{ binding: 0, location: 3, format: FMT.RG_F32,   offset: Vertex.dst_end.offsetof          },
	{ binding: 0, location: 4, format: FMT.RG_F32,   offset: Vertex.src_start.offsetof        },
	{ binding: 0, location: 5, format: FMT.RG_F32,   offset: Vertex.src_end.offsetof          },
	{ binding: 0, location: 6, format: FMT.R_F32,    offset: Vertex.border_thickness.offsetof },
	{ binding: 0, location: 7, format: FMT.R_F32,    offset: Vertex.edge_softness.offsetof    },
	{ binding: 0, location: 8, format: FMT.R_F32,    offset: Vertex.raised.offsetof           },
	{ binding: 0, location: 9, format: FMT.R_U32,    offset: Vertex.has_texture.offsetof      },
];

union Rect
{
	Vec2[2] v;
	struct { Vec2 p0, p1; };
}

alias UIHash = u64;

alias UIPair = KVPair!(UIHash, UIItem*);

struct UIKey
{
	string text, hash_text;
	u64    hash;
}

enum bool KeyType(T)        = (StringType!(T) || is(T == UIKey) || is(T == const(UIKey)));
enum bool ItemAndKeyType(T) = (KeyType!(T)    || is(T == UIItem*));

void


void
InitUICtx(PlatformWindow* window)
{
	version(ENABLE_RENDERER)
	{
		version(linux)
		{
			PlatformHandles handles = {
				display: window.display,
				window:  window.window,
			};
		}

		version(Windows)
		{

		}
	}

	g_UI_NIL             = cast(UIItem*)&g_ui_nil_item;
	g_UI_NIL_INPUT       = cast(UIInput*)&g_ui_nil_input;

	Arena arena = CreateArena(MB(4));
	
	UIBuffer[FRAME_OVERLAP] buffers;

	UICtx* ctx = &g_ui_ctx;

	ctx.items           = CreateHashTable!(UIHash, UIItem*)(12);
	ctx.free_items      = Alloc!(UIItem)(&arena);
	ctx.arena           = arena;
	ctx.temp_arena      = CreateArena(MB(1));
	ctx.stack_arena     = CreateArena(MB(1));
	ctx.transient_items = g_UI_NIL;
	ctx.font            = OpenFont(cast(u8[])FONT_BYTES);
	ctx.tab_width       = 2;
	ctx.syntax_colors   = [DEFAULT_COLORS, SYNTAX_COLORS];

	assert(ctx.font);

	UIItem* fi = ctx.free_items;
	fi.first = fi.last = fi.next = fi.prev = fi.parent = g_UI_NIL;

	version(ENABLE_RENDERER)
	{
		ctx.rd = InitRenderer(handles, MB(16), MB(8));

		DescLayoutBinding[2] layout_bindings = [
			{ binding: 0, descriptorType: DT.Image,   descriptorCount: FONT_SIZES, stageFlags: SS.All },
			{ binding: 1, descriptorType: DT.Sampler, descriptorCount: 1,          stageFlags: SS.All },
		];

		ctx.desc_set_layout = CreateDescSetLayout(&ctx.rd, layout_bindings);
		ctx.pipeline_layout = CreatePipelineLayout(&ctx.rd, ctx.desc_set_layout, PushConst.sizeof);

		for(u64 i = 0; i < FRAME_OVERLAP; i += 1)
		{
			ctx.buffers[i].m_vtx = CreateMappedBuffer!(Vertex)(&ctx.rd, BT.Vertex, VERTEX_MAX_COUNT);
			ctx.buffers[i].m_idx = CreateMappedBuffer!(u32)(&ctx.rd, BT.Index, 6);
			ctx.buffers[i].vtx   = ctx.buffers[i].m_vtx.data;
			ctx.buffers[i].idx   = ctx.buffers[i].m_idx.data;
			ctx.buffers[i].idx[0 .. $] = [0, 1, 2, 2, 1, 3];
			ctx.desc_sets[i]     = AllocDescSet(&ctx.rd, ctx.desc_set_layout);
		}

		GfxPipelineInfo ui_info = {
			vertex_shader:     cast(u8[])VERTEX_BYTES,
			frag_shader:       cast(u8[])FRAGMENT_BYTES,
			input_rate:        IR.Instance,
			input_rate_stride: Vertex.sizeof,
			layout:            ctx.pipeline_layout,
			vertex_attributes: attributes,
			src_color:         BF.SrcAlpha,
			dst_color:         BF.OneMinusSrcAlpha,
			color_op:          BO.Add,
			src_alpha:         BF.One,
			dst_alpha:         BF.Zero,
			alpha_op:          BO.Add,
		};

		for(u64 i = 0; i < ctx.font_descs.length; i += 1)
		{
			for(u32 j = 0; j < ctx.font_descs[i].length; j += 1)
			{
				CreateImageViewTex(&ctx.rd, &ctx.font_descs[i][j], ATLAS_DIMENSION, ATLAS_DIMENSION, DT.Image, 0, j);
				ctx.glyph_sets[j].index = j;
			}
		}

		FontAtlasBuf* abuf = GetFontAtlas(g_text_size_default);

		LoadFontGlyphs();

		CreateGraphicsPipeline(&ctx.rd, &ctx.pipeline, &ui_info);
		ctx.sampler = CreateSampler(&ctx.rd, MipmapMode.Nearest, 1);

		u8[512*512*4] white_tex = 255;
		for(u64 i = 0; i < ctx.font_descs.length; i += 1)
		{
			for(u32 j = 1; j < ctx.font_descs[i].length; j += 1)
			{
				Transfer(&ctx.rd, &ctx.font_descs[i][j].view, white_tex, ATLAS_DIMENSION, ATLAS_DIMENSION);
			}
		}

		for(u64 i = 0; i < FRAME_OVERLAP; i += 1)
		{
			Write(&ctx.rd, ctx.desc_sets[i], ctx.font_descs[i]);
			Write(&ctx.rd, ctx.desc_sets[i], &ctx.sampler);
		}	

		SetClearColors(&ctx.rd, [0.0, 0.0, 0.0, 1.0], [0.0, 0.0, 0.0, 0.0]);
	}

	MakeItem("###root", UIF.Parent);
}

FontAtlasBuf*
GetFontAtlas(u32 size)
{
	return &GetFontGlyphs(size).abuf;
}

f32
GetLineHeight(u32 size)
{
	return GetFontAtlas(size).atlas.line_height;
}

FontGlyphs*
GetFontGlyphs(u32 size)
{
	UICtx* ctx = GetCtx();

	FontGlyphs* fg = null;
	for(u64 i = 0; i < ctx.glyph_sets_used; i += 1)
	{
		if(size == ctx.glyph_sets[i].size)
		{
			fg = &ctx.glyph_sets[i];
			break;
		}
	}
	
	if(!fg)
	{
		u32 i = ctx.glyph_sets_used;
		fg    = &ctx.glyph_sets[i];

		assert(ctx.font);

		fg.abuf   = CreateAtlas(&ctx.arena, ctx.font, size, UVec2(ATLAS_DIMENSION));
		fg.size   = size;
		fg.loaded = false;

		ctx.glyphs_to_load   = true;
		ctx.glyph_sets_used += 1;
	}

	return fg;
}

void
LoadFontGlyphs()
{
	UICtx* ctx = GetCtx();
	u64    fi  = ctx.f_idx;

	for(u64 i = 0; i < ctx.glyph_sets_used; i += 1)
	{
		FontGlyphs* fg = ctx.glyph_sets.ptr + i;
		if(!fg.loaded[fi])
		{
			Transfer(&ctx.rd, &ctx.font_descs[fi][i].view, fg.abuf.data, ATLAS_DIMENSION, ATLAS_DIMENSION);
			fg.loaded[fi] = true;
		}
	}

	Write(&ctx.rd, ctx.desc_sets[fi], ctx.font_descs[fi]);

	ctx.glyphs_to_load[fi] = false;
}

UIItem*
MakeItem(T)(T k, f32 x, f32 y, f32 w, f32 h, UIFlags flags = UIF.None) if(KeyType!(T))
{
	UICtx*  ctx  = GetCtx();
	UIItem* item = Get(k);

	item.flags   = flags; 
	item.parent  = item.parent_stack.value;
	item.rect.p0 = Vec2(x,   y);
	item.rect.p1 = Vec2(x+w, y+h);

	if(Nil(ctx.root_first))
	{
		ctx.root_first = ctx.root_last = item;
	}
	else if(!Nil(item.parent))
	{
		DLLPush(item.parent, item, g_UI_NIL);
	}

	if(flags & UIF.Parent)
	{
		Push(&ctx.parent_stack, item);
	}

	item.last_frame = ctx.frame;

	Push(&ctx.key_item_stack);

	return item;
}

static string
AssignItem(T, alias p)()
{
	import std.format : format;

	enum string id = __traits(identifier, p);
	string result  = "";
	static if(is(T == UIItem*))
	{
		result = id;
	}
	else static if(is(T == UIKey) || StringType!(T))
	{
		result = format("Get(%s)", id);
	}
	else static assert(false, "Unknown type for AssignItem");

	return result;
}

static string 
AssignKey(T, alias p)()
{
	import std.format : format;

	enum string id = __traits(identifier, p);
	string result  = "";
	static if(is(T == UIItem*))
	{
		result = format("%s.key", id);
	}
	else static if(is(T == UIKey) || is(T == const(UIKey)))
	{
		result = id;
	}
	else static if(StringType!(T))
	{
		result = format("MakeKey(%s)", id);
	}
	else static assert(false, "Unknown type for AssignKey");

	return result;
}

bool
Ready(T)(T param) if(ItemAndKeyType!(T))
{
	UIItem* item = mixin(AssignItem!(T, param));
	return item.ready_t > 0.0001;
}

bool
Hovered(bool current_frame, T, U)(T param, U* ptr, U index)
{
	bool result = Hovered!(current_frame)(param);
	if(result)
	{
		*ptr = index;
	}

	return result;
}

bool
Hovered(bool current_frame, T)(T param)
{
	UICtx* ctx = GetCtx();
	bool result;

	UIKey key = mixin(AssignKey!(T, param));

	if(!ZeroKey(ctx.hover_key))
	{
		static if(current_frame)
		{
			result = key.hash == ctx.hover_key.hash && ctx.last_hover_frame == ctx.frame;
		}
		else
		{
			result = key.hash == ctx.hover_key_hash;
		}
	}

	return result;
}

void
PushUIEvent(UICtx* ctx, UIInput input)
{
	UIInput* ev = Alloc!(UIInput)(&ctx.temp_arena);
	*ev         = input;

	DLLPush(&ctx.events, ev, null);
}

void
DrawUI(UICtx* ctx, u32 atlas_index)
{
	if(ctx.pc.atlas_index != atlas_index) 
	{
		DrawUI(ctx);
		ctx.pc.atlas_index = atlas_index;
		PushConstants(&ctx.rd, ctx.pipeline, &ctx.pc);
	}
}

void
DrawUI(UICtx* ctx)
{
	version(ENABLE_RENDERER)
	{
		UIBuffer* b = ctx.buffers.ptr + ctx.f_idx;
		if(b.vtx_offset != b.count)
		{
			u32 count = b.count - b.vtx_offset;
			BindBuffers(&ctx.rd, &b.m_idx, &b.m_vtx);
			DrawIndexed(&ctx.rd, 6, count, 0, 0, b.vtx_offset);
			b.vtx_offset += count;
		}
	}
}

void
BeginUI(Inputs* inputs)
{
	UICtx* ctx = GetCtx();
	Arena* a   = &ctx.temp_arena;

	Reset(a);

	// Convert Inputs
	ctx.events.first = ctx.events.last = null;

	bool mouse_moved;
	static bool dragging;
	static bool mouse_down;
	for(InputEvent* i = inputs.first; i; i = i.next)
	{
		switch(i.key) 
		{
			case Input.LeftClick:
				{
					mouse_down = i.pressed;
					if(!mouse_down)
					{
						PushUIEvent(ctx, UIInput(dragging ? UIE.DragRelease : UIE.Click));
					}
					dragging   = false;
				} break;
			case Input.MouseMotion:
				{
					mouse_moved   = true;
					ctx.mouse_pos = IVec2(i.x, i.y);

					if(!dragging && mouse_down)
					{
						PushUIEvent(ctx, UIInput(type: UIE.DragStart, pos: IVec2(i.x, i.y)));
					}
				
					dragging = mouse_down;

					if(dragging)
					{
						PushUIEvent(ctx, UIInput(type: UIE.Drag, rel: IVec2(i.rel_x, i.rel_y)));
					}
				} break;
			case Input.ScrollUp, Input.ScrollDown:
				{
					if(i.pressed)
					{
						PushUIEvent(ctx, UIInput(type: UIE.Scroll, md: i.md, scroll: i.key == Input.ScrollUp ? -1 : 1));
					}
				} break;
			default:
				{
					if(i.pressed)
					{
						PushUIEvent(ctx, UIInput(type: UIE.Press, md: i.md, key: i.key, text: i.pressed ? i.text : []));
					}
				} break;
		}
	}

	u64 next_frame = ctx.frame+1;

	// Check current mouse target
	if(mouse_moved && !Nil(ctx.root_last))
	{
		UIKey hovered = ZeroKey();
		for(Stack!(UIItem*)* node = ctx.key_item_stack; node; node = node.next)
		{
			if(InBounds(ctx.mouse_pos, &node.value.rect))
			{
				hovered = node.value.key;
				break;
			}
		}

		if(!ZeroKey(hovered))
		{
			ctx.last_hover_frame = next_frame;
			ctx.hover_key        = hovered;
		}
		else
		{
			ctx.hover_key = ZeroKey();
		}
	}


	Reset(&ctx.stack_arena);
	ctx.parent_stack   = Alloc!(Stack!(UIItem*))(&ctx.stack_arena);
	ctx.key_item_stack = Alloc!(Stack!(UIItem*))(&ctx.stack_arena);

	SetNil(&ctx.root_first, &ctx.root_last, &ctx.parent_stack.value, &ctx.key_item_stack.value);

	// Clean up items
	KVPair!(UIHash, UIItem*)*[] items = GetAllNodes(&ctx.temp_arena, &ctx.items);
	foreach(i; 0 .. items.length)
	{
		UIItem* item = items[i].value;
		if(item.last_frame != ctx.frame)
		{
			Delete(&ctx.items, items[i].key);
			memset(item, 0, UIItem.sizeof);
			DLLPush(ctx.free_items, item, g_UI_NIL);
		}
	}

	// Ctx state
	ctx.frame  = next_frame;
	ctx.f_idx  = ctx.frame%FRAME_OVERLAP;
	ctx.inputs = inputs;

	assert(!mouse_moved || ZeroKey(ctx.hover_key) || (ctx.frame == ctx.last_hover_frame));

	ctx.animation_rate = 1.0 - pow(2.0, (-30.0f * g_delta));
	ctx.fade_rate      = 1.0 - pow(2.0, (-50.0f * g_delta));
	ctx.scroll_rate    = 1.0 - pow(2.0, (-60.0f * g_delta));

	version(ENABLE_RENDERER)
	{
		BeginFrame(&ctx.rd);

		if(ctx.glyphs_to_load[ctx.f_idx])
		{
			LoadFontGlyphs();
		}

		BeginRendering(&ctx.rd);

		SetStencilTest(&ctx.rd, false);

		Vec2 ext = GetExtent();
		if(ext != ctx.res)
		{
			ctx.res = ext;
			Ortho(&ctx.pc.projection, 0.0, 0.0, ext.x, ext.y, -10.0, 10.0);
		}

		PushConstants(&ctx.rd, ctx.pipeline, &ctx.pc);
		Bind(&ctx.rd, ctx.pipeline, ctx.desc_sets[ctx.f_idx]);
	}
	else
	{
		Vec2 ext = GetExtent();
		if(ext != ctx.res)
		{
			ctx.res = ext;
		}
	}

	memset(ctx.buffers[ctx.f_idx].vtx.ptr, 0, Vertex.sizeof * ctx.buffers[ctx.f_idx].count);
	ctx.buffers[ctx.f_idx].count      = 0;
	ctx.buffers[ctx.f_idx].vtx_offset = 0;
}

void
EndUI()
{
	UICtx* ctx = GetCtx();

	version(ENABLE_RENDERER)
	{
		DrawUI(ctx);
		FinishRendering(&ctx.rd);
		SubmitAndPresent(&ctx.rd);
	}

	if(!ZeroKey(ctx.drag_key))
	{
		for(UIInput* i = ctx.events.first; !CheckNil(g_UI_NIL_INPUT, i); i = i.next)
		{
			if(i.type == UIE.DragRelease)
			{
				ctx.drag_key = ZeroKey();
				break;
			}
		}
	}
}

void
SetNil(Args...)(Args args)
{
	static foreach(i; 0 .. Args.length)
	{
		static if(is(typeof(Args[i]) == UIItem**))
		{
			*args[i] = g_UI_NIL;
		}
		else
		{
			*args[i] = null;
		}
	}
}

void
Clamp(UICtx* ctx, Vertex* v)
{
	v.dst_start = Clamp(v.dst_start, Vec2(0.0), Vec2(ctx.res));
	v.dst_end   = Clamp(v.dst_end,   Vec2(0.0), Vec2(ctx.res));
}

bool
Focused(T)(T param) if(ItemAndKeyType!(T))
{
	UIItem* item = mixin(AssignItem(T, param));
	return g_ui_ctx.focus_item == item;
}

void
UpdateState(f32* v, bool cond)
{
	*v += g_ui_ctx.animation_rate * (cast(f32)(cond) - *v);
	f32 diff = fabsf((*v)-1.0);
	if(diff < 0.00009)
	{
		*v = 1.0;
	}
	else if(diff > 0.99998)
	{
		*v = 0.0;
	}
}

void
UpdateState(ISF flags)(UICtx* ctx, UIItem* item)
{
	static if(flags & ISF.Hot)  	UpdateState(&item.hot_t, cast(bool)(flags & ISF.SetHot) || Hovered!(true)(item));
	static if(flags & ISF.Ready)  UpdateState(&item.ready_t, cast(bool)(flags & ISF.SetReady));
	static if(flags & ISF.Active)	UpdateState(&item.active_t, cast(bool)(flags & ISF.SetActive) || Focused(item));
}

pragma(inline) void
AnimateHot(/* Vec4*[] */ Args...)(f32 hot, Vec4 hl_col, Args cols)
{
	if(hot != 0.0)
	{
		static foreach(i; 0 .. Args.length)
		{
			*(cols[i]) = Mix(*(cols[i]), hl_col, hot);
		}
	}
}

pragma(inline) void
AnimateReady(Args...)(f32 ready, Args cols)
{
	if(ready != 1.0)
	{
		static foreach(i; 0 .. Args.length)
		{
			static if(is(typeof(Args[i]) == f32*))
			{
				*(cols[i]) *= item.ready_t;
			}
			else static if(is(typeof(Args[i]) == Vec4*))
			{
				cols[i].a *= item.ready_t;
			}
		}
	}
}

void
DrawDropShadow(UICtx* ctx, UIItem* item)
{
	Vec2 size = item.rect.p1-item.rect.p0;

	f32 px    = clamp(((size.x+size.y)/2.0)*0.004, 1.0, f32.max);
	f32 alpha = 0.1;
	AnimateReady(item.ready_t, &alpha);

	Vertex* v       = GetVertex(ctx);
	v.dst_start     = item.rect.p0 - Vec2(px);
	v.dst_end       = item.rect.p1 + Vec2(px);
	v.cols          = Vec4(0.0, 0.0, 0.0, alpha);
	v.corner_radius = 0.8f;
	v.edge_softness = 8.0f;

	Clamp(ctx, v);
}

void
DrawBorder(UICtx* ctx, UIItem* item)
{
	Vec4 col = item.border_col;
	AnimateHot(item.hot_t, BORDER_HL_COL, &col);
	AnimateReady(item.ready_t, &col);

	Vertex* v          = GetVertex(ctx);
	v.dst_start        = item.rect.p0;
	v.dst_end          = item.rect.p1;
	v.cols             = col;
	v.corner_radius    = item.corner_radius;
	v.border_thickness = item.border_thickness;
	v.edge_softness    = item.edge_softness;

	Clamp(ctx, v);
}

void
DrawBackground(UICtx* ctx, UIItem* item, bool bordered = false)
{
	Vec4 col = item.bg_col;
	AnimateHot(item.hot_t, BG_HL_COL, &col);
	AnimateReady(item.ready_t, &col);

	Vertex* v       = GetVertex(ctx);
	v.dst_start     = item.rect.p0 + item.border_thickness;
	v.dst_end       = item.rect.p1 - item.border_thickness;
	v.cols          = col;
	v.corner_radius = item.corner_radius*(cast(f32)(bordered)*0.5);
	v.edge_softness = item.edge_softness;

	Clamp(ctx, v);
}

enum TextAlign
{
	Left,
	Center,
	Right,
} alias TA = TextAlign;

void
DrawText(T, U)(UICtx* ctx, string text, T size_param, U col_param, Rect rect, TextAlign text_align, f32 ready = 1.0) 
	if((is(T == FontGlyphs*) || (is(T == u32)) && is(U == Vec4) || is(U == u8[])))
{
	DrawUI(ctx, fg.index);

	static if(is(T == FontGlyphs*))
	{
		FontGlyphs* fg = size_param;
	}
	else 
	{
		FontGlyphs* fg = GetFontGlyphs(size_param);
	}

	f32 x = rect.p0.x;
	if(text_align & TA.Right)
	{
		x = rect.p1.x - CalcTextWidth(text, &fg.abuf);
	}
	else if(text_align & TA.Center)
	{
		x = ((rect.p1.x-rect.p0.x - CalcTextWidth(text, &fg.abuf)) / 2.0);
	}
	x = clamp(x, rect.p0.x, rect.p1.x);

	Glyphs[] glyphs = fg.abuf.atlas.glyphs[0 .. $];
	static if(is(U == u8[]))
	{
		u8[]     tks    = col_param;
		foreach(j; 0 .. str.length)
		{
			u8     ch  = text[j];
			Glyph* g   = ch < glyphs.length ? glyphs.ptr + ch : null;
			Vec4   col = syntax_cols[tks[j]];

			AnimateReady(ready, &col);
			DrawGlyph(rect, g, &x, rect.p0.y, line_height, col);
		}
	}
	else static if(is(U == Vec4))
	{
		Vec4 col = col_param;
		AnimateReady(ready, &col);

		foreach(j; 0 .. str.length)
		{
			u8     ch = str[j];
			Glyph* g  = ch < glyphs.length ? glyphs.ptr + ch : null;
			DrawGlyph(rect, g, &x, rect.p0.y, line_height, text_col);
		}
	}
}

void
BeginScissor(UICtx* ctx, UIItem* item, bool scissor_x = true, bool scissor_y = true)
{
	DrawUI(ctx);

	i32 x = cast(i32)clamp(scissor_x ? floor(item.rect.p0.x)     : 0,         0.0, ctx.res.x);
	i32 y = cast(i32)clamp(scissor_y ? floor(item.rect.p0.y)     : 0,         0.0, ctx.res.y);
	i32 w = cast(i32)clamp(scissor_x ? floor(item.rect.p1.x) - x : ctx.res.x, 0.0, ctx.res.x);
	i32 h = cast(i32)clamp(scissor_y ? floor(item.rect.p1.y) - y : ctx.res.y, 0.0, ctx.res.y);

	SetScissor(&ctx.rd, x, y, w, h);

	ResetScissor(&ctx.rd);
}

void
EndScissor(UICtx* ctx)
{
	ResetScissor(&ctx.rd);
}

u32[2]
GetExtent()
{
	version(ENABLE_RENDERER) return RendererGetExtent(&g_ui_ctx.rd); else return [1280, 720];
}

void
Push(T)(Stack!(T)** stack, T value)
{
	UICtx* ctx = GetCtx();

	Stack!(T)* node = Alloc!(Stack!(T))(&ctx.stack_arena);

	node.next  = *stack;
	node.value = value;

	*stack = node;
}

T
Pop(T)(Stack!(T)** stack, T value)
{
	UICtx* ctx = GetCtx();

	Stack!(T)* node = *stack;

	if((*stack).next != null)
	{
		*stack = (*stack).next;
	}

	return node.value;
}

template 
StackIDs(string stack, string ctx = "ctx")
{
	import std.string : replace;
	struct Identifiers { string stack, stack_top_node; }

	enum Identifiers StackIDs = {
		stack:           replace(ctx~".@",     "@", stack),
		stack_top_node:  replace(ctx~".@_top", "@", stack),
	};
}

T*
Recurse(bool pre = true, T)(T* node, T* nil)
{
	T* child = pre ? node.first : node.last;

	T* result = nil;
	if(!Nil(child))
	{
		result = child;
	}
	else for(T* p = node; !Nil(p); p = p.parent)
	{
		T* sibling = pre ? p.next : p.prev;
		if(!Nil(sibling))
		{
			result = sibling;
			break;
		}
	}

	return result;
}

UICtx*
GetCtx()
{
	return &g_ui_ctx;
}

Vec2
RootSize()
{
	return cast(Vec2)GetExtent();
}

UIKey
MakeKey(Args...)(string str, Args args)
{
	char[] key = sformat(ScratchAlloc!(char)(cast(u64)(str.length)), str, args);
	return MakeKey(key);	
}

UIKey
MakeKey(T)(T str) if(StringType!T)
{
	static if(is(T: string))
	{
		string id = str;
	}
	else
	{
		string id = Str(str);
	}

	UIKey key;

	if(str.length)
	{
		bool hash_only = false;
		i64 pos = 0;
		u32 hash_count = 0;
		for(i64 i = id.length-1; i >= 0; i -= 1)
		{
			if(i == 0 && id[i] == '#')
			{
				hash_only = true;
			}

			if(hash_count == 2)
			{
				if(id[i] == '#')
				{
					hash_count += 1;
					pos = i;
				}

				break;
			}

			if(id[i] == '#')
			{
				pos = i;
				hash_count += 1;
			}
		}

		if(hash_count < 2 || hash_only)
		{
			key.text      = hash_only ? "" : id;
			key.hash_text = hash_only ? id : "";
			key.hash      = Hash(id);
		}
		else if(hash_count == 2 || hash_count == 3)
		{
			key.text      = id[0 .. pos];
			key.hash_text = id[pos .. $];
			key.hash      = hash_count == 2 ? Hash(id) : Hash(id[pos+hash_count .. $]);
		}
	}

	return key;
}

static Vec4
HexCol(u64 col)
{
	return Vec4(
		cast(f32)((col >> 16) & 0xFF) / 255.0,
		cast(f32)((col >> 8 ) & 0xFF) / 255.0,
		cast(f32)((col >> 0 ) & 0xFF) / 255.0,
		1.0
	);
}

bool
KeyEq(UIKey k0, UIKey k1)
{
	return k0.hash == k1.hash;
}

static UIKey
ZeroKey()
{
	return UIKey();
}

bool
ZeroKey(T)(T param) if(is(T == UIItem*) || is(T == UIKey))
{
	UIKey key = mixin(AssignKey!(T, param));
	return key.hash == 0 && key.hash_text.length == 0;
}

UIItem*
NewItem(UICtx* ctx)
{
	UIItem* item = g_UI_NIL;

	item = DLLPop(ctx.free_items, g_UI_NIL);
	if(Nil(item))
	{
		item = Alloc!(UIItem)(&ctx.arena);
	}

	return item;
}

UIItem*
Get(Args...)(string str, Args args)
{
	char[] key = sformat(ScratchAlloc!(char)(cast(u64)(str.length*1.5)), str, args);
	return Get(key);
}

pragma(inline) UIItem*
Get(T)(T k) if(KeyType!(T))
{
	UIKey   key = mixin(AssignKey!(T, k));
	UICtx*  ctx = GetCtx();
	UIItem* item;

	Result!(UIItem*) res = ctx.items[key.hash];
	if(res.ok)
	{
		item = res.value;
	}
	else
	{
		item = NewItem(ctx);
		HTPush(&ctx.items, key.hash, item);
	}

	item.key  = key;
	item.next = item.prev = item.first = item.last = item.parent = g_UI_NIL;

	return item;
}

f32
CalcTextWidth(T, U)(T text, U param) if((is(T == string) || StringType!T) && (is(U: u32) || is(U == FontAtlasBuf*) ))
{
	static if(is(T == string))
	{
		string str = text;
	}
	else
	{
		string str = Str(text);
	}

	static if(is(U: u32))
	{
		FontAtlasBuf* abuf = GetFontAtlas(param);
	}
	else
	{
		FontAtlasBuf* abuf = param;
	}

	u32 tab_width = g_ui_ctx.tab_width;

	f32 width = 0.0;
	for(u64 i = 0; i < str.length; i += 1)
	{
		width += GlyphWidth(abuf.atlas.glyphs.ptr + str.ptr[i], abuf);
	}

	return width;
}

pragma(inline) f32
GlyphWidth(Glyph* g, FontAtlasBuf* abuf)
{
	return g.ch == '\t' ? (abuf.atlas.glyphs[' '].advance*cast(f32)(g_ui_ctx.tab_width)) : g.advance;
}

pragma(inline) void
DrawGlyph(Rect rect, Glyph* glyph, f32* x_pos, f32 y, f32 line_height, Vec4 col = Vec4(1.0))
{
	if(glyph)
	{
		UICtx* ctx  = GetCtx();
		Vertex* v   = null;
		f32 advance = glyph.advance;

		if(glyph.ch == ' ' || glyph.ch == '\n')
		{
			*x_pos += advance;
		}
		else if(glyph.ch == '\t')
		{
			*x_pos += advance * (ctx.tab_width - 1);
		}
		else
		{
			f32 w  = glyph.plane_right-glyph.plane_left;
			f32 h  = glyph.plane_bottom-glyph.plane_top;

			v = GetVertex(ctx);

			f32 y_pos = y + glyph.plane_top;

			v.dst_start   = Vec2(*x_pos+glyph.plane_left,   y_pos);
			v.dst_end     = Vec2(*x_pos+glyph.plane_left+w, y_pos+h);
			v.cols        = col;
			v.src_start   = Vec2(glyph.atlas_left,  glyph.atlas_top);
			v.src_end     =	Vec2(glyph.atlas_right, glyph.atlas_bottom);
			v.has_texture = true;

			f32 end_x   = *x_pos + advance;
			f32 width   = end_x - *x_pos;
			f32 bound_x = rect.p1.x;
			if(end_x > bound_x)
			{
				f32 cull_pct = (end_x - bound_x) / width;
				v.dst_end.x -= (v.dst_end.x - v.dst_start.x) * cull_pct;
				v.src_end.x -= (v.src_end.x - v.src_start.x) * cull_pct;
			}

			f32 end_y  = y + line_height;
			f32 height = end_y - y;
			if(end_y > rect.p1.y)
			{
				f32 cull_pct = (end_y - rect.p1.y) / height;
				v.dst_end.y -= (v.dst_end.y - v.dst_start.y) * cull_pct;
				v.src_end.y -= (v.src_end.y - v.src_start.y) * cull_pct;
			}

			static foreach(axis; A2D.min .. A2D.max)
			{
				static foreach(i; 0 .. v.pos.length)
				{
					v.pos[i].end[axis] = v.pos[i].start.v[axis] > v.pos[i].end.v[axis] ? v.pos[i].start.v[axis] : v.pos[i].end.v[axis]; 
				}
			}

			*x_pos += advance;
		}
	}
}

pragma(inline) Vertex*
GetVertex(UICtx* ctx)
{
	assert(ctx.buffers[ctx.f_idx].count < VERTEX_MAX_COUNT);
	return ctx.buffers[ctx.f_idx].vtx.ptr + ctx.buffers[ctx.f_idx].count++;
}

bool
Nil(UIItem* item)
{
	return item == null || item == g_UI_NIL;
}

pragma(inline) bool
InBounds(T)(T pos, Rect* rect)
{
	return pos.x >= rect.p0.x && pos.x <= rect.p1.x && pos.y >= rect.p0.y && pos.y <= rect.p1.y;
}

static f32 
SRGB(f32 v)
{
	return v < 0.0031308 ? v * 12.92 : pow(v, 0.41666) * 1.055 - 0.055;
}

static Vec4
LinearToSRGB(Vec4 v)
{
	return Vec4(SRGB(v.v[0]), SRGB(v.v[1]), SRGB(v.v[2]), v.v[3]);
}

static Vec4
SRGBVec4(f32 v)
{
	return LinearToSRGB(Vec4(v));
}

static Vec4
SRGBVec4(f32 r, f32 g, f32 b, f32 a)
{
	return LinearToSRGB(Vec4(r, g, b, a));
}

static Vec4
SRGBToOklab(Vec4 v)
{
	f32 l = 0.4122214708f * v.r + 0.5363325363f * v.g + 0.0514459929f * v.b;
	f32 m = 0.2119034982f * v.r + 0.6806995451f * v.g + 0.1073969566f * v.b;
	f32 s = 0.0883024619f * v.r + 0.2817188376f * v.g + 0.6299787005f * v.b;

	f32 l_ = cbrtf(l);
	f32 m_ = cbrtf(m);
	f32 s_ = cbrtf(s);

	return Vec4(
		0.2104542553f*l_ + 0.7936177850f*m_ - 0.0040720468f*s_,
		1.9779984951f*l_ - 2.4285922050f*m_ + 0.4505937099f*s_,
		0.0259040371f*l_ + 0.7827717662f*m_ - 0.8086757660f*s_,
		v.a
	);
}

static Vec4
OklabToSRGB(Vec4 v)
{
	f32 l_ = v.r + 0.3963377774f * v.g + 0.2158037573f * v.b;
	f32 m_ = v.r - 0.1055613458f * v.g - 0.0638541728f * v.b;
	f32 s_ = v.r - 0.0894841775f * v.g - 1.2914855480f * v.b;

	float l = l_*l_*l_;
	float m = m_*m_*m_;
	float s = s_*s_*s_;

	return Vec4(
		+4.0767416621f * l - 3.3077115913f * m + 0.2309699292f * s,
		-1.2684380046f * l + 2.6097574011f * m - 0.3413193965f * s,
		-0.0041960863f * l - 0.7034186147f * m + 1.7076147010f * s,
		v.a
	);
}

unittest
{
	{ // UI Key
		u8[] str = cast(u8[])r"Test String##123";
		UIKey key = MakeKey(str);

		assert(key.text == cast(u8[])r"Test String");
		assert(key.hash == Hash(str));

		u8[] str1 = cast(u8[])r"Test String###123123";
		key = MakeKey(str1);

		assert(key.text == cast(u8[])r"Test String");
		assert(key.hash == Hash(r"123123"));
	}

	{
		Inputs inputs;
		InitUICtx(null);
		BeginUI(&inputs);

		UICtx* ctx = GetCtx();

		Vec4    w   = Vec4(1.0);
		Vec4[4] col = w;

		Push!("bg_col")(col);
		assert(ctx.bg_col.top.value == col);

		EndUI();

		Vec2 extent = GetExtent();
		assert(extent == ctx.root.size);

		BeginUI(&inputs);
		assert(ctx.bg_col.top.value == BG_COL);
		EndUI();

		// Layouts
		{
			BeginUI(&inputs);

			Push!("size_info")(MakeUISizeX(ST.Percentage, 0.5));
			UIItem* root = ctx.root;

			UIItem* i0 = MakeItem("###i0");
			UIItem* i1 = MakeItem("###i1");

			assert(i0.parent == root);
			assert(i1.parent == root);

			EndUI();

			Vec2 expected = Vec2(floor(root.size.x * 0.5), root.size.y);

			assert(i0.size == expected);
			assert(i1.size == expected);
		}
	}
}

