#include "IUI/ImGuiExtensions.h"

#include "IUI/ImGui/imgui.h"
#include "IUI/ImGui/imgui_internal.h"
#include "IUI/extras/imguiwrap.dear.h"

#include <math/scalar.h>
#include <math/vec3.h>
#include <math/quat.h>

#include <algorithm>
#include <limits>


namespace ImGui
{
ImU32 ColorU32(kit::color::sRGBAi c)
{
	return (c.a << 24) | (c.b << 16) | (c.g << 8) | c.r;
}

ImU32 ColorU32(kit::color::sRGBi c)
{
	return (0xFF << 24) | (c.b << 16) | (c.g << 8) | c.r;
}

void AddDottedLine(ImDrawList& list, ImVec2 p1, ImVec2 p2, ImU32 col, float dotLength, float thickness)
{
	if (dotLength == 0)
		return;

	ImVec2 dir = p2 - p1;
	if (dir.x == 0.f && dir.y == 0.f)
		return;
	const float length = math::sqrt(dir.x * dir.x + dir.y * dir.y);
	dir /= length;

	const size_t segments = size_t(length / dotLength) / 2;
	const ImVec2 inc = dir * dotLength;
	ImVec2 p = p1;
	for (size_t i = 0; i < segments; i++)
	{
		ImVec2 pp2 = p + inc;
		list.AddLine(p, pp2, col, thickness);
		p = pp2 + inc;
	}
}

ImVec2 GetWindowContentPos()
{
	return GetWindowPos() + GetWindowContentRegionMin();
}

ImVec2 GetWindowContentSize()
{
	return GetWindowContentRegionMax() - GetWindowContentRegionMin();
}

ImVec2 GetWindowContentBottomRightSide()
{
	return GetWindowSize() - ImVec2(GetStyle().WindowPadding);
}

int8_t GetMouseButtonClicked()
{
	const bool leftBtn = IsMouseClicked(ImGuiMouseButton_Left);
	const bool midBtn = IsMouseClicked(ImGuiMouseButton_Middle);
	const bool rightBtn = IsMouseClicked(ImGuiMouseButton_Right);
	return leftBtn ? ImGuiMouseButton_Left : midBtn ? ImGuiMouseButton_Middle : rightBtn ? ImGuiMouseButton_Right : -1;
}

int8_t GetMouseButtonDown()
{
	const bool leftBtn = IsMouseDown(ImGuiMouseButton_Left);
	const bool midBtn = IsMouseDown(ImGuiMouseButton_Middle);
	const bool rightBtn = IsMouseDown(ImGuiMouseButton_Right);
	return leftBtn ? ImGuiMouseButton_Left : midBtn ? ImGuiMouseButton_Middle : rightBtn ? ImGuiMouseButton_Right : -1;
}

int8_t GetMouseButtonReleased()
{
	const bool leftBtn = IsMouseReleased(ImGuiMouseButton_Left);
	const bool midBtn = IsMouseReleased(ImGuiMouseButton_Middle);
	const bool rightBtn = IsMouseReleased(ImGuiMouseButton_Right);
	return leftBtn ? ImGuiMouseButton_Left : midBtn ? ImGuiMouseButton_Middle : rightBtn ? ImGuiMouseButton_Right : -1;
}


static tl::pair<kit::color::sRGBf, float> decomposeHDR(kit::color::lRGBhdr color)
{
	const auto c = kit::color::cast_to<math::vec3f>(color);
	const float maxComponent = math::max(math::cwise::max_component(c), 1.f);
	const kit::color::sRGBf ldr = kit::color::cast_to_space<kit::color::Space::sRGB>(kit::color::cast_to<kit::color::lRGBf>(c / maxComponent));
	return tl::make_pair(ldr, maxComponent);
}

static kit::color::sRGBf hdrToLdr(kit::color::lRGBhdr color, float intensity)
{
	const auto c = kit::color::cast_to<math::vec3f>(color);
	return kit::color::cast_to_space<kit::color::Space::sRGB>(kit::color::cast_to<kit::color::lRGBf>(c / intensity));
}

static kit::color::lRGBAhdr recomposeHDR(kit::color::sRGBAf srgba, float intensity)
{
	auto c = kit::color::cast_to<math::vec4f>(kit::color::cast_to_space<kit::color::Space::Linear>(srgba));
	c.x *= intensity;
	c.y *= intensity;
	c.z *= intensity;
	return kit::color::cast_to<kit::color::lRGBAhdr>(c);
}

bool ColorEditor(kit::color::lRGBAhdr& io_color, bool& saveUndo, bool isHDR, bool hasAlpha, const tl::string& label, float alignNameX) noexcept
{
	bool hasChanges = false;

	ImGuiColorEditFlags flags = ImGuiColorEditFlags_Float;
	flags |= hasAlpha ? (ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_AlphaPreviewHalf) : 0;

	TextUnformatted(label.c_str());
	SameLine();
	SetCursorPosX(GetCursorPosX() + alignNameX - GetItemRectSize().x);

	const float square_sz = GetFrameHeight();
	const float w_button = square_sz + GetStyle().ItemSpacing.x;

	{
		dear::ScopedItemWidth iw(-w_button);
		if (hasAlpha)
			hasChanges |= ColorEdit4("##edit", &io_color.r, flags | ImGuiColorEditFlags_NoSmallPreview);
		else
			hasChanges |= ColorEdit3("##edit", &io_color.r, flags | ImGuiColorEditFlags_NoSmallPreview);
		if (IsItemDeactivatedAfterEdit())
			saveUndo = true;
	}

	//using this static to provide a stable intensity value while editing.
	//Otherwise there is a conflict between the intensity and the brightness of the color
	static float stableIntensity = 1.f;
	static ImVec4 initialColor;

	{
		auto [sRGB, intensity] = decomposeHDR(kit::color::cast_to<kit::color::lRGBhdr>(io_color));
		const ImVec4 color(sRGB.r, sRGB.g, sRGB.b, io_color.a);
		SameLine();
		if (ColorButton("##button", color, flags))
		{
			initialColor = color;
			//when opening the picker, compute the real, current intensity and store it for the whole picker session.
			stableIntensity = intensity;
			OpenPopup("Picker");
		}
	}

	PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8.f, 8.f));
	if (BeginPopup("Picker"))
	{
		const auto sRGB = hdrToLdr(kit::color::cast_to<kit::color::lRGBhdr>(io_color), stableIntensity);
		ImVec4 color(sRGB.r, sRGB.g, sRGB.b, io_color.a);

		Text("Picker");
		Separator();
		if (ColorPicker4("##picker", &color.x, flags, &initialColor.x))
		{
			hasChanges = true;
			io_color = recomposeHDR(kit::color::sRGBAf(color.x, color.y, color.z, color.w), stableIntensity);
		}
		if (IsItemDeactivatedAfterEdit())
			saveUndo = true;

		if (isHDR)
		{
			if (SliderFloat("Intensity", &stableIntensity, 1.f, 1048576.f, "%.3f", ImGuiSliderFlags_AlwaysClamp))
			{
				hasChanges = true;
				io_color = recomposeHDR(kit::color::sRGBAf(color.x, color.y, color.z, color.w), stableIntensity);
			}
			if (IsItemDeactivatedAfterEdit())
				saveUndo = true;

			float stops = std::log2f(stableIntensity);
			if (SliderFloat("f-Stops", &stops, 0.f, 20.f, "%.4f", ImGuiSliderFlags_AlwaysClamp))
			{
				stableIntensity = std::pow(2.f, stops);
				hasChanges = true;
				io_color = recomposeHDR(kit::color::sRGBAf(color.x, color.y, color.z, color.w), stableIntensity);
			}
			if (IsItemDeactivatedAfterEdit())
				saveUndo = true;
		}

		EndPopup();
	}
	PopStyleVar();

	return hasChanges;
}


bool CheckBoxTristate(const char* label, int* v_tristate)
{
	bool ret;
	if (*v_tristate == -1)
	{
		PushItemFlag(ImGuiItemFlags_MixedValue, true);
		bool b = true;
		ret = Checkbox(label, &b);
		if (ret)
			*v_tristate = 0;
		PopItemFlag();
	}
	else
	{
		bool b = (*v_tristate != 0);
		ret = Checkbox(label, &b);
		if (ret)
			*v_tristate = (int)b;
	}
	return ret;
}

bool BeginIconMenu(kit::icons::Icon icon, const char* label)
{
	const float lineHeight = GetFrameHeight();
	const float iconSize = lineHeight * 0.8f;

	Icon(icon, {lineHeight, lineHeight}, {iconSize, iconSize});
	SameLine();

	//AlignTextToFramePadding();
	return BeginMenu(label);
}
bool BeginIconMenu(kit::icons::FontIcon icon, const char* label)
{
	//AlignTextToFramePadding();
	TextUnformatted(icon.icon);
	SameLine();

	//AlignTextToFramePadding();
	return BeginMenu(label);
}

bool MenuIconItem(kit::icons::Icon icon, const char* label, bool* selected)
{
	const float lineHeight = GetFrameHeight();
	const float iconSize = lineHeight * 0.8f;

	Icon(icon, {lineHeight, lineHeight}, {iconSize, iconSize});
	SameLine();

	PushID(label);
	const bool activated = MenuItem(label, "", selected); 
	PopID();

	return activated;
}
bool MenuIconItem(kit::icons::FontIcon icon, const char* label, bool* selected)
{
	TextUnformatted(icon.icon);
	SameLine();

	PushID(label);
	const bool activated = MenuItem(label, "", selected); 
	PopID();

	return activated;
}

void Icon(kit::icons::FontIcon icon, ImVec2 frameSize, ImVec2 _size, ImVec4 tint_col, ImVec4 border_col)
{
	TextUnformatted(icon.icon);
}
void Icon(kit::icons::Icon icon, ImVec2 frameSize, ImVec2 _size, ImVec4 tint_col, ImVec4 border_col)
{
	ImVec2 size = _size;
	if (size.x == 0)
		size.x = frameSize.x;
	if (size.y == 0)
		size.y = frameSize.y;

	const ImVec2 offset = (frameSize - size) * 0.5f;

	const ImGuiWindow* window = GetCurrentWindow();
	if (window->SkipItems)
		return;

	ImRect bb(window->DC.CursorPos, window->DC.CursorPos + frameSize);
	if (border_col.w > 0.0f)
		bb.Max += ImVec2(2, 2);
	ItemSize(bb);
	if (!ItemAdd(bb, 0))
		return;

	if (border_col.w > 0.0f)
	{
		window->DrawList->AddRect(bb.Min + offset, bb.Max - offset, GetColorU32(border_col), 0.0f);
		window->DrawList->AddImage(icon.texture.get(),
		                           bb.Min + ImVec2(1, 1) + offset,
		                           bb.Max - ImVec2(1, 1) - offset,
		                           icon.uv0,
		                           icon.uv1,
		                           GetColorU32(tint_col));
	}
	else
	{
		window->DrawList->AddImage(icon.texture.get(),
		                           bb.Min + offset,
		                           bb.Max - offset,
		                           icon.uv0,
		                           icon.uv1,
		                           GetColorU32(tint_col));
	}
}

bool IconButton(const char* str_id, kit::icons::Icon icon, ImVec2 size, bool active, ImVec4 bg_col, ImVec4 tint_col)
{
	if (active)
		PushStyleColor(ImGuiCol_Button, GetStyle().Colors[ImGuiCol_ButtonActive]);

	const bool clicked = ImageButton(str_id ? str_id : "",
		icon.texture.get(),
		size,
		icon.uv0,
		icon.uv1,
		bg_col,
		tint_col);

	if (active)
		PopStyleColor();

	return clicked;
}

bool IconButton(const char* str_id, kit::icons::FontIcon icon, const char* label, bool active)
{
	if (active)
		PushStyleColor(ImGuiCol_Button, GetStyle().Colors[ImGuiCol_ButtonActive]);

	PushID(str_id ? str_id : icon.icon);

	const tl::string text = icon ? tl::format("{}{}", icon.icon, label ? label : "") : tl::string(label);
	const bool clicked = ButtonEx(text.c_str());

	PopID();

	if (active)
		PopStyleColor();

	return clicked;
}

bool ToggleButton(const char* str_id, bool* v)
{
	const ImVec4* colors = GetStyle().Colors;
	ImDrawList* draw_list = GetWindowDrawList();

	ImVec2 frame_padding = GetStyle().FramePadding;
	const ImVec2 p = GetCursorScreenPos() + frame_padding;
	const float height = GetFrameHeight() - frame_padding.y * 2;
	const float width = height * 1.55f;
	const float radius = height * 0.50f;

	bool clicked = false;
	InvisibleButton(str_id, ImVec2(width, height));
	if (IsItemClicked())
	{
		*v = !*v;
		clicked = true;
	}
	//ImGuiContext& gg = *GImGui;
	//float ANIM_SPEED = 0.085f;
	//if (gg.LastActiveId == gg.CurrentWindow->GetID(str_id)) // && g.LastActiveIdTimer < ANIM_SPEED)
	//	float t_anim = ImSaturate(gg.LastActiveIdTimer / ANIM_SPEED);
	if (IsItemHovered())
		draw_list->AddRectFilled(p, ImVec2(p.x + width, p.y + height), GetColorU32(*v ? colors[ImGuiCol_ButtonActive] : ImVec4(0.78f, 0.78f, 0.78f, 1.0f)), height * 0.5f);
	else
		draw_list->AddRectFilled(p, ImVec2(p.x + width, p.y + height), GetColorU32(*v ? colors[ImGuiCol_Button] : ImVec4(0.85f, 0.85f, 0.85f, 1.0f)), height * 0.50f);
	draw_list->AddCircleFilled(ImVec2(p.x + radius + (*v ? 1 : 0) * (width - radius * 2.0f), p.y + radius), radius - 1.5f, IM_COL32(255, 255, 255, 255));

	return clicked;
}

bool Splitter(bool split_vertically, float thickness, float* size1, float* size2, float min_size1, float min_size2, float splitter_long_axis_size)
{
	using namespace ImGui;
	const ImGuiContext& g = *GImGui;
	ImGuiWindow* window = g.CurrentWindow;
	const ImGuiID id = window->GetID("##Splitter");
	ImRect bb;
	bb.Min = window->DC.CursorPos + (split_vertically ? ImVec2(*size1, 0.0f) : ImVec2(0.0f, *size1));
	bb.Max = bb.Min + CalcItemSize(split_vertically ? ImVec2(thickness, splitter_long_axis_size) : ImVec2(splitter_long_axis_size, thickness), 0.0f, 0.0f);
	return SplitterBehavior(bb, id, split_vertically ? ImGuiAxis_X : ImGuiAxis_Y, size1, size2, min_size1, min_size2, 0.0f);
}


static ImVector<ImRect> s_GroupPanelLabelStack;

void BeginGroupPanel(const char* name, ImVec2 size)
{
	BeginGroup();

	auto cursorPos = GetCursorScreenPos();
	const auto itemSpacing = GetStyle().ItemSpacing;
	PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 0.0f));
	PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f));

	auto frameHeight = GetFrameHeight();
	BeginGroup();

	ImVec2 effectiveSize = size;
	if (size.x < 0.0f)
		effectiveSize.x = GetContentRegionAvail().x;
	else
		effectiveSize.x = size.x;
	Dummy(ImVec2(effectiveSize.x, 0.0f));

	Dummy(ImVec2(frameHeight * 0.5f, 0.0f));
	SameLine(0.0f, 0.0f);
	BeginGroup();
	Dummy(ImVec2(frameHeight * 0.5f, 0.0f));
	SameLine(0.0f, 0.0f);
	TextUnformatted(name);
	const auto labelMin = GetItemRectMin();
	const auto labelMax = GetItemRectMax();
	SameLine(0.0f, 0.0f);
	Dummy(ImVec2(0.0, frameHeight + itemSpacing.y));
	BeginGroup();

	//GetWindowDrawList()->AddRect(labelMin, labelMax, IM_COL32(255, 0, 255, 255));

	PopStyleVar(2);

#if IMGUI_VERSION_NUM >= 17301
	GetCurrentWindow()->ContentRegionRect.Max.x -= frameHeight * 0.5f;
	GetCurrentWindow()->WorkRect.Max.x -= frameHeight * 0.5f;
	GetCurrentWindow()->InnerRect.Max.x -= frameHeight * 0.5f;
#else
    GetCurrentWindow()->ContentsRegionRect.Max.x -= frameHeight * 0.5f;
#endif
	GetCurrentWindow()->Size.x -= frameHeight;

	const auto itemWidth = CalcItemWidth();
	PushItemWidth(ImMax(0.0f, itemWidth - frameHeight));

	s_GroupPanelLabelStack.push_back(ImRect(labelMin, labelMax));
}

void EndGroupPanel()
{
	PopItemWidth();

	const auto itemSpacing = GetStyle().ItemSpacing;

	PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 0.0f));
	PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f));

	auto frameHeight = GetFrameHeight();

	EndGroup();

	//GetWindowDrawList()->AddRectFilled(GetItemRectMin(), GetItemRectMax(), IM_COL32(0, 255, 0, 64), 4.0f);

	EndGroup();

	SameLine(0.0f, 0.0f);
	Dummy(ImVec2(frameHeight * 0.5f, 0.0f));
	Dummy(ImVec2(0.0, frameHeight - frameHeight * 0.5f - itemSpacing.y));

	EndGroup();

	const auto itemMin = GetItemRectMin();
	const auto itemMax = GetItemRectMax();
	//GetWindowDrawList()->AddRectFilled(itemMin, itemMax, IM_COL32(255, 0, 0, 64), 4.0f);

	auto labelRect = s_GroupPanelLabelStack.back();
	s_GroupPanelLabelStack.pop_back();

	const ImVec2 halfFrame = ImVec2(frameHeight * 0.25f, frameHeight) * 0.5f;
	const ImRect frameRect = ImRect(itemMin + halfFrame, itemMax - ImVec2(halfFrame.x, 0.0f));
	labelRect.Min.x -= itemSpacing.x;
	labelRect.Max.x += itemSpacing.x;
	for (int i = 0; i < 4; ++i)
	{
		switch (i)
		{
		// left half-plane
		case 0: PushClipRect(ImVec2(-FLT_MAX, -FLT_MAX), ImVec2(labelRect.Min.x, FLT_MAX), true);
			break;
		// right half-plane
		case 1: PushClipRect(ImVec2(labelRect.Max.x, -FLT_MAX), ImVec2(FLT_MAX, FLT_MAX), true);
			break;
		// top
		case 2: PushClipRect(ImVec2(labelRect.Min.x, -FLT_MAX), ImVec2(labelRect.Max.x, labelRect.Min.y), true);
			break;
		// bottom
		case 3: PushClipRect(ImVec2(labelRect.Min.x, labelRect.Max.y), ImVec2(labelRect.Max.x, FLT_MAX), true);
			break;
		}

		GetWindowDrawList()->AddRect(
			frameRect.Min,
			frameRect.Max,
			ImColor(GetStyleColorVec4(ImGuiCol_Border)),
			halfFrame.x);

		PopClipRect();
	}

	PopStyleVar(2);

#if IMGUI_VERSION_NUM >= 17301
	GetCurrentWindow()->ContentRegionRect.Max.x += frameHeight * 0.5f;
	GetCurrentWindow()->WorkRect.Max.x += frameHeight * 0.5f;
	GetCurrentWindow()->InnerRect.Max.x += frameHeight * 0.5f;
#else
    GetCurrentWindow()->ContentsRegionRect.Max.x += frameHeight * 0.5f;
#endif
	GetCurrentWindow()->Size.x += frameHeight;

	Dummy(ImVec2(0.0f, 0.0f));

	EndGroup();
}

void AddUnderLine(ImColor col)
{
	ImVec2 min = GetItemRectMin();
	const ImVec2 max = GetItemRectMax();
	min.y = max.y;
	GetWindowDrawList()->AddLine(min, max, col, 1.0f);
}

// hyperlink urls
void TextURL(const char* name, const char* url)
{
	PushStyleColor(ImGuiCol_Text, GetStyle().Colors[ImGuiCol_ButtonHovered]);
    Text(name);
	PopStyleColor();
	if (IsItemHovered())
	{
		//if (IsMouseClicked(0))
			//PlatformOpenURLInBrowser(url);

		AddUnderLine( GetStyle().Colors[ImGuiCol_ButtonHovered] );
		SetTooltip("Open in browser\n%s", url);
	}
	else
		AddUnderLine( GetStyle().Colors[ImGuiCol_Button] );
}


} // namespace ImGuiExt
