#include "../entity.h"
#include "../item.h"
#include "../layers.h"
#include <string>
#define OLC_PGE_APPLICATION
#include "../pixelGameEngine.h"
#define OLC_PGEX_SPLASHSCREEN
#include "../splash.h"
#define OLC_SOUNDWAVE
#include "../defines.h"
#include "../soundwaveEngine.h"
#include "../tiles.h"
#include "../references.h"
#include "../states.h"
#include "../flags.h"
#include <assert.h>
#include "../cutscene.h"
#include "../encounters.h"
#include "../particle.h"
#include "../effect.h"
#include "../battleproperty.h"
#include "test.h"
#include "../SeasonI.h"
#include "../map.h"

extern std::array<Entity*,7> PARTY_MEMBER_STATS;
extern Entity::pstats_t partyMemberDefaultStats;
extern std::map<std::string,Decal*> SPRITES;
extern std::map<std::string,Animation*> ANIMATIONS;
extern std::array<Object*,4> PARTY_MEMBER_OBJ;
extern std::map<int,Object*> OBJ_INFO;
extern std::map<BattleMoveName,Battle::Move*>MOVELIST;
extern std::vector<Encounter*>ENCOUNTER_LIST;
extern std::map<Property,BattleProperty*> BATTLE_PROPERTIES;
extern std::map<MapName::Map,Map*> MAPS;

int testCount=0;

void Test(std::string name,bool success) {
	testCount++;
	std::cout<<"Test ("<<testCount<<") "<<name<<"..."<<std::endl;
	assert(success);
	std::cout<<"  Passed"<<std::endl;
};

void TestSpriteUndefined(std::vector<std::string> sprList) {
	for (int i=0;i<sprList.size();i++) {
		Test("Sprite "+sprList[i]+" should not be initialized",
			SPRITES[sprList[i]]==nullptr);
	}
}
void TestSpriteInitialized(std::vector<std::string> sprList) {
	for (int i=0;i<sprList.size();i++) {
		Test("Sprite "+sprList[i]+" should be initialized",
			SPRITES[sprList[i]]!=nullptr);
		Test("Sprite "+sprList[i]+" should have a non-zero width and height",
				SPRITES[sprList[i]]->sprite->width!=0&&SPRITES["terrainmap.png"]->sprite->height!=0);
	}
}

bool SeasonI::OnUserCreate(){
	for (int i=0;i<PARTY_MEMBER_STATS.size();i++) {
		Test("Party Member stats should not be initialized",
			PARTY_MEMBER_STATS[i]==nullptr);
	}
	Test("No battle moves are loaded",
		MOVELIST.size()==0);
	SetupPartyMemberStats();
	Test("Battle moves now exist (still not defined)",
		MOVELIST.size()>0);
	for (int i=0;i<PARTY_MEMBER_STATS.size();i++) {
		Test("Party Members get initialized with correct defaults",
			PARTY_MEMBER_STATS[i]->stats==partyMemberDefaultStats);
	}
	TestSpriteUndefined({"terrainmap.png","pixel.png","bufficons.png","rollingcounter.png","additionalFont.png"});
	SetupAnimations();
	TestSpriteInitialized({"terrainmap.png","pixel.png","bufficons.png","rollingcounter.png","additionalFont.png"});
	Test("Player animation is undefined",
		ANIMATIONS.count("player.png")==0);
	Test("Party Member Object is not setup",
		PARTY_MEMBER_OBJ[0]==nullptr);
	vd2d testPos={0,0},testScale={2,2};
	CreateObjectInfo(new Standard_Obj(PLAYER,"player",testPos,nullptr,testScale,MAGENTA,32),"player.png",32);
	Test("Player animation is defined",
		ANIMATIONS["player.png"]!=nullptr);
	Decal*playerAnimPointer=ANIMATIONS["player.png"]->spr;
	Test("Player animation is setup correctly",
		ANIMATIONS["player.png"]->frames>0&&ANIMATIONS["player.png"]->spr!=nullptr);
	Test("Object Info database now has 1 entry",
		OBJ_INFO.size()==1);
	Test("Object Info is correctly assigned",
		OBJ_INFO[0]->name=="player"&&OBJ_INFO[0]->id==PLAYER&&OBJ_INFO[0]->GetPos()==testPos&&OBJ_INFO[0]->GetScale()==testScale&&OBJ_INFO[0]->color==MAGENTA&&OBJ_INFO[0]->spr->width==32);
	delete OBJ_INFO[PLAYER];
	OBJ_INFO.erase(PLAYER);
	Test("Object Info is cleared",
		OBJ_INFO.size()==0);
	SetupObjectInfo();
	Test("Using the same sprite definition does not cause memory leak",
		playerAnimPointer==OBJ_INFO[0]->spr->spr);
	Test("No encounters are loaded",
		ENCOUNTER_LIST.size()==0);
	SetupEncounters();
	Test("SetupEncounters() loads up new encounters",
		ENCOUNTER_LIST.size()>0);
	Encounter*testEncounter=new Encounter(encounter::ENCOUNTER_1,{0,0},std::array<vd2d,4>{vd2d
		{grid(1,2)},{grid(2,2)},{grid(3,2)},{grid(4,2)}},
		std::vector<Entity*>{
			new Entity(new Standard_Obj(
				NPC1_4,"Test Obj",3,2,ANIMATIONS["player.png"]),
				{HP:70,maxHP:70,PP:10,maxPP:10,baseAtk:14,speed:5,resistances:{0,0,0,0}},std::vector<Battle::Move*>{
				MOVELIST[BattleMoveName::TESTMOVE1],
				MOVELIST[BattleMoveName::TESTMOVE2],
				MOVELIST[BattleMoveName::TESTMOVE3],
			},
			{ITEMLIST[ItemName::COOKIE]},
			돈 20),
			new Entity(new Standard_Obj(
				NPC1_4,"Test Obj 2",1,3,ANIMATIONS["player.png"]),
				{HP:70,maxHP:70,PP:10,maxPP:10,baseAtk:14,speed:5,resistances:{0,0,0,0}},std::vector<Battle::Move*>{
				MOVELIST[BattleMoveName::TESTMOVE1],
				MOVELIST[BattleMoveName::TESTMOVE2],
			},
			{ITEMLIST[ItemName::PIZZA]}),
			new Entity(new Standard_Obj(
				NPC1_4,"Test Obj 3",2,2,ANIMATIONS["player.png"]),
				{HP:70,maxHP:70,PP:10,maxPP:10,baseAtk:14,speed:5,resistances:{0,0,0,0}},std::vector<Battle::Move*>{
				MOVELIST[BattleMoveName::TESTMOVE1],
			}),
		});
	Test("Test encounter has three entities",
		testEncounter->objs.size()==3);
	Test("Test encounter uses ID ENCOUNTER_1",
		testEncounter->id==encounter::ENCOUNTER_1);
	testPos={0,0};
	Test("Test encounter uses position ("+std::to_string(testPos.x)+","+std::to_string(testPos.y)+")",
		testEncounter->pos==testPos);
	Test("Test encounter player positions are correct",
		testEncounter->playerPos==std::array<vd2d,4>{vd2d{grid(1,2)},{grid(2,2)},{grid(3,2)},{grid(4,2)}});
	Test("Test encounter grid positions are correct",
		testEncounter->playerPos==std::array<vd2d,4>{vd2d{grid(1,2)},{grid(2,2)},{grid(3,2)},{grid(4,2)}});
	Test("Enemy 1 has 3 battle moves",
		testEncounter->objs[0]->moveSet==std::vector<Battle::Move*>{
				MOVELIST[BattleMoveName::TESTMOVE1],
				MOVELIST[BattleMoveName::TESTMOVE2],
				MOVELIST[BattleMoveName::TESTMOVE3],
			});
	Test("Enemy 1 has a cookie as a drop",
		testEncounter->objs[0]->inventory==std::vector<Item*>{ITEMLIST[ItemName::COOKIE]});
	Test("Enemy 1 gives 20 money.",
		testEncounter->objs[0]->money==20);
	Test("Enemy 2 has 2 battle moves.",
		testEncounter->objs[1]->moveSet==std::vector<Battle::Move*>{
				MOVELIST[BattleMoveName::TESTMOVE1],
				MOVELIST[BattleMoveName::TESTMOVE2],
			});
	Test("Enemy 2 has a pizza as a drop",
		testEncounter->objs[1]->inventory==std::vector<Item*>{ITEMLIST[ItemName::PIZZA]});
	Test("Enemy 2 gives 0 money.",
		testEncounter->objs[1]->money==0);
	Test("Enemy 3 has 1 battle move.",
		testEncounter->objs[2]->moveSet==std::vector<Battle::Move*>{
				MOVELIST[BattleMoveName::TESTMOVE1],
			});
	Test("Enemy 3 has no items to drop",
		testEncounter->objs[2]->inventory==std::vector<Item*>{});
	Test("Enemy 3 gives 0 money.",
		testEncounter->objs[2]->money==0);
	Test("There are no battle properties",
		BATTLE_PROPERTIES.size()==0);
	SetupBattleProperties();
	Test("Battle Properties populated",
		BATTLE_PROPERTIES.size()>0);
	Test("Test Move 1 exists in the map.",
		MOVELIST.count(BattleMoveName::TESTMOVE1));
	Test("Test Move 1 is undefined",
		MOVELIST[BattleMoveName::TESTMOVE1]==nullptr);
	MOVELIST[BattleMoveName::TESTMOVE1]=new Battle::Move{"Test Move 1","An attack",baseDmg:30,randomDmg:5,range:6,channelTime:27,friendly:false};
	Test("Verify Test Move 1's now set properly",
		MOVELIST[BattleMoveName::TESTMOVE1]!=nullptr);
	Test("Verify Test Move 1's Power Name shows up correctly",
		MOVELIST[BattleMoveName::TESTMOVE1]->GetPowerName()=="Test Move 1");
	MOVELIST[BattleMoveName::TESTMOVE1]->grade=GAMMA;
	Test("Verify Test Move 1's Power Name shows up correctly with a grade",
		MOVELIST[BattleMoveName::TESTMOVE1]->GetPowerName()=="Test Move 1 "+std::string(1,(char)GAMMA));
	Test("Cookie is undefined",
		ITEMLIST[ItemName::COOKIE]==nullptr);
	SetupItemList();
	Test("Cookie is defined",
		ITEMLIST[ItemName::COOKIE]!=nullptr);
	delete ITEMLIST[ItemName::COOKIE];
	ITEMLIST.erase(ItemName::COOKIE);
	ITEMLIST[ItemName::COOKIE]=new Item("Cookie","A delightful little treat. Restores 40 HP.",2,{hpRecovery:40,consumable:Consumable::FRIENDLY,sellPrice:2});
	Test("Cookie name is set properly",
		ITEMLIST[ItemName::COOKIE]->name=="Cookie");
	Test("Cookie description is set properly",
		ITEMLIST[ItemName::COOKIE]->description=="A delightful little treat. Restores 40 HP.");
	Test("Cookie drop chance is set properly",
		ITEMLIST[ItemName::COOKIE]->dropChance==2);
	delete ITEMLIST[ItemName::FREEZE_PACKET];
	ITEMLIST.erase(ItemName::FREEZE_PACKET);
	ITEMLIST[ItemName::FREEZE_PACKET]=new Item("Freeze Packet","Lets out some blistering cold weather.",256,{consumable:Consumable::ENEMY,sellPrice:36},MOVELIST[BattleMoveName::FREEZE_PACKET]);
	Test("Freeze Packet name is set properly",
		ITEMLIST[ItemName::FREEZE_PACKET]->name=="Freeze Packet");
	Test("Freeze Packet description is set properly",
		ITEMLIST[ItemName::FREEZE_PACKET]->description=="Lets out some blistering cold weather.");
	Test("Freeze Packet drop chance is set properly",
		ITEMLIST[ItemName::FREEZE_PACKET]->dropChance==256);
	return true;
}

bool SeasonI::OnUserUpdate(float fElapsedTime)
{
	return true;
}

int main() {
	SeasonI demo;
	if (demo.Construct(WIDTH, HEIGHT, 4, 4))
		demo.Start();

	return 0;
}