#pragma once
#include "pixelGameEngine.h"
#include "Editor.h"
#include "geometry2d.h"
#include <optional>
using namespace olc;

const float PI = 3.14159f;

enum GAMEMODE {
	GAME,
	EDITOR,
	LEVELCOMPLETE
};

enum Direction {
	NORTH = 1,
	EAST = 2,
	SOUTH = 4,
	WEST = 8
};

struct vec2d
{
	float u = 0;
	float v = 0;
	float w = 1;
};


struct vec3d
{
	float x = 0;
	float y = 0;
	float z = 0;
	float w = 1;
	friend std::ostream& operator << (std::ostream& os, const vec3d& rhs) { os << rhs.x << "," << rhs.y << "," << rhs.z; return os; }
};

struct Player {
	private:
	vec3d pos;
	olc::utils::geom2d::circle<float> playerR;
	public:
	Player(vec3d pos,olc::utils::geom2d::circle<float>r):pos(pos), playerR(r) {}
	void UpdatePos(vec3d pos) {
		this->pos = pos;
		playerR.pos = { pos.x,pos.z };
	}
	float GetRadius() {
		return playerR.radius;
	}
	vec3d&GetPos() {
		return pos;
	}
};

struct Triangle
{
	vec3d p[3];
	vec2d uv[3];
	Pixel col[3];
	Decal* tex;
};

struct MapSquare {
	Decal* wallN = NULL;
	Decal* wallE = NULL;
	Decal* wallS = NULL;
	Decal* wallW = NULL;
};

struct Mesh
{
	std::vector<Triangle> tris;
	Decal* texture = NULL;

	Mesh() {}

	Mesh(std::string filename, Decal* tex)
		:texture(tex) {
		LoadFromObjectFile(filename,tex);
	}

private:
	void Parse(std::string str, int& v, int& uv) {
		//std::cout << str << "\n";
		std::stringstream s(str.substr(0, str.find("/") + 1));
		s >> v;
		str.erase(0, str.find("/") + 1);
		std::stringstream s2(str.substr(0, str.find("/") + 1));
		s2 >> uv;
		//std::cout<<" "<<v<<"/"<<uv<<"\n";
	}

	bool LoadFromObjectFile(std::string sFilename,Decal*tex)
	{
		std::ifstream f(sFilename);
		if (!f.is_open())
			return false;

		// Local cache of verts
		std::vector<vec3d> verts;
		std::vector<vec2d> uvs;

		std::string data;
		while (f.good()) {
			f >> data;
			if (data == "v") {
				float x, y, z;
				f >> x >> y >> z;
				verts.push_back({ x,y,z });
				//std::cout << x << " " << y << " " << z << "\n";
			}
			else
				if (data == "vt") {
					float u, v;
					f >> u >> v;
					uvs.push_back({ u,1 - v });
					//std::cout << u << " " << v << "\n";
				}
				else
					if (data == "f") {
						//std::cout<<"face\n";
						std::string t1, t2, t3;
						f >> t1 >> t2 >> t3;
						int v1, v2, v3, uv1, uv2, uv3;
						Parse(t1, v1, uv1);
						Parse(t2, v2, uv2);
						Parse(t3, v3, uv3);
						tris.push_back({ verts[v1 - 1],verts[v2 - 1],verts[v3 - 1],
							uvs[uv1 - 1],uvs[uv2 - 1],uvs[uv3 - 1],{WHITE,WHITE,WHITE},tex });
					}

		}

		return true;
	}

};

struct Object {
	Mesh mesh;
	vec3d pos = { 0,0 };
	float rot = 0;
	float radius = 0.2f;
};

struct Bullet : Object{
	vf2d spd = { 0,0 };
	Pixel col = GREEN;
	bool friendly=true;
	EnemyID shooterID;
	int shooterIndex = -1;
	bool shooterBlinking = false;
	bool Update(float fElapsedTime);
};

struct Powerup : Object {
	PowerupType type;
	Powerup(Mesh mesh, vec3d pos, float rot, PowerupType type);
	Pixel col;
	float colorCycle=1;
	float aliveTime=0;
	bool opened = false;
	bool Update(float fElapsedTime);
};

struct PowerupData {
	std::string name;
	Pixel col;
};

struct EnemyData {
	std::string name;
	Mesh mesh;
	Pixel col=YELLOW;
	int health = 1;
	float movSpd = 1;
	float rotSpd = PI / 8;
	int ammo = 2;
	float fireDelay = 1;
	float radius = 0.2f;
	bool explosive = false; //Explodes on contact.
	PowerupType powerupDrop=PowerupType::NONE;
	bool blinking = false;
};

struct mat4x4
{
	float m[4][4] = { 0 };
};

enum class Phase {
	DEFAULT,
	TURNING,
};

struct Enemy : public Object {
	private:
		EnemyID id;
		int health=1;
		std::vector<float> shots;
		float fireDelay=0;
		float deathTimer=0;
		bool exploded = false; //If contacted and explosive, then explosion flag is set instead.
		float colorFactor = 0;
		Phase phase=Phase::DEFAULT;
		float lastHitTime = 0;
		bool blinking = false;
		float aliveTime = 0;
	public:
		float turnAmt = 0;
		bool flippedTriangles = false;
		bool finishedAnimation = false;
		float blinkingAmt = 1; //>0.5 the enemy is visible.
		vi2d roundedCoords = vi2d{int(pos.x),int(pos.z)};
		Enemy(EnemyID id, vec3d pos, float rot, float radius);
		EnemyID GetID();
		//Can set the damage to 0 to cause just a visual hit.
		void Hurt(int damage=1);
		bool isDead();
		void increaseDeathTimer(float fElapsedTime);
		bool deathAnimationOver();
		void increaseColorFactor(float fElapsedTime);
		float getColorFactor();
		void decreaseColorFactor();
		Phase GetPhase();
		void SetPhase(Phase phase);
		bool CanShoot();
		void ShootBullet(int myIndex);
		void ReloadBullet(float fElapsedTime);
		bool isLastHitTimerActive();
		void reduceLastHitTimer(float fElapsedTime);
		bool isExplosive();
		void setExploded(bool exploded);
		bool isBlinking();
		bool isExploded();
		bool Update(float fElapsedTime);
		void OnDeathEvent();
		vf2d GetPlayerPosition();
		Enemy freshCopy(bool randomizeLoc=false);
};

class FaceBall : public PixelGameEngine
{
	bool freeRoam = false;
	public:
	FaceBall()
	{
		sAppName = "3D Demo";
	}

	EnemyData GetData(EnemyID id);
	Decal* circle,*arrow,*YAZAWA;
	std::map<EnemyID, EnemyData>enemyData;
	std::map<PowerupType, PowerupData>powerupData;
	std::vector<Enemy>enemies;
	std::vector<Enemy>wave2Enemies;
	std::vector<Enemy>wave3Enemies;
	std::vector<Powerup>powerups;
	private:
	Mesh mapWalls,mapFloor,enemy_ShootMe,undefined,
		enemy_Sonar, mapExit,enemy_ShootMe2,powerup,powerup2,
		enemy_IShoot;

	Sprite*mapSpr;
	Decal*mapDecal;

	vf2d minimapGridSize={16,16};

	Decal* dot, * enemy_ShootMe_tex,*bullet_tex,*wall_tex,*floor_tex,
		*enemy_Sonar_tex,*hud,*exit_wall_tex,*enemy_ShootMe2_tex,*enemy_IShoot_tex,
		*life4,*life3,*life2,*life1,*crosshair,*hudmeter,*powerup_tex,*powerup2_tex,
		*powerups_tex;
	vi2d exitCoords = { 0,0 };
	std::vector<vf2d>knownGoodPositions;
	std::vector<std::vector<MapSquare>>map;
	std::vector<Object>objects;
	GAMEMODE mode=GAMEMODE::GAME;
	Editor editor;
	int MapWallsObjectIndex = -1;
	bool exitWallsCleared = false;
	int level=1;
	int tagsRemaining = 10;
	int lives = 3;
	double gameTimer = 0;
	int lastPowerupCollidedWith = -1;
	std::string hudDisplayText = "";
	std::string lookingAtText = "";
	float stopDuration = 0,shieldDuration=0,camoDuration=0;
	bool hasMapUpgrade = false;
	std::array<Pixel,14>colorCycle={
		RED,
		BLACK,
		YELLOW,
		WHITE,
		GREEN,
		WHITE,
		CYAN,
		BLACK,
		MAGENTA,
		BLACK,
		GREY,
		WHITE,
		BLUE,
		BLACK
	};
	int topTargetColInd,botTargetColInd;
	Pixel topTargetCol,botTargetCol;
	Pixel topCurrentCol,botCurrentCol;
	Pixel topCol,botCol;
	float colTransferAmt=0;

	mat4x4 matProj;

	vec3d vLookDir;

	float zOffset = 2;

	float fTheta = 0;
	float fYaw = 0;
	float pitch = -PI / 6;

	std::array<vf2d,4> meter_ARMOR = {
		vf2d{1039,218},
		vf2d{1040,260},
		vf2d{1166,244},
		vf2d{1164,195}
	},
		meter_SPEED = {
		vf2d{1038,360},
		vf2d{1038,405},
		vf2d{1167,408},
		vf2d{1167,360}
	},
		meter_SHOTS = {
		vf2d{1035,512},
		vf2d{1032,558},
		vf2d{1162,575},
		vf2d{1163,525}
	};
public:
	Player player = { {3.7,0.3,0.7}, {{0.5,0.5},0.2} };
	vi2d playerRoundedCoords = {int(player.GetPos().x),int(player.GetPos().z)};
	const int baseHP = 3;
	int hp = baseHP;
	int maxHP=hp;
	int score = 0;
	int lastAwardedScore = 0;
	Object walls;
	Object exit;
	vec3d freeRoamCamera = { 1,0.5,1 };
	float freeRoamCamera_pitch = pitch;
	float freeRoamCamera_yaw = fYaw;

	int armorUpgrades = 0, maxArmorUpgrades=20;
	int speedUpgrades = 0, maxSpeedUpgrades=5;
	int shotsUpgrades = 0, maxShotsUpgrades=3;

	const float baseMoveSpd = 2.0f;
	const float baseTurnSpd = 2.0f;
	const float baseShotSpd = 4.0f;
	int baseShotLimit = 2;

	int shotLimit = baseShotLimit;
	float moveSpd = baseMoveSpd;
	float turnSpd = baseTurnSpd;
	float hudOffset = 0;
	float hudOffsetAcc = 0;
	float hudShakeAmt = 0;
	Pixel screenCol = WHITE;
	float screenAlpha = 0;
	float screenAlpha2 = 255.f;
	float respawnTimer = 0;
	vec3d spawnLoc = { 0,0.3,0 };
	FacingDirection spawnFacingDir = FacingDirection::NORTH;

	EnemyID lastHitBy=EnemyID::NONE;
	bool lastHitByBlinking = false;

	int triRenderCount = 0;

	vec3d Matrix_MultiplyVector(mat4x4& m, vec3d& i);
	mat4x4 Matrix_MakeIdentity();
	mat4x4 Matrix_MakeRotationX(float fAngleRad);
	mat4x4 Matrix_MakeRotationY(float fAngleRad);
	mat4x4 Matrix_MakeRotationZ(float fAngleRad);
	mat4x4 Matrix_MakeTranslation(float x, float y, float z);
	mat4x4 Matrix_MakeProjection(float fFovDegrees, float fAspectRatio, float fNear, float fFar);
	mat4x4 Matrix_MultiplyMatrix(mat4x4& m1, mat4x4& m2);
	mat4x4 Matrix_PointAt(vec3d& pos, vec3d& target, vec3d& up);
	mat4x4 Matrix_QuickInverse(mat4x4& m);
	vec3d Vector_Add(vec3d& v1, vec3d& v2);
	vec3d Vector_Sub(vec3d& v1, vec3d& v2);
	vec3d Vector_Mul(vec3d& v1, float k);
	vec3d Vector_Div(vec3d& v1, float k);
	float Vector_DotProduct(vec3d& v1, vec3d& v2);
	float Vector_Length(vec3d& v);
	vec3d Vector_Normalise(vec3d& v);
	vec3d Vector_CrossProduct(vec3d& v1, vec3d& v2);
	vec3d Vector_IntersectPlane(vec3d& plane_p, vec3d& plane_n, vec3d& lineStart, vec3d& lineEnd, float& t);
	int Triangle_ClipAgainstPlane(vec3d plane_p, vec3d plane_n, Triangle& in_tri, Triangle& out_tri1, Triangle& out_tri2);
	void RenderWorld();
	void HandleKeys(float fElapsedTime);
	void AddWall(int dir, vi2d gridSquare);
	bool OnUserCreate() override;
	bool OnUserUpdate(float fElapsedTime) override;
	void OnTextEntryComplete(const std::string& sText) override;
	void InitializeEnemyData();
	void InitializePowerupColors();
	void LoadLevel(int level);
	void RenderBulletMesh(mat4x4& matView, std::vector<Triangle>& vecTrianglesToRaster, Bullet& b,bool translucent=true);
	void RenderMesh(mat4x4&matView, std::vector<Triangle>&vecTrianglesToRaster,Object&o,bool translucent=false);
	void RenderMeshDeathScreen(mat4x4& matView, std::vector<Triangle>& vecTrianglesToRaster, Object& o);
	void RenderPowerupMesh(mat4x4& matView, std::vector<Triangle>& vecTrianglesToRaster, Powerup&p);
	void RunEnemyAI(Enemy& e,float fElapsedTime,int myIndex);
	void RenderHud(float fElapsedTime);
	void ConvertBulletColor(Mesh& bullet, Pixel col);
	void Display3DKillerModel();
	int CheckPowerupCollision(vec3d movementVector, vf2d pos, float radius);
	void ResetScore();
	int EnemiesAlive();
	public:
	vi2d MAP_SIZE;
	float restingTriangleYDepth = 0.f;
	std::vector<Bullet>bullets;
	float hudShakeTime = 0;
	Mesh bullet;
	float shotSpd = baseShotSpd;
	static bool CheckCollision(vec3d movementVector,vf2d pos,float radius);
	static int CheckEnemyCollision(vec3d movementVector, vf2d pos, float radius, int ignoreIndex = -1);
	static int CheckExplosiveEnemyCollision(vec3d movementVector, vf2d pos, float radius, int ignoreIndex = -1);
	static bool CheckPlayerCollision(vec3d movementVector, vf2d pos, float radius);
	void SubtractTag();
	void HurtPlayer(EnemyID id, int damage=1,bool blinking = false);
	void SpawnPowerup(PowerupType type, vec3d pos);
	bool PlayerHasCamo();
	vf2d GetPlayerPos();
	void AddScore(int score);
	vf2d GetRandomizedSpawnPosition();
	bool PlayerHasMapUpgrade();
	void RegenerateMinimap();
};