#include "Player.h"
#include "Game.h"
#include "PlayerHand.h"
#include "ItemFilter.h"


Player::Player(Framework::Vec3<float> location, int dimensionId, int entityId)
	: Entity(PlayerEntityType::INSTANCE, location, dimensionId, entityId),
	BasicShapedCrafter(3, 3, this, "inventory")
{
	for (int i = 0; i < 10; i++)
	{
		ItemSlot* slot = new ItemSlot("ItemBar", 50, 0, i, 0, ANY_DIRECTION, 0);
		itemBar.add(slot);
		addSlot(slot);
	}
	for (int i = 0; i < 30; i++)
	{
		ItemSlot* slot = new ItemSlot("Inventory", 50, 0, i + 10, 0, ANY_DIRECTION, 0);
		addSlot(slot);
	}
	leftHandPosition = 0;
	maxHP = 10;
	currentHP = 10;
	stamina = 10;
	maxStamina = 10;
	hunger = 10;
	maxHunger = 10;
	thirst = 10;
	maxThirst = 10;
	keyState = 0;
	jumping = 0;
	faceOffset = { 0.f, 0.f, 1.5f };
	targetDistanceLimit = 4;
	maxMovementSpeed = 4;
}

void Player::onTargetChange()
{
	NetworkMessage* msg = new NetworkMessage();
	ActionTarget::toMessage(zTarget(), msg);
	Game::INSTANCE->sendMessage(msg, this);
}

Framework::Text Player::getInventoryUIML()
{
	Framework::Text result = "<dialog id=\"player_inventory\" title=\"Inventory\" width=\"610\" height=\"450\">";

	result += "<craftingGrid id=\"crafting\" margin-top=\"9\" align-top=\"start\" align-left=\"start\" margin-left=\"9\" width=\"282\" height=\"172\" rowSize=\"3\" colSize=\"3\" numOutputSlots=\"1\" target=\"";
	result += getId();
	result += "\"/>";

	result += "<inventory id=\"inventory\" margin-bottom=\"18\" align-bottom=\"item_bar\" align-left=\"start\" margin-left=\"9\" width=\"592\" height=\"172\" rowSize=\"10\" numSlots=\"30\" slotNameFilter=\"Inventory\" target=\"";
	result += getId();
	result += "\"/>";

	result += "<inventory id=\"item_bar\" margin-bottom=\"9\" align-bottom=\"end\" align-left=\"start\" margin-left=\"9\" width=\"592\" height=\"52\" rowSize=\"10\" numSlots=\"10\" slotNameFilter=\"ItemBar\" target=\"";
	result += getId();
	result += "\"/>";

	result += "</dialog>";
	return result;
}

Framework::Text Player::getPlayerGUI()
{
	Framework::Text result = "<gui id=\"player_gui\">";

	result += "<itemBar id=\"gui_item_bar\" margin-bottom=\"9\" align-bottom=\"end\" align-left=\"center\" width=\"592\" height=\"52\" rowSize=\"10\" slotNameFilter=\"ItemBar\" target=\"";
	result += getId();
	result += "\"/>";

	result += "</gui>";
	return result;
}

void Player::useItemSlot(ItemSlot* zSlot)
{
	if (zSlot->zStack())
	{
		ItemStack* stack = takeItemsOut(zSlot, 1, NO_DIRECTION);
		if (stack)
		{
			Item* item = stack->extractFromStack();
			Entity::useItem(item->zItemType(), item);
			if (item->getDurability() > 0)
			{ // put used item back
				stack->addToStack(item);
				if (!zSlot->numberOfAddableItems(stack, NO_DIRECTION))
				{ // move other items to other space
					ItemStack* oldItems = takeItemsOut(zSlot, zSlot->zStack()->getSize(), NO_DIRECTION);
					addItems(zSlot, stack, NO_DIRECTION);
					addItems(oldItems, NO_DIRECTION);
					if (oldItems->getSize() > 0)
					{
						// TODO: drop remaining items
					}
				}
				else
					addItems(zSlot, stack, NO_DIRECTION);
			}
			else
			{ // item is broken
				// move other items of the same type to the slot
				Array< ItemSlot*> fromSlots;
				for (ItemSlot* slot : *this)
				{
					if (slot != zSlot)
						fromSlots.add(slot);
				}
				Array<ItemSlot*> targetSlots;
				targetSlots.add(zSlot);
				TypeItemFilter filter(item->zItemType());
				localTransaction(&fromSlots, &targetSlots, &filter, zSlot->getFreeSpace(), NO_DIRECTION, NO_DIRECTION);
				// place broken item in inventory
				const ItemType* brokenType = item->zItemType()->zBrokenItemType();
				if (brokenType)
				{
					Item* broken = item->zItemType()->breakItem(item);
					if (broken)
					{
						stack->addToStack(broken);
						addItems(stack, NO_DIRECTION);
						if (stack->getSize() > 0)
						{
							// TODO: drop remaining items
						}
					}
				}
				item->release();
			}
			stack->release();
		}
	}
	else
		Entity::useItem(PlayerHandItemType::INSTANCE, 0); // hand usage
}

void Player::setName(Framework::Text name)
{
	this->name = name;
}

const char* Player::getName() const
{
	return name;
}

void Player::tick(const Dimension* zDimension)
{
	if ((keyState | Key::LEFT_HAND_ACTION) == keyState)
		useItemSlot(itemBar.get(leftHandPosition));
	if ((keyState | Key::RIGHT_HAND_ACTION) == keyState)
		useItemSlot(itemBar.get((leftHandPosition + 1) % itemBar.getEintragAnzahl()));
	return Entity::tick(zDimension);
}

void Player::playerApi(Framework::StreamReader* zRequest, NetworkMessage* zResponse)
{
	char byte;
	zRequest->lese(&byte, 1);
	switch (byte)
	{
	case 0:
		// stop action
		zRequest->lese(&byte, 1);
		switch (byte)
		{
		case 8:
			keyState = keyState & ~Key::LEFT_HAND_ACTION;
			break;
		case 9:
			keyState = keyState & ~Key::RIGHT_HAND_ACTION;
			break;
		}
		break;
	case 1:
		// begin action
		zRequest->lese(&byte, 1);
		switch (byte)
		{
		case 8:
			keyState = keyState | Key::LEFT_HAND_ACTION;
			break;
		case 9:
			keyState = keyState | Key::RIGHT_HAND_ACTION;
			break;
		}
		break;
	case 2:
		// set movement
	{
		MovementFrame frame;
		zRequest->lese((char*)&frame.direction.x, 4);
		zRequest->lese((char*)&frame.direction.y, 4);
		zRequest->lese((char*)&frame.direction.z, 4);
		zRequest->lese((char*)&frame.targetPosition.x, 4);
		zRequest->lese((char*)&frame.targetPosition.y, 4);
		zRequest->lese((char*)&frame.targetPosition.z, 4);
		zRequest->lese((char*)&frame.movementFlags, 4);
		zRequest->lese((char*)&frame.duration, 8);
		addMovementFrame(frame);
		calculateTarget(frame.targetPosition, frame.direction);
		break;
	}
	case 3:
	{ // switch item bar position
		zRequest->lese((char*)&leftHandPosition, 4);
		leftHandPosition = leftHandPosition % itemBar.getEintragAnzahl();
		NetworkMessage* msg = new NetworkMessage();
		msg->addressGui("gui_item_bar");
		char* message = new char[5];
		message[0] = 3; // set selected slot
		*(int*)(message + 1) = leftHandPosition;
		msg->setMessage(message, 5);
		Game::INSTANCE->sendMessage(msg, this);
		break;
	}
	case 4:
	{
		// open inventory
		zResponse->openDialog("player_inventory");
		Text uiml = getInventoryUIML();
		int msgSize = 4 + uiml.getLength();
		char* msg = new char[msgSize];
		*(int*)msg = uiml.getLength();
		memcpy(msg + 4, uiml.getText(), uiml.getLength());
		zResponse->setMessage(msg, msgSize);
		break;
	}
	case 5:
	{
		// request gui
		Text uiml = getPlayerGUI();
		int msgSize = 6 + uiml.getLength();
		char* msg = new char[msgSize];
		msg[0] = 2; // gui message
		msg[1] = 2; // set gui
		*(int*)(msg + 2) = uiml.getLength();
		memcpy(msg + 6, uiml.getText(), uiml.getLength());
		zResponse->setMessage(msg, msgSize);
		break;
	}
	case 6:
	{ // inventory transaction
		bool isEntity;
		zRequest->lese((char*)&isEntity, 1);
		Inventory* source;
		if (isEntity)
		{
			int id;
			zRequest->lese((char*)&id, 4);
			source = Game::INSTANCE->zEntity(id, getCurrentDimensionId());
		}
		else
		{
			int dim;
			Framework::Vec3<int> pos;
			zRequest->lese((char*)&dim, 4);
			zRequest->lese((char*)&pos.x, 4);
			zRequest->lese((char*)&pos.y, 4);
			zRequest->lese((char*)&pos.z, 4);
			source = Game::INSTANCE->zBlockAt(pos, dim);
		}
		int sourceSlotId;
		zRequest->lese((char*)&sourceSlotId, 4);
		zRequest->lese((char*)&isEntity, 1);
		Inventory* target;
		if (isEntity)
		{
			int id;
			zRequest->lese((char*)&id, 4);
			target = Game::INSTANCE->zEntity(id, getCurrentDimensionId());
		}
		else
		{
			int dim;
			Framework::Vec3<int> pos;
			zRequest->lese((char*)&dim, 4);
			zRequest->lese((char*)&pos.x, 4);
			zRequest->lese((char*)&pos.y, 4);
			zRequest->lese((char*)&pos.z, 4);
			target = Game::INSTANCE->zBlockAt(pos, dim);
		}
		if (source && target)
		{
			int targetSlotId;
			zRequest->lese((char*)&targetSlotId, 4);
			SpecificSlotFilter filter(sourceSlotId, targetSlotId);
			source->interactWith(target, Direction::NO_DIRECTION).pushItems(source->zSlot(sourceSlotId)->getNumberOfItems(), &filter);
		}
		break;
	}
	case 7: // craft action
	{
		bool isEntity;
		zRequest->lese((char*)&isEntity, 1);
		BasicShapedCrafter* target;
		if (isEntity)
		{
			int id;
			zRequest->lese((char*)&id, 4);
			target = dynamic_cast<BasicShapedCrafter*>(Game::INSTANCE->zEntity(id, getCurrentDimensionId()));
		}
		else
		{
			int dim;
			Framework::Vec3<int> pos;
			zRequest->lese((char*)&dim, 4);
			zRequest->lese((char*)&pos.x, 4);
			zRequest->lese((char*)&pos.y, 4);
			zRequest->lese((char*)&pos.z, 4);
			target = dynamic_cast<BasicShapedCrafter*>(Game::INSTANCE->zRealBlockInstance(pos, dim));
		}
		if (target)
			target->applyCurrentRecipie();
		break;
	}
	}
}

void Player::onFall(float collisionSpeed)
{
	Entity::onFall(collisionSpeed);
	gravityMultiplier = 1.f;
	jumping = 0;
}



PlayerEntityType::PlayerEntityType()
	: EntityType(ID, ModelInfo("player", "player.ltdb/player.png", 6))
{}

void PlayerEntityType::loadSuperEntity(Entity* zEntity, Framework::StreamReader* zReader) const
{
	Player* zPlayer = dynamic_cast<Player*>(zEntity);
	if (!zPlayer)
		throw "PlayerEntityType::loadSuperEntity was called with an entity witch is not an instance of Player";
	zReader->lese((char*)&zPlayer->leftHandPosition, 4);
	char len;
	zReader->lese(&len, 1);
	char* name = new char[(int)len + 1];
	zReader->lese(name, (int)len);
	name[(int)len] = 0;
	zPlayer->name = name;
	delete[] name;
	EntityType::loadSuperEntity(zPlayer, zReader);
}

void PlayerEntityType::saveSuperEntity(Entity* zEntity, Framework::StreamWriter* zWriter) const
{
	Player* zPlayer = dynamic_cast<Player*>(zEntity);
	if (!zPlayer)
		throw "PlayerEntityType::saveSuperEntity was called with an entity witch is not an instance of Player";
	zWriter->schreibe((char*)&zPlayer->leftHandPosition, 4);
	char len = (char)textLength(zPlayer->getName());
	zWriter->schreibe(&len, 1);
	zWriter->schreibe(zPlayer->getName(), (int)len);
	EntityType::saveSuperEntity(zEntity, zWriter);
}

Entity* PlayerEntityType::createEntity(Framework::Vec3<float> position, int dimensionId, int entityId) const
{
	return new Player(position, dimensionId, entityId);
}