#include <iostream>

#include "PlayerGame.h"
#include "Human.h"
#include "Townsfolk.h"
#include "Soldier.h"
#include "Artisan.h"
#include "TownBuilding.h"
#include "Den.h"

// test
#include <iosfwd>
#include "../Graphics/Animation.h"

namespace mechanics {
	PlayerGame* PlayerGame::sCurrent = NULL;

	void PlayerGame::UpdatePlayerGame() {
		if(mIsPaused) return;
		currentGameTime+=(gge::Main.CurrentTime-mPrevTime)*mGameSpeed;
		inGameTime = currentGameTime * assets::constants::timeConst + Map::current().provider().startingHour*3600;
		mPrevTime=gge::Main.CurrentTime;

		//!todo move out
		for(auto it=mMoverList.begin();it!=mMoverList.end();++it) {
			(*it)->updateMovement();
		}
		for(auto it=mHumanList.begin();it!=mHumanList.end();++it) {
			(*it)->checkAction();
		}
		for(auto it=mWildlingList.begin();it!=mWildlingList.end();++it) {
			(*it)->checkAction();

			if(!sCurrent) return;
		}

		CheckTaxes();
		SpawnCheck();
	}

	void PlayerGame::CheckTaxes() {
		if(mGoldDec != 0 && currentGameTime - mLastGoldConsume > 20) {
			if(mGoldDec < 0) {
				if(mGoldDec <= 9) {
					int rnd = gge::utils::Random(1, 9);
					mGoldDec += rnd;
					mGold -= rnd;
				}
				else {
					mGoldDec++;
					mGold--;
				}
			}
			else {
				if(mGoldDec >= 9) {
					int rnd = gge::utils::Random(1, 9);
					mGoldDec -= rnd;
					mGold += rnd;
				}
				else {
					mGoldDec--;
					mGold++;
				}
			}

			mLastGoldConsume = currentGameTime;
		}

		float taxHappiness = getTaxHappiness();
		int aliveCitizens = getCitizensAlive();

		// for each citizen
		for(auto it=mTownsfolkList.begin();it!=mTownsfolkList.end();++it) {
			// move to a new local variable if you like
			if(currentGameTime - (*it)->lastSocialCheck() > 1*MINUTE) {
				int diff=(taxHappiness - mCurrentHappiness);
				int sgn=(diff==0 ? 0 : (diff>0?1:-1));

				float mod = (assets::constants::happiness::base / aliveCitizens) * ( sgn + diff / assets::constants::happiness::differenceDenominator);
				
				mCurrentHappiness += mod;
				
				if(diff>0 && mCurrentHappiness>taxHappiness)
					mCurrentHappiness=taxHappiness;
				else if(diff<0 && mCurrentHappiness<taxHappiness)
					mCurrentHappiness=taxHappiness;

				IncreaseGold(taxRate());

				(*it)->lastSocialCheck(currentGameTime);
			}
		}

		// for each soldier
		for(auto it=mSoldierList.begin();it!=mSoldierList.end();++it) {
			// move to a new local variable if you like
			if(currentGameTime - (*it)->lastPaymentCheck() > 1*MINUTE) {
				float payment = assets::constants::gold::base + ((*it)->level() * assets::constants::gold::lvlMultiplier) + ((*it)->level() * (*it)->level() * assets::constants::gold::lvlSqrMultiplier);

				ConsumeGold(payment);

				(*it)->lastPaymentCheck(currentGameTime);
			}
		}
		for(auto it=mArtisanList.begin();it!=mArtisanList.end();++it) {
			if(currentGameTime - (*it)->lastPaymentCheck() > 1*MINUTE) {
				float payment = assets::constants::gold::base + ((*it)->level() * assets::constants::gold::lvlMultiplier) + ((*it)->level() * (*it)->level() * assets::constants::gold::lvlSqrMultiplier);

				ConsumeGold(payment);

				(*it)->lastPaymentCheck(currentGameTime);
			}
		}
	}

	bool PlayerGame::SearchBuilding(std::string buildingId) {
		bool found = false;

		for(unsigned j=0;j<mMilitaryBuildingList.size();j++) {
			if(buildingId == mMilitaryBuildingList[j]->provider().ID) {
				found = true;
				break;
			}
		}

		return found;
	}

	bool PlayerGame::CheckCraft(std::string itemId, bool log) {
		if(assets::Item::list.find(itemId) == assets::Item::list.end()) {
			return false;
		}

		assets::Item &item = *assets::Item::list[itemId];

		if(mGold < item.cost) {
			if(log) AddToGameLog("Not enough coins for that.");
			return false;
		}

		StorageBuilding *storage=NULL;
		bool found=false;
		for(unsigned j=0;j<mMilitaryBuildingList.size();j++) {
			if(dynamic_cast<StorageBuilding*>(mMilitaryBuildingList[j])) {
				auto mb=dynamic_cast<StorageBuilding*>(mMilitaryBuildingList[j]);
				if(mb->storageType()==item.type) {
					storage=mb;
					if(mb->isAvailable()) {
						found=true;
						break;
					}
				}
			}
		}

		int notfound = 0;
		std::stringstream ss;
		ss<<itemId<<" requires ";

		if(!storage) {
			// AddToGameLog("No storage building for "+item.ID);//!better be item type
			// return false;
			if(notfound++ > 0) {
				ss<<", ";
			}
			ss<<"storage building";
		}
		else if(!found) {
			// AddToGameLog("Not enough capacity to hold "+item.ID);
			// return false;
			if(notfound++ > 0) {
				ss<<", ";
			}
			ss<<"storage capacity";
		}

		for(unsigned i=0;i<item.requiredBuilding.size();i++) {
			if(!SearchBuilding(item.requiredBuilding[i])) {
				if(notfound++ > 0) {
					ss<<", ";
				}
				ss<<item.requiredBuilding[i];
			}
		}

		if(notfound > 0) {
			ss<<".";
			if(log) AddToGameLog(ss.str());
			return false;
		}

		return true;
	}

	gge::utils::Point PlayerGame::findHumanSpawnPoint() {
		gge::utils::Bounds bounds = getTownBounds();

		gge::utils::Point point = gge::utils::Point(gge::utils::Random(bounds.Left, bounds.Right), gge::utils::Random(bounds.Top, bounds.Bottom));

		PointInfo pi = Map::current().getPointInfoMap()(point);
		while(!pi.walkable || !pi.buildable) {
			point = gge::utils::Point(gge::utils::Random(bounds.Left, bounds.Right), gge::utils::Random(bounds.Top, bounds.Bottom));
			pi = Map::current().getPointInfoMap()(point);
		}

		return point;
	}

	gge::utils::Point PlayerGame::findTownBuildingSpawnPoint(gge::utils::Size size) {
		gge::utils::Bounds bounds = getTownBounds();

		gge::utils::Point point = gge::utils::Point(gge::utils::Random(bounds.Left, bounds.Right - size.Width), gge::utils::Random(bounds.Top, bounds.Bottom - size.Height));
		gge::utils::Point pointFinal = point + size;

		bool fit=false;
		int limit=50;

		while(!fit && limit--) {
			fit=true;
			for(int y = point.y; y < pointFinal.y; y++) {
				for(int x = point.x; x < pointFinal.x; x++) {
					PointInfo pi = Map::current().getPointInfoMap()(x, y);
					if(!pi.walkable || !pi.buildable || pi.object != NULL) {
						fit=false;
					}
				}
			}
		}

		if(fit)
			return point;
		else
			return gge::utils::Point(-1,-1);
	}

	TownBuilding *PlayerGame::spawnTownBuilding() {
		float totalodds=0;
		for(auto it=assets::TownBuilding::list.begin();it!=assets::TownBuilding::list.end();++it) {
			totalodds+=it->second->odds;
		}

		float selection=gge::utils::Random(0.f, totalodds);
		assets::TownBuilding *selected=NULL;

		for(auto it=assets::TownBuilding::list.begin();it!=assets::TownBuilding::list.end();++it) {
			selection-=it->second->odds;

			if(selection<=0) {
				selected=it->second;
				break;
			}
		}

		if(!selected)
			throw std::runtime_error("Waa?? no townbuildings????");

		TownBuilding *tb=createTownBuilding(selected->ID);

		gge::utils::Point point = findTownBuildingSpawnPoint(selected->size);
		if(point!=gge::utils::Point(-1, -1)) {
			tb->locateTo(point);
			addTownBuilding(*tb);
		}
		else {
			delete tb;
			return spawnTownBuilding();
		}
		

		return tb;
	}

	void PlayerGame::SpawnCheck() {
		if(!mCurrentHappiness) return;

		//!citizen leave

		int timeout = (assets::constants::happiness::timeMultiplier1 * MINUTE) /
			          (assets::constants::happiness::currentHappinessMultiplier *
					  mCurrentHappiness + assets::constants::happiness::currentHappinessModifier *
					  (mCurrentHappiness > 0 ? 1 : -1));

		if(timeout > 0 && currentGameTime - mLastTownsfolkSpawn > (unsigned)timeout && getCitizensAlive()<populationLimit()) {
			Townsfolk *tf = spawnTownsfolk();
			mLastTownsfolkSpawn = currentGameTime;
		}

		int timeout2 = (assets::constants::happiness::timeMultiplier2 * MINUTE) /
					   (assets::constants::happiness::currentHappinessMultiplier *
					   mCurrentHappiness + assets::constants::happiness::currentHappinessModifier *
					   (mCurrentHappiness > 0 ? 1 : -1));

		if(timeout2 > 0 && currentGameTime - mLastTownBuildingSpawn > (unsigned)timeout2) {
			if(mCurrentTownsfolkCapacity <= getCitizensAlive()) {
				spawnTownBuilding();
			}

			mLastTownBuildingSpawn = currentGameTime;
		}

		for(auto it=mDenList.begin();it!=mDenList.end();++it) {
			(*it)->checkSpawn();

		}
	}

	void PlayerGame::addTownsfolk(Townsfolk &townsfolk) {
		mHumanList.push_back(&townsfolk);
		mTownsfolkList.push_back(&townsfolk);
		mMoverList.push_back(&townsfolk);
	}

	void PlayerGame::addSoldier(Soldier &soldier) {
		mHumanList.push_back(&soldier);
		mSoldierList.push_back(&soldier);
		mMoverList.push_back(&soldier);
	}

	void PlayerGame::addArtisan(Artisan &artisan) {
		mHumanList.push_back(&artisan);
		mArtisanList.push_back(&artisan);
		mMoverList.push_back(&artisan);
	}

	void PlayerGame::destroyHuman(Human *ptr) {
		for(unsigned i=0; i < mHumanList.size(); i++) {
			if(mHumanList[i] == ptr) {
				mHumanList.erase(mHumanList.begin() + i);
				break;
			}
		}
		for(unsigned i=0; i < mMoverList.size(); i++) {
			if(mMoverList[i] == ptr) {
				mMoverList.erase(mMoverList.begin() + i);
				break;
			}
		}
	}

	void PlayerGame::destroyTownsfolk(Townsfolk *ptr) {
		destroyHuman(ptr);
		for(unsigned i=0; i < mTownsfolkList.size(); i++) {
			if(mTownsfolkList[i] == ptr) {
				mTownsfolkList.erase(mTownsfolkList.begin() + i);
				break;
			}
		}
	}

	void PlayerGame::destroySoldier(Soldier *ptr) {
		destroyHuman(ptr);
		for(unsigned i=0; i < mSoldierList.size(); i++) {
			if(mSoldierList[i] == ptr) {
				mSoldierList.erase(mSoldierList.begin() + i);
				break;
			}
		}
	}

	void PlayerGame::destroyArtisan(Artisan *ptr) {
		destroyHuman(ptr);
		for(unsigned i=0; i < mArtisanList.size(); i++) {
			if(mArtisanList[i] == ptr) {
				mArtisanList.erase(mArtisanList.begin() + i);
				break;
			}
		}
	}

	Townsfolk *PlayerGame::spawnTownsfolk() {
		Townsfolk *townsfolk = createTownsfolk();
		addTownsfolk(*townsfolk);

		//find a proper location for him and place on the map
		gge::utils::Point point = findHumanSpawnPoint();
		townsfolk->locateTo(point);

		return townsfolk;
	}

	void PlayerGame::killTownsfolk(Townsfolk *ptr) {
		AddToGameLog(ptr->title() + " (townsfolk) died.");
		destroyTownsfolk(ptr);

		PlayerGame::current().addHappiness(-assets::constants::happiness::happinesDeathModifier);
	}

	void PlayerGame::killSoldier(Soldier *ptr) {
		AddToGameLog(ptr->title() + " (soldier) died.");
		destroySoldier(ptr);

		PlayerGame::current().addHappiness(-assets::constants::happiness::happinesDeathModifier);
	}

	void PlayerGame::killArtisan(Artisan *ptr) {
		AddToGameLog(ptr->title() + " (artisan) died.");
		destroyArtisan(ptr);

		//!reduce happiness
	}

	void PlayerGame::addTownBuilding(TownBuilding &tb) {
		mBuildingList.push_back(&tb);
		mTownBuildingList.push_back(&tb);
		mCurrentTownsfolkCapacity+=tb.capacity();

		AddToGameLog(tb.provider().ID + " is built.");
	}

	void PlayerGame::destroyBuilding(Building &tb) {
		for(unsigned i=0;i<mBuildingList.size();i++) {
			if(mBuildingList[i]==&tb) {
				mBuildingList.erase(mBuildingList.begin()+i);
				break;
			}
		}
	}

	void PlayerGame::destroyTownBuilding(TownBuilding &tb) {
		destroyBuilding(tb);
		for(unsigned i=0;i<mTownBuildingList.size();i++) {
			if(mTownBuildingList[i]==&tb) {
				mTownBuildingList.erase(mTownBuildingList.begin()+i);
				mCurrentTownsfolkCapacity-=tb.capacity();
				PlayerGame::current().addHappiness(assets::constants::happiness::happinesDeathModifier);

				break;
			}
		}

		AddToGameLog(tb.provider().ID + " is collapsed.");
	}

	MilitaryBuilding *PlayerGame::createMilitaryBuilding(const std::string &ID) {
		if(!ConsumeGold(assets::MilitaryBuilding::list[ID]->constructionCost)) {
			AddToGameLog("cannot build, not enough coins.");
			return NULL;
		}

		return ::createMilitaryBuilding(ID);
	}

	void PlayerGame::addMilitaryBuilding(MilitaryBuilding &tb) {
		mBuildingList.push_back(&tb);
		mMilitaryBuildingList.push_back(&tb);

		if(dynamic_cast<HousingBuilding*>(&tb)) {
			mSoldierHousing+=dynamic_cast<HousingBuilding*>(&tb)->capacity();
		}

		AddToGameLog(tb.provider().ID + " is built.");
	}

	void PlayerGame::destroyMilitaryBuilding(MilitaryBuilding &tb) {
		destroyBuilding(tb);
		for(unsigned i=0;i<mMilitaryBuildingList.size();i++) {
			if(mMilitaryBuildingList[i]==&tb) {
				if(dynamic_cast<HousingBuilding*>(&tb)) {
					mSoldierHousing-=dynamic_cast<HousingBuilding*>(&tb)->capacity();
				}

				mMilitaryBuildingList.erase(mMilitaryBuildingList.begin()+i);

				break;
			}
		}

		AddToGameLog(tb.provider().ID + " is collapsed.");
	}

	void PlayerGame::addDen(Den &den) {
		mDenList.push_back(&den);
	}

	void PlayerGame::destroyDen(Den &den) {
		for(unsigned i=0;i<mDenList.size();i++) {
			if(mDenList[i]==&den) {
				mDenList.erase(mDenList.begin()+i);

				break;
			}
		}
	}

	void PlayerGame::addWildling(Wildling &wildling) {
		mWildlingList.push_back(&wildling);
		mMoverList.push_back(&wildling);
	}

	void PlayerGame::destroyWildling(Wildling &wildling) {
		for(unsigned i=0;i<mWildlingList.size();i++) {
			if(mWildlingList[i]==&wildling) {
				mWildlingList.erase(mWildlingList.begin()+i);

				break;
			}
		}
		for(unsigned i=0; i < mMoverList.size(); i++) {
			if(mMoverList[i] == &wildling) {
				mMoverList.erase(mMoverList.begin() + i);
				break;
			}
		}
	}

	void PlayerGame::addItem(Item &item) {
		mCraftedItemList.push_back(&item);
	}

	void PlayerGame::destroyItem(Item &item) {
		takeItem(item);
	}

	StorageBuilding *PlayerGame::takeItem(Item &item) {
		for(unsigned i=0;i<mCraftedItemList.size();i++) {
			if(mCraftedItemList[i]==&item) {
				mCraftedItemList.erase(mCraftedItemList.begin()+i);
				break;
			}
		}

		for(auto it=mMilitaryBuildingList.begin();it!=mMilitaryBuildingList.end();++it) {
			if(dynamic_cast<StorageBuilding*>(*it)) {
				if(dynamic_cast<StorageBuilding*>(*it)->takeOut(&item))
					return dynamic_cast<StorageBuilding*>(*it);
			}
		}

		return NULL;
	}

	int PlayerGame::income() const {
		int income=(int)mTownsfolkList.size()*taxRate();

		for(auto it=mSoldierList.begin();it!=mSoldierList.end();++it) {
			float payment = assets::constants::gold::base + ((*it)->level() * assets::constants::gold::lvlMultiplier) + ((*it)->level() * (*it)->level() * assets::constants::gold::lvlSqrMultiplier);
			income-=(int)payment;
		}
		for(auto it=mArtisanList.begin();it!=mArtisanList.end();++it) {
			float payment = assets::constants::gold::base + ((*it)->level() * assets::constants::gold::lvlMultiplier) + ((*it)->level() * (*it)->level() * assets::constants::gold::lvlSqrMultiplier);
			income-=(int)payment;
		}

		return income;
	}

}
