#pragma once
#include "olcPixelGameEngine.h"
#include "Animation.h"
#include "olcUTIL_Animate2D.h"
#include "Monster.h"
#include "olcPGEX_TransformedView.h"
#include "Player.h"
#include "olcUTIL_Camera2D.h"
#include "Bullet.h"
#include "Effect.h"
#include "Map.h"
#include "TMXParser.h"
#include "olcUTIL_DataFile.h"
#include "Key.h"

class Crawler : public olc::PixelGameEngine
{
	friend class State_GameRun;
	friend class sig::Animation;
	Camera2D camera;
	std::unique_ptr<Player>player;
public:
	Pathfinding pathfinder;
	static InputGroup KEY_ATTACK;
	static InputGroup KEY_LEFT;
	static InputGroup KEY_RIGHT;
	static InputGroup KEY_UP;
	static InputGroup KEY_DOWN;
	static float SIZE_CHANGE_SPEED;
	float levelTime;
private:
	std::vector<std::unique_ptr<Effect>>foregroundEffects,backgroundEffects,foregroundEffectsToBeInserted,backgroundEffectsToBeInserted;
	std::vector<TileRenderData*>tilePreparationList,tileForegroundList;
	std::vector<vf2d>circleCooldownPoints;
	std::map<MapName,Map>MAP_DATA;
	std::map<std::string,TilesetData>MAP_TILESETS;
	vf2d worldShake={};
	float worldShakeTime=0;
	float lastWorldShakeAdjust=0;
	vf2d worldShakeVel={};
	const float WORLD_SHAKE_ADJUST_MAX_TIME=0.4;
	MapName currentLevel=MapName::CAMPAIGN_1_1;
	std::vector<TileGroup>foregroundTileGroups;
	std::vector<TileGroup>upperForegroundTileGroups;
	int bridgeLayerIndex=-1;
	float bridgeFadeFactor=0.f;
	void InitializeClasses();
	int DEBUG_PATHFINDING=0;
	std::vector<Monster*>monstersBeforeLower,monstersAfterLower,monstersBeforeUpper,monstersAfterUpper;
	std::vector<Bullet*>bulletsLower,bulletsUpper;
	std::vector<Effect*>backgroundEffectsLower,backgroundEffectsUpper,foregroundEffectsLower,foregroundEffectsUpper;
	float reflectionUpdateTimer=0;
	float reflectionStepTime=0;
	std::set<vi2d>visibleTiles;
	float bossDisplayTimer=0;
	std::string bossName;
	int totalDamageDealt=0;
	float encounterDuration=0;
	bool encounterStarted=false;
	int totalBossEncounterMobs=0;

	std::vector<Monster>monstersToBeSpawned;
public:
	Crawler();
	bool OnUserCreate() override;
	bool OnUserUpdate(float fElapsedTime) override;
	bool OnUserDestroy() override;
public:
	geom2d::rect<int>NO_COLLISION={};
	TileTransformedView view;
	void InitializeLevel(std::string mapFile,MapName map);
	void LoadLevel(MapName map);
	void HandleUserInput(float fElapsedTime);
	void UpdateCamera(float fElapsedTime);
	void UpdateEffects(float fElapsedTime);
	void UpdateBullets(float fElapsedTime);
	void RenderWorld(float fElapsedTime);
	void RenderHud();
	void RenderMenu();
	void AddEffect(std::unique_ptr<Effect>foreground,std::unique_ptr<Effect>background);
	//If back is true, places the effect in the background
	void AddEffect(std::unique_ptr<Effect>foreground,bool back=false);
	void HurtEnemies(vf2d pos,float radius,int damage,bool upperLevel,float z);
	vf2d GetWorldMousePos();
	bool LeftHeld();
	bool RightHeld();
	bool UpHeld();
	bool DownHeld();
	bool LeftReleased();
	bool RightReleased();
	bool UpReleased();
	bool DownReleased();
	Player*GetPlayer();
	void SetupWorldShake(float duration);
	//tileID is the tile number from the tilesets.
	bool IsForegroundTile(TilesheetData sheet,int tileID);
	//tileID is the tile number from the tilesets.
	bool IsUpperForegroundTile(int tileID);
	//tileID is the tile number from the tilesets.
	TilesheetData GetTileSheet(MapName map,int tileID);
	//Gets the rectangle of the tile collision at this tile. If upperLevel is set to true, the collision tile must be in a Bridge class layer for the tile to hit. Also, zones containing LowerBridgeCollision will apply when upperLevel is set to false.
	geom2d::rect<int>GetTileCollision(MapName map,vf2d pos,bool upperLevel=false);
	//Checks if the point resides inside of a collision tile.
	bool HasTileCollision(MapName map,vf2d pos,bool upperLevel=false);
	MapName GetCurrentLevel();
	bool IsBridgeLayer(LayerTag&layer);
	std::map<std::string,std::vector<geom2d::rect<int>>>&GetZoneData(MapName map);
	void PopulateRenderLists();
	void ChangePlayerClass(Class cl);
	std::string GetString(std::string key);
	datafilestringdata GetStringList(std::string key);
	int GetInt(std::string key);
	datafileintdata GetIntList(std::string key);
	float GetFloat(std::string key);
	datafilefloatdata GetFloatList(std::string key);
	double GetDouble(std::string key);
	datafiledoubledata GetDoubleList(std::string key);
	static void OutputDebugInfo(const char*key,std::size_t len);
	void InitializeLevels();
	void RenderTile(vi2d pos,TilesheetData tileSheet,int tileSheetIndex,vi2d tileSheetPos);
	void RenderTile(TileRenderData&tileSheet,Pixel col);
	bool IsReflectiveTile(TilesheetData tileSheet,int tileID);
	void SpawnMonster(vf2d pos,MonsterData*data,bool upperLevel=false,bool isBossSpawn=false); //Queues a monster for spawning on the next frame.
	void DrawPie(vf2d center,float radius,float degreesCut,Pixel col);
	void RenderCooldowns();
	void InitializeDefaultKeybinds();
	void SetBossNameDisplay(std::string name,float time=5);
	bool InBossEncounter();
	void StartBossEncounter();
	void DisplayBossEncounterInfo();
	void BossDamageDealt(int damage);
	void ReduceBossEncounterMobCount();
	void InitializeGraphics();
	MapTag GetCurrentMap();

	struct TileGroupData{
		vi2d tilePos;
		int layer;
		bool operator<(const TileGroupData&rhs)const{
			return layer<rhs.layer||(layer==rhs.layer&&tilePos<rhs.tilePos);
		}
	};
};