
#include "Regen.h"
#include "Client.h"
#include "Utils/MoveUtils.h"
#include "Utils/TimeUtils.h"
#include "Utils/ChatUtils.h"
#include "Utils/MathUtils.h"
#include "Utils/ImGuiRenderUtils.h"
#include "Utils/ColorUtils.h"
#include "Utils/PlayerUtils.h"

#include "Scaffold.h"
#include "Interface.h"
#include "KillAura.h"

auto Regen::isAir(glm::vec3 pos) -> bool {
	glm::vec3 floorpos = glm::vec3(
		std::floor(pos.x),
		std::floor(pos.y),
		std::floor(pos.z)
	);
	glm::ivec3 ifloorpos = glm::ivec3(
		floorpos.x,
		floorpos.y,
		floorpos.z
	);
	return Client::clientinstance->getBlockSource()->getBlock(&ifloorpos)->getBlockLegacy()->getName() == _("tile.air");
}

auto Regen::handlePacket(Packet* packet) -> void {
	auto scaffoldMod = Client::findMod<Scaffold>(_("Scaffold"));
	if (scaffoldMod->enabled) return;

	auto clientinstance = Client::clientinstance;
	if (!clientinstance) return;

	auto localplayer = clientinstance->getLocalPlayer();
	if (!localplayer) return;

	auto now = Utils::Time::getMs();
	static long long startjump = now;
	if (stopoffground && !Utils::Move::isOnGround(localplayer)) {
		if (now - startjump >= 100.f) {
			return;
		}
	}
	else {
		startjump = now;
	}

	if (Utils::Players::getAbsorption(localplayer) >= 10) return;

	auto killauraMod = Client::findMod<KillAura>(Client::findMod<Interface>(_("Interface"))->renameaura ? _("Aura") : _("Kill Aura"));

	if (packet->getID() == PacketID::PlayerAuthInput) {
		auto playerauthinput = reinterpret_cast<PlayerAuthInputPacket*>(packet);

		float currentSpeed = (currentlyrate / 1.6f) / speed;
		if (killauraMod && killauraMod->enabled && !killauraMod->list.empty()) {
			currentSpeed = (currentlyrate / 1.6f) / auraspeed;
		}

		static long long rot = now;
		if (!onceRot && !dorot && now - start >= currentSpeed) {
			dorot = true;
			onceRot = true;
			rot = now;
		}
		else {
			if (dorot) {
				if (now - rot >= 100.f * waitrottime) {
					dorot = false;
					rot = now;
				}
			}
		}

		if (dorot) {
			glm::vec2 angle = Utils::Math::getAngle(Utils::Move::getPosition(localplayer), blockpos);

			switch (rotations) {
			case 0: {
				playerauthinput->setPitch(angle.x);
				playerauthinput->setYaw(angle.y);
				playerauthinput->setHeadYaw(angle.y);
				break;
			}
			}
		}
	}
}

auto Regen::onWorldTick(Player* _this) -> void {
	if (!enabled) return;

	auto scaffoldMod = Client::findMod<Scaffold>(_("Scaffold"));
	if (scaffoldMod->enabled) return;

	auto killauraMod = Client::findMod<KillAura>(Client::findMod<Interface>(_("Interface"))->renameaura ? _("Aura") : _("Kill Aura"));

	auto clientinstance = Client::clientinstance;
	if (!clientinstance) return;

	auto blocksource = clientinstance->getBlockSource();
	if (!blocksource) return;

	auto gamemode = _this->getGameMode();
	if (!gamemode) return;

	auto playerinventory = _this->getPlayerInventory();
	if (!playerinventory) return;

	auto inventory = playerinventory->getInventory();
	if (!inventory) return;

	auto level = _this->getLevel();
	if (!level) return;

	auto hitresult = level->getHitResult();

	static bool backed = false;
	if (Utils::Players::getAbsorption(_this) >= 10) {
		if (!backed) {
			playerinventory->setSelectedSlot(oldslot);
			backed = true;
		}
		noredstones = true;
		return;
	}
	else {
		backed = false;
	}

	now = Utils::Time::getMs();

	static long long startjump = now;
	if (stopoffground && !Utils::Move::isOnGround(_this)) {
		if (now - startjump >= 100.f) {
			return;
		}
	}
	else {
		startjump = now;
	}

	glm::vec3 position = Utils::Move::getPosition(_this);

	static bool bruh = true;
	static bool bruh2 = true;

	if (oldslot != -1) {
		playerinventory->setHandSlot(oldslot);
		Client::disableSwitchAnim = true;
	}

	static long long nextstart = now;
	if (breaknow) {
		if (Utils::Math::getDistance(Utils::Move::getPosition(_this), blockpos) > range) {
			Utils::Chat::display_(_("Canceled"), Utils::Chat::Type::RED);
			breaknow = false;
			return;
		}

		destroying = true;
		if (!bruh2 && uncover == 1) {
			glm::ivec3 top2 = glm::ivec3(blockpos.x, blockpos.y + 2, blockpos.z);
			if (blocksource->getBlock(&top2)->getBlockLegacy()->getName() != _("tile.air")) {
				uncovering = true;
				blockpos.y += 2;
			}
			else {
				glm::ivec3 top1 = glm::ivec3(blockpos.x, blockpos.y + 1, blockpos.z);
				if (blocksource->getBlock(&top1)->getBlockLegacy()->getName() != _("tile.air")) {
					uncovering = true;
					blockpos.y += 1;
				}
				else {
					uncovering = false;
				}
			}
			bruh2 = true;
		}

		int face = 1;

		static std::vector<glm::vec3> neighbours = {
			glm::vec3(0, 1, 0),
			glm::vec3(0, -1, 0),
			glm::vec3(0, 0, 1),
			glm::vec3(0, 0, -1),
			glm::vec3(1, 0, 0),
			glm::vec3(-1, 0, 0)
		};

		for (int face_ = 0; face_ < neighbours.size(); face_++) {
			glm::vec3 floorpos = glm::vec3(
				std::floor(blockpos.x),
				std::floor(blockpos.y),
				std::floor(blockpos.z)
			);

			glm::vec3 offset = glm::vec3(
				floorpos.x + neighbours[face].x,
				floorpos.y + neighbours[face].y,
				floorpos.z + neighbours[face].z
			);

			if (isAir(offset)) {
				face = face_;

				break;
			}
		}

		if (autopickaxe) {
			int quality = 0;
			int slot = 0;
			for (int i = 0; i < 9; i++) {
				auto itemstack = inventory->getItemStack(i);
				if (itemstack->getItem()) {
					std::string itemname = (*itemstack->getItem())->getName();

					if (itemname.find(_("_pickaxe")) != std::string::npos) {
						int quality_ = 0;
						if (itemname.find(_("wooden_")) != std::string::npos) quality_ = 0;
						if (itemname.find(_("stone_")) != std::string::npos) quality_ = 1;
						if (itemname.find(_("iron_")) != std::string::npos) quality_ = 2;
						if (itemname.find(_("diamond_")) != std::string::npos) quality_ = 3;
						if (itemname.find(_("netherite_")) != std::string::npos) quality_ = 4;

						if (quality < quality_) slot = i;
					}
				}
			}
			if (oldslot == -1)
				oldslot = playerinventory->getSelectedSlot();
			if (spoof == 1) {
				//Client::disableSwitchAnim = true;
				//playerinventory->setHandSlot(oldslot);
			}
			playerinventory->setSelectedSlot(slot);
		}

		if (bruh) {
			gamemode->startDestroyBlock(blockpos, face, bruh);
			bruh = false;
		}

		currentlyrate = gamemode->getDestroyRate(*blocksource->getBlock(&blockpos)) * 10000.f;

		hitresult.mFacing = face;
		hitresult.mBlock = blockpos;

		float currentSpeed = (currentlyrate) / speed;
		if (killauraMod && killauraMod->enabled && !killauraMod->list.empty()) {
			currentSpeed = (currentlyrate) / auraspeed;
		}

		if (now - start >= currentSpeed) {
			Utils::Chat::display_(_("Steal Timed Out"));

			nextstart = now;

			if (!uncovering) breaknow = false;
			else {
				bruh = true;
				bruh2 = false;
				uncovering = false;
				blockpos.y -= 1;
				if (swing != 2) _this->swing();

				gamemode->destroyBlock(blockpos, face);

				start = now;
				return;
			}
			glm::ivec3 topblock = glm::ivec3(blockpos.x, blockpos.y + 1, blockpos.z);
			glm::ivec3 underblock = glm::ivec3(blockpos.x, blockpos.y - 1, blockpos.z);
			glm::ivec3 sideblock11 = glm::ivec3(blockpos.x - 1, blockpos.y, blockpos.z);
			glm::ivec3 sideblock12 = glm::ivec3(blockpos.x + 1, blockpos.y, blockpos.z);
			glm::ivec3 sideblock21 = glm::ivec3(blockpos.x, blockpos.y, blockpos.z + 1);
			glm::ivec3 sideblock22 = glm::ivec3(blockpos.x, blockpos.y, blockpos.z - 1);


			if (blocksource->getBlock(&topblock)->getBlockLegacy()->getName() == _("tile.air") ||
				blocksource->getBlock(&underblock)->getBlockLegacy()->getName() == _("tile.air") ||
				blocksource->getBlock(&sideblock11)->getBlockLegacy()->getName() == _("tile.air") ||
				blocksource->getBlock(&sideblock12)->getBlockLegacy()->getName() == _("tile.air") ||
				blocksource->getBlock(&sideblock21)->getBlockLegacy()->getName() == _("tile.air") ||
				blocksource->getBlock(&sideblock22)->getBlockLegacy()->getName() == _("tile.air")
				) {
				if (swing == 1) Client::swingspeed = 0.f;

				if (swing != 2) _this->swing();
				cancelswing = true;
				gamemode->destroyBlock(blockpos, face);
				breaking = false;
				cancelswing = false;
				ghostblocks.push_back(blockpos);
			}
			else {
				Utils::Chat::display_(_("Ore Was Covered"), Utils::Chat::Type::RED);
			}
		}
	}
	else {
		if (now - nextstart >= 1000.f * waittime) {
			breaking = true;
			if (autopickaxe && oldslot != -1) {
				playerinventory->setSelectedSlot(oldslot);
			}
			bruh = true;
			bruh2 = false;
			destroying = false;

			start = now;
			std::vector<glm::ivec3> blocks;

			for (int x = -range; x < range; x++) {
				for (int y = -range; y < range; y++) {
					for (int z = -range; z < range; z++) {
						glm::ivec3 blockpos_ = glm::ivec3(position.x + x, position.y + y, position.z + z);
						if (Utils::Math::getDistance(Utils::Move::getPosition(_this), blockpos_) > range) continue;

						if (noghosts) {
							bool cancel;
							for (glm::ivec3 ghostblock : ghostblocks) {
								if (blockpos_ == ghostblock) {
									cancel = true;
									break;
								}
							}
							if (cancel)
								continue;
						}

						if (uncover == 1) {
							glm::ivec3 topblock1 = glm::ivec3(blockpos_.x, blockpos_.y + 1, blockpos_.z);
							glm::ivec3 topblock2 = glm::ivec3(blockpos_.x, blockpos_.y + 2, blockpos_.z);
							glm::ivec3 topblock3 = glm::ivec3(blockpos_.x, blockpos_.y + 3, blockpos_.z);

							if (blocksource->getBlock(&topblock1)->getBlockLegacy()->getName() != _("tile.air") &&
								blocksource->getBlock(&topblock2)->getBlockLegacy()->getName() != _("tile.air") &&
								blocksource->getBlock(&topblock3)->getBlockLegacy()->getName() != _("tile.air")
								) {
								continue;
							}

						}
						else {
							glm::ivec3 topblock = glm::ivec3(blockpos_.x, blockpos_.y + 1, blockpos_.z);
							glm::ivec3 underblock = glm::ivec3(blockpos_.x, blockpos_.y - 1, blockpos_.z);
							glm::ivec3 sideblock11 = glm::ivec3(blockpos_.x - 1, blockpos_.y, blockpos_.z);
							glm::ivec3 sideblock12 = glm::ivec3(blockpos_.x + 1, blockpos_.y, blockpos_.z);
							glm::ivec3 sideblock21 = glm::ivec3(blockpos_.x, blockpos_.y, blockpos_.z + 1);
							glm::ivec3 sideblock22 = glm::ivec3(blockpos_.x, blockpos_.y, blockpos_.z - 1);

							if (blocksource->getBlock(&topblock)->getBlockLegacy()->getName() != _("tile.air") &&
								blocksource->getBlock(&underblock)->getBlockLegacy()->getName() != _("tile.air") &&
								blocksource->getBlock(&sideblock11)->getBlockLegacy()->getName() != _("tile.air") &&
								blocksource->getBlock(&sideblock12)->getBlockLegacy()->getName() != _("tile.air") &&
								blocksource->getBlock(&sideblock21)->getBlockLegacy()->getName() != _("tile.air") &&
								blocksource->getBlock(&sideblock22)->getBlockLegacy()->getName() != _("tile.air")
								) continue;
						}

						std::string name = blocksource->getBlock(&blockpos_)->getBlockLegacy()->getName();
						if (name == _("tile.redstone_ore") || name == _("tile.lit_redstone_ore")) {
							blocks.push_back(blockpos_);
						}
					}
				}
			}

			if (!blocks.empty()) {
				float mindistance = range;
				glm::ivec3 nearblock = glm::ivec3();
				for (glm::ivec3 block : blocks) {
					float distance = Utils::Math::getDistance(position, block);
					if (distance < mindistance) {
						mindistance = distance;
						nearblock = block;
					}
				}


				blockpos = nearblock;

				//face = -2;
				breaknow = true;
				noredstones = false;
				uncovering = false;
				onceRot = false;
				oldslot = -1;
				return;
			}

			noredstones = true;
		}
	}
}

auto Regen::onSwing(Player* _this, bool* cancel) -> void {
	if (cancelswing)
		*cancel = true;
}



auto Regen::onPacketSendBefore(Packet* packet) -> void {
	if (!enabled) return;
	if (norenderrots) return;

	handlePacket(packet);
}

auto Regen::onPacketSendAfter2(Packet* packet, bool* cancel) -> void {
	if (!enabled) return;
	if (!norenderrots) return;

	handlePacket(packet);
}

auto Regen::onImGuiRender() -> void {
	auto interfaceMod = Client::findMod<Interface>(_("Interface"));
	auto killauraMod = Client::findMod<KillAura>(Client::findMod<Interface>(_("Interface"))->renameaura ? _("Aura") : _("Kill Aura"));

	auto clientinstance = Client::clientinstance;
	if (!clientinstance) return;

	auto localplayer = clientinstance->getLocalPlayer();
	if (!localplayer) return;

	switch (visuals) {
	case 1: {
		static float animation = 0.f;
		animation = Utils::Math::animate(noredstones ? 0.f : 1.f, animation, Client::deltaTime * 2.5f);

		static float progress = 0.f;
		if (breaking) {

			float currentSpeed = currentlyrate / speed;
			if (killauraMod && killauraMod->enabled && !killauraMod->list.empty()) {
				currentSpeed = currentlyrate / auraspeed;
			}

			if (!stopoffground || stopoffground && Utils::Move::isOnGround(localplayer))
				progress = Utils::Math::animate(static_cast<float>(now - start) * 100 / currentSpeed, progress, Client::deltaTime * 10.f);
		}
		else {
			progress = 100.f;
		}

		if (progress < 0.f)
			progress = 0.f;
		if (progress > 100.f)
			progress = 100.f;

		int progressInt = static_cast<int>(progress);

		if (progressInt < 0)
			progressInt = 0;
		if (progressInt > 100)
			progressInt = 100;

		std::string str = (uncovering ? _("Uncovering ") : _("Mining ")) + std::to_string(progressInt) + _("%");
		Utils::ImGuiRender::setDrawList(ImGui::GetBackgroundDrawList());
		Utils::ImGuiRender::setFont(ImGui::GetIO().Fonts->Fonts[(bold ? 1 : 0) + (4 * interfaceMod->font)]);
		Utils::ImGuiRender::setSize(1.1f * animation);

		ImVec2 size = Utils::ImGuiRender::getTextSize(str);
		ImVec2 pos = ImVec2(Client::windowsize.x / 2 - (size.x / 2), Client::windowsize.y / 1.2f);

		ImVec4 rect = ImVec4(pos.x - (75.f * animation), pos.y, (pos.x + size.x) + (75.f * animation), pos.y + size.y);
		ImVec4 progressrect = ImVec4(pos.x - (75.f * animation), pos.y, (pos.x - (75.f * animation)) + ((size.x + ((75.f * 2) * animation)) * (progress / 100)), pos.y + size.y);

		Utils::ImGuiRender::setColor(ImColor(0.f, 0.f, 0.f, animation * 1.f));
		Utils::ImGuiRender::setRound(10.f);
		Utils::ImGuiRender::setBlurStreangth(blurs);
		Utils::ImGuiRender::fillBlurRectangle(rect);

		Utils::ImGuiRender::setColor(ImColor(0.f, 0.f, 0.f, animation * opacity));
		Utils::ImGuiRender::fillRectangle(rect);

		Utils::ImGuiRender::setColor(Utils::Color::getColor(3.f, 0, animation * 0.6f));
		Utils::ImGuiRender::fillRectangle(progressrect);

		Utils::ImGuiRender::setColor(ImColor(0.f, 0.f, 0.f, animation));
		Utils::ImGuiRender::setSize(10.f);
		Utils::ImGuiRender::setThickness(480.f);
		Utils::ImGuiRender::fillShadowRectangle(rect);

		Utils::ImGuiRender::setSize(1.1f * animation);
		Utils::ImGuiRender::setColor(ImColor(1.f, 1.f, 1.f, animation));
		Utils::ImGuiRender::drawText(str, pos);
		break;
	}
	case 2: {
		static float animation = 0.f;
		animation = Utils::Math::animate(noredstones ? 0.f : 1.f, animation, Client::deltaTime * 2.5f);

		static float progress = 0.f;
		if (breaking) {
			progress = Utils::Math::animate(static_cast<float>(now - start) * 100 / (currentlyrate / speed), progress, Client::deltaTime * 10.f);
		}
		else {
			progress = 100.f;
		}
		if (progress < 0.f)
			progress = 0.f;
		if (progress > 100.f)
			progress = 100.f;

		std::string str = (uncovering ? _("Uncovering ") : _("Mining (")) + std::to_string(static_cast<int>(Utils::Players::getAbsorption(localplayer))) + _("/10)");
		Utils::ImGuiRender::setDrawList(ImGui::GetBackgroundDrawList());
		Utils::ImGuiRender::setFont(ImGui::GetIO().Fonts->Fonts[(bold ? 1 : 0) + (4 * interfaceMod->font)]);
		Utils::ImGuiRender::setSize(1.3f * animation);

		ImVec2 size = Utils::ImGuiRender::getTextSize(str);
		ImVec2 pos = ImVec2(Client::windowsize.x / 2 - (size.x / 2), Client::windowsize.y / 1.4);

		ImVec4 progressrect = ImVec4(pos.x - (75.f * animation), pos.y - (2.5f * animation), (pos.x - (75.f * animation)) + ((size.x + ((75.f * 2) * animation)) * (progress / 100)), pos.y + ((size.y + 2.5f) * animation));

		Utils::ImGuiRender::setSize(1.3f * animation);
		Utils::ImGuiRender::setColor(ImColor(1.f, 1.f, 1.f, animation));
		Utils::ImGuiRender::drawText(str, pos);

		Utils::ImGuiRender::setDrawList(ImGui::GetForegroundDrawList());
		Utils::ImGuiRender::pushClip(progressrect);
		Utils::ImGuiRender::setColor(Utils::Color::getColor(4.f, 0, animation));
		Utils::ImGuiRender::drawText(str, pos);
		Utils::ImGuiRender::popClip();
		break;
	}
	}
}

auto Regen::onDisabled() -> void {
	noredstones = true;
	Client::swingspeed = -1;
	destroying = false;
	Client::disableSwitchAnim = false;

	auto clientinstance = Client::clientinstance;
	if (!clientinstance) return;

	auto localplayer = clientinstance->getLocalPlayer();
	if (!localplayer) return;

	auto playerinventory = localplayer->getPlayerInventory();
	if (!playerinventory) return;

	if (autopickaxe && oldslot != -1) {
		playerinventory->setSelectedSlot(oldslot);
	}
}

auto Regen::onEnabled() -> void {
	start = Utils::Time::getMs();
}

auto Regen::onClient() -> void {
	for (Setting* setting : Client::findSetting(this, _("Opacity"))) {
		setting->visible = visuals == 1;
	}
	for (Setting* setting : Client::findSetting(this, _("Blurs"))) {
		setting->visible = visuals == 1;
	}
	for (Setting* setting : Client::findSetting(this, _("Bold"))) {
		setting->visible = visuals != 0;
	}
}

auto Regen::getMode() -> std::string {
	switch (mode) {
	case 0:
		return _("Flareon");
	}

	return _("Unknown");
}