#include "ImGuiRenderUtils.h"

#include "Mod/Interface.h"

auto Utils::ImGuiRender::getCustomFontSize() -> float {
	auto interfaceMod = Client::findMod<Interface>(_("Interface"));

	if (interfaceMod) {
		switch (interfaceMod->font) {
		case 0:
			return 1.f;
		case 1:
			return 0.85f;
		case 2:
			return 0.8f;
		}
	}

	return 1.f;
}

auto Utils::ImGuiRender::setDrawList(ImDrawList* list) -> void { ImGuiRender::list = list; }
auto Utils::ImGuiRender::setColor(ImColor color) -> void { ImGuiRender::color = color; }
auto Utils::ImGuiRender::setSecondColor(ImColor secondcolor) -> void { ImGuiRender::secondcolor = secondcolor; }
auto Utils::ImGuiRender::setFont(ImFont* font) -> void { ImGuiRender::font = font; }
auto Utils::ImGuiRender::setSize(float size) -> void { ImGuiRender::size = size * getCustomFontSize(); }
auto Utils::ImGuiRender::setThickness(float thickness) -> void { ImGuiRender::thickness = thickness; }
auto Utils::ImGuiRender::setRound(float round) -> void { ImGuiRender::round = round; }
auto Utils::ImGuiRender::setBlurStreangth(float blurstreangth) -> void { ImGuiRender::blurstreangth = blurstreangth; }
auto Utils::ImGuiRender::setSegments(int segments) -> void { ImGuiRender::segments = segments; }

auto Utils::ImGuiRender::setOutline(bool outline) -> void { ImGuiRender::outline = outline; }

auto Utils::ImGuiRender::drawText(std::string str, ImVec2 pos) -> void {
	auto interfaceMod = Client::findMod<Interface>(_("Interface"));

	if (interfaceMod) {
		if (interfaceMod->lowercase)
			Utils::String::toLowercase(str);

		if (interfaceMod->font == 1)
			list->AddText(font, (size * 20), ImVec2(pos.x + 2.f, pos.y + 2.f), ImColor(color.Value.x * 0.2f, color.Value.y * 0.2f, color.Value.z * 0.2f, color.Value.w), str.c_str());
	}

	list->AddText(font, (size * 20), pos, color, str.c_str());
}

auto Utils::ImGuiRender::fillRectangle(ImVec4 pos) -> void {
	list->AddRectFilled(ImVec2(pos.x, pos.y), ImVec2(pos.z, pos.w), color, round);
}

auto Utils::ImGuiRender::fillBlurRectangle(ImVec4 pos) -> void {
	ImBlur::Begin(list);
	ImBlur::AddBlur(blurstreangth, pos, round);
	ImBlur::End();
}

auto Utils::ImGuiRender::fillBlurRectangleWithClip(ImVec4 pos, ImVec4 clip) -> void {
	ImBlur::Begin(list);
	ImBlur::PushLayer(clip);
	ImBlur::AddBlur(blurstreangth, pos, round);
	ImBlur::PopLayer();
	ImBlur::End();
}

auto Utils::ImGuiRender::fillRectangleCustomRadius(ImVec4 pos, ImVec4 round) -> void {
	list->AddRectFilledCustomRadius(ImVec2(pos.x, pos.y), ImVec2(pos.z, pos.w), color, round.x, round.y, round.z, round.w);
}

auto Utils::ImGuiRender::fillShadowRectangle(ImVec4 pos) -> void {
	list->AddShadowRect(ImVec2(pos.x, pos.y), ImVec2(pos.z, pos.w), color, thickness, ImVec2(0, 0), 0, round);
}

auto Utils::ImGuiRender::fillGradientOpaqueRectangle(ImVec4 pos) -> void {
	/*
	ImVec2 topLeft = ImVec2(pos.x, pos.y);
	ImVec2 bottomRight = ImVec2(pos.z, pos.w);

	int startBufferSize = list->VtxBuffer.Size;
	list->AddRectFilled(topLeft, bottomRight, color, round, ImDrawFlags_RoundCornersBottom);
	int endBufferSize = list->VtxBuffer.Size;
	list->AddRectFilled(topLeft, bottomRight, color, round, ImDrawFlags_RoundCornersBottom);
	int endBufferSize2 = list->VtxBuffer.Size;

	ImGui::ShadeVertsLinearColorGradientKeepAlpha(list, startBufferSize, endBufferSize, topLeft, bottomRight, color, secondcolor);
	ImGui::ShadeVertsLinearColorGradientKeepAlpha(list, endBufferSize, endBufferSize2, topLeft, bottomRight, color, secondcolor);
	*/
	list->AddRectFilledMultiColor(ImVec2(pos.x, pos.y), ImVec2(pos.z, pos.w),
		ImColor(color.Value.x, color.Value.y, color.Value.z, secondcolor.Value.w),
		ImColor(secondcolor.Value.x, secondcolor.Value.y, secondcolor.Value.z, secondcolor.Value.w),
		ImColor(secondcolor.Value.x, secondcolor.Value.y, secondcolor.Value.z, color.Value.w),
		ImColor(color.Value.x, color.Value.y, color.Value.z, color.Value.w)
	);
}

auto Utils::ImGuiRender::fillCircle(ImVec2 pos) -> void {
	list->AddCircleFilled(pos, size, color, segments);
}

auto Utils::ImGuiRender::fillShadowCircle(ImVec2 pos) -> void {
	list->AddShadowCircle(pos, size, color, thickness, ImVec2(0, 0), 0, segments);
}

auto Utils::ImGuiRender::drawShadow(ImVec2 pos) -> void {
	ImVec2 points[4];
	points[0] = ImVec2(pos.x - size / 2.f, pos.y - size / 2.f);
	points[1] = ImVec2(pos.x + size / 2.f, pos.y - size / 2.f);
	points[2] = ImVec2(pos.x + size / 2.f, pos.y + size / 2.f);
	points[3] = ImVec2(pos.x - size / 2.f, pos.y + size / 2.f);

	list->AddShadowConvexPoly(points, 4, color, thickness, ImVec2(0, 0), 0);
}

auto Utils::ImGuiRender::drawLine(ImVec2 pos1, ImVec2 pos2) -> void {
	list->AddLine(pos1, pos2, color, thickness);
}

auto Utils::ImGuiRender::drawCircle(ImVec2 pos) -> void {
	list->AddCircle(pos, size, color, segments);
}

auto Utils::ImGuiRender::drawRectangle(ImVec4 pos) -> void {
	list->AddRect(ImVec2(pos.x, pos.y), ImVec2(pos.z, pos.w), color, round, 0, thickness);
}

auto Utils::ImGuiRender::pushClip(ImVec4 pos) -> void {
	list->PushClipRect(ImVec2(pos.x, pos.y), ImVec2(pos.z, pos.w), true);
}

auto Utils::ImGuiRender::popClip() -> void {
	list->PopClipRect();
}

auto Utils::ImGuiRender::getTextSize(std::string str) -> ImVec2 {
	auto interfaceMod = Client::findMod<Interface>(_("Interface"));

	if (interfaceMod && interfaceMod->lowercase)
		Utils::String::toLowercase(str);

	return font->CalcTextSizeA(size * 20, FLT_MAX, -1, str.c_str());
}

auto Utils::ImGuiRender::isHoveringRect(ImVec4 pos) -> bool {
	if (ImGui::GetIO().MousePos.x >= pos.x && ImGui::GetIO().MousePos.y >= pos.y && ImGui::GetIO().MousePos.x < pos.z && ImGui::GetIO().MousePos.y < pos.w)
		return true;

	return false;
}

auto Utils::ImGuiRender::getWorldPosition(glm::vec3 pos) -> ImVec2 {
	auto clientinstance = Client::clientinstance;
	if (!clientinstance) return ImVec2(-1.f, -1.f);

	auto levelrender = clientinstance->getLevelRender();
	if (!levelrender) return ImVec2(-1.f, -1.f);

	auto levelrenderplayer = levelrender->getLevelRenderPlayer();
	if (!levelrenderplayer) return ImVec2(-1.f, -1.f);

	auto matrix_ = clientinstance->getMatrix();

	glm::vec3 origin = levelrenderplayer->getCameraPos();
	glm::vec2 fov = glm::vec2(clientinstance->getFovX(), clientinstance->getFovY());

	pos = glm::vec3(pos.x - origin.x, pos.y - origin.y, pos.z - origin.z);

	glm::mat4 matrix;
	{
		for (int i = 0; i < 4; i++) {
			matrix[i][0] = matrix_.matrix[0 + i];
			matrix[i][1] = matrix_.matrix[4 + i];
			matrix[i][2] = matrix_.matrix[8 + i];
			matrix[i][3] = matrix_.matrix[12 + i];
		}
	}

	glm::vec4 pos_ = matrix * glm::vec4(pos.x, pos.y, pos.z, 1.f);

	if (pos_.z > 0) return ImVec2(0.f, 0.f);

	float mX = (float)Client::windowsize.x / 2.f;
	float mY = (float)Client::windowsize.y / 2.f;

	return ImVec2(
		mX + (mX * pos_.x / -pos_.z * fov.x),
		mY - (mY * pos_.y / -pos_.z * fov.y)
	);
}

auto Utils::ImGuiRender::fillBox(glm::vec3 lower, glm::vec3 upper) -> void {
	glm::vec3 diff;
	diff.x = upper.x - lower.x;
	diff.y = upper.y - lower.y;
	diff.z = upper.z - lower.z;

	glm::vec3 vertices[8];
	vertices[0] = glm::vec3(lower.x, lower.y, lower.z);
	vertices[1] = glm::vec3(lower.x + diff.x, lower.y, lower.z);
	vertices[2] = glm::vec3(lower.x, lower.y + diff.y, lower.z);
	vertices[3] = glm::vec3(lower.x + diff.x, lower.y + diff.y, lower.z);
	vertices[4] = glm::vec3(lower.x, lower.y, lower.z + diff.z);
	vertices[5] = glm::vec3(lower.x + diff.x, lower.y, lower.z + diff.z);
	vertices[6] = glm::vec3(lower.x, lower.y + diff.y, lower.z + diff.z);
	vertices[7] = glm::vec3(lower.x + diff.x, lower.y + diff.y, lower.z + diff.z);

	{
		std::vector<ImVec2> screenCords;
		for (int i = 0; i < 8; i++) {
			ImVec2 pos = getWorldPosition(vertices[i]);
			if (pos.x > 0.f && pos.y > 0.f)
				screenCords.push_back(pos);
		}

		if (screenCords.size() < 8) return;

		std::vector<std::tuple<int, int, int, int>> faces = {
			{0, 1, 3, 2},
			{4, 5, 7, 6},
			{0, 1, 5, 4},
			{2, 3, 7, 6},
			{1, 3, 7, 5},
			{0, 2, 6, 4}
		};

		for (auto face : faces) {
			ImVec2 posScreenCords = ImVec2(screenCords[std::get<0>(face)].x, screenCords[std::get<0>(face)].y);
			ImVec2 posScreenCords1 = ImVec2(screenCords[std::get<1>(face)].x, screenCords[std::get<1>(face)].y);
			ImVec2 posScreenCords2 = ImVec2(screenCords[std::get<2>(face)].x, screenCords[std::get<2>(face)].y);
			ImVec2 posScreenCords3 = ImVec2(screenCords[std::get<3>(face)].x, screenCords[std::get<3>(face)].y);
			list->AddQuadFilled(posScreenCords, posScreenCords1, posScreenCords2, posScreenCords3, color);
		}
	}

	{
		std::vector<std::tuple<int, ImVec2>> screenCords;
		for (int i = 0; i < 8; i++) {
			ImVec2 pos = getWorldPosition(vertices[i]);
			if (pos.x > 0.f && pos.y > 0.f) {
				screenCords.emplace_back(outline ? (int)screenCords.size() : i, pos);
			}
		}

		if (screenCords.size() < 2) return;

		if (outline) {
			auto it = screenCords.begin();
			std::tuple<int, ImVec2> start = *it;
			it++;
			for (; it != screenCords.end(); it++) {
				auto cur = *it;
				if (std::get<1>(cur).x < std::get<1>(start).x) {
					start = cur;
				}
			}

			std::vector<int> indices;

			auto current = start;
			indices.push_back(std::get<0>(current));
			ImVec2 lastDir(0, -1);
			do {
				float smallestAngle = 3.1415927f * 2;
				ImVec2 smallestDir;
				std::tuple<int, ImVec2> smallestE;
				auto lastDirAtan2 = atan2(lastDir.y, lastDir.x);
				for (auto cur : screenCords) {
					if (std::get<0>(current) == std::get<0>(cur))
						continue;

					ImVec2 dir = ImVec2(std::get<1>(cur).x - std::get<1>(current).x, std::get<1>(cur).y - std::get<1>(current).y);
					float angle = atan2(dir.y, dir.x) - lastDirAtan2;
					if (angle > 3.1415927f) {
						angle -= 2 * 3.1415927f;
					}
					else if (angle <= -3.1415927f) {
						angle += 2 * 3.1415927f;
					}
					if (angle >= 0 && angle < smallestAngle) {
						smallestAngle = angle;
						smallestDir = dir;
						smallestE = cur;
					}
				}
				indices.push_back(std::get<0>(smallestE));
				lastDir = smallestDir;
				current = smallestE;
			} while (std::get<0>(current) != std::get<0>(start) && indices.size() < 8);

			ImVec2 lastVertex;
			bool hasLastVertex = false;
			for (auto& indice : indices) {
				ImVec2 curVertex = std::get<1>(screenCords[indice]);
				if (!hasLastVertex) {
					hasLastVertex = true;
					lastVertex = curVertex;
					continue;
				}
				ImVec2 lastVertexPos = ImVec2(lastVertex.x, lastVertex.y);
				ImVec2 curVertexPos = ImVec2(curVertex.x, curVertex.y);
				list->AddLine(lastVertexPos, curVertexPos, secondcolor, thickness);
				lastVertex = curVertex;
			}
			return;
		}
		else {
			for (auto it = screenCords.begin(); it != screenCords.end(); it++) {
				auto from = *it;
				auto fromOrig = vertices[std::get<0>(from)];

				for (auto to : screenCords) {
					auto toOrig = vertices[std::get<0>(to)];

					bool shouldDraw = false;
					shouldDraw |= fromOrig.y == toOrig.y && fromOrig.z == toOrig.z && fromOrig.x < toOrig.x;
					shouldDraw |= fromOrig.x == toOrig.x && fromOrig.z == toOrig.z && fromOrig.y < toOrig.y;
					shouldDraw |= fromOrig.x == toOrig.x && fromOrig.y == toOrig.y && fromOrig.z < toOrig.z;

					ImVec2 posForm = ImVec2(std::get<1>(from).x, std::get<1>(from).y);
					ImVec2 posTo = ImVec2(std::get<1>(to).x, std::get<1>(to).y);
					if (shouldDraw) list->AddLine(posForm, posTo, secondcolor, thickness);
				}
			}
			return;
		}
	}
}

auto Utils::ImGuiRender::drawBox(glm::vec3 lower, glm::vec3 upper) -> void {
	glm::vec3 diff;
	diff.x = upper.x - lower.x;
	diff.y = upper.y - lower.y;
	diff.z = upper.z - lower.z;

	glm::vec3 vertices[8];
	vertices[0] = glm::vec3(lower.x, lower.y, lower.z);
	vertices[1] = glm::vec3(lower.x + diff.x, lower.y, lower.z);
	vertices[2] = glm::vec3(lower.x, lower.y + diff.y, lower.z);
	vertices[3] = glm::vec3(lower.x + diff.x, lower.y + diff.y, lower.z);
	vertices[4] = glm::vec3(lower.x, lower.y, lower.z + diff.z);
	vertices[5] = glm::vec3(lower.x + diff.x, lower.y, lower.z + diff.z);
	vertices[6] = glm::vec3(lower.x, lower.y + diff.y, lower.z + diff.z);
	vertices[7] = glm::vec3(lower.x + diff.x, lower.y + diff.y, lower.z + diff.z);

	{
		std::vector<std::tuple<int, ImVec2>> screenCords;
		for (int i = 0; i < 8; i++) {
			ImVec2 pos = getWorldPosition(vertices[i]);
			if (pos.x > 0.f && pos.y > 0.f) {
				screenCords.emplace_back(outline ? (int)screenCords.size() : i, pos);
			}
		}

		if (screenCords.size() < 2) return;

		if (outline) {
			auto it = screenCords.begin();
			std::tuple<int, ImVec2> start = *it;
			it++;
			for (; it != screenCords.end(); it++) {
				auto cur = *it;
				if (std::get<1>(cur).x < std::get<1>(start).x) {
					start = cur;
				}
			}

			std::vector<int> indices;

			auto current = start;
			indices.push_back(std::get<0>(current));
			ImVec2 lastDir(0, -1);
			do {
				float smallestAngle = 3.1415927f * 2;
				ImVec2 smallestDir;
				std::tuple<int, ImVec2> smallestE;
				auto lastDirAtan2 = atan2(lastDir.y, lastDir.x);
				for (auto cur : screenCords) {
					if (std::get<0>(current) == std::get<0>(cur))
						continue;

					ImVec2 dir = ImVec2(std::get<1>(cur).x - std::get<1>(current).x, std::get<1>(cur).y - std::get<1>(current).y);
					float angle = atan2(dir.y, dir.x) - lastDirAtan2;
					if (angle > 3.1415927f) {
						angle -= 2 * 3.1415927f;
					}
					else if (angle <= -3.1415927f) {
						angle += 2 * 3.1415927f;
					}
					if (angle >= 0 && angle < smallestAngle) {
						smallestAngle = angle;
						smallestDir = dir;
						smallestE = cur;
					}
				}
				indices.push_back(std::get<0>(smallestE));
				lastDir = smallestDir;
				current = smallestE;
			} while (std::get<0>(current) != std::get<0>(start) && indices.size() < 8);

			ImVec2 lastVertex;
			bool hasLastVertex = false;
			for (auto& indice : indices) {
				ImVec2 curVertex = std::get<1>(screenCords[indice]);
				if (!hasLastVertex) {
					hasLastVertex = true;
					lastVertex = curVertex;
					continue;
				}
				ImVec2 lastVertexPos = ImVec2(lastVertex.x, lastVertex.y);
				ImVec2 curVertexPos = ImVec2(curVertex.x, curVertex.y);
				list->AddLine(lastVertexPos, curVertexPos, secondcolor, thickness);
				lastVertex = curVertex;
			}
			return;
		}
		else {
			for (auto it = screenCords.begin(); it != screenCords.end(); it++) {
				auto from = *it;
				auto fromOrig = vertices[std::get<0>(from)];

				for (auto to : screenCords) {
					auto toOrig = vertices[std::get<0>(to)];

					bool shouldDraw = false;
					shouldDraw |= fromOrig.y == toOrig.y && fromOrig.z == toOrig.z && fromOrig.x < toOrig.x;
					shouldDraw |= fromOrig.x == toOrig.x && fromOrig.z == toOrig.z && fromOrig.y < toOrig.y;
					shouldDraw |= fromOrig.x == toOrig.x && fromOrig.y == toOrig.y && fromOrig.z < toOrig.z;

					ImVec2 posForm = ImVec2(std::get<1>(from).x, std::get<1>(from).y);
					ImVec2 posTo = ImVec2(std::get<1>(to).x, std::get<1>(to).y);
					if (shouldDraw) list->AddLine(posForm, posTo, secondcolor, thickness);
				}
			}
			return;
		}
	}
}
