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

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;
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 BG_COL        = Vec4(0.13, 0.13, 0.13, 1.0);
enum Vec4 BG_HL_COL     = Vec4(0.24, 0.45, 0.81, 1.0);
enum Vec4 BORDER_COL    = Vec4(0.254, 0.254, 0.266, 1.0);
enum Vec4 BORDER_HL_COL = Vec4(0.035, 0.549, 0.824, 1.0);
enum Vec4 TEXT_COL      = Vec4(1.0);
enum Vec4 TEXT_HL_COL   = Vec4(0.0, 0.0, 0.0, 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;

//const u8[] FONT_BYTES     = import("pc-9800.ttf");
//const u8[] FONT_BYTES     = 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_bg_col_default            = BG_COL;
Vec4      g_bg_col_end_default        = BG_COL;
Vec4      g_bg_hl_col_default         = BG_HL_COL;
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);
Vec2      g_view_offset_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;
__gshared const Stack!f32      g_nil_f32_stack;
__gshared       Stack!f32*     g_NIL_F32_STACK;
__gshared const Stack!Vec4[4]  g_nil_gradient_col_stack;
__gshared       Stack!Vec4[4]* g_NIL_GRAD_COL_STACK;
__gshared const Stack!Vec4     g_nil_vec4_stack;
__gshared       Stack!Vec4*    g_NIL_VEC4_STACK;
__gshared const Stack!u32      g_nil_u32_stack;
__gshared       Stack!u32*     g_NIL_U32_STACK;

enum Axis2D
{
	X,
	Y,
	Max,
}

alias A2D = Axis2D;

enum UIFlags
{
	None              = 0,
	DrawBackground    = 1<<0,
	DrawText          = 1<<1,
	DrawBorder        = 1<<2,
	Clickable         = 1<<3,
	Draggable         = 1<<4,
	ScrollX           = 1<<5,
	ScrollY           = 1<<6,
	ClampX            = 1<<7,
	ClampY            = 1<<8,
	Resizeable        = 1<<9,
	ResizeAdjacent    = 1<<10,
	FloatingWindow    = 1<<11, // todo
	Window            = 1<<12,
	TextInput         = 1<<13, // todo
	RightAlignText    = 1<<14,
	CenterAlignText   = 1<<15, // todo
	TextWrap          = 1<<16, 
	PortalViewX       = 1<<17, 
	PortalViewY       = 1<<18,
	FixedPosition     = 1<<19,
	FixedPosAnimated  = 1<<20, // todo: add fixed_pos_target then interpolate position every frame
	OverflowX         = 1<<21,
	OverflowY         = 1<<22,
	Gradient          = 1<<23,
	VerticalAlignText = 1<<24,

	Clamp             = UIFlags.ClampX      | UIFlags.ClampY,
	PortalView        = UIFlags.PortalViewX | UIFlags.PortalViewY,
	Scroll            = UIFlags.ScrollX     | UIFlags.ScrollY,
	Overflow          = UIFlags.OverflowX   | UIFlags.OverflowY,
}
alias UIF = UIFlags;

const UIFlags AUTO_FLAGS = UIF.ResizeAdjacent;
const UIFlags DRAW_FLAGS = UIF.DrawBackground|UIF.DrawBorder|UIF.DrawText|UIF.PortalView;

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;

mixin template 
UICtxParameter(T, string name)
{
	static string
	CtxParameterGen(T, string name)()
	{
		import std.traits, std.array;

		string stack_top  = "\tStackTop!("~T.stringof~") "~name~";\n";
		string stack      = "\tStack!("~T.stringof~")* "~name~"_top;\n";

		return stack_top~stack;
	}

	mixin(CtxParameterGen!(T, name)());
}

template 
CtxMemberInfo(int i)
{
	import std.string : endsWith, startsWith;
	import std.traits : isInstanceOf, isPointer;

	struct MemberInfo 
	{
		string id;
		bool   is_stack, is_top;
	}

	enum string id         = __traits(identifier, UICtx.tupleof[i]);
	enum bool is_top       = endsWith(id, "_top");
	enum bool is_stack     = startsWith(typeof(UICtx.tupleof[i]).stringof, "StackTop!");

	enum MemberInfo CtxMemberInfo = MemberInfo(id: id, is_stack: is_stack, is_top: is_top);
}

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

	Arena                       arena;
	Arena                       temp_arena;
	Inputs*                     inputs;
	u64                         frame;
	u64                         f_idx;

	LinkedList!(UIInput)        events;
	IVec2                       mouse_pos;
	IVec2                       drag_start;

	PlatformWindow*             window;
	Renderer                    rd;
	Descriptor[FONT_SIZES]      font_descs;
	Descriptor                  default_tex;
	Descriptor                  sampler;
	Pipeline                    pipeline;
	DescSetLayout               desc_set_layout;
	DescSet                     desc_set;
	PipelineLayout              pipeline_layout;
	Mat4                        projection;
	Vec2                        res;

	FontFace                    font;
	FontGlyphs[FONT_SIZES]      glyph_sets;
	u32                         glyph_sets_used;

	UIBuffer[FRAME_OVERLAP]     buffers;

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

	UIItem*                     root;
	UIItem*                     drag_item;
	UIItem*                     focus_item;

	mixin UICtxParameter!(Vec4,              "bg_col");
	mixin UICtxParameter!(Vec4,              "bg_col_end");
	mixin UICtxParameter!(Vec4,              "bg_hl_col");
	mixin UICtxParameter!(Vec4,              "border_col");
	mixin UICtxParameter!(Vec4,              "border_hl_col");
	mixin UICtxParameter!(UISize[2],         "size_info");
	mixin UICtxParameter!(Vec4,              "text_col");
	mixin UICtxParameter!(Vec4,              "text_hl_col");
	mixin UICtxParameter!(Vec2[2],           "scroll_clamp");
	mixin UICtxParameter!(Vec2,              "scroll_target");
	mixin UICtxParameter!(Vec2[2],           "padding");
	mixin UICtxParameter!(Vec2,              "view_offset");
	mixin UICtxParameter!(Vec2,              "fixed_pos");
	mixin UICtxParameter!(UIItem*,           "parent");
	mixin UICtxParameter!(string,            "display_string");
	mixin UICtxParameter!(u8[],              "syntax_tokens");
	mixin UICtxParameter!(Axis2D,            "layout_axis");
	mixin UICtxParameter!(Vec4,              "corner_radius");
	mixin UICtxParameter!(f32,               "border_thickness");
	mixin UICtxParameter!(f32,               "edge_softness");
	mixin UICtxParameter!(u32,               "text_size");
	mixin UICtxParameter!(UISyntaxHighlight, "syntax_highlight");

	debug bool                  dbg;
	debug u64                   item_count;
}

struct FontGlyphs
{
	u32          size;
	u32          index;
	FontAtlasBuf abuf;
}

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

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

static string
UIItemParameterGen()
{
	string fields = "";
	static foreach(i, m; UICtx.tupleof)
	{
		{
			enum info = CtxMemberInfo!(i);
			static if(info.is_stack)
			{
				fields ~= typeof(UICtx.tupleof[i].top.value).stringof ~ " " ~ info.id;
				static if(is(typeof(UICtx.tupleof[i]): f32))
				{
					fields ~= " = 0.0";
				}
				fields ~= ";\n";
			}
		}
	}

	return fields;
}

mixin template 
UIItemParameters()
{
	mixin(UIItemParameterGen());
}

struct UIItem
{
	IVec2    dragged;
	UIKey    key;

	u64      last_frame;
	UISignal signal;
	UIFlags  flags;

	UIItem*  next, prev, first, last; // parent in mixin
	UIItem*  transient_next;
	UIItem*  last_relative_item;
	
	Rect     rect;
	Vec2     size;

	string[] text_lines;
	u8[][]   token_lines;
	Vec2     scroll_offset;
	u8[]     text_buffer;
	f32      max_text_width;
	f32      resize_pct;
	bool     rendered;

	mixin UIItemParameters!();
}

enum SizeType
{
	Pixels,
	Percentage,
	ChildrenSum,
	TextSize,
}

alias ST = SizeType;

struct UISize
{
	SizeType type;
	f32      value;
	f32      strictness = 1.0;
}

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    cols_end;
	Vec4    corner_radius;
	Vec2[2] bounds;
	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 texture;
	u32 atlas_index;
}

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

Attribute[15] attributes = [
	{ binding: 0, location: 0,  format: FMT.RGBA_F32, offset: Vertex.cols.offsetof                         },
	{ binding: 0, location: 1,  format: FMT.RGBA_F32, offset: Vertex.cols_end.offsetof                     },
	{ binding: 0, location: 2,  format: FMT.R_F32,    offset: Vertex.corner_radius.offsetof + f32.sizeof*0 },
	{ binding: 0, location: 3,  format: FMT.R_F32,    offset: Vertex.corner_radius.offsetof + f32.sizeof*1 },
	{ binding: 0, location: 4,  format: FMT.R_F32,    offset: Vertex.corner_radius.offsetof + f32.sizeof*2 },
	{ binding: 0, location: 5,  format: FMT.R_F32,    offset: Vertex.corner_radius.offsetof + f32.sizeof*3 },
	{ binding: 0, location: 6,  format: FMT.RG_F32,   offset: Vertex.dst_start.offsetof                    },
	{ binding: 0, location: 7,  format: FMT.RG_F32,   offset: Vertex.dst_end.offsetof                      },
	{ binding: 0, location: 8,  format: FMT.RG_F32,   offset: Vertex.src_start.offsetof                    },
	{ binding: 0, location: 9,  format: FMT.RG_F32,   offset: Vertex.src_end.offsetof                      },
	{ binding: 0, location: 10, format: FMT.R_F32,    offset: Vertex.border_thickness.offsetof             },
	{ binding: 0, location: 11, format: FMT.R_F32,    offset: Vertex.edge_softness.offsetof                },
	{ binding: 0, location: 12, format: FMT.R_F32,    offset: Vertex.raised.offsetof                       },
	{ binding: 0, location: 13, format: FMT.R_U32,    offset: Vertex.texture.offsetof                      },
	{ binding: 0, location: 14, format: FMT.R_U32,    offset: Vertex.atlas_index.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 StringType(T) = (is(T: string) || is(T: u8[]) || is(T: char[]));

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;
	g_NIL_F32_STACK      = cast(Stack!f32*)&g_nil_f32_stack;
	g_NIL_GRAD_COL_STACK = cast(Stack!Vec4[4]*)&g_nil_gradient_col_stack;
	g_NIL_VEC4_STACK     = cast(Stack!Vec4*)&g_nil_vec4_stack;
	g_NIL_U32_STACK      = cast(Stack!u32*)&g_nil_u32_stack;

	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.drag_item       = g_UI_NIL;
	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.transient_next = fi.first = fi.last = fi.next = fi.prev = fi.parent = g_UI_NIL;

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

		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];
		}

		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.desc_set        = AllocDescSet(&ctx.rd, ctx.desc_set_layout);
		ctx.pipeline_layout = CreatePipelineLayout(&ctx.rd, ctx.desc_set_layout, Mat4.sizeof);


		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(u32 i = 0; i < ctx.font_descs.length; i += 1)
		{
			CreateImageViewTex(&ctx.rd, &ctx.font_descs[i], ATLAS_DIMENSION, ATLAS_DIMENSION, DT.Image, 0, i);
			ctx.glyph_sets[i].index = i;
		}

		FontAtlasBuf* abuf = GetFontAtlas(g_text_size_default);

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

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

		Write(&ctx.rd, ctx.desc_set, ctx.font_descs);
		Write(&ctx.rd, ctx.desc_set, &ctx.sampler);
		SetClearColors(&ctx.rd, [0.0, 0.0, 0.0, 1.0], [0.0, 0.0, 0.0, 0.0]);
	}

	InitStacks(ctx);
}

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

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, cast(f32)size, ATLAS_DIMENSION);
		fg.size = size;

		Transfer(&ctx.rd, &ctx.font_descs[i].view, fg.abuf.data, ATLAS_DIMENSION, ATLAS_DIMENSION);
		Write(&ctx.rd, ctx.desc_set, &ctx.font_descs[i]);

		ctx.glyph_sets_used += 1;
	}

	return fg;
}

void
Set(UIItem* item, UICtx* ctx)
{
	static foreach(i, m; UICtx.tupleof)
	{
		static foreach(j, m2; UIItem.tupleof)
		{
			{
				enum ctx_id  = __traits(identifier, UICtx.tupleof[i]);
				enum item_id = __traits(identifier, UIItem.tupleof[j]);
				static if(ctx_id == item_id)
				{
					mixin("item."~item_id~" = ctx."~ctx_id~".top.value;\n");
				}
			}
		}
	}
}

UIItem*
MakeItem(Args...)(string str, Args args)
{
	static if(Args.length)
	{
		enum has_flag = Args[Args.length-1].stringof == UIFlags.stringof;

		UIFlags flags = has_flag ? args[args.length-1] : UIF.None;
		enum    len   = has_flag ? Args.length-1       : Args.length;
		
		string key = Scratchf(str, args[0 .. len]);
	}
	else
	{
		string  key   = str;
		UIFlags flags = UIF.None;
	}

	return MakeItem(key, flags);
}

UIItem*
MakeItem(T)(T k, UIFlags flags = UIF.None) if(is(T: UIKey) || StringType!T)
{
	UICtx*  ctx  = GetCtx();
	UIItem* item = Get(k);
	item.flags   = flags;

	Set(item, ctx);
	if(item.flags & (UIF.Window | UIF.FloatingWindow))
	{
		item.parent  = g_UI_NIL;
		UIItem* next = ctx.root;
		while(!Nil(next.next))
		{
			next = next.next;
		}

		next.next = item;
		item.prev = next;
	}
	else if(!Nil(item.parent))
	{
		DLLPush(item.parent, item, g_UI_NIL);
	}
	
	AutoPopStacks(ctx);

	if(item.last_frame != ctx.frame-1 && item.flags & UIF.Resizeable)
	{
		if(item.size_info[item.parent.layout_axis].type != ST.Percentage)
		{
			Logf("Warning: Resizing not percentage SizeType item, disabling flag");
			item.flags &= ~UIF.Resizeable;
		}
		else
		{
			item.resize_pct = item.size_info[item.layout_axis].value;
		}
	}

	FontAtlasBuf* abuf  = GetFontAtlas(item.text_size);
	item.max_text_width = 0.0;
	string str          = item.display_string.length ? item.display_string : item.key.text;
	if(item.flags & UIF.TextWrap)
	{

		f32 width       = item.size_info[A2D.X].type == ST.TextSize ? item.parent.size.x : item.size.x;
		u32 ch_per_line = cast(u32)floor(width/abuf.atlas.max_advance);
		u64 lines       = (str.length/ch_per_line) + (str.length%ch_per_line ? 1 : 0);

		item.text_lines     = ScratchAlloc!(string)(lines);

		u64 start;
		for(u64 i = 0; i < lines; i += 1)
		{
			u64 end             = str.length-start <= ch_per_line ? str.length : start+ch_per_line;
			item.text_lines[i]  = str[start .. end];
			f32 text_width      = CalcTextWidth(item.text_lines[i], abuf);
			if(text_width > item.max_text_width)
			{
				item.max_text_width = text_width;
			}
		}

		if(item.syntax_tokens.length)
		{
			item.token_lines = ScratchAlloc!(u8[])(lines);
			for(u64 i = 0; i < lines; i += 1)
			{
				u64 end             = str.length-start <= ch_per_line ? str.length : start+ch_per_line;
				item.token_lines[i] = item.syntax_tokens[start .. end];
			}
		}
	}
	else
	{
		item.text_lines     = ScratchAlloc!(string)(1);
		item.text_lines[0]  = str;
		item.max_text_width = CalcTextWidth(str, abuf);

		if(item.syntax_tokens.length)
		{
			item.token_lines    = ScratchAlloc!(u8[])(1);
			item.token_lines[0] = item.syntax_tokens;
		}
	}

	f32 scroll_speed = 1 - pow(2, (-60.0f * g_delta));
	static foreach(axis; A2D.min .. A2D.max)
	{
		if(item.flags & (UIF.ScrollX << axis))
		{
			if(fabsf(item.scroll_offset.v[axis] - item.scroll_target.v[axis]) > 0.0009)
			{
				f32 v = scroll_speed * (item.scroll_target.v[axis] - item.scroll_offset.v[axis]);
				item.scroll_offset.v[axis] += v;

				if(fabsf(item.scroll_offset.v[axis] - item.scroll_target.v[axis]) < 2.0)
				{
					item.scroll_offset.v[axis] = item.scroll_target.v[axis];
				}
			}
		}

		if(item.flags & (UIF.ClampX << axis))
		{
			item.scroll_offset.v[axis] = clamp(item.scroll_offset.v[axis], item.scroll_clamp[axis].x, item.scroll_clamp[axis].y);
		}
	}

	if(item.flags & UIF.DrawBorder && item.border_thickness < 0.0009)
	{
		item.border_thickness = 1.0;
	}

	item.last_frame      = ctx.frame;
	item.rendered        = false;

	return item;
}

void
Signal(UIItem* item)
{
	UICtx* ctx = GetCtx();

	item.signal     = UIS.None;
	bool mouse_over = InBounds(ctx.mouse_pos, &item.rect);
	
	if(mouse_over)
	{
		item.signal |= UIS.Hovered;
	}

	item.dragged = 0;

	for(UIInput* i = ctx.events.first; !CheckNil(g_UI_NIL_INPUT, i); i = i.next)
	{
		bool taken;

		if(item.flags & UIF.Clickable && i.type == UIE.Click && InBounds(ctx.mouse_pos, &item.rect))
		{
			item.signal |= UIS.Clicked;
			taken        = true;
		}
		
		if(Nil(ctx.drag_item) && item.flags & UIF.Draggable && i.type == UIE.DragStart && InBounds(i.pos, &item.rect))
		{
			item.signal  |= UIS.Dragged;
			ctx.drag_item = item;
			taken         = true;
		}

		if(ctx.drag_item == item && i.type == UIE.Drag)
		{
			item.signal  |= UIS.Dragged;
			item.dragged += i.rel;
			taken         = true;
		}

		if(ctx.drag_item == item && i.type == UIE.DragRelease)
		{
			ctx.drag_item = g_UI_NIL;
			taken         = true;
		}

		if(taken)
		{
			DLLRemove(&ctx.events, i, g_UI_NIL_INPUT);
		}
	}
}

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

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

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;

	debug ctx.item_count = 0;

	Reset(a);

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

	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 && !dragging)
					{	
						PushUIEvent(ctx, UIInput(UIE.Click));	
					}
					else if(!mouse_down)
					{
						PushUIEvent(ctx, UIInput(UIE.DragRelease));
					}
					dragging   = false;
				} break;
			case Input.MouseMotion:
				{
					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;
		}
	}

	// 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);
		}
	}

	for(UIItem* item = ctx.transient_items; !Nil(item);)
	{
		UIItem* next = item.transient_next;
		memset(item, 0, UIItem.sizeof);
		DLLPush(ctx.free_items, item, g_UI_NIL);
		item = next;
	}


	// Ctx state
	ctx.transient_items = g_UI_NIL;
	ctx.frame          += 1;
	ctx.f_idx           = ctx.frame%FRAME_OVERLAP;
	ctx.inputs          = inputs;

	ResetStacks(ctx);

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

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

	version(ENABLE_RENDERER)
	{
		PushConstants(&ctx.rd, ctx.pipeline, &ctx.projection);
		Bind(&ctx.rd, ctx.pipeline, ctx.desc_set);
	}

	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;

	// Root Item
	UISize[2] sizes = [UISize(ST.Pixels, ctx.res.x), UISize(ST.Pixels, ctx.res.y)];
	Push!("size_info")(sizes);
	
	ctx.root        = MakeItem("###root");

	Pop!("size_info");
	Push!("parent")(ctx.root);
}

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

	// Automatic signals
	for(UIItem* item = ctx.root; !Nil(item); item = Recurse!(false)(item, g_UI_NIL))
	{
		if(item.flags & AUTO_FLAGS)
		{
			Signal(item);

			if(item.flags & UIF.ResizeAdjacent && item.dragged != IVec2(0))
			{
				UIItem* prev    = !Nil(item.prev) ? item.prev : g_UI_NIL;
				UIItem* next    = !Nil(item.next) ? item.next : g_UI_NIL;
				Axis2D  axis    = item.parent.layout_axis;
				f32     mov_pct = Remap(item.dragged.v[axis], 0.0, item.parent.size.v[axis], 0.0, 1.0);

				debug if(!(prev.flags & UIF.Resizeable) && !(next.flags & UIF.Resizeable))
				{
					Logf("Warning: ResizeAdjacent flag set with no adjacent items having Resizeable flag");
				}

				if(prev.flags & UIF.Resizeable)
				{
					prev.resize_pct -= mov_pct;
					prev.resize_pct  = clamp(prev.resize_pct, 0.0, 1.0);
				}

				if(next.flags & UIF.Resizeable)
				{
					next.resize_pct += mov_pct;
					next.resize_pct  = clamp(next.resize_pct, 0.0, 1.0);
				}
			}
		}
	}

	// Calculate item properties
	static foreach(axis; A2D.min .. A2D.max)
	{
		// Pixel sizes
		for(UIItem* item = ctx.root; !Nil(item); item = Recurse!(true)(item, g_UI_NIL))
		{
			if(item.size_info[axis].type == ST.Pixels)
			{
				item.size.v[axis] = AxisPadding!(axis)(item) + item.size_info[axis].value;
			}
			else if(item.size_info[axis].type == ST.TextSize)
			{
				f32 size          = axis == A2D.X ? item.max_text_width : GetFontAtlas(item.text_size).atlas.line_height*item.text_lines.length;
				item.size.v[axis] = size + AxisPadding!(axis)(item);
			}
		}

		// Percentage sizes
		for(UIItem* item = ctx.root; !Nil(item); item = Recurse!(true)(item, g_UI_NIL))
		{
			if(item.size_info[axis].type == ST.Percentage)
			{
				if(item.parent.layout_axis == axis && item.flags & UIF.Resizeable)
				{
					item.size_info[axis].value = item.resize_pct;
				}

				for(UIItem* p = item.parent; !Nil(p); p = p.parent)
				{
					if(p.size_info[axis].type == ST.Pixels)
					{
						item.size.v[axis] = floor(item.size_info[axis].value * InnerSize!(axis)(item.parent));
						break;
					}
				}
			}
		}

		// Sum of children sizes
		for(UIItem* item = ctx.root; !Nil(item); item = Recurse!(false)(item, g_UI_NIL))
		{
			if(item.size_info[axis].type == ST.ChildrenSum)
			{
				if(item.layout_axis == axis)
				{
					for(UIItem* c = item.first; !Nil(c); c = c.next)
					{
						item.size.v[axis] += c.size.v[axis];
					}
				}
				else
				{
					for(UIItem* c = item.first; !Nil(c); c = c.next)
					{
						item.size.v[axis] = item.size.v[axis] < c.size.v[axis] ? c.size.v[axis] : item.size.v[axis];
					}
				}
			}
		}

		// Violations
		for(UIItem* item = ctx.root; !Nil(item); item = Recurse!(true)(item, g_UI_NIL))
		{
			f32 size = InnerSize!(axis)(item);

			if(item.flags & (UIF.PortalViewX << axis))
			{
				continue;
			}

			if(axis == item.layout_axis)
			{
				f32 children_size = 0.0;
				for(UIItem* c = item.first; !Nil(c); c = c.next)
				{
					if(c.flags & UIF.FixedPosition) continue;
					children_size += c.size.v[axis];
				}

				if(children_size > size)
				{
					f32 excess = children_size - size;
					for(UIItem* c = item.last; !Nil(c); c = c.prev)
					{
						if(c.flags & (UIF.FixedPosition | (UIF.OverflowX<<axis)))
						{
							continue;
						}

						f32 leniency = c.size.v[axis] - c.size.v[axis]*c.size_info[axis].strictness;
						f32 reduced  = Min(excess, leniency);

						excess         -= reduced;
						c.size.v[axis] -= reduced;

						if(excess <= 0.0009) break;
					}

					if(excess > 0.0009)
					{
						for(UIItem* c = item.last; !Nil(c); c = c.prev)
						{
							if(c.flags & (UIF.FixedPosition | (UIF.OverflowX<<axis)))
							{
								continue;
							}

							f32 reduced     = Min(excess, c.size[axis]);
							excess         -= reduced;
							c.size.v[axis] -= reduced;

							if(excess < 0.0009) break;
						}
					}
				}
			}
			else
			{
				for(UIItem* c = item.first; !Nil(c); c = c.next)
				{
					if(c.flags & (UIF.FixedPosition | (UIF.OverflowX<<axis)))
					{
						continue;
					}

					c.size.v[axis] = c.size.v[axis] > size ? size : c.size.v[axis];
				}
			}
		}

		// Calculate final sizes
		{
			f32 pos = 0.0;
			for(UIItem* item = ctx.root; !Nil(item); item = Recurse!(true)(item, g_UI_NIL))
			{
				item.last_relative_item = g_UI_NIL;

				bool    fixed  = cast(bool)(item.flags & UIF.FixedPosition);
				UIItem* parent = item.parent;
				if(fixed)
				{
					item.rect.p0.v[axis] = parent.rect.p0.v[axis] + item.fixed_pos.v[axis] + InnerOffset!(axis, true)(item) + parent.view_offset.v[axis];
					item.rect.p1.v[axis] = item.rect.p0.v[axis]   + item.size.v[axis]      - InnerOffset!(axis, false)(item);
				}
				else if(axis != parent.layout_axis)
				{
					item.rect.p0.v[axis] = parent.rect.p0.v[axis] + parent.view_offset.v[axis] + InnerOffset!(axis, true )(item);
					item.rect.p1.v[axis] = parent.rect.p0.v[axis] + item.size.v[axis]          - InnerOffset!(axis, false)(item);
				}
				else if(Nil(item.parent.last_relative_item))
				{
					item.rect.p0.v[axis] = parent.rect.p0.v[axis] + InnerOffset!(axis, true)(item) + parent.view_offset.v[axis];
					item.rect.p1.v[axis] = item.rect.p0.v[axis]   + item.size.v[axis]              - InnerOffset!(axis, false)(item);
				}
				else
				{
					UIItem* prev = parent.last_relative_item;

					item.rect.p0.v[axis] = prev.rect.p1.v[axis] + InnerOffset!(axis, false)(prev) + InnerOffset!(axis, true )(item);
					item.rect.p1.v[axis] = item.rect.p0.v[axis] + item.size.v[axis]               - InnerOffset!(axis, false)(item);
				}

				if(axis == parent.layout_axis && !fixed && !Nil(parent))
				{
					parent.last_relative_item = item;
				}

				assert(!isNaN(item.rect.p0.v[axis]));
				assert(!isNaN(item.rect.p1.v[axis]));
			}
		}
	}

	// Render Items
	RenderItems(ctx.root);

	version(ENABLE_RENDERER)
	{
		DrawUI(ctx);

		FinishRendering(&ctx.rd);
		SubmitAndPresent(&ctx.rd);
	}

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

void
FocusItem(Args...)(string str, Args args)
{
	g_ui_ctx.focus_item = Get(str, args);
}

void
FocusItem(T)(T focus) if(is(T == UIKey) || StringType!T || is(T == UIItem*))
{
	static if(is(T == UIItem*))
	{
		assert(!Nil(focus));
		g_ui_ctx.focus_item = focus;
	}
	else
	{
		g_ui_ctx.focus_item = Get(focus);
	}
}

pragma(inline) void
RenderItem(UICtx* ctx, UIItem* item)
{
	if(item.rendered)              return;
	if(!(item.flags & DRAW_FLAGS)) return;

	Vertex* v = null;

	if(item.flags & UIF.DrawBackground)
	{
		v               = GetVertex(ctx);
		v.dst_start     = item.rect.p0;
		v.dst_end       = item.rect.p1;
		v.cols          = item.bg_col;
		v.cols_end      = item.flags & UIF.Gradient   ? item.bg_col_end        : item.bg_col;
		v.corner_radius = item.flags & UIF.DrawBorder ? item.corner_radius*0.5 : item.corner_radius;
		v.edge_softness = item.edge_softness;
		v.bounds        = ItemBounds(item.parent);

		AddVertexCount(ctx);
	}

	if(item.flags & UIF.DrawBorder)
	{
		v                  = GetVertex(ctx);
		v.dst_start        = item.rect.p0 - Vec2(InnerOffset!(A2D.X, true )(item), InnerOffset!(A2D.Y, true )(item));
		v.dst_end          = item.rect.p1 + Vec2(InnerOffset!(A2D.X, false)(item), InnerOffset!(A2D.Y, false)(item));
		v.cols             = item.border_col;
		v.cols_end         = item.flags & UIF.Gradient ? item.bg_col_end : item.bg_col;
		v.corner_radius    = item.corner_radius;
		v.border_thickness = item.border_thickness;
		v.edge_softness    = item.edge_softness;
		v.bounds           = ItemBounds(item.parent);

		AddVertexCount(ctx);
	}

	if(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));
	}

	if(item.flags & UIF.DrawText || item.display_string)
	{
		FontGlyphs* fg     = GetFontGlyphs(item.text_size);
		f32    y_pos       = item.rect.p0.y;
		Vec4[] syntax_cols = ctx.syntax_colors[item.syntax_highlight];
		f32 line_height    = fg.abuf.atlas.line_height;

		if(item.flags & UIF.VerticalAlignText)
		{
			y_pos += (item.rect.p1.y - item.rect.p0.y - line_height) / 2.0;
		}
		
		foreach(i; 0 .. item.text_lines.length)
		{
			string str = item.text_lines[i];
			u8[]   tks = item.token_lines.length ? item.token_lines[i] : [];
			f32 x_pos  = 0.0;
			if(item.flags & UIF.RightAlignText)
			{
				x_pos = item.rect.p1.x - item.padding[A2D.X].x - CalcTextWidth(str, &fg.abuf);
			}
			else if(item.flags & UIF.CenterAlignText)
			{
				x_pos = ((item.rect.p1.x-item.rect.p0.x - CalcTextWidth(str, &fg.abuf)) / 2.0) + item.padding[A2D.X].x;
				Logf("%f", x_pos);
			}
			else
			{
				x_pos = item.rect.p0.x + item.padding[A2D.X].x;
			}

			x_pos = clamp(x_pos, item.rect.p0.x, item.rect.p1.x);

			if(tks.length)
			{
				foreach(j; 0 .. str.length)
				{
					u8     ch = str[j];
					Glyph* g  = ch < fg.abuf.atlas.glyphs.length ? fg.abuf.atlas.glyphs.ptr + ch : null;
					DrawGlyph(item, g, fg.index, &x_pos, y_pos, line_height, fg.index, syntax_cols[tks[j]]);
				}
			}
			else
			{
				foreach(j; 0 .. str.length)
				{
					u8     ch = str[j];
					Glyph* g  = ch < fg.abuf.atlas.glyphs.length ? fg.abuf.atlas.glyphs.ptr + ch : null;
					DrawGlyph(item, g, fg.index, &x_pos, y_pos, line_height, fg.index, item.text_col);
				}
			}

			y_pos += line_height;
		}
	}

	// Doesn't really support nesting scissors, will maybe change this later.
	bool scissor_x = cast(bool)(item.flags & UIF.PortalViewX);
	bool scissor_y = cast(bool)(item.flags & UIF.PortalViewY);
	if(scissor_x || scissor_y)
	{
		DrawUI(ctx);

		Vec2 p0 = Clamp(item.rect.p0 - item.padding[A2D.X], Vec2(0.0), Vec2(ctx.res.x, ctx.res.y));
		Vec2 p1 = Clamp(item.rect.p1 + item.padding[A2D.Y], Vec2(0.0), Vec2(ctx.res.x, ctx.res.y));

		u32 x = cast(u32)(scissor_x ? floor(p0.x)     : 0);
		u32 y = cast(u32)(scissor_y ? floor(p0.y)     : 0);
		u32 w = cast(u32)(scissor_x ? floor(p1.x) - x : ctx.res.x);
		u32 h = cast(u32)(scissor_y ? floor(p1.y) - y : ctx.res.y);

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

		UIItem* first = item.first;
		UIItem*	last  = item.last;
		
		item.first = item.last = g_UI_NIL;

		UIItem* next = Recurse!(true)(item, g_UI_NIL);

		item.first = first;
		item.last  = last;

		for(UIItem* d = item.first; !Nil(d) && d != next; d = Recurse!(true)(d, g_UI_NIL))
		{
			RenderItem(ctx, d);
			d.rendered = true;
		}

		DrawUI(ctx);

		ResetScissor(&ctx.rd);
	}
}

void
RenderItems(UIItem* root)
{
	UICtx* ctx = GetCtx();
	for(UIItem* item = root; !Nil(item); item = Recurse!(true)(item, g_UI_NIL))
	{
		RenderItem(ctx, item);
	}
}

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

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),
	};
}

void
PushSizeInfo(SizeType type0, f32 value0, f32 strictness0, SizeType type1, f32 value1, f32 strictness1, bool auto_pop = false)
{
	UISize[2] size_info = [
		UISize(type0, value0, strictness0),
		UISize(type1, value1, strictness1),
	];

	Push!("size_info")(size_info, auto_pop);
}

void
PushSizeInfoVec(int i)(SizeType type, f32 value, f32 strictness = 1.0, bool auto_pop = false)
{
	UISize[2] size_info = g_size_info_default;

	size_info[i].type       = type;
	size_info[i].value      = value;
	size_info[i].strictness = strictness;

	Push!("size_info")(size_info, auto_pop);
}

void
PushSizeInfoX(SizeType type, f32 value, f32 strictness = 1.0, bool auto_pop = false)
{
	PushSizeInfoVec!(0)(type, value, strictness, auto_pop);
}

void
PushSizeInfoY(SizeType type, f32 value, f32 strictness = 1.0, bool auto_pop = false)
{
	PushSizeInfoVec!(1)(type, value, strictness, auto_pop);
}

void
PushCornerRadius(f32 v, bool auto_pop)
{
	Vec4 value = v;
	Push!("corner_radius")(value, auto_pop);
}

void
PushScrollClampVec(int i)(Vec2 clamp, bool auto_pop)
{
	Vec2[2] scroll_clamp = g_scroll_clamp_default;
	scroll_clamp[i] = clamp;
	Push!("scroll_clamp")(scroll_clamp, auto_pop);
}

void
PushScrollClampX(f32 start, f32 end, bool auto_pop = false)
{
	PushScrollClampVec!(0)(Vec2(start, end), auto_pop);
}

void
PushScrollClampY(f32 start, f32 end, bool auto_pop = false)
{
	PushScrollClampVec!(1)(Vec2(start, end), auto_pop);
}

void
PushPaddingX(f32 padding, bool auto_pop = false)
{
	PushPadding(Vec2(padding, 0.0), auto_pop);
}

void
PushPaddingY(f32 padding, bool auto_pop = false)
{
	PushPadding(Vec2(0.0, padding), auto_pop);
}

void
PushViewOffsetX(f32 offset, bool auto_pop = false)
{
	PushViewOffset(Vec2(offset, 0.0), auto_pop);
}

void
PushViewOffsetY(f32 offset, bool auto_pop = false)
{
	PushViewOffset(Vec2(0.0, offset), auto_pop);
}

void
PushScrollTargetX(f32 target, bool auto_pop = false)
{
	PushScrollTarget(Vec2(target, 0.0), auto_pop);
}

void
PushScrollTargetY(f32 target, bool auto_pop = false)
{
	PushScrollTarget(Vec2(0.0, target), auto_pop);
}

static string
PushScope(string stack, string value)()
{
	import std.conv;
	return i"Push!(\"$(stack)\")($(value)); scope(exit) Pop!(\"$(stack)\")();".text;
}

static string
PushOnce(UIPushInfo[] info)()
{
	import std.conv;

	string result = "";
	static foreach(i; info)
	{
		result ~= i"Push!(\"$(i.s)\")($(i.v), true); scope(exit) Pop!(\"$(i.s)\")();".text;
	}

	return result;
}

struct UIPushInfo
{
	string s;  // stack
	string v;  // value
}

void
Push(string stack_str, T)(T value, bool auto_pop = false)
{
	import std.string : replace;
	
	enum ids = StackIDs!(stack_str, "g_ui_ctx");
	alias ST = typeof(mixin(ids.stack ~ ".top.value"));

	auto stack = &mixin(ids.stack);
	auto top   = mixin(ids.stack_top_node);

	Stack!(ST)* node = stack.free;
	if(node)
	{
		stack.free = node.next;
	}
	else
	{
		node = Alloc!(Stack!(ST))(&g_ui_ctx.temp_arena);
	}

	node.next     = stack.top;
	node.value    = value;
	node.auto_pop = auto_pop;

	stack.top      = node;
}

static string
PushScope(string stack_str, alias __value)()
{
	enum string id = __traits(identifier, __value);
	static if(id == "__value")
	{
		string value_str = "cast(typeof("~StackIDs!(stack_str, "g_ui_ctx").stack~".top.value))(" ~ __value.stringof ~ ")";
	}
	else
	{
		string value_str = __traits(identifier, __value);
	}


	return "Push!(\"" ~ stack_str ~ "\")(" ~ value_str ~ ");\n scope(exit) Pop!(\"" ~ stack_str ~ "\");";
}

auto
Pop(string stack_str)()
{
	import std.string : replace;

	enum ids = StackIDs!(stack_str, "g_ui_ctx");

	auto stack = &mixin(ids.stack);
	auto top   = mixin(ids.stack_top_node);

	auto pop = stack.top;
	if(pop != top)
	{
		stack.top  = pop.next;

		pop.next   = stack.free;
		stack.free = pop;
	}

	return pop.value;
}

void
Pop(stack_strs...)()
{
	static foreach(stack; stack_strs)
	{
		Pop!(stack)();
	}
}

static string
GenPushFuncs()
{
	import std.array : split;
	import std.uni   : toUpper;

	string funcs = "";
	static foreach(i, m; UICtx.tupleof)
	{
		{
			enum info = CtxMemberInfo!(i);
			static if(info.is_stack)
			{
				string[] parts = split(info.id, "_");

				string fn_name = "Push";
				foreach(p; parts)
				{
					if(p.length == 1)
					{
						fn_name ~= toUpper(p[0 .. 1]);
					}
					else
					{
						fn_name ~= toUpper(p[0 .. 1]) ~ p[1 .. $];
					}
				}

				funcs ~= "pragma(inline) void " ~ fn_name ~ "(T)(T value, bool auto_pop = false){ Push!(\"" ~ info.id ~ "\")(value, auto_pop); }\n";
			}
		}
	}

	return funcs;
}

mixin(GenPushFuncs());

void
AutoPopStacks(UICtx* ctx)
{
	import std.string : endsWith, replace;
	import std.traits : isInstanceOf, isPointer;

	static foreach(i, mem; UICtx.tupleof)
	{
		{
			enum member_info = CtxMemberInfo!(i);
			static if(member_info.is_stack)
			{
				if(ctx.tupleof[i].top.auto_pop)
				{
					ctx.tupleof[i].top.auto_pop = false;
					Pop!(member_info.id);
				}
			}
		}	
	}
}

void
InitStacks(UICtx* ctx)
{
	import std.string : endsWith, chomp, replace;
	import std.traits : isInstanceOf, isPointer;

	static foreach(i, mem; UICtx.tupleof)
	{
		{
			enum member_info = CtxMemberInfo!(i);
			static if(member_info.is_top)
			{
				enum global_default  = replace("g_@_default", "@", chomp(member_info.id, "_top"));

				ctx.tupleof[i]       = Alloc!(typeof(*UICtx.tupleof[i]))(&ctx.arena);
				ctx.tupleof[i].value = mixin(global_default);
				ctx.tupleof[i].next  = null;
			}
		}
	}
}

void
ResetStacks(UICtx* ctx)
{
	import std.string : endsWith, replace;
	import std.traits : isInstanceOf, isPointer;
	
	static foreach(i, mem; UICtx.tupleof)
	{
		{
			enum member_info = CtxMemberInfo!(i);
			static if(member_info.is_stack)
			{
				enum top                = replace("ctx.@_top", "@", member_info.id);
				ctx.tupleof[i].top      = mixin(top);
				ctx.tupleof[i].free     = null;

				assert(ctx.tupleof[i].top.next == null, "Top stack node next isn't null");
			}
		}
	}
}

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
	);
}

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

bool
ZeroKey(UIKey key)
{
	return key.hash == 0 && key.hash_text.length == 0;
}

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

	debug ctx.item_count += 1;

	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(is(T: UIKey) || StringType!T)
{
	static if(is(T: UIKey))
	{
		UIKey key = k;
	}
	else
	{
		UIKey key = MakeKey(k);
	}

	UIItem* item;
	UICtx*  ctx = GetCtx();

	if(ZeroKey(key))
	{
		item = NewItem(ctx);
		item.transient_next = ctx.transient_items;
		ctx.transient_items = item;
	}
	else
	{
		Result!(UIItem*) res = ctx.items[key.hash];
		if(res.ok)
		{
			debug ctx.item_count += 1;
			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(UIItem* item, Glyph* glyph, u32 atlas_index, f32* x_pos, f32 y, f32 line_height, u32 index, 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 * (GetCtx().tab_width - 1);
		}
		else
		{
			f32 w  = glyph.plane_right-glyph.plane_left;
			f32 h  = glyph.plane_bottom-glyph.plane_top;

			v = ctx.buffers[ctx.f_idx].vtx.ptr + ctx.buffers[ctx.f_idx].count;

			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.cols_end    = col;
			v.texture     = true;
			v.bounds      = ItemBounds(item);
			v.atlas_index = index;
			v.src_start   = Vec2(glyph.atlas_left,  glyph.atlas_top);
			v.src_end     =	Vec2(glyph.atlas_right, glyph.atlas_bottom);

			f32 end_x   = *x_pos + advance;
			f32 width   = end_x - *x_pos;
			f32 bound_x = item.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 > item.rect.p1.y)
			{
				f32 cull_pct = (end_y - item.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]; 
				}
			}

			AddVertexCount(ctx);

			*x_pos += advance;
		}
	}
}

pragma(inline) Vec2[2]
ItemBounds(UIItem* item)
{
	return [item.rect.p0, item.rect.p1];
}

pragma(inline) f32
AxisPadding(Axis2D axis)(UIItem* item)
{
	return item.padding[axis].x + item.padding[axis].y;
}

pragma(inline) f32
InnerSize(Axis2D axis)(UIItem* item)
{
	return clamp(item.size[axis] - item.border_thickness*2.0, 0.0, f32.max);
}

pragma(inline) f32
InnerSize(UIItem* item, Axis2D axis)
{
	return clamp(item.size[axis] - item.border_thickness*2.0, 0.0, f32.max);
}

pragma(inline) f32
InnerOffset(Axis2D axis, bool start)(UIItem* item)
{
	return item.border_thickness;
}

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

static UISize[2]
UIS2(SizeType t0 = ST.Percentage, SizeType t1 = ST.Percentage, f32 v0 = 1.0, f32 v1 = 1.0, f32 s0 = 1.0, f32 s1 = 1.0)
{
	return [UISize(t0, v0, s0), UISize(t1, v1, s1)];
}

static UISize[2]
UISX(SizeType type, f32 value = 1.0, f32 strictness = 1.0)
{
	return [UISize(type, value, strictness), UISize(ST.Percentage, 1.0)];
}

static UISize[2]
UISY(SizeType type, f32 value = 1.0, f32 strictness = 1.0)
{
	return [UISize(ST.Percentage, 1.0), UISize(type, value, strictness)];
}

pragma(inline) void
AddVertexCount(UICtx* ctx)
{
	ctx.buffers[ctx.f_idx].count += 1;
	assert(ctx.buffers[ctx.f_idx].count < VERTEX_MAX_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 Vec2[2]
Vec2A2(f32 x0, f32 y0, f32 x1, f32 y1)
{
	return [Vec2(x0, y0), Vec2(x1, y1)];
}

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);
		}
	}
}

