#ifndef _ENGINE_H_
#define _ENGINE_H_

#include "../Framework3/OpenGL/OpenGLRenderer.h"
#include "../Framework3/Util/Model.h"
#include "../Framework3/Util/BSP.h"
#include "../Framework3/Util/Queue.h"
#include "../Framework3/Math/Frustum.h"
#include "../Framework3/Util/Thread.h"
#include "../Framework3/Audio/Audio.h"

struct Material;
struct Cube;
struct Rocket;

#define THREAD_COMPUTE_VISIBLE_CUBES 0

// Material flags
#define SELFSHADOW 0x1
#define EMISSIVE   0x2
#define SKYBOX     0x4

#define PARTICLE_COUNT 32

struct MaterialDesc {
	char *name;
	float parallax;
	float gloss;
	uint flags;
};

// Light flags
#define NOSHADOWS 0x1
#define INVISIBLE 0x2


#define EXPLOSION_TEXTURES 1

struct Light {
	Light(const vec3 &pos, const float rad, const vec3 &col, const float vol, const float corona, const float flicker, const uint flgs = 0){
		position = pos;
		lightRadius = rad;
		color = vec4(col, 1);

		shadowMap = TEXTURE_NONE;
		volumetricy = vol;

		fov = wx = wy = 0;

		flickerFactor = flicker;

		coronaIntensity = corona;
		currentCoronaIntensity = 0;
		query = 0;

		flags = flgs;
	}
	Light(const vec3 &pos, const float iwx, const float iwy, const float ifov, const float rad, const vec3 &col, const float vol, const float corona, const float flicker, const uint flgs = 0){
		position = pos;
		lightRadius = rad;
		color = vec4(col, 1);

		shadowMap = TEXTURE_NONE;
		volumetricy = vol;

		fov = ifov;
		wx = iwx;
		wy = iwy;

		flickerFactor = flicker;

		coronaIntensity = corona;
		currentCoronaIntensity = 0;
		query = 0;

		flags = flgs;
	}
	~Light(){
		if (query) glDeleteQueriesARB(1, &query);
	}
	void setupClipPlanes();

	vec3 position;
	float lightRadius;
	vec4 color;
	float volumetricy;
	TextureID shadowMap;
	float fov, wx, wy;
	float flickerFactor;

	float sortDist;
	float coronaIntensity;
	float currentCoronaIntensity;
	GLuint query;
	uint flags;

	bool isVisible;
};

struct ParticleSystem {
	ParticleSystem(const vec3 &pos, const float h, const float s, const vec3 &rTrans = vec3(1, 0, 0), const vec3 &gTrans = vec3(0, 1, 0), const vec3 &bTrans = vec3(0, 0, 1)){
		position = pos;
		height = h;
		size = s;

		red   = rTrans;
		green = gTrans;
		blue  = bTrans;
	};
	vec3 position;
	float height;
	float size;
	vec3 red, green, blue;
};

struct AmbientSound {
	vec3 position;
	SoundSourceID source;
};

class Engine : public Thread {
public:
	Engine();
	virtual ~Engine();

	bool collision(const vec3 &p0, const vec3 &p1, vec3 *point = NULL, vec3 *normal = NULL, float *offset = NULL) const;
	bool push(vec3 &pos, const float radius) const;
	bool isInOpenSpace(const vec3 &pos) const;

	void makeKickerSound(const vec3 &kickerPos, const float volume){
		audio->setSourcePosition(boingSource, kickerPos);
		audio->setSourceGain(boingSource, volume);
		audio->play(boingSource);
	}

	void addLight(Light *light){
		lights.addLast(light);
	}
	void addParticleSystem(ParticleSystem *pSys){
		//pSys->soundSource = audio->addSoundSource(fireSound, LOOPING);
		//audio->setSourcePosition(pSys->soundSource, pSys->position);
		//audio->setSourceAttenuation(pSys->soundSource, 0.06f, 3.0f);
		particleSystems.addLast(pSys);

		addAmbientSoundSource(fireSound, pSys->position);
	}

	SoundID addAmbientSound(char *fileName){
		return audio->addSound(fileName);
	}
	void addAmbientSoundSource(const SoundID sound, const vec3 &position){
		AmbientSound as;

		as.source = audio->addSoundSource(sound, LOOPING);
		as.position = position;
		audio->setSourcePosition(as.source, position);
		audio->setSourceAttenuation(as.source, 0.06f, 3.0f);

		ambientSounds.add(as);
	}

	void addRocket(const vec3 &camPos, const vec3 &dir);
	void updateRockets(const float frameTime, vec3 &camPos, vec3 &camDir, const float soundGain);

	bool loadGeometry(const char *srcModel, const float cSize, bool useCache);
	void save() const;

	bool makeDrawable(Renderer *renderer, MaterialDesc *matDesc, const uint nMats, const RasterizerStateID cullBackState, const DepthStateID noDepthTestState, const DepthStateID noDepthWriteState, const SamplerStateID linearClampState);
	void unmakeDrawable(Renderer *renderer);

	void computeVisibility(Renderer *renderer, const int n, bool useCache);
	void updateVisibility(Renderer *renderer, const vec3 &camPos, const int width, const int height, const int resW, const int resH);

	void updateAudio(const vec3 &camPos, const vec3 &zDir, const float soundGain, const float musicGain);

private:
	void computeVisibleCubes(const int thread, const uint dstCubeIndex, const int x, const int y, const int z);
	void processMessage(const int thread, const int message, void *data, const int size);


	// Debug
	void drawVisibleCubes(Renderer *renderer, Cube *cube, const bool invert);


	void drawSkyBox(Renderer *renderer, const mat4 &mvp, const vec3 &camPos, Cube *cube);
	void drawGun(Renderer *renderer, const mat4 &mvp, const mat4 &modelview, const mat4 &gunMV, const vec3 &camPos);
	void drawAmbientEmissive(Renderer *renderer, const mat4 &mvp, const vec3 &camPos, const Frustum &frustum, Cube *cube);
	void drawLighting(Renderer *renderer, const mat4 &mvp, const mat4 &modelview, const vec3 &camPos, const Frustum &frustum, const int width, const int height, const float time, Cube *cube);
	void drawVolumetricLighting(Renderer *renderer, const mat4 &mvp, const mat4 &modelview, const mat4 &gunMV, const vec3 &camPos, const Frustum &frustum, const int width, const int height, const float frameTime, Cube *cube);
	void drawLights(Renderer *renderer, const mat4 &projection, const mat4 &modelview, const bool debug = false);
	void drawExplosions(Renderer *renderer, const mat4 &projection, const mat4 &modelview);
	void drawParticleSystems(Renderer *renderer, const mat4 &mvp, const mat4 &modelview, const float time, const Frustum &frustum);
public:
	float draw(Renderer *renderer, const vec3 &camPos, const float wx, const float wy, const float time, const float frameTime, const float gunZ, const int width, const int height, const bool debug, const bool invert);


	void setClipPlanesEnable   (const bool enable){ useClipPlanes       = enable; }
	void setScissorEnable      (const bool enable){ useScissor          = enable; }
	void setStencilBranchEnable(const bool enable){ useStencilBranching = enable; }
	void setSpecularEnable     (const bool enable){ useSpecular         = enable; }
	void setParallaxEnable     (const bool enable){ useParallax         = enable; }
	void setShadowEnable       (const bool enable){ useShadow           = enable; }
	void setSelfShadowEnable   (const bool enable){ useSelfShadow       = enable; }
	void setVolumetricEnable   (const bool enable){ useVolumetric       = enable; }

protected:
	void precomputeCube(const int tx, const int ty, const int tz);
	int getCube(const vec3 &pos) const;
	bool getCubeCoords(const vec3 &pos, int &x, int &y, int &z) const;

	void drawSelected(Renderer *renderer, Cube *cube, const int mat = -1, const int slot = 0);

	TextureID prerenderShadowMap(Renderer *renderer, const Light *light, const int size) const;
	void updateShadowMap(Renderer *renderer, Light *light, const RasterizerStateID cullBack);

	TextureID getAmbientEmissiveShader(Renderer *renderer, const bool hasDiffuse, const bool hasEmissive, const bool hasParallax);
	TextureID ambientEmissive[2][2][2];

	TextureID getLitShader(Renderer *renderer, const bool isSpotLight);
	TextureID litShader[2];

	TextureID getLightingShader(Renderer *renderer, const bool isSpotLight, const bool hasSpecular, const bool hasParallax, const bool hasShadow, const bool hasSelfShadow);
	TextureID lightingShader[2][2][2][2][2];

	TextureID getWeaponShader(Renderer *renderer, const bool isSpotLight, const bool hasShadow);
	TextureID weaponShader[2][2];



	Model *global;

	Cube *cubes;
	float cubeSize;
	Queue <Light *> lights;
	Queue <Rocket *> rockets;
	Queue <ParticleSystem *> particleSystems;

	BSP bsp;


	int sizeX, sizeY, sizeZ;
	vec3 minLim, maxLim;

	Material *materials;
	uint nMaterials;

	ShaderID depth, volumetric, weaponVolumetric, skyBoxShader, particleShader;
	TextureID lightTex, angleMap, explosion[EXPLOSION_TEXTURES], spot;

	// Render targets
	TextureID omniShadowMapColorRT, spotShadowMapColorRT, shadowMapDepthRT;

	BlendStateID blendAdd, blendOneInvSrcAlpha, blendOneInvSrcColor, blendInvDstColorOne, noColorWrite;	

	RasterizerStateID cullBack;
	DepthStateID noDepthTest, noDepthWrite;
	SamplerStateID linearClamp, bilinearClamp, trilinearClamp, trilinearAniso, linearLod;


	Model *rocketLauncher;

	VertexBufferID particleBuffer;
	VertexFormatID particleFormat;


	float fov;
	bool useClipPlanes, useScissor, useStencilBranching;
	bool useSpecular, useParallax, useShadow, useSelfShadow, useVolumetric;

	intptr *indexOffsets;
	GLint *indexCounts;
	uint indexSize;
	GLenum indexType;

	// Audio stuff
	Audio *audio;
	Array <AmbientSound> ambientSounds;

	SoundID fireSound;
	SoundID boomSound;
	SoundID boingSound;
	SoundSourceID boingSource;
	SoundID shootSound;
	SoundSourceID shootSource;
	SoundID music;
	SoundSourceID musicSource;

	bool needsSave;
#if USE_OCC_QUERY_WORKAROUND
	bool useOccWorkaround;
#else
#define useOccWorkaround false
#endif


	// Stats
	int nDrawnBatches;
	int nDrawnLights;
	int nDrawnVolumeLights;
public:
	int getDrawnLights() const { return nDrawnLights; }
	int getDrawnVolumeLights() const { return nDrawnVolumeLights; }
};

#endif
