#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;
	bool Update(float fElapsedTime);
};

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;
};

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;
		float colorFactor = 0;
		Phase phase=Phase::DEFAULT;
	public:
		float turnAmt = 0;
		Enemy(EnemyID id, vec3d pos, float rot, float radius);
		EnemyID GetID();
		void Hurt();
		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();
		void ReloadBullet(float fElapsedTime);
};

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::vector<Enemy>enemies;
	private:
	Mesh mapWalls,mapFloor,enemy_ShootMe,undefined,
		enemy_Sonar, mapExit,enemy_IShoot;

	Decal* dot, * enemy_ShootMe_tex,*bullet_tex,*wall_tex,*floor_tex,
		*enemy_Sonar_tex,*hud,*exit_wall_tex,*enemy_IShoot_tex;
	vi2d MAP_SIZE;
	vi2d exitCoords = { 0,0 };
	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;

	mat4x4 matProj;

	vec3d vLookDir;

	float zOffset = 2;

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

	Player player = { {3.7,0.3,0.7}, {{0.5,0.5},0.2} };
	Object walls;
	Object exit;
	vec3d freeRoamCamera = { 1,0.5,1 };
	float freeRoamCamera_pitch = pitch;
	float freeRoamCamera_yaw = fYaw;

	float moveSpd = 2.0f;
	float hudOffset = 0;
	float hudOffsetAcc = 0;
	float screenAlpha = 0;

	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 InitializeBulletColors();
	void LoadLevel(int level);
	void RenderBulletMesh(mat4x4& matView, std::vector<Triangle>& vecTrianglesToRaster, Bullet& b);
	void RenderMesh(mat4x4&matView, std::vector<Triangle>&vecTrianglesToRaster,Object&o);
	void RunEnemyAI(Enemy& e,float fElapsedTime);
	void RenderHud(float fElapsedTime);
	void ConvertBulletColor(Mesh& bullet, Pixel col);
	public:
	std::vector<Bullet>bullets;
	Mesh bullet;
	float shotSpd = 4.0f;
	static bool CheckCollision(vec3d movementVector,vf2d pos,float radius);
	static int CheckEnemyCollision(vec3d movementVector, vf2d pos, float radius);
	void SubtractTag();
};