import dlib;
import ui;
import editor;
import parsing;
import buffer;

import std.algorithm.comparison : clamp;
import std.format;
import std.conv;

__gshared const Panel  g_nil_panel;
__gshared Panel*       g_NIL_PANEL;
__gshared const Editor g_nil_ed;
__gshared Editor*      g_NIL_ED;
__gshared const UIKey  ZERO = ZeroKey();

Vec4 BG_COL        = Vec4(0.18, 0.18, 0.18, 1.0);

Vec4 CMD_COL       = Vec4(0.22, 0.22, 0.22, 1.0);
Vec4 HL_BG_COL     = Vec4(0.160, 0.533, 0.803, 1.0);
Vec4 HL_BORDER_COL = Vec4(0.172, 0.643, 0.988, 1.0);
Vec4 BLUE          = HexCol(0x4EA9FF);
Vec4 RED           = HexCol(0xFF3268);
Vec4 YELLOW        = HexCol(0xF7C443);

shared static this()
{
	g_NIL_PANEL = cast(Panel* )&g_nil_panel;
	g_NIL_ED    = cast(Editor*)&g_nil_ed;
}

struct Panel
{
	Panel*  first, last, next, prev, parent;
	Axis2D  layout_axis;
	Editor* ed;
	u64     id;
	u32     text_size;
}

void
LineCounterView(FontAtlasBuf* abuf, u64 max_line, u64 lines, i64 line_offset, f32 view_offset)
{
	UICtx* ctx  = GetCtx();

	u64 ch_width = max_line.toChars().length;
	f32 lc_width = cast(f32)(ch_width+1)*abuf.atlas.max_advance;

	enum UIPushInfo[] lc_params = [
		{ "layout_axis",      q{ A2D.Y }                                         },
		{ "view_offset",      q{ Vec2(0.0, view_offset) }                        },
		{ "padding",          q{ Vec2(4.0) }                                     },
		{ "size_info",        q{ UIS2(ST.Pixels, ST.Percentage, lc_width, 1.0) } },
		{ "edge_softness",    q{ 0.0 }                                           },
		{ "border_thickness", q{ 1.0 }                                           }, 
	];

	mixin(PushOnce!(lc_params));

	UIItem* line_count = MakeItem(ZERO, UIF.DrawBorder|UIF.PortalViewY);

	mixin(PushScope!("text_col",  q{ Vec4(1.0) } ));
	mixin(PushScope!("size_info", q{ UISY(ST.Pixels, abuf.atlas.line_height) } ));
	mixin(PushScope!("parent",    q{ line_count } ));

	u64 end_line = lines+line_offset;
	for(u64 i = line_offset; i < end_line && i < max_line; i += 1)
	{
		char[] buf = ScratchAlloc!(char)(ch_width);
		Push!("display_string")(Scratchf("%s", i), true);
		MakeItem(ZERO, UIF.DrawText);
	}
}

void
EditorTextView(UIItem* editor, Panel* p, FontAtlasBuf* abuf, u64 lines, i64 line_offset, f32 view_offset)
{
	Editor* ed = p.ed;

	PushLayoutAxis(A2D.Y, true);

	f32 text_size  = cast(f32)p.text_size;
	f32 clamp_y    = cast(f32)(ed.buf.line_count-lines)*text_size;
	f32 scroll_pos = cast(f32)(ed.line_offset)*text_size; 
	f32 padding    = 8.0;

	enum UIPushInfo[] text_view_params = [
		{ "layout_axis",      q{ A2D.Y }                  },
		{ "border_col",       q{ Vec4(1.0) }              },
		{ "size_info",        q{ UIS2() }                 },
		{ "scroll_target",    q{ Vec2(0.0, scroll_pos) }  },
		{ "scroll_clamp",     q{ Vec2(0.0, clamp_y) }     },
		{ "view_offset",      q{ Vec2(0.0, view_offset) } },
		{ "padding",          q{ Vec2(4.0) }              },
		{ "edge_softness",    q{ 0.0 }                    },
		{ "border_thickness", q{ 1.0 }                    }, 
	];
	mixin(PushOnce!(text_view_params));

	editor = MakeItem(editor.key, UIF.DrawBorder|UIF.ScrollY|UIF.ClampY|UIF.PortalViewY);

	mixin(PushScope!("parent", q{ editor }));

	UIKey zero = ZeroKey();

	// Cursor
	{
		LineBuffer* lb = GetLine(&ed.buf, ed.cursor_pos.y);

		u8  ch       = lb.text.length > ed.cursor_pos.y ? lb.text[ed.cursor_pos.y] : 0;
		f32 line_h   = abuf.atlas.line_height;
		f32 cursor_x = CalcTextWidth(lb.text[0 .. ed.cursor_pos.x], abuf);
		f32 cursor_y = cast(f32)(ed.cursor_pos.y - line_offset)*line_h;
		Glyph* g     = abuf.atlas.glyphs.length > ch ? abuf.atlas.glyphs.ptr + ch : abuf.atlas.glyphs.ptr + ' ';

		enum UIPushInfo[] cursor_info = [
			{ "size_info", q{ UIS2(ST.Pixels, ST.Pixels, g.advance, line_h) } },
			{ "fixed_pos", q{ Vec2(cursor_x, cursor_y) }                      },
			{ "bg_col",    q{ Vec4(1.0) }                                     },
		];
		mixin(PushOnce!(cursor_info));

		UIItem* cursor = MakeItem("###cursor", UIF.DrawBackground|UIF.FixedPosition);
	}

	mixin(PushScope!("size_info",        q{ UISY(ST.TextSize) } ));
	mixin(PushScope!("syntax_highlight", q{ UISH.D } ));

	// Do a thing like the bottom 3 colours on lost trail (arknights) on highlighting but make it slide to the right one colour every time you scroll down

	u64 i = line_offset;
	for(LineBuffer* lb = GetLine(&ed.buf, i); !CheckNil(g_NIL_LINE_BUF, lb) && i < line_offset+lines; i += 1, lb = GetLine(&ed.buf, i))
	{
		enum UIPushInfo[] lc_info = [
			{ "display_string", q{ Str(lb.text) }   },
			{ "syntax_tokens",  q{ cast(u8[])(lb.style) } },
		];
		mixin(PushOnce!(lc_info));

		UIItem* line = MakeItem(zero, UIF.DrawText);
	}
}

void
EditorView(Panel* p)
{
	Editor*       ed   = p.ed; 
	UICtx*        ctx  = GetCtx();
	UIKey         zero = ZeroKey();
	FontAtlasBuf* abuf = GetFontAtlas(p.text_size);

	mixin(PushScope!("text_size", q{ p.text_size }));

	if(CheckNil(g_NIL_ED, ed))
	{
		for(Panel* c = p.first; !CheckNil(g_NIL_PANEL, c); c = c.next)
		{
			EditorView(c);
		}
	}
	else
	{
		UIKey   ed_key = MakeKey("###ed_%s", ed.editor_id);
		UIItem* editor = Get(ed_key);

		f32 text_size = abuf.atlas.line_height;

		u64 frame_line_offset = ed.line_offset;
		f32 frame_view_offset = -(editor.scroll_offset.y%text_size);

		enum UIPushInfo[] c_info = [
			{ "bg_col",    q{ BG_COL } },
			{ "size_info", q{ UIS2() } },
		];
		mixin(PushOnce!(c_info));

		UIItem* container = MakeItem(zero, UIF.DrawBackground);

		mixin(PushScope!("parent",     q{ container } ));
		mixin(PushScope!("border_col", q{ Vec4(1.0) } ));

		u64 view_lines;
		if(editor.size.y > 0.0)
		{
			view_lines = cast(u64)floor(editor.size.y/text_size)+1;
			const u64 SCROLL_BUFFER = 4;

			i64 start = ed.line_offset;
			i64 end   = start+view_lines;

			if(ed.cursor_pos.y < start)
			{
				u64 pos = ed.cursor_pos.y > SCROLL_BUFFER ? ed.cursor_pos.y - SCROLL_BUFFER : ed.cursor_pos.y;
				ed.line_offset = clamp(pos, 0, ed.buf.line_count);
			}
			else if(ed.cursor_pos.y > end)
			{
				ed.line_offset = clamp(ed.cursor_pos.y + SCROLL_BUFFER - view_lines, 0, ed.buf.line_count);
			}	
		}

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

			if(i.type == UIE.Scroll)
			{
				ed.line_offset = clamp(ed.line_offset+(i.scroll*2), 0, ed.buf.line_count);
				if(ed.cursor_pos.y < ed.line_offset)
				{
					ed.cursor_pos.y = ed.line_offset;
				}
				else if(ed.cursor_pos.y > ed.line_offset+view_lines)
				{
					ed.cursor_pos.y = ed.line_offset+view_lines-1;
				}

				taken = true;
			}
			else
			{
				HandleInputs(p, &ctx.events);
			}

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

		ed.cursor_pos = VecPos(&ed.buf);

		u64 start = cast(u64)floor(editor.scroll_offset.y/text_size);
		LineCounterView(abuf, ed.buf.line_count, view_lines, start, frame_view_offset);
		EditorTextView(editor, p, abuf, view_lines, start, frame_view_offset);
	}

	ResetBuffer(&ed.buf);
}

void
CommandPalette(CmdPalette* cmd)
{
	UICtx*         ctx = GetCtx();
	Vec2           ext = GetExtent();
	FontAtlasBuf* abuf = &g_ui_ctx.glyph_sets[0].abuf;

	f32 w = ext.x*0.4;
	f32 h = ext.y*0.7;

	enum UIPushInfo[] cmd_params = [
		{ "layout_axis",      q{ A2D.Y }                            },
		{ "fixed_pos",        q{ Vec2(ext.x*0.3, ext.y*0.1) }       },
		{ "bg_col",           q{ BG_COL }                           },
		{ "border_col",       q{ HL_BORDER_COL }                    },
		{ "border_thickness", q{ 4.0 }                              },
		{ "corner_radius",    q{ Vec4(8.0) }                        },
		{ "size_info",        q{ UIS2(ST.Pixels, ST.Pixels, w, h) } },
	];

	mixin(PushOnce!(cmd_params));
	UIItem* cmd_item = MakeItem("###cmd_palette", UIF.Window|UIF.FixedPosition|UIF.DrawBackground|UIF.DrawBorder);

	f32 padding_y = 4.0;

	mixin(PushScope!("parent",  q{ cmd_item } ));
	mixin(PushScope!("padding", q{ Vec2(4.0, padding_y) }));

	enum UIPushInfo[] cmd_input_params = [
		{ "bg_col",         q{ HL_BG_COL }         },
		{ "size_info",      q{ UISY(ST.TextSize) } },
		{ "display_string", q{ Str(cmd.buffer[0 .. cmd.icount]) } },
	];

	mixin(PushOnce!(cmd_input_params));

	UIKey zero = ZeroKey();
	MakeItem(zero, UIF.DrawBorder|UIF.DrawBackground|UIF.DrawText|UIF.Overflow);

	enum UIPushInfo[] sep_params = [
		{ "padding",          q{ Vec2(0.0) }                },
		{ "size_info",        q{ UISY(ST.Pixels, 4.0) }     },
		{ "bg_col",           q{ HL_BORDER_COL }            },
	];

	mixin(PushOnce!(sep_params));
	MakeItem(zero, UIF.DrawBackground);

	mixin(PushScope!("padding",   q{ Vec2(4.0) }         ));
	mixin(PushScope!("size_info", q{ UISY(ST.TextSize) } ));

	u64 max_opts = cast(u64)ceil(h/abuf.atlas.line_height);

	Vec4[2] opt_cols = [
		Vec4(0.22, 0.22, 0.22, 1.0),
		Vec4(0.35, 0.35, 0.35, 1.0),
	];

	for(u64 i = 0; i < cmd.opt_strs.length && i < max_opts; i += 1)
	{
		PushDisplayString(Str(cmd.opt_strs[i]), true);
		PushBgCol(cmd.selected == i ? HL_BG_COL : opt_cols[i%2], true);
		MakeItem(zero, UIF.DrawBackground|UIF.DrawText);
	}
}

UIItem*
Container(Axis2D axis, UISize[2] size_info)
{
	enum UIPushInfo[] container_info = [
		{ "layout_axis", q{ axis }  },
	  { "size_info",   q{ size_info }},
	];
	
	mixin(PushOnce!(container_info));

	return MakeItem(ZeroKey());
}

void
StatusBar(EditorCtx* ed_ctx)
{
	mixin(PushScope!("edge_softness", q{ 0.6 }));

	enum UIPushInfo[] bar_info = [
		{ "size_info",     q{ UISY(ST.Percentage, 0.02)} },
		{ "layout_axis",   q{ A2D.X} },
	];
	mixin(PushOnce!(bar_info));

	Vec4 status_col = BLUE;
	string status   = "Normal";
	if(ed_ctx.state == ES.InputMode)
	{
		status     = "Input";
		status_col = YELLOW;
	}
	else if(ed_ctx.state == ES.CmdOpen)
	{
		status     = "Command";
		status_col = RED;
	}

	UIItem* bar = MakeItem(ZERO);

	mixin(PushScope!("parent", q{ bar }));

	enum UIPushInfo[] status_info = [
		{ "bg_col",        q{ status_col }                     },
		{ "corner_radius", q{ Vec4(8.0) } },
		{ "text_size", q{ 16 }},
		{ "padding", q{ Vec2A2(8.0, 8.0, 0.0, 0.0) } },
		{ "size_info",     q{ UISX(ST.TextSize) }        },
		{ "display_string", q{ status }             },
	];
	mixin(PushOnce!(status_info));

	MakeItem(ZERO, UIF.DrawBackground|UIF.DrawText|UIF.VerticalAlignText);

	enum UIPushInfo[] rest_info = [
		{ "bg_col", q{ BG_COL }},
		{ "corner_radius", q{ Vec4(0.0, 0.0, 0.0, 8.0) }},
		{ "size_info", q{ UIS2() }},
	];

	MakeItem(ZERO, UIF.DrawBackground);
}

