#include "Scaffold.h"
#include "Client.h"
#include "Utils/MoveUtils.h"
#include "Utils/MathUtils.h"
#include "Utils/ImGuiRenderUtils.h"
#include "Utils/ColorUtils.h"

#include "Mod/KillAura.h"
#include "Mod/Interface.h"

auto Scaffold::canPlace(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 Scaffold::buildBlock(glm::vec3 pos, GameMode* gamemode, Player* localplayer, PlayerInventory* playerinventory, Inventory* inventory) -> bool {
	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(pos.x),
			std::floor(pos.y),
			std::floor(pos.z)
		);

		glm::vec3 offset = glm::vec3(
			floorpos.x - neighbours[face].x,
			floorpos.y - neighbours[face].y,
			floorpos.z - neighbours[face].z
		);
		if (!canPlace(offset)) {
			glm::ivec3 ioffset = glm::ivec3(
				(int)offset.x,
				(int)offset.y,
				(int)offset.z
			);

			if (autoblock) {
				int counts = 0;
				int slot = -1;
				for (int i = 0; i < 9; i++) {
					auto itemstack = inventory->getItemStack(i);
					if (itemstack->getItem() && itemstack->getBlock() && !((*itemstack->getItem())->getName().find(_("boombox")) != std::string::npos)) {
						if (counts < itemstack->getCount()) {
							counts = itemstack->getCount();
							slot = i;
						}
					}
				}

				if (slot != -1) {
					playerinventory->setSelectedSlot(slot);
				}
			}

			localplayer->swing();

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

			{
				static long long start = now;
				if (hasTowered ? mintowerplacedelay != 0.f : minplacedelay != 0.f) {
					static float currentdelay = Utils::Math::getrandom(hasTowered ? mintowerplacedelay : minplacedelay, hasTowered ? maxtowerplacedelay : maxplacedelay);
					if (now - start >= currentdelay) {
						gamemode->buildBlock(ioffset, face, false);
						currentdelay = Utils::Math::getrandom(minplacedelay, maxplacedelay);
						start = now;
					}
				}
				else {
					gamemode->buildBlock(ioffset, face, false);
					start = now;
				}
			}

			placing = false;

			Client::swingspeed = -1;

			return true;
		}
	}

	return false;
}

auto Scaffold::predictBlock(glm::vec3 pos, GameMode* gamemode, Player* localplayer, PlayerInventory* playerinventory, Inventory* inventory) -> bool {
	static std::vector<glm::vec3> blocks;
	if (blocks.empty()) {
		for (int y = -5; y <= 0; y++) {
			for (int x = -5; x <= 5; x++) {
				for (int z = -5; z <= 5; z++) {
					blocks.push_back(glm::vec3(x, y, z));
				}
			}
		}


		std::sort(blocks.begin(), blocks.end(), [](glm::vec3 start, glm::vec3 end) {
			return sqrtf((start.x * start.x) + (start.y * start.y) + (start.z * start.z)) < sqrtf((end.x * end.x) + (end.y * end.y) + (end.z * end.z));
			});
	}


	glm::vec3 floorpos = glm::vec3(
		std::floor(pos.x),
		std::floor(pos.y),
		std::floor(pos.z)
	);


	for (glm::vec3 offset : blocks) {
		glm::vec3 currentblock = glm::vec3(
			floorpos.x + offset.x,
			floorpos.y + offset.y,
			floorpos.z + offset.z
		);
		if (buildBlock(currentblock, gamemode, localplayer, playerinventory, inventory)) {
			return true;
		}
	}

	return false;
}

auto Scaffold::onSwing(Player* _this, bool* cancel) -> void {
	if (!enabled) return;

	if (cancelswing) {
		*cancel = true;
	}
}

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

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

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

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

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

	glm::vec3 pos = Utils::Move::getBlockPosition(_this);
	glm::vec3 floorpos = glm::vec3(
		std::floor(pos.x),
		std::floor(pos.y),
		std::floor(pos.z)
	);

	glm::vec3 velocity = Utils::Move::getVelocity(_this);
	float length = std::sqrt(velocity.x * velocity.x + velocity.y * velocity.y + velocity.z * velocity.z);
	if (length != 0) {
		velocity = glm::vec3(velocity.x / length, velocity.y / length, velocity.z / length);
	}
	else {
		velocity = glm::vec3(0, 0, 0);
	}

	if (
		Utils::Move::isOnGround(_this) ||
		pos.y < targetY ||
		(moveabletower ? true : !Utils::Move::isMoving()) && Utils::Move::isJumping() ||
		!ylock && pos.y > targetY
		)
	{
		targetY = floorpos.y - 0.5f;
	}

	if (tower) {
		glm::vec3 velocity_ = Utils::Move::getVelocity(_this);
		if ((moveabletower ? true : !Utils::Move::isMoving()) && Utils::Move::isJumping() && velocity.y > -0.2f &&
			canPlace(Utils::Move::getBlockPosition(_this))) {
			Utils::Move::setVelocity(_this, glm::vec3(velocity_.x, (Utils::Move::isMoving() ? moveabletowerspeed : towerspeed) * 0.8f, velocity_.z));
			hasTowered = true;
		}
		else {
			if (!Utils::Move::isOnGround(_this) && hasTowered) {
				Utils::Move::setVelocity(_this, glm::vec3(velocity_.x, (-towerspeed * 10.f), velocity_.z));
				hasTowered = false;
			}
		}
	}


	static glm::vec3 targetBlock = {};
	targetBlock = pos;
	targetBlock.y = targetY;

	targetBlock.x += velocity.x * extend;
	targetBlock.z += velocity.z * extend;

	auto now = Utils::Time::getMs();
	if (subplace) {
		static long long start = now;
		static float currentdelay = Utils::Math::getrandom(minplacedelay, maxplacedelay);
		if (start - now >= currentdelay) {
			targetBlock.x += 1;
			targetBlock.z += 1;

			currentdelay = Utils::Math::getrandom(minplacedelay, maxplacedelay);
			start = now;
		}

	}

	blockpos = targetBlock;

	switch (swing) {
	case 1:
		Client::swingspeed = 0;
		break;
	case 2:
		cancelswing = true;
		break;
	}

	switch (placement) {
	case 1:
		targetBlock.y -= 0.49f;
		break;
	}

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

	if (canPlace(targetBlock)) {
		placing = true;
		predictBlock(targetBlock, gamemode, _this, playerinventory, inventory);
	}

	cancelswing = false;
}

auto Scaffold::handlePacket(Packet* packet, Player* arg1) -> void {
	auto localplayer = arg1;
	if (!localplayer) return;

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

		long long now = Utils::Time::getMs();
		switch (placement) {
		case 1:
			glm::vec3 pos = playerauthinput->getPos();
			playerauthinput->setPos(glm::vec3(pos.x, pos.y - ydown, pos.z));
			break;
		}

		glm::vec2 angle = Utils::Math::getAngle(Utils::Move::getEyePosition(localplayer), blockpos);
		switch (rotations) {
		case 0:
		case 3:
			playerauthinput->setPitch(angle.x);
			playerauthinput->setYaw(angle.y);
			playerauthinput->setHeadYaw(angle.y);
			break;
		case 1:
			playerauthinput->setPitch(angle.x);
			playerauthinput->setYaw(angle.y + 180.f);
			playerauthinput->setHeadYaw(angle.y + 180.f);
			break;
		case 2: {
			playerauthinput->setPitch(angle.x);
			break;
		}
		}
	}
}

auto Scaffold::onPacketSendBefore(Packet* packet) -> void {
	if (!enabled) return;
	//if (!placing) return;
	if (rotations == 3) return;

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

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

	auto actorrotationcomponent = localplayer->getActorRotationComponent();
	if (!actorrotationcomponent) return;

	auto killauraMod = Client::findMod<KillAura>(Client::findMod<Interface>(_("Interface"))->renameaura ? _("Aura") : _("Kill Aura"));
	if (killauraMod && !killauraMod->list.empty()) return;

	handlePacket(packet, localplayer);
}

auto Scaffold::onPacketSendAfter2(Packet* packet, bool* cancel) -> void {
	if (!enabled) return;
	//if (!placing) return;
	if (rotations != 3) return;

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

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

	auto actorrotationcomponent = localplayer->getActorRotationComponent();
	if (!actorrotationcomponent) return;

	if (!Client::findMod<KillAura>(
		Client::findMod<Interface>(_("Interface"))->renameaura ? _("Aura") : _("Kill Aura")
	)->list.empty()) return;

	handlePacket(packet, localplayer);
}

auto Scaffold::onImGuiRender() -> void {
	auto interfaceMod = Client::findMod<Interface>(_("Interface"));

	if (Client::screenName == _("start_screen")) return;

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

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

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

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

	static float animation;
	animation = Utils::Math::animate(enabled ? 1.f : 0.f, animation, Client::deltaTime * 5.f);

	static int oldcount = 0;
	int counts = 0;
	for (int i = 0; i < 9; i++) {
		auto itemstack = inventory->getItemStack(i);
		if (itemstack->getItem() && itemstack->getBlock() && !((*itemstack->getItem())->getName().find(_("boombox")) != std::string::npos)) {
			counts += itemstack->getCount();
		}
	}

	switch (blocksleft) {
	case 1: {
		std::string str = _("Blocks: ");

		Utils::ImGuiRender::setDrawList(ImGui::GetBackgroundDrawList());
		Utils::ImGuiRender::setFont(ImGui::GetIO().Fonts->Fonts[(bold ? 1 : 0) + (4 * interfaceMod->font)]);
		Utils::ImGuiRender::setSize(animation * 1.4f);

		int counts1 = counts % 10;
		int counts2 = (counts / 10) % 10;

		float countssize = Utils::ImGuiRender::getTextSize(std::to_string(counts1)).y;

		float countssize_ = Utils::ImGuiRender::getTextSize(std::to_string(counts1)).x;
		float countssize__ = Utils::ImGuiRender::getTextSize(std::to_string(counts2)).x;

		ImVec2 strsize = Utils::ImGuiRender::getTextSize(str);
		ImVec2 pos = ImVec2(Client::windowsize.x / 2 - (strsize.x / 2), Client::windowsize.y / 1.2);
		ImVec4 rect = ImVec4(pos.x - 10.f, pos.y - 5.f, pos.x + (strsize.x + countssize_ + (counts2 != 0 ? countssize__ : 0.f)) + 5.f, pos.y + strsize.y + 5.f);
		Utils::ImGuiRender::setColor(ImColor(0.f, 0.f, 0.f, 0.4f * animation));
		Utils::ImGuiRender::setRound(10.f);
		Utils::ImGuiRender::fillRectangle(rect);

		Utils::ImGuiRender::setThickness(10.f);
		Utils::ImGuiRender::setSize(10.f);
		Utils::ImGuiRender::fillShadowRectangle(rect);

		Utils::ImGuiRender::setSize(animation * 1.4f);
		int index = 0;
		for (char s : str) {
			std::stringstream ss;
			ss << s;
			Utils::ImGuiRender::setColor(Utils::Color::getColor(3.f, 60.f * index, animation));
			Utils::ImGuiRender::drawText(ss.str(), pos);
			pos.x += Utils::ImGuiRender::getTextSize(ss.str()).x;
			index++;
		}

		ImVec2 _countspos = ImVec2(pos.x + strsize.x + 5.f, pos.y);
		ImVec4 countsrect = ImVec4(pos.x - 5.f, _countspos.y, pos.x + strsize.x + 10.f, pos.y + strsize.y);
		//Utils::ImGuiRender::fillRectangle(countsrect);
		Utils::ImGuiRender::pushClip(countsrect);

		float allnumssize2 = 0.f;
		for (int i = 0; i <= 9; i++) {
			allnumssize2 += Utils::ImGuiRender::getTextSize(std::to_string(i)).y;
		}

		float allnumsize2_ = 0.f;
		for (int i = 0; i <= counts1; i++) {
			allnumsize2_ += Utils::ImGuiRender::getTextSize(std::to_string(i)).y;
		}
		float allnumsize3_ = 0.f;
		for (int i = 0; i <= counts2; i++) {
			allnumsize3_ += Utils::ImGuiRender::getTextSize(std::to_string(i)).y;
		}

		static float anim1_ = 0.f;
		anim1_ = Utils::Math::animate(allnumsize2_, anim1_, Client::deltaTime * 5.f);
		static float anim2_ = 0.f;
		anim2_ = Utils::Math::animate(allnumsize3_, anim2_, Client::deltaTime * 5.f);

		ImVec2 countspos = ImVec2(pos.x + (counts2 != 0 ? 10.f : 0.f), (_countspos.y - allnumssize2) + anim1_);
		ImVec2 countspos_ = ImVec2(pos.x + (counts2 != 0 ? 10.f : 0.f), (_countspos.y - allnumssize2) + anim2_);

		ImVec2 countspos2 = ImVec2(pos.x, pos.y);

		for (int i = 9; i >= 0; i--) {
			Utils::ImGuiRender::setColor(ImColor(1.f, 1.f, 1.f, 1.f));
			Utils::ImGuiRender::drawText(std::to_string(i), countspos);

			countspos.y += countssize;
		}

		countspos_.x -= countssize_;

		if (counts2 != 0) {
			for (int i = 9; i >= 0; i--) {
				Utils::ImGuiRender::setColor(ImColor(1.f, 1.f, 1.f, 1.f));
				Utils::ImGuiRender::drawText(std::to_string(i), countspos_);

				countspos_.y += countssize;
			}
		}

		Utils::ImGuiRender::popClip();
		break;
	}
	case 2: {
		std::string str = _("Blocks: ") + std::to_string(counts);
		Utils::ImGuiRender::setColor(ImColor(1.f, 1.f, 1.f, animation));
		Utils::ImGuiRender::setDrawList(ImGui::GetBackgroundDrawList());
		Utils::ImGuiRender::setFont(ImGui::GetIO().Fonts->Fonts[(bold ? 1 : 0) + (4 * interfaceMod->font)]);
		Utils::ImGuiRender::setSize(animation * 1.4f);

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

auto Scaffold::onClient() -> void {
	for (Setting* setting : Client::findSetting(this, _("Tower"))) {
		if (setting->name == _("Tower")) continue;

		setting->visible = tower;
	}

	for (Setting* setting : Client::findSetting(this, _("Bold"))) {
		setting->visible = (blocksleft == 1 || blocksleft == 2);
	}

	for (Setting* setting : Client::findSetting(this, _("Moveable Tower"))) {
		setting->visible = tower;
	}

	for (Setting* setting : Client::findSetting(this, _("Sub Place Delay"))) {
		setting->visible = subplace;
	}
}

auto Scaffold::onEnabled() -> void {
	auto clientinstance = Client::clientinstance;
	if (!clientinstance) return;

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

	glm::vec3 pos = Utils::Move::getBlockPosition(localplayer);
	glm::vec3 floorpos = glm::vec3(
		std::floor(pos.x),
		std::floor(pos.y),
		std::floor(pos.z)
	);

	if (Utils::Move::isOnGround(localplayer)) {
		targetY = floorpos.y - 0.5f;
	}
	else {
		targetY = floorpos.y - 0.6f;
	}
}

auto Scaffold::onDisabled() -> void {
	Client::swingspeed = -1;

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

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

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

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

	Client::disableSwitchAnim = false;

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

	if (hasTowered) {
		glm::vec3 velocity_ = Utils::Move::getVelocity(localplayer);
		Utils::Move::setVelocity(localplayer, glm::vec3(velocity_.x, (-towerspeed * 10.f), velocity_.z));
	}
}

auto Scaffold::getMode() -> std::string {
	switch (placement) {
	case 0:
		return _("Normal");
	case 1:
		return _("Flareon");
	}

	return _("Unknown");
}
