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

import vulkan;
import widgets;
import std.stdio;
import std.math.traits : isNaN;
import std.math.rounding : ceil;
import std.format : sformat;
import core.stdc.string : memset;
import core.stdc.math : fabsf;

import parsing;
import editor;

const u8[] FONT_BYTES = import("pc-9800.ttf");
const u8[] VERTEX_BYTES = import("gui.vert.spv");
const u8[] FRAGMENT_BYTES = import("gui.frag.spv");

const UI_COUNT = 5000;

UICtx g_ui_ctx;

const UIItem g_ui_nil_item;
UIItem* g_UI_NIL;

const UIItemNode g_ui_nil_item_node;
UIItemNode* g_UI_NIL_NODE;

enum Axis2D
{
	X,
	Y,
	Max
}

alias A2D = Axis2D;

enum UIFlags
{
	None           = 0x0000,
	DrawBackground = 0x0001,
	DrawText       = 0x0002,
	DrawBorder     = 0x0004,
	Clickable      = 0x0008,
	Draggable      = 0x0010,
	DragBorder     = 0x0020,
	ClickBorder    = 0x0040,
	BorderX0       = 0x0080,
	BorderX1       = 0x0100,
	BorderY0       = 0x0200,
	BorderY1       = 0x0400,
	TextInput      = 0x0800,
	Window         = 0x1000,
	DeferredBorder = 0x2000,
}

alias UIF = UIFlags;

enum UISignal
{
	None          = 0x00,
	Clicked       = 0x01,
	Dragged       = 0x02,
	ClickedBorder = 0x04,
	DraggedBorder = 0x08,


	BorderX0      = 0x080,
	BorderX1      = 0x100,
	BorderY0      = 0x200,
	BorderY1      = 0x400,
}

alias UIS = UISignal;

enum SizeType
{
	Pixels,
	Percentage,
	FitChild,
}

alias ST = SizeType;

struct UICtx
{
	HashTable!(UIHash, UIItem*) items;
	Arena                       arena;
	Arena                       temp_arena;
	Inputs*                     inputs;
	u64                         frame;
	u64                         f_idx;

	PlatformWindow*             window;
	Renderer                    rd;
	ImageView                   font_atlas;
	Pipeline                    pipeline;
	DescSetLayout               desc_set_layout;
	DescSet                     desc_set;
	PipelineLayout              pipeline_layout;
	PushConst                   pc;
	Vec2                        res;

	u8[]			                  font_data;
	FontFace                    font;
	FontAtlasBuf                atlas_buf;

	UIBuffer[FRAME_OVERLAP]     buffers;

	UIItem*                     root;
	UIItemNode*                 top_parent;
	UIItemNode*                 prev_sibling;
	u32                         panel_level;
	UIItem*                     drag_item;
	UIItem*                     focused;

	UIItemStackList             stack_free_list;

	UIStack!(Vec4[4])*          color;
	UIStack!(Vec4[4])*          border_color;
	UIStack!(Vec4)*             text_color;
	UIStack!(Axis2D)*           layout_axis;
	UIStack!(Vec2)*             adjustment;
	UIStack!(Vec2)*             offset;
	UIStack!(Vec2)*             padding;
	UIStack!(f32)*              border_thickness;
	UIStack!(f32)*              corner_radius;
	UIStack!(f32)*              edge_softness;

	i64                         highlighted_char;
	I64Vec2                     selected;
	u32                         tab_width;
	f32                         text_size;

	debug u32                   item_count; 
	debug u32                   final_count;
	debug bool                  dbg;
}

struct UIStack(T)
{
	UIStack!(T)* next;
	T            value;
}

struct UIItemStackList
{
	UIItemNode* first;
	UIItemNode* last;
}

struct UIItemNode
{
	UIItem*      item;
	UIItemNode*  next;
}

struct UIItem
{
	UIFlags         flags;
	UIKey           key;
	UISignal        signal;

	UIItem*         next;
	UIItem*         prev;
	UIItem*         first; // first child
	UIItem*         last; // last child
	UIItem*         parent;

	u64             last_frame;
	u32             level;
	Vec2            dragged;

	// Build Parameters
	Axis2D          layout_axis;
	UISize[2]       size_info;
	Vec2            adjustment;
	i64             highlighted_char;
	I64Vec2         selected;

	// Calculated Parameters
	Vec4[4]         color;
	Vec4[4]         border_color;
	TokenStyle[]    token_styles;
	Rect            rect;
	Vec2            size;
	Vec2            last_click_pos;
	Vec2            offset;
	Vec2            padding;
	f32             border_thickness;
	f32             corner_radius;
	f32             edge_softness;
	Rect            culling;
}

struct UISize
{
	SizeType type;
	f32      value;
}

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

struct PushConst
{
	Mat4 projection;
}

struct GlyphBounds
{
	f32 r = 0.0;
	f32 l = 0.0;
	f32 t = 0.0;
	f32 b = 0.0;
	f32 w = 0.0;
	f32 h = 0.0;
	f32 atlas_r = 0.0;
	f32 atlas_l = 0.0;
	f32 atlas_t = 0.0;
	f32 atlas_b = 0.0;
}

struct Vertex
{
	Vec4[4] cols;
	Vec2 dst_start;
	Vec2 dst_end;
	Vec2 src_start;
	Vec2 src_end;
	f32  border_thickness;
	f32  corner_radius;
	f32  edge_softness;
	f32  raised;
	f32  z_index;
	u32  texture;
}

union Rect
{
	Vec2[2] v;
	struct
	{
		Vec2 vec0;
		Vec2 vec1;
	};
	struct
	{
		f32 x0;
		f32 y0;
		f32 x1;
		f32 y1;
	};
}

alias UIHash = u64;

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

struct UIKey
{
	u8[] text;
	u8[] hash_text;
	u64  hash;
}

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

	version(Windows)
	{

	}

	g_UI_NIL = cast(UIItem*)&g_ui_nil_item;
	g_UI_NIL_NODE = cast(UIItemNode*)&g_ui_nil_item_node;

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

	FontFace font = OpenFont(cast(u8[])FONT_BYTES);
	FontAtlasBuf atlas_buf = CreateAtlas(&arena, font, 16.0, 256);

	Vec4 b = Vec4(Vec3(0.0), 1.0);

	UICtx ctx = {
		rd: InitRenderer(handles, MB(16), MB(8)),
		items: CreateHashTable!(UIHash, UIItem*)(12),
		arena: arena,
		temp_arena: CreateArena(MB(1)),
		root: g_UI_NIL,
		top_parent: g_UI_NIL_NODE,
		prev_sibling: g_UI_NIL_NODE,
		drag_item: g_UI_NIL,
		focused: g_UI_NIL,
		atlas_buf: atlas_buf,
		font: font,
		font_data: cast(u8[])FONT_BYTES,
		text_size: 16.0,
		tab_width: 2,
		highlighted_char: -1,
	};

	u64 vertex_size = 10000;
	for(u64 i = 0; i < FRAME_OVERLAP; i += 1)
	{
		ctx.buffers[i].m_vtx = CreateMappedBuffer!(Vertex)(&ctx.rd, BT.Vertex, vertex_size);
		ctx.buffers[i].m_idx = CreateMappedBuffer!(u32)(&ctx.rd, BT.Index, cast(u64)(ceil(vertex_size * 1.5)));
		ctx.buffers[i].vtx = ctx.buffers[i].m_vtx.data;
		ctx.buffers[i].idx = ctx.buffers[i].m_idx.data;
	}

	DescLayoutBinding[2] layout_bindings = [
		{ binding: 0, descriptorType: DT.Image, descriptorCount: 1, stageFlags: SS.All },
		{ binding: 1, descriptorType: DT.Storage, 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, PushConst.sizeof);

	Attribute[14] attributes = [
		{ binding: 0, location: 0,  format: FMT.RGBA_F32, offset: Vertex.cols.offsetof + Vec4.sizeof * 0},
		{ binding: 0, location: 1,  format: FMT.RGBA_F32, offset: Vertex.cols.offsetof + Vec4.sizeof * 1},
		{ binding: 0, location: 2,  format: FMT.RGBA_F32, offset: Vertex.cols.offsetof + Vec4.sizeof * 2},
		{ binding: 0, location: 3,  format: FMT.RGBA_F32, offset: Vertex.cols.offsetof + Vec4.sizeof * 3},
		{ binding: 0, location: 4,  format: FMT.RG_F32,   offset: Vertex.dst_start.offsetof },
		{ binding: 0, location: 5,  format: FMT.RG_F32,   offset: Vertex.dst_end.offsetof },
		{ binding: 0, location: 6,  format: FMT.RG_F32,   offset: Vertex.src_start.offsetof },
		{ binding: 0, location: 7,  format: FMT.RG_F32,   offset: Vertex.src_end.offsetof },
		{ binding: 0, location: 8,  format: FMT.R_F32,    offset: Vertex.border_thickness.offsetof },
		{ binding: 0, location: 9,  format: FMT.R_F32,    offset: Vertex.corner_radius.offsetof },
		{ binding: 0, location: 10, format: FMT.R_F32,    offset: Vertex.edge_softness.offsetof },
		{ binding: 0, location: 11, format: FMT.R_F32,    offset: Vertex.raised.offsetof },
		{ binding: 0, location: 12, format: FMT.R_F32,    offset: Vertex.z_index.offsetof },
		{ binding: 0, location: 13, format: FMT.R_U32,    offset: Vertex.texture.offsetof },
	];

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

	CreateGraphicsPipeline(&ctx.rd, &ctx.pipeline, &ui_info);

	CreateImageView(&ctx.rd, &ctx.font_atlas, ctx.atlas_buf.atlas.width, ctx.atlas_buf.atlas.height, 4, ctx.atlas_buf.data);

	Write(&ctx.rd, ctx.desc_set, &ctx.font_atlas, 0, DT.Image);

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

	g_ui_ctx = ctx;

	InitWidgets();
}

void
ClearStacks(UICtx* ctx)
{
	import std.traits;

	static foreach(i; 0 .. ctx.tupleof.length)
	{
		{
			alias T = typeof(ctx.tupleof[i]);

			static if(
					isPointer!(T) &&
					hasMember!(T, "value") &&
					is(typeof(*ctx.tupleof[i]): UIStack!(typeof(ctx.tupleof[i].value)))
					)
			{
				ctx.tupleof[i] = null;
				ctx.tupleof[i] = Alloc!(UIStack!(typeof(ctx.tupleof[i].value)))(&ctx.temp_arena);
			}
		}
	}
}

void
Push(string id, T)(T value)
{
	import std.string;
	import std.traits;

	enum string s = "g_ui_ctx.@".replace("@", id);
	alias Y = typeof(mixin(s));

	enum is_ptr = isPointer!(Y);
	enum has_mem = is_ptr && hasMember!(typeof(*mixin(s)), "next");

	static if(is_ptr && has_mem)
	{
		enum stack = true;
		alias U = typeof(mixin(s~".value"));
	}
	else
	{
		enum stack = false;
		alias U = typeof(mixin(s));
	}

	static if(!stack)
	{
		mixin(s) = value;
	}
	else static if(stack)
	{
		static if(is(U: Vec4[4]) && is(T: Vec4))
		{
			UIStack!(U)* node = Alloc!(UIStack!(U))(&g_ui_ctx.temp_arena);
			node.value[] = value;
		}
		else
		{
			UIStack!(U)* node = Alloc!(UIStack!(U))(&g_ui_ctx.temp_arena);
			node.value = value;
		}

		node.next = mixin(s);
		mixin(s) = node;
	}
}

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

auto
Pop(string stack)()
{
	import std.string;
	import std.traits;

	enum string s = "g_ui_ctx.@".replace("@", stack);
	enum string v = s~".value";
	enum string n = s~".next";

	static assert(isPointer!(typeof(mixin(s))) && hasMember!(typeof(*mixin(s)), "next"));

	assert(mixin(s) != null);
	auto value = mixin(v);
	mixin(s) = mixin(n);
	return value;
}

auto
Get(string id)()
{
	import std.string;
	import std.traits;

	enum string s = "g_ui_ctx.@".replace("@", id);

	static if(isPointer!(typeof(mixin(s))) && hasMember!(typeof(*mixin(s)), "next"))
	{
		assert(mixin(s) != null);
		return mixin(s~".value");
	}
	else
	{
		return mixin(s);
	}
}

UIItemNode*
PopItemNode()
{
	UICtx* ctx = GetCtx();

	UIItemNode* node;
	if(!CheckNil(g_UI_NIL_NODE, ctx.stack_free_list.first))
	{
		node = SLLPop(&ctx.stack_free_list, g_UI_NIL_NODE);
	}
	else
	{
		node = Alloc!(UIItemNode)(&ctx.arena);
	}

	node.item = g_UI_NIL;
	node.next = g_UI_NIL_NODE;

	return node;
}

void
PushItemNode(UIItemNode* node)
{
	UICtx* ctx = GetCtx();
	SLLPushFront(&ctx.stack_free_list, node, g_UI_NIL_NODE);
}

void
PushSiblingPanel(UIItem* panel)
{
	UICtx* ctx = GetCtx();
	UIItemNode* node = PopItemNode();

	node.item = panel;
	node.next = ctx.prev_sibling;
	ctx.prev_sibling = node;
}

UIItem*
PopSiblingPanel()
{
	UICtx* ctx = GetCtx();
	UIItem* panel = g_UI_NIL;
	UIItemNode* node = ctx.prev_sibling;

	if(node != g_UI_NIL_NODE)
	{
		panel = node.item;
		ctx.prev_sibling = node.next;
		PushItemNode(node);
	}

	return panel;
}

void
PopSiblingPanels()
{
	UIItem* sibling = PeekSiblingPanel();
	while(!Nil(sibling))
	{
		sibling = PopSiblingPanel();
	}
}

UIItem*
PeekSiblingPanel()
{
	return g_ui_ctx.prev_sibling == g_UI_NIL_NODE ? g_UI_NIL : g_ui_ctx.prev_sibling.item;
}

UIItem*
PrevSiblingPanel(UIItem* panel)
{
	UICtx* ctx = GetCtx();
	UIItem* result = g_UI_NIL;
	UIItemNode* prev = ctx.prev_sibling;
	for(;;)
	{
		if(panel == prev.item)
		{
			prev = prev.next;
		}

		if(prev == g_UI_NIL_NODE)
		{
			break;
		}

		if(prev.item.level < panel.level)
		{
			break;
		}

		if(prev.item.level == panel.level)
		{
			result = prev.item;
			break;
		}

		prev = prev.next;
	}

	return result;
}

UIItem*
PeekParent()
{
	return g_ui_ctx.top_parent == g_UI_NIL_NODE ? g_UI_NIL : g_ui_ctx.top_parent.item;
}

UIItem*
PopParent()
{
	UICtx* ctx = GetCtx();
	UIItemNode* node = ctx.top_parent;
	UIItem* item = node.item;

	assert(!Nil(item));
	ctx.top_parent = node.next;
	PushItemNode(node);
	ctx.panel_level -= 1;

	return item;
}

void
PushParent(UIItem* parent)
{
	UICtx* ctx = GetCtx();
	UIItemNode* node = PopItemNode();

	node.item = parent;
	node.next = ctx.top_parent;
	ctx.top_parent = node;
	ctx.panel_level += 1;
}

void
SetDebug(bool dbg)
{
	debug
	{
		g_ui_ctx.dbg = dbg;
	}
}

void
BeginBuild(Inputs* inputs)
{
	UICtx* ctx = GetCtx();

	debug ctx.item_count = 0;
	debug ctx.final_count = 0;

	Reset(&ctx.temp_arena);
	ClearStacks(ctx);
	Push!("text_size")(16.0);
	Push!("tab_width")(2);

	KVPair!(u64, UIItem*)*[] items = GetAllNodes(&ctx.temp_arena, &ctx.items);
	for(u64 i = 0; i < items.length; i += 1)
	{
		UIItem* item = items[i].value;
		if(item.last_frame != ctx.frame-1)
		{
			Delete(&ctx.items, items[i].key);
		}
	}

	ctx.f_idx = ctx.frame%FRAME_OVERLAP;

	ctx.inputs = inputs;
	
	ctx.panel_level = 0;

	ctx.root = Root();
	PushParent(ctx.root);
}

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

	PopParent();
	PopSiblingPanels();

	assert(ctx.prev_sibling == g_UI_NIL_NODE);
	assert(ctx.top_parent == g_UI_NIL_NODE, "Parent panel not popped");

	CalcFixedSizes(ctx.root);
	CalcPercentageSizes(ctx.root);
	static foreach(axis; A2D.min .. A2D.max)
	{
		CalcPositions!(axis)(ctx.root);
	}
	FixViolations(ctx.root);
	PrepRendering(ctx);

	DrawUI(ctx, ctx.root);

	debug if(ctx.dbg)
	{
		DrawDebugUI(ctx, ctx.root);
	}

	debug assert(ctx.item_count == ctx.final_count);

	BindBuffers(&ctx.rd, &ctx.buffers[ctx.f_idx].m_idx, &ctx.buffers[ctx.f_idx].m_vtx);
	DrawIndexed(&ctx.rd, 6, ctx.buffers[ctx.f_idx].count, 0);

	CompleteRendering(ctx);

	ctx.frame += 1;

	debug
	{
		static u32 prev_count = 0;
		if(prev_count != ctx.item_count)
		{
			//Logf("New node count, printing node debug info: ");
			//PrintNodes(ctx.root);
			prev_count = ctx.item_count;
		}
	}
}

void
PrepRendering(UICtx* ctx)
{
	BeginFrame(&ctx.rd);
	
	BeginRendering(&ctx.rd);

	Vec2 ext = GetExtent(&ctx.rd);
	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_set);

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

void
CompleteRendering(UICtx* ctx)
{
	FinishRendering(&ctx.rd);
	SubmitAndPresent(&ctx.rd);
}

void
PrintNodes(UIItem* item)
{
	if(!Nil(item))
	{
		Logf("%s:", cast(char[])item.key.text);
		Logf("x0 %s x1 %s y0 %s y1 %s", item.rect.x0, item.rect.x1, item.rect.y0, item.rect.y1);

		PrintNodes(item.first);
		PrintNodes(item.next);
	}
}

void
FixViolations(UIItem* item)
{
	A2D axis = A2D.Y;
	for(UIItem* i = item; !Nil(i); i = Recurse(i))
	{
		if(!Nil(i.parent))
		{
			f32 bounds_start = i.parent.rect.vec0.v[axis] - i.parent.culling.vec0.v[axis];
			if(i.rect.vec0.v[axis] < bounds_start)
			{
				i.culling.vec0.v[axis] = bounds_start - i.rect.vec0.v[axis];
			}

			f32 bounds_end = i.parent.rect.vec1.v[axis] - i.parent.culling.vec1.v[axis];
			if(i.rect.vec1.v[axis] > bounds_end)
			{
				i.culling.vec1.v[axis] = i.rect.vec1.v[axis] - bounds_end;
			}
		}
	}
}

void
CalcFixedSizes(UIItem* item)
{
	for(UIItem* i = item; !Nil(i); i = Recurse(i))
	{
		static foreach(axis; A2D.min .. A2D.max)
		{
			if(i.size_info[axis].type == ST.Pixels)
			{
				i.size.v[axis] = i.size_info[axis].value + i.adjustment.v[axis];
				assert(!isNaN(i.size.v[axis]));
			}
		}
	}
}

void
CalcPercentageSizes(UIItem* item)
{
	for(UIItem* i = item; !Nil(i); i = Recurse(i))
	{
		static foreach(axis; A2D.min .. A2D.max)
		{
			if(i.size_info[axis].type == ST.Percentage)
			{
				i.size.v[axis] = (i.parent.size.v[axis]*i.size_info[axis].value) + fabsf(i.adjustment.v[axis]);
				assert(!isNaN(i.size.v[axis]));
			}
		}
	}
}

void
CalcPositions(alias axis)(UIItem* item)
{
	f32 pos = 0.0;
	for(UIItem* i = item; !Nil(i);)
	{
		f32 end_pos = 0.0, next_pos = 0.0;
		if(i.flags & UIF.Window)
		{
			end_pos = i.offset.v[axis] + i.size.v[axis];
			i.rect.vec0.v[axis] = i.offset.v[axis];
			i.rect.vec1.v[axis] = end_pos;

			pos = i.rect.vec0.v[axis];
		}
		else
		{
			pos += i.offset.v[axis];
			end_pos = pos + i.size.v[axis];
			i.rect.vec0.v[axis] = pos;
			i.rect.vec1.v[axis] = end_pos;


			if(item.parent.key.text == r"###cmd_palette_input_cntr")
			{
				Logf("pos %s end_pos %s", pos, end_pos);
			}

			assert(!isNaN(i.rect.vec0.v[axis]));
			assert(!isNaN(i.rect.vec1.v[axis]));

			next_pos = i.parent.layout_axis == axis ? end_pos : pos;
		}

		if(!Nil(i.first))
		{
			i = i.first;
		}
		else if(!Nil(i.next))
		{
			i = i.next;
			pos = next_pos;
		}
		else for(UIItem* p = i.parent; true; p = p.parent)
		{
			if(!Nil(p.next))
			{
				i = p.next;
				pos = i.parent.layout_axis == axis ? i.prev.rect.vec1.v[axis] : i.prev.rect.vec0.v[axis];
				break;
			}

			if(Nil(p))
			{
				i = g_UI_NIL;
				break;
			}
		}
	}
}

void
DrawDebugUI(UICtx* ctx, UIItem* item)
{
	for(UIItem* i = item; !Nil(i); i = Recurse(i))
	{
		if(i.flags & UIF.DrawBackground)
		{
			DrawPanelDebug(ctx, i);
		}
	}
}

void
DrawUI(UICtx* ctx, UIItem* item)
{
	for(UIItem* i = item; !Nil(i) && i != ctx.root.next; i = Recurse(i))
	{
		if(i.flags & UIF.DrawBackground)
		{
			DrawRect(ctx, i);
		}

		if(i.flags & UIF.DrawBorder)
		{
			DrawBorder(ctx, i);
		}

		if(i.flags & UIF.DrawText)
		{
			DrawLine(i);
		}

		debug ctx.final_count += 1;
	}

	for(UIItem* i = item; !Nil(i); i = Recurse(i))
	{
		if(i.flags & UIF.DeferredBorder)
		{
			DrawBorder(ctx, i);
		}
	}

	for(UIItem* i = item.next; !Nil(i); i = Recurse(i))
	{
		if(i.flags & UIF.DrawBackground)
		{
			DrawRect(ctx, i);
		}

		if(i.flags & UIF.DrawBorder)
		{
			DrawBorder(ctx, i);
		}

		if(i.flags & UIF.DrawText)
		{
			DrawLine(i);
		}

		debug ctx.final_count += 1;
	}
}

UIItem*
Recurse(UIItem* item)
{
	UIItem* result = g_UI_NIL;
	if(!Nil(item.first))
	{
		result = item.first;
	}
	else if(!Nil(item.next))
	{
		result = item.next;
	}
	else for(UIItem* i = item.parent; !Nil(i); i = i.parent)
	{
		if(!Nil(i.next))
		{
			result = i.next;
			break;
		}
	}

	return result;
}

UIItem*
RecurseB(UIItem* item)
{
	UIItem* result = g_UI_NIL;
	if(!Nil(item.last))
	{
		result = item.last;
	}
	else if(!Nil(item.prev))
	{
		result = item.prev;
	}
	else for(UIItem* i = item.parent; !Nil(i); i = i.parent)
	{
		if(!Nil(i.prev))
		{
			result = i.prev;
			break;
		}
	}

	return result;
}

void
BuildItem(UIItem* item, UISize size_x, UISize size_y, UIFlags flags)
{
	UICtx* ctx = GetCtx();

	item.first = item.last = item.next = item.prev = item.parent = g_UI_NIL;
	
	item.flags = flags;
	item.size_info[A2D.X] = size_x;
	item.size_info[A2D.Y] = size_y;
	item.level = ctx.panel_level;
	item.last_frame = ctx.frame;
	item.color = Get!("color")();
	item.layout_axis = Get!("layout_axis")();
	item.adjustment = Get!("adjustment")();
	item.border_color = Get!("border_color")();
	item.border_thickness = Get!("border_thickness")();
	item.corner_radius = Get!("corner_radius")();
	item.edge_softness = Get!("edge_softness")();
	item.highlighted_char = Get!("highlighted_char")();
	item.offset = Get!("offset")();
	item.padding = Get!("padding")();
	item.culling.vec0 = Vec2(0.0);
	item.culling.vec1 = Vec2(0.0);

	if(flags & UIF.Window)
	{
		assert(ctx.root != g_UI_NIL);
		auto n = ctx.root;
		for(;;)
		{
			if(Nil(n.next))
			{
				n.next = item;
				item.prev = n;
				break;
			}
		}
	}
	else
	{
		item.parent = ctx.top_parent == g_UI_NIL_NODE ? g_UI_NIL : ctx.top_parent.item;
		if(!Nil(item.parent))
		{
			DLLPush(item.parent, item, g_UI_NIL);
		}
	}

	debug ctx.item_count += 1;
}

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

	item.signal &= UIS.BorderX0|UIS.BorderX1|UIS.BorderY0|UIS.BorderY1;
	item.dragged = 0.0;

	for(DNode!(InputEvent)* n = ctx.inputs.list.first; !CheckNil(null, n); n = n.next)
	{
		bool taken = false;

		if(BorderClicked(item, n))
		{
			taken = true;
			if(item.signal & UIS.DraggedBorder)
			{
				ctx.drag_item   = item;
			}
		}

		else if(item.flags & UIF.Clickable && Clicked(item, n))
		{
			item.last_click_pos = Vec2(n.value.x, n.value.y) - item.rect.vec0;
			item.signal |= UIS.Clicked;
			taken = true;
		}

		else if(item.flags & UIF.Draggable && Clicked(item, n) && Nil(ctx.drag_item))
		{
			item.signal |= UIS.Dragged;
			ctx.drag_item = item;
			taken = true;
		}

		else if(ctx.drag_item == item && n.value.key == Input.LeftClick && !n.value.pressed)
		{
			ctx.drag_item = g_UI_NIL;
			item.signal = UIS.None;
			taken = true;
		}

		else if(ctx.drag_item == item && n.value.key == Input.MouseMotion)
		{
			item.signal    |= (item.flags & (UIF.DragBorder)) ? UIS.DraggedBorder : UIS.Dragged;
			item.dragged.x += cast(f32)n.value.rel_x;
			item.dragged.y += cast(f32)n.value.rel_y;
			taken = true;
		}

		if(taken)
		{
			DLLRemove(&ctx.inputs.list, n, null);
		}
	}
}

UICtx*
GetCtx()
{
	return &g_ui_ctx;
}

Vec2
RootSize()
{
	Vec2 size = GetExtent(&g_ui_ctx.rd);
	return size;
}

UIKey
MakeKey(u8[] id)
{
	UIKey key;

	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)
	{
		key.text      = id[0 .. pos];
		key.hash_text = id[pos .. $];
		key.hash      = Hash(id);
	}
	else if(hash_count == 3)
	{
		key.text      = id[0 .. pos];
		key.hash_text = id[pos .. $];
		key.hash      = Hash(id[pos+hash_count .. $]);
	}

	return key;
}

pragma(inline) UIItem*
Get(UIKey key)
{
	Result!(UIItem*) result = g_ui_ctx.items[key.hash];
	if(!result.ok)
	{
		result.value = Alloc!(UIItem)(&g_ui_ctx.arena);
		HTPush(&g_ui_ctx.items, key.hash, result.value);
	}

	result.value.key = key;

	return result.value;
}

UIItem*
Get(string id)
{
	u8[] u8_id = CastStr!(u8)(id);
	return Get(u8_id);
}

UIItem*
Get(u8[] id)
{
	UIKey key = MakeKey(id);
	return Get(key);
}

f32
CalcTextWidth(u8[] str)
{
	u32 tab_width = g_ui_ctx.tab_width;
	Glyph* space = g_ui_ctx.atlas_buf.atlas.glyphs.ptr + ' ';

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

	return width;
}

pragma(inline) f32
GlyphWidth(Glyph* g)
{
	f32 width = 0.0;
	if(g.ch == '\t')
	{
		width += g_ui_ctx.atlas_buf.atlas.glyphs[' '].advance * cast(f32)(g_ui_ctx.tab_width);
	}
	else
	{
		width += g.advance;
	}

	return width;
}

struct TextBuffer
{
	u8[] text;
	TS[] style;
}

Node!(TextBuffer)*
MakeMultiline(u8[] text, f32 width, u8[] parent_id, u64 line_no, TS[] style = [])
{
	f32 scaled_width = width * (g_ui_ctx.atlas_buf.atlas.size/g_ui_ctx.text_size);
	f32 text_width = CalcTextWidth(text);
	
	u64 line_count = cast(u64)(ceil(text_width/scaled_width));
	Node!(TextBuffer)* node = null;
	if(line_count > 0)
	{
		f32 w = 0.0;
		u64 line = 0;
		u64 start = 0;
		const u64 extra_buf = 20;
		for(u64 i = 0; i < text.length; i += 1)
		{
			f32 ch_w = GlyphWidth(g_ui_ctx.atlas_buf.atlas.glyphs.ptr + text[i]);

			if(ch_w + w > scaled_width || i == text.length-1)
			{
				u64 len = i-start+1;
				u8[extra_buf] buf = 0;
				(cast(char[])buf).sformat("%s%05s%05s", cast(char[])parent_id, line_no, line);

				u8[] str = ScratchAlloc!(u8)(len+extra_buf);

				str[0 .. len] = text[start .. start+len];
				str[len .. len+extra_buf] = buf[0 .. $];

				TS[] stl = [];
				if(style.length > 0)
				{
					stl = ScratchAlloc!(TS)(len);
					stl[0 .. len] = style[start .. start+len];
				}

				Node!(TextBuffer)* n = node;
				for(;;)
				{
					if(node == null)
					{
						node = ScratchAlloc!(Node!(TextBuffer))();

						node.value.text  = str;
						node.value.style = stl;
						node.next        = null;

						break;
					}

					if(n.next == null)
					{
						n.next = ScratchAlloc!(Node!(TextBuffer))();

						n.next.value.text  = str;
						n.next.value.style = stl;
						n.next.next        = null;

						break;
					}

					n = n.next;
				}

				line += 1;
				start = i;
				w = 0.0;
			}

			w += ch_w;
		}
	}

	return node;
}

void
DrawPanelDebug(UICtx* ctx, UIItem* item)
{
	if(item.first)
	{
		u8[] text = item.key.text;
		Rect rect = item.rect;

		f32 y = rect.y0 + (rect.y1 - rect.y0) / 3;
		f32 x = rect.x0 + (rect.x1 - rect.x0) / 3;
		f32 x0 = x;
		for(u64 i = 0; i < text.length; i += 1)
		{
			DrawGlyph(item, &ctx.atlas_buf.atlas.glyphs[text[i]], ctx.atlas_buf.atlas.size/ctx.text_size, &x0, y);
		}

		f32 pct = item.parent.layout_axis == A2D.X ? item.size_info[A2D.X].value : item.size_info[A2D.Y].value;

		u8[512] buf = 0;
		(cast(char[])buf).sformat("%f%%", pct);
			y += 16.0;
		for(u64 i = 0; i < buf.length && buf[i] != 0; i += 1)
		{
			DrawGlyph(item, &ctx.atlas_buf.atlas.glyphs[buf[i]], ctx.atlas_buf.atlas.size/ctx.text_size, &x, y);;
		}
	}
}

void
DrawLine(UIItem* item)
{
	UICtx* ctx = GetCtx();
	f32 text_size = Get!("text_size")();
	f32 y = item.rect.y0 + text_size + item.padding.y;
	f32 x = item.rect.x0 + item.padding.x;
	FontAtlas* atlas = &ctx.atlas_buf.atlas;

	bool edit_mode = EditModeActive();

	Vertex* v;
	if(item.highlighted_char >= 0)
	{
		v = ctx.buffers[ctx.f_idx].vtx.ptr + ctx.buffers[ctx.f_idx].count;
		AddUIIndices(ctx);
	}

	for(u64 i = 0; i < item.key.text.length && item.key.text[i] != '\0'; i += 1)
	{
		u8 ch = item.key.text.ptr[i];
		TS ts = i < item.token_styles.length ? item.token_styles[i] : TS.None;
		if(ch < 128)
		{
			DrawGlyph(item, &atlas.glyphs[ch], atlas.size/ctx.text_size, &x, y, !edit_mode && i == item.highlighted_char, ts);
		}
	}

	if(v)
	{
		y = item.rect.y0 + 2;
		x = item.rect.x0;

		f32 adv = atlas.glyphs[' '].advance;
		for(u64 i = 0; i < item.highlighted_char; i += 1)
		{
			if(item.key.text[i] == '\t')
			{
				x += adv*2;
			}
			else
			{
				x += adv;
			}
		}

		v.dst_start.x = x;
		v.dst_start.y = y;
		v.dst_end.x = x + adv;
		v.dst_end.y = y + ctx.text_size - 2;
		v.cols = Vec4(1.0);

		if(edit_mode)
		{
			v.dst_end.x = v.dst_start.x + 2;
		}

		AddUIIndices(ctx);
	}
}

pragma(inline) bool
CullText(UIItem* item, GlyphBounds* gb)
{
	bool skip = false;

	if(item.culling.x0 != 0.0 || item.culling.x1 != 0.0)
	{
		if(gb.w <= item.culling.x0+item.culling.x1)
		{
			skip = true;
		}
		else
		{
			f32 start_pct = 1.0-((gb.w-item.culling.x0)/gb.w);
			f32 end_pct   = 1.0-((gb.w-item.culling.x1)/gb.w);

			f32 atlas_len = gb.atlas_r - gb.atlas_l;
			gb.atlas_l += atlas_len * start_pct;
			gb.atlas_r -= atlas_len * end_pct;

			gb.l += item.culling.x0;
			gb.r -= item.culling.x1;

			gb.w = gb.r-gb.l;
		}
	}

	if(!skip && (item.culling.y0 != 0.0 || item.culling.y1 != 0.0))
	{
		if(gb.h <= item.culling.y0+item.culling.y1)
		{
			skip = true;
		}
		else
		{
			f32 start_pct = 1.0-((gb.h-item.culling.y0)/gb.h);
			f32 end_pct   = 1.0-((gb.h-item.culling.y1)/gb.h);

			f32 atlas_len = gb.atlas_b-gb.atlas_t;
			gb.atlas_t += atlas_len * start_pct;
			gb.atlas_b -= atlas_len * end_pct;

			gb.t += item.culling.y0;
			gb.b -= item.culling.y1;

			gb.h = gb.b-gb.t;
		}
	}

	return skip;
}

pragma(inline) void
DrawGlyph(UIItem* item, Glyph* glyph, f32 scale, f32* x_pos, f32 y, bool highlight = false, TokenStyle ts = TS.None, Vec4 col = Vec4(1.0))
{
	UICtx* ctx = GetCtx();
	Vertex* bg_v = null, v = null;

	if(glyph.ch == '\t')
	{
		*x_pos += glyph.advance * (GetCtx().tab_width - 1);
	}
	
	f32 r = glyph.plane_right  * scale;
	f32 l = glyph.plane_left   * scale;
	f32 t = glyph.plane_top    * scale;
	f32 b = glyph.plane_bottom * scale;

	GlyphBounds gb = {
		r: r,
		l: l,
		t: t,
		b: b,
		w: r - l,
		h: b - t,
		atlas_r: glyph.atlas_right,
	 	atlas_l: glyph.atlas_left,
	 	atlas_t: glyph.atlas_top,
	 	atlas_b: glyph.atlas_bottom,
	};

	//bool skip = false;
	bool skip = CullText(item, &gb);
	
	if(!skip)
	{
		f32 y_pos = t;

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

		v.dst_start.x = *x_pos + gb.l;
		v.dst_start.y = y - y_pos;
		v.dst_end.x = *x_pos + gb.w + gb.l;
		v.dst_end.y = y + gb.h - y_pos;

		if(glyph.ch != '\t' && glyph.ch != '\n')
		{
			v.src_start.x = gb.atlas_l;
			v.src_start.y = gb.atlas_t;
			v.src_end.x = gb.atlas_r;
			v.src_end.y = gb.atlas_b;
		}

		if(highlight)
		{
			col = Vec4(Vec3(1.0)-col.xyz, 1.0);
		}
		else if(ts != TS.None)
		{
			col = SYNTAX_COLORS[ts];
		}

		v.cols = col;

		v.texture = 1;

		AddUIIndices(ctx);
	}

	*x_pos += glyph.advance * scale;
}

pragma(inline) void
DrawRect(UICtx* ctx, UIItem* item)
{
	// Y reversed
	Vertex* v = ctx.buffers[ctx.f_idx].vtx.ptr + ctx.buffers[ctx.f_idx].count;
	v.dst_start = item.rect.vec0;
	v.dst_end = item.rect.vec1;
	v.cols = item.color;
	v.border_thickness = 0.0;
	v.corner_radius = item.corner_radius;
	v.edge_softness = 0.0;
	v.raised = 0.0;

	if(item.border_thickness > 0.0)
	{
		v.dst_start.x += item.border_thickness;
		v.dst_start.y += item.border_thickness;
		v.dst_end.x   -= item.border_thickness;
		v.dst_end.y   -= item.border_thickness;
	}

	AddUIIndices(ctx);
}

pragma(inline) void
DrawBorder(UICtx* ctx, UIItem* item)
{
	Vertex* v = ctx.buffers[ctx.f_idx].vtx.ptr + ctx.buffers[ctx.f_idx].count;
	v.dst_start = item.rect.vec0;
	v.dst_end = item.rect.vec1;
	v.cols = item.border_color;
	v.border_thickness = item.border_thickness;
	v.corner_radius = item.corner_radius;
	v.edge_softness = item.edge_softness;
	v.raised = 0.0;

	AddUIIndices(ctx);
}

pragma(inline) void
AddUIIndices(UICtx* ctx)
{
	ctx.buffers[ctx.f_idx].idx[0] = 0; 
	ctx.buffers[ctx.f_idx].idx[1] = 1; 
	ctx.buffers[ctx.f_idx].idx[2] = 2; 
	ctx.buffers[ctx.f_idx].idx[3] = 2; 
	ctx.buffers[ctx.f_idx].idx[4] = 1; 
	ctx.buffers[ctx.f_idx].idx[5] = 3; 

	ctx.buffers[ctx.f_idx].count += 1;
}

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

void
ClickedCharIndex(UIItem* item)
{
	UICtx* ctx = GetCtx();
	f32 w = 0.0;
	for(u64 i = 0; i < item.key.text.length; i += 1)
	{
		u8 ch = item.key.text[i];
		if(ch < ctx.atlas_buf.atlas.glyphs.length)
		{
			f32 ch_w = GlyphWidth(ctx.atlas_buf.atlas.glyphs.ptr + ch);
			if(ch_w + w > item.last_click_pos.x)
			{
				Logf("char clicked");
				//item.clicked_char_idx = i;
				break;
			}
			w += ch_w;
		}
	}
}

bool
Clicked(UIItem* item, DNode!(InputEvent)* n)
{
	bool result = false;
	InputEvent* ev = &n.value;

	if(
		ev.key == Input.LeftClick &&
		ev.x >= item.rect.x0 &&
		ev.x <= item.rect.x1 &&
		ev.y >= item.rect.y0 &&
		ev.y <= item.rect.y1 &&
		ev.pressed
	)
	{
		result = true;
	}

	return result;
}

bool
BorderClicked(UIItem* item, DNode!(InputEvent)* n)
{
	InputEvent* ev = &n.value;
	bool taken     = false;
	
	if(item.flags & (UIF.ClickBorder | UIF.DragBorder) && ev.key == Input.LeftClick && ev.pressed)
	{
		taken = true;

		UISignal signal = (item.flags & UIF.DragBorder) ? UIS.DraggedBorder : UIS.ClickedBorder;

		f32 x0 = item.rect.x0;
		f32 x1 = item.rect.x1;
		f32 y0 = item.rect.y0;
		f32 y1 = item.rect.y1;
		f32 b  = item.border_thickness*2.0;
		
		if(item.flags & UIF.BorderX0 && ev.x >= x0 && ev.x <= x0+b)
		{
			item.signal |= (signal | UIS.BorderX0);
		}
		else if(item.flags & UIF.BorderX1 && ev.x <= x1 && ev.x >= x1-b)
		{
			item.signal |= (signal | UIS.BorderX1);
		}
		else if(item.flags & UIF.BorderY0 && ev.y >= y0 && ev.y <= y0+b)
		{
			item.signal |= (signal | UIS.BorderY0);
		}
		else if(item.flags & UIF.BorderY1 && ev.y <= y1 && ev.y >= y1-b)
		{
			item.signal |= (signal | UIS.BorderY1);
		}
		else taken = false;

		if(taken)
		{
			Logf("%s Clicked or Dragged", cast(char[])item.key.hash_text);
		}
	}

	return taken;
}

u8[]
ScratchName(u64 num_len, u8[] base, u64 iteration)
{
	import core.stdc.stdio : sprintf;

	char[64] ch_buf;
	char[] s = ch_buf.sformat("%%0%sllu%%s", num_len);

	u8[] id = ScratchAlloc!(u8)(base.length+num_len);
	sprintf(cast(char*)id.ptr, s.ptr, iteration, cast(char*)base.ptr);

	return id;
}

u8[]
ScratchName(string fmt, u64 len, u64 iteration)
{
	u8[] id = ScratchAlloc!(u8)(len);
	(cast(char[])id).sformat(fmt, iteration);
	return id;
}

u8[]
ScratchName(u8[] base, u8[] append)
{
	u8[] id = ScratchAlloc!(u8)(base.length+append.length);
	id[0 .. base.length] = base[0 .. $];
	id[base.length .. $] = append[0 .. $];
	return id;
}

u8[]
ScratchName(u8[] base, string append)
{
	u8[] u8_append = CastStr!(u8)(append);
	return ScratchName(base, u8_append);
}

void
SetFocus(UIItem* item)
{
	g_ui_ctx.focused = item;
}

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

