#ifndef __ABSTRACT_RENDERER_H__
#define __ABSTRACT_RENDERER_H__

#include "BVHTop.h"
#include "MaterialCache.h"
#include "RayCaster.h"
#include "RendererUtil.h"
#include "HaltonSampler.h"
#include "UniformSampler.h"
#include "FPSSampler.h"
#include "RenderTileData.h"
#include "RayTracerCommon.h"

//head light diffuse color
#define HEAD_LIGHT_COLOR Vector4(1.0f, 1.0f, 1.0f, 1.0f)

// use GLSL filtering for rendering the image
#define USE_GL_FILTER 1

// sampling strategies
#define HALTON16_STRATEGY 9
#define HALTON8_STRATEGY 10
#define HALTONFULL_STRATEGY 11
#define ADAPTIVE_UNIFORM_STRATEGY 12

// max recursion depth
#define MAX_RECURSION_DEPTH 8

namespace VRUT
{

	class RayTracer;
	class VisualDebugger;

	class AbstractRenderer
	{

	public:
		struct LightInfoEntry
		{
			NODE_ID lightID;
			Light light;
			Matrix inverseWorldMat;
			Vector3 position;
			Vector3 direction;
			IESLight * iesLight;
		};

	protected:
		int frameId;
		int maxLights;
		int maxRecursionDepth;
		int trianglesPerLeaf;
		int samplingStrategy;
		int samplesPerPixel;
		int rrStartDepth;
		bool headlight;
		Vector4 headLightColor;
		bool fullPolarization;
		bool castShadowRays;
		float shadowIntensity;
		bool sync;
		float rayOffset;

		bool rerender;

		int optRatio;
		int optThreshold;
		int optRandomThreshold;
		int optMaxIterations;

		std::vector<float> viewConeAngles;

		/// ray tracer module pointer
		RayTracer * rayTracer;

		/// hint whether to use path tracing for rendering
		bool usePathTracing;
#ifdef USE_TEMPORAL
		bool useTemporalPathTracing;
		int texturesUsed;
#endif
		unsigned int camerasCount; // Also corresponds to the count of tex data buffers
		RayTracerCamera **cameras;
		RayCaster rayCaster;
		smart_pointer_v2<Scene> scene;
		SceneShadow * sceneShadow;

#ifndef USE_TEMPORAL
		float ** texData; // Tex data buffers, all need to have the same size
#else
		float ***texData; // Tex data buffers, all need to have the same size
#endif
		unsigned int texWidth;
		unsigned int texHeight;

		std::atomic<unsigned int> samplesOverall;

		BVHTop bvh;
		MaterialCache materialCache;

		std::vector<AbstractSampler*> samplers;

		std::vector<LightInfoEntry> headlightInfo; // Headlights, one for each camera with the same index
		std::vector<LightInfoEntry> lightInfo; // Scene lights
		WX_DECLARE_STRING_HASH_MAP(IESLight, IESLightHashMap);
		IESLightHashMap iesLightMap;

		/// visual debugger pointer
		VisualDebugger *mVisualDebugger;

		/// vector of tiles to render when acting as server
		std::queue<RenderTileData *> tileRenderQueue;
		wxCriticalSection tileRenderQueueLock;

#ifndef USE_TEMPORAL
		void WriteColorToTexDataAdd(unsigned int dataIndex, unsigned int x, unsigned int y, const Vector4 & color)
		{
			unsigned int pos = ((texHeight - y - 1) * texWidth + x) * COLOR_COMPONENTS;
			texData[dataIndex][pos] += color.x;
			texData[dataIndex][pos + 1] += color.y;
			texData[dataIndex][pos + 2] += color.z;
#if COLOR_COMPONENTS == 4
			unsigned int tmp = *((unsigned int*) &texData[dataIndex][pos + 3]) + (unsigned int) color.w;
			texData[dataIndex][pos + 3] = *((float*) (&tmp));
#endif
		}
		void WriteColorToTexData(unsigned int dataIndex, unsigned int x, unsigned int y, const Vector4 & color)
		{
			unsigned int pos = ((texHeight - y - 1) * texWidth + x) * COLOR_COMPONENTS;
			texData[dataIndex][pos] = color.x;
			texData[dataIndex][pos + 1] = color.y;
			texData[dataIndex][pos + 2] = color.z;
#if COLOR_COMPONENTS == 4
			unsigned int tmp = (0x40000000) | ((frameId << 16) & 0x3FFF0000) | ((unsigned int) color.w & 0xFFFF);
			texData[dataIndex][pos + 3] = *((float*) (&tmp));
#endif
		}
#else
		void WriteColorToTexDataAdd(unsigned int dataIndex, unsigned int x, unsigned int y, const Vector4 & color, int tex)
		{
			unsigned int pos = ((texHeight - y - 1) * texWidth + x) * COLOR_COMPONENTS;
			texData[tex][dataIndex][pos] += color.x;
			texData[tex][dataIndex][pos + 1] += color.y;
			texData[tex][dataIndex][pos + 2] += color.z;
#if COLOR_COMPONENTS == 4
			unsigned int tmp = *((unsigned int*)&texData[tex][dataIndex][pos + 3]) + (unsigned int)color.w;
			texData[tex][dataIndex][pos + 3] = *((float*)(&tmp));
#endif


		}
		void WriteColorToTexData(unsigned int dataIndex, unsigned int x, unsigned int y, const Vector4 & color, int tex)
		{
			unsigned int pos = ((texHeight - y - 1) * texWidth + x) * COLOR_COMPONENTS;
			texData[tex][dataIndex][pos] = color.x;
			texData[tex][dataIndex][pos + 1] = color.y;
			texData[tex][dataIndex][pos + 2] = color.z;
#if COLOR_COMPONENTS == 4
			unsigned int tmp = (0x40000000) | ((frameId << 15) & 0xFFFF0000) | ((unsigned int)color.w & 0xFFFF);
			texData[tex][dataIndex][pos + 3] = *((float*)(&tmp));
#endif
		}
		void WriteColorToTexData4(unsigned int dataIndex, unsigned int x, unsigned int y, const Vector4 & color, int tex)
		{
			unsigned int pos = ((texHeight - y - 1) * texWidth + x) * COLOR_COMPONENTS;
			texData[tex][dataIndex][pos] = color.x;
			texData[tex][dataIndex][pos + 1] = color.y;
			texData[tex][dataIndex][pos + 2] = color.z;
			texData[tex][dataIndex][pos + 3] = color.w;
		}
#endif

		bool SamplePixels(Vector2 pixels[], Tile & tile);

		unsigned int MaxLODForSamples(int samplesPerPixel);

		void CacheHeadlightInfo();
		// cache information about lights
		void CacheLightInfo();
		bool HasLight(NODE_ID nodeID);

	public:

		AbstractRenderer(RayTracer *rayTracer);
		virtual ~AbstractRenderer(void);

#ifndef USE_TEMPORAL
		virtual void Render(SceneShadow * sceneShadow, unsigned int camerasCount, RayTracerCamera ** cameras, unsigned int texWidth, unsigned int texHeight, float ** texData, RayTracerBHV * bvh,
				bool & sceneChanged, bool & lightChanged) = 0;
#else
		virtual void Render(SceneShadow * sceneShadow, unsigned int camerasCount, RayTracerCamera ** cameras, unsigned int texWidth, unsigned int texHeight, float *** texData, RayTracerBHV * bvh,
			bool & sceneChanged, bool & lightChanged) = 0;
#endif
		virtual void WaitForFirstLOD() = 0;
		virtual void RenderImage(SceneShadow * sceneShadow, RayTracerCamera * camera, unsigned int texWidth, unsigned int texHeight, const wxString &filename, RayTracerBHV * bvh, bool &sceneChanged,
				bool & lightChanged, bool save, bool addImage, int cubeSide, const wxString & imageRefName) = 0;

		virtual void RenderSlave(SceneShadow * sceneShadow, unsigned int camerasCount, RayTracerCamera ** cameras, RayTracerBHV * bvh, bool & sceneChanged, bool & lightChanged) = 0;

		virtual void WaitUntilAllWorkDone(void) = 0;

		bool ShouldRerender(void);
		void Rerender(void);

		void DeleteGeometryCache(void);
		void InvalidateGeometryCacheEntry(unsigned int index);
		void InvalidateImageCacheEntry(unsigned int index);
		bool BuildGeoAcc(void);

		int GetMaxLights(void);
		void SetMaxLights(int maxLights);

		int GetMaxRecursionDepth(void);
		void SetMaxRecursionDepth(int maxRecursionDepth);

		int GetTrianglesPerLeaf(void);
		void SetTrianglesPerLeaf(int trianglesPerLeaf);

		int GetSamplingStrategy(void);
		void SetSamplingStrategy(int samplingStrategy);

		int GetSamplesPerPixel(void);
		void SetSamplesPerPixel(int samplesPerPixel);

		bool GetHeadlight(void);
		void SetHeadlight(const Vector4 &color);

		bool GetFullPolarization(void);
		void SetFullPolarization(bool fullPolarization);

		bool GetCastShadowRays(void);
		void SetShadowIntensity(float castShadowRaysAmount);

		bool GetSync(void);
		void SetSync(bool sync);

		float GetRayOffset(void);
		void SetRayOffset(float rayOffset);

		int GetRRStartDepth(void);
		void SetRRStartDepth(int rrStartDepth);

		void SetOptRatio(int optRatio);
		void SetOptThreshold(int optRandomThreshold);
		void SetOptRandomThreshold(int optTerminationThreshold);
		void SetOptMaxIteraions(int optMaxIterations);

		void SetViewConeAngles(wxArrayInt& coneArray);

		void SetUsePathTracing(const bool b)
		{
			usePathTracing = b;
		}
#ifdef USE_TEMPORAL
		void SetUseTemporalPathTracing(const bool b)
		{
			useTemporalPathTracing = b;
			texturesUsed = (useTemporalPathTracing) ? TEXTURES : TEXTURES_NOTERMPORAL;
		}
#endif

		float GetAvgSamples() const
		{
			return samplesOverall / float(texWidth * texHeight);
		}
		
		Vector3 GetNormalMapNormal(const Vector3& normalTBN, const Vector3& normal, const Vector3& tangent, const Vector3& bitangent) const;

		virtual void SetSlaveMode(const bool slaveMode) {}

		virtual void SetRemoteTiles(const int tiles) {}

		virtual void SetRenderThreads(const int threads) {}

		virtual void EnqueueReceivedTile(std::shared_ptr<RenderTileData> tileData) = 0;

		AbstractSampler* GetSampler(const Tile& tile);

		friend class RayTracer;
	};

}

#endif /* __ABSTRACT_RENDERER_H__ */
