#include "Room.h"
#include "Item.h"
#include "Main.h"

int Room::sRoomCount = 0;
int Room::sPuzzleRoomCount = 0;

using namespace std;

Exit::Exit(string aName, Room* aToRoom):mName(aName), mToRoom(aToRoom)
{
}

void Exit::Move()
{
	Main::Get()->mCurrentRoom = GetToRoom();
	Main::Get()->ClearScreen();
}


ExitIfItem::ExitIfItem(std::string aName, Room* aToRoom, std::string aCantGo, std::string aEnableMessage) :
				Exit(aName, aToRoom), mCantGo(aCantGo), mEnableMessage(aEnableMessage), mEnabled(0)
{}

void ExitIfItem::UpdateExit()
{
	//First check if they all have the items they need
	for (list<ItemHolder*>::iterator i = mItems.begin(); i != mItems.end(); ++i)
		if (!(*i)->HoldingCorrectItem())
			return;

	mEnabled = true;
	Main::Get()->mOut.EndLines(1);
	Main::Get()->mOut.Text(mEnableMessage);
}

void ExitIfItem::Move()
{
	if (mEnabled)
	{
		Main::Get()->mCurrentRoom = GetToRoom();
		Main::Get()->ClearScreen();
	}
	else
		Main::Get()->mOut.Text(mCantGo);
}

void ExitIfItem::AddItemHolder(ItemHolder* aItemHolder)
{
	if (aItemHolder)
		mItems.push_back(aItemHolder);
}



Room::Room(std::string _name, std::string _verboseDes):
			mVisited(false)
			,mName(_name)
			,mVerboseDes(_verboseDes)
			,mId(++sRoomCount)
{}

Room::~Room()
{
	while(!mExits.empty())
	{
		delete mExits.front();
		mExits.front() = 0;
		mExits.pop_front();
	}

	while(!mItems.empty())
	{
		delete mItems.front();
		mItems.front() = 0;
		mItems.pop_front();
	}

	while(!mNPCs.empty())
	{
		delete mNPCs.front();
		mNPCs.front() = 0;
		mNPCs.pop_front();
	}

}

string Room::GetDescription()
{
	//First time is always verbose
	if (!mVisited && Main::Get()->mCurrentRoom == this)
	{
		//mVisited is set to true when the score is updated in ShowDescription
		return mVerboseDes;
	}

	std::string result = "";

	//verboseDes if verbose
	if (Main::Get()->mDesMode == eVerbose)
		result = mVerboseDes;

	return result;
}

void Room::UpdateExits()
{
	for (list<Exit*>::iterator i = mExits.begin(); i != mExits.end(); ++i)
		(*i)->UpdateExit();
}

void Room::ShowDescription()
{
	Main* main = Main::Get();

	main->mOut.Text("  " + Main::Get()->mCurrentRoom->GetName());
	main->mOut.EndLines(1);

	string desStr = main->mCurrentRoom->GetDescription();
	if (desStr != "")
	{
		main->mOut.Text(desStr);
		main->mOut.EndLines(1);
	}

	//Show Items
	if (!mItems.empty())
	{
		string items = "In the room, you see: ";

		list<Item*>::iterator i = mItems.begin();
		items.append((*i)->GetName());

		for (++i; i != mItems.end(); ++i)
		{
			items.append(", " + (*i)->GetName());
		}

		items.append(".");

		main->mOut.Text(items);
		main->mOut.EndLines(1);
	}

	//Show Exits
	if (!mExits.empty())
	{
		string exits = "You can see exits to the: ";

		list<Exit*>::iterator i = mExits.begin();
		exits.append((*i)->GetName());

		for (++i; i != mExits.end(); ++i)
		{
			exits.append(", " + (*i)->GetName());
		}

		exits.append(".");

		main->mOut.Text(exits);
		main->mOut.EndLines(1);
	}

	//Show NPCs
	if (!mNPCs.empty())
	{
		string npcs = "NPCs: ";

		list<NPC*>::iterator i = mNPCs.begin();
		npcs.append((*i)->GetNPCName());

		for (++i; i != mNPCs.end(); ++i)
		{
			npcs.append(", " + (*i)->GetNPCName());
		}

		npcs.append(".");

		main->mOut.Text(npcs);
		main->mOut.EndLines(1);
	}

	//Add Score
	if (!mVisited)
	{
		mVisited = true;
		Main::Get()->AddScore(sScoreNewRoom);
	}

}

void Room::Move(Exit* aUsedExit)
{
	aUsedExit->Move();
}


RoomPuzzleTime::RoomPuzzleTime(std::string aName, std::string aVerboseDes, int aTurns, Item* aItemTakeToStart) :
						Room(aName, aVerboseDes)
						,mTurnsToSolve(aTurns)
						,mSolved(0)
						,mStarted(0)
						,mPuzzleExit(0)
{
	++sPuzzleRoomCount;

	mPuzzleItems.push_front(aItemTakeToStart);
	mItems.push_front(aItemTakeToStart);
	mItemTakeToStart = aItemTakeToStart;
}

void RoomPuzzleTime::Update()
{
	if (mSolved)
		return;

	//If the puzzle is going
	if (mStarted)
	{
		//Finished the puzzle!
		if (mPuzzleExit->GetEnabled())
		{
			Main::Get()->mOut.EndLines(1);
			Main::Get()->AddScore(sScorePuzzleRoom); //Add Score
			mSolved = true;
			mStarted = false;
			return;
		}

		//More turns
		if (--mTurnCounter > 0)
		{
			Main::Get()->mOut.EndLines(1);
			Main::Get()->mOut.Text("Turns Remaining: ");
			Main::Get()->mOut.Int(mTurnCounter);
		}
		//All done
		else
		{
			Main::Get()->mOut.EndLines(1);
			Main::Get()->mOut.Text("Not fast enough! The puzzle has reset.");
			RestartRoom();
			mStarted = false;
		}

	}
	//Else start it up
	else if (!mStarted && mItems.front() != mItemTakeToStart) //If it isnt the front item, it must be gone
	{
		mStarted = true;
		mTurnCounter = mTurnsToSolve;

		Main::Get()->mOut.EndLines(1);
		Main::Get()->mOut.Text("Turns Remaining: ");
		Main::Get()->mOut.Int(mTurnCounter);
	}
}

void RoomPuzzleTime::AddPuzzleExit(ExitIfItem* aExit)
{
	if (aExit)
	{
		if (mPuzzleExit)
			Main::Get()->mOut.Text("Already had an exit, overwriting!");

		mPuzzleExit = aExit;
		mExits.push_back(aExit);
	}
}

void RoomPuzzleTime::Move(Exit* aUsedExit)
{
	if (aUsedExit)
	{
		//Don't restart if you have finished the puzzle
		if (!mSolved)
			RestartRoom();

		aUsedExit->Move();
	}
}

void RoomPuzzleTime::RestartRoom()
{
	mStarted = false;
	mPuzzleItems = mPuzzleItemsOrg;

	//Remove puzzle items from players inventory and from room, and also updates the ItemHolder's held items
	list<Item*>::iterator h = mPuzzleItemsHeld.begin(); //For going through held items in order
	for (list<Item*>::iterator i = mPuzzleItems.begin(); i != mPuzzleItems.end(); ++i)
	{
		mItems.remove(*i);
		Main::Get()->mInventory.remove(*i);

		if ((*i)->GetType() == eHold)
		{
			ItemHolder* tempHolder = (ItemHolder*)(*i);
			tempHolder->RemoveItem();
			tempHolder->AddItem(*h);
			++h;
		}
	}

	//Put puzzle items beck into the room
	mItems.insert(mItems.begin(), mPuzzleItems.begin(), mPuzzleItems.end());
}

void RoomPuzzleTime::SetRestartState()
{
	mPuzzleItemsOrg = mPuzzleItems;

	//Check all itemHolder's held items and save them (in order!) so that you can reload them on RestartRoom
	for (list<Item*>::iterator i = mPuzzleItemsOrg.begin(); i != mPuzzleItemsOrg.end(); ++i)
	{
		if ((*i)->GetType() == eHold)
		{
			ItemHolder* tempHolder = (ItemHolder*)(*i);
			mPuzzleItemsHeld.push_back(tempHolder->GetHeld());
		}
	}

	//Remove the starting item and make sure it is at the front of the list
	mPuzzleItemsOrg.remove(mItemTakeToStart);
	mPuzzleItemsOrg.push_front(mItemTakeToStart);
}

void RoomPuzzleTime::AddPuzzleItem(Item* aItem)
{
	if (aItem)
	{
		mItems.push_back(aItem);
		mPuzzleItems.push_back(aItem);
	}
}


RoomPuzzleBox::RoomPuzzleBox(std::string aName, std::string aVerboseDes, int aMaxX, int aMaxY, int aBoxPos, int aSwitchPos) :
						Room(aName, aVerboseDes)
						,mMaxX(aMaxX)
						,mMaxY(aMaxY)
						,mBoxPos(aBoxPos)
						,mSwitchPos(aSwitchPos)
						,mSolved(0)
						,mShowRoomStart(0)
						,mPuzzleExit(0)
						,mBox(0)
						,mSwitch(0)
{
	++sPuzzleRoomCount;

	mRoomArray = new int[mMaxX*mMaxY];
	memset(mRoomArray, 0, sizeof(int) * (mMaxX*mMaxY));

	InitializeBoxPuzzle();
}

RoomPuzzleBox::~RoomPuzzleBox()
{
	delete [] mRoomArray;
}

void RoomPuzzleBox::Update()
{
	//Show the room every first time in the room
	if (!mShowRoomStart)
	{
		ShowRoom();
		mShowRoomStart = true;
	}

	//If the box is on the switch
	if (!mSolved && mBoxPos == mSwitchPos)
	{
		Main::Get()->mOut.EndLines(1);
		Main::Get()->AddScore(sScorePuzzleRoom); //Add Score
		mSolved = true;

		mSwitch->AddItem(mBox); //Makes for certain that the switch gets the box
		UpdateExits(); //This makes for certain that the exit updates and sees that the switch has the box
	}
}

void RoomPuzzleBox::Move(Exit* aUsedExit)
{
	if (aUsedExit)
	{
		mShowRoomStart = false; //To show the room when you come back

		aUsedExit->Move();
	}
}

void RoomPuzzleBox::InitializeBoxPuzzle()
{
	mRoomArray[mBoxPos] = eBox;
	mRoomArray[mSwitchPos] = eSwitch;

	//Add top and bottom walls
	for (int x = 0; x < mMaxX; ++x)
	{
		mRoomArray[x] = eWall;
		mRoomArray[x + (mMaxX*(mMaxY-1))] = eWall;
	}

	//Add left and right walls
	for (int y = 0; y < mMaxX*mMaxY; y+=mMaxX)
	{
		mRoomArray[y] = eWall;
		mRoomArray[y + (mMaxX-1)] = eWall;
	}
}

void RoomPuzzleBox::ShowRoom()
{
	Main* main = Main::Get();
	for (int i = 0; i < (mMaxX*mMaxY); ++i)
	{
		if (i % mMaxX == 0)
			main->mOut.EndLines(1);

		if (mRoomArray[i] == eEmpty)
			main->mOut.Char(mEmptyChar);
		else if (mRoomArray[i] == eWall)
			main->mOut.Char(mWallChar);
		else if (mRoomArray[i] == eBox)
			main->mOut.Char(mBoxChar);
		else if (mRoomArray[i] == eSwitch)
			main->mOut.Char(mSwitchChar);
	}
}

void RoomPuzzleBox::AddWall(int aPos)
{
	if (aPos > 0 && aPos < mMaxX * mMaxY)
	{
		mRoomArray[aPos] = eWall;
	}
}

void RoomPuzzleBox::Push(int x, int y)
{
	if (mSolved)
	{
		DontMoveFinishedMessage();
		return;
	}


	if (x)
	{
		//Check if you can actually move it
		if (mRoomArray[mBoxPos + x] == eWall)
			{ InvalidBoxMove(); return; }
		else if (mRoomArray[mBoxPos - x] == eWall)
			{ InvalidPlayerMove(); return; }

		int temp = mBoxPos;
		mRoomArray[mBoxPos += x] = eBox;
		mRoomArray[temp] = eEmpty;

		ShowRoom();
		return;
	}
	else if (y)
	{
		if (mRoomArray[mBoxPos + (y*mMaxX)] == eWall)
			{ InvalidBoxMove(); return; }
		else if (mRoomArray[mBoxPos - (y*mMaxX)] == eWall)
			{ InvalidPlayerMove(); return; }

		int temp = mBoxPos;
		mRoomArray[mBoxPos += (y*mMaxX)] = eBox;
		mRoomArray[temp] = eEmpty;

		ShowRoom();
		return;
	}

	Main::Get()->mOut.Text("Some weird number was put into RoomPuzzleBox::Push!");
}

void RoomPuzzleBox::Pull(int x, int y)
{
	if (mSolved)
	{
		DontMoveFinishedMessage();
		return;
	}

	if (x)
	{
		//Check if you can actually move it
		if (mRoomArray[mBoxPos + x] == eWall)
			{ InvalidBoxMove(); return; }
		else if (mRoomArray[mBoxPos + x*2] == eWall)
			{ InvalidPlayerMove(); return; }

		int temp = mBoxPos;
		mRoomArray[mBoxPos += x] = eBox;
		mRoomArray[temp] = eEmpty;

		ShowRoom();
		return;
	}
	else if (y)
	{
		if (mRoomArray[mBoxPos + (y*mMaxX)] == eWall)
			{ InvalidBoxMove(); return; }
		else if (mRoomArray[mBoxPos + (y*mMaxX*2)] == eWall)
			{ InvalidPlayerMove(); return; }

		int temp = mBoxPos;
		mRoomArray[mBoxPos += (y*mMaxX)] = eBox;
		mRoomArray[temp] = eEmpty;

		ShowRoom();
		return;
	}

	Main::Get()->mOut.Text("Some weird number was put into RoomPuzzleBox::Pull!");
}

void RoomPuzzleBox::AddPuzzleExit(ExitIfItem* aExit)
{
	if (aExit)
	{
		if (mPuzzleExit)
			Main::Get()->mOut.Text("Already had an exit, overwriting!");

		mPuzzleExit = aExit;
		mExits.push_back(aExit);
	}
}

void RoomPuzzleBox::InvalidBoxMove()
{
	Main::Get()->mOut.Text("You can not move that there.");
}

void RoomPuzzleBox::InvalidPlayerMove()
{
	Main::Get()->mOut.Text("You can not position yourself there to move it.");
}

void RoomPuzzleBox::DontMoveFinishedMessage()
{
	Main::Get()->mOut.Text("Whoa, don't move that! It is all working, don't mess with it!");
}

void RoomPuzzleBox::AddPuzzleBox(ItemPushable* aBox)
{
	if (aBox)
	{
		if (mPuzzleExit)
			Main::Get()->mOut.Text("Already had an puzzleBox, overwriting!");

		mBox = aBox;
		mItems.push_back(aBox);
	}
}

void RoomPuzzleBox::AddPuzzleSwitch(ItemHolder* aSwitch)
{
	if (aSwitch)
	{
		if (mSwitch)
			Main::Get()->mOut.Text("Already had an Switch, overwriting!");

		mSwitch = aSwitch;
		mItems.push_back(aSwitch);
	}
}