#ifndef __ABSTRACT_SAMPLER_H__
#define __ABSTRACT_SAMPLER_H__

#include "3dmath.h"
#include <atomic>
#include <wx/thread.h>
#include "RenderTileData.h"
#include "RayBundle.h"
#include "RayTracer.h"

#define LOG_TILE_SIZE 5
#define TILE_SIZE (1 << LOG_TILE_SIZE)
#define TILE_AREA (TILE_SIZE * TILE_SIZE)

#define MAX_SAMPLES (MAX_RAYS_IN_BUNDLE >> 1)

namespace VRUT
{
	
	class Halton
	{

	public:

		float Sample(int i, int base)
		{
			float r = 0;
			float f = 1.0f / base;
			int j = i;
			while (j > 0)
			{
				r += f * (j % base);
				j = floor(float(j) / base);
				f /= base;
			}
			return r;
		}

		Vector2 Sample2D(int i, int base1, int base2)
		{
			return Vector2(Sample(i,base1), Sample(i,base2));
		}

	};

	//http://en.wikipedia.org/wiki/Hilbert_curve
	class Hilbert2D
	{
	private:

		//rotate/flip a quadrant appropriately
		void rot(int n, int & x, int & y, int rx, int ry)
		{
			if (ry == 0) {
				if (rx == 1) {
					x = n-1 - x;
					y = n-1 - y;
				}
				//Swap x and y
				int t  = x;
				x = y;
				y = t;
			}
		}

	public:

		//convert (x,y) to d
		int xy2d(int n, int x, int y)
		{
			int rx, ry, s, d=0;
			for (s=n/2; s>0; s/=2) {
				rx = (x & s) > 0;
				ry = (y & s) > 0;
				d += s * s * ((3 * rx) ^ ry);
				rot(s, x, y, rx, ry);
			}
			return d;
		}

		//convert d to (x,y)
		void d2xy(int n, int d, int & x, int & y)
		{
			int rx, ry, s, t=d;
			x = y = 0;
			for (s=1; s<n; s*=2) {
				rx = 1 & (t/2);
				ry = 1 & (t ^ rx);
				rot(s, x, y, rx, ry);
				x += s * rx;
				y += s * ry;
				t /= 4;
			}
		}

	};


	struct Tile
	{
		Vector2i min;
		Vector2i max;
		int LOD;
		std::atomic<int> skip; // 0 means nobody is processing this tile. Positive number is number of required processing requests
		unsigned int offset;
		unsigned int samples;
		unsigned int samplesOverall;
		bool antialiasing;
		unsigned int cameraIndex;
		Tile(void) {}
		unsigned int Size(void) const { return unsigned((max.x - min.x) * (max.y - min.y)); }
		void Reset(void) { offset = 0; }
		bool Complete(int samplesPerPixel)
		{ 
			float threshold = float(samplesPerPixel);
			if (threshold == 0.0f)
				threshold = 1.0f;
			else if (threshold < 0.0f)
				threshold = -1.0f / threshold;
			return samplesOverall / float(Size()) >= threshold; 
		}
	};


	typedef std::vector<Vector2> SampleBuffer;

	class AbstractSampler
	{

	protected:

		std::vector<SampleBuffer> samples;
		std::vector<SampleBuffer> subSamples;

	public:

        //helper function for coordinates conversion for pixel map
        inline static uint64_t Vec2iToU64(int x, int y)
        {
            uint64_t t;
            int32_t* ptr = (int32_t*)&t;
            ptr[0] = x;
            ptr[1] = y;

            return t;
        }

        inline static Vector2i U64ToVec2i(uint64_t val)
        {
            int32_t* ptr = (int32_t*)&val;
            int x = ptr[0];
            int y = ptr[1];

            return Vector2i(x,y);
        }

        inline static uint64_t Vec2iToU64(const Vector2i& v)
        {
            return Vec2iToU64(v.x, v.y);
        }

		AbstractSampler(void) {}
		virtual ~AbstractSampler(void) {}

        //Loads pixel coordinates into 
		virtual bool SamplePixels(Vector2 pixels[], Tile & tile) = 0;

        //Takes tile and pixel array, adds pixel color to appropriate sparse pixel array
        //virtual void AddToRemoteTilePixel(const Tile* tile, Vector4* pixelsColors, const Vector2& pixelCoords, const Vector4& color, unsigned int LOD, unsigned int processingRounds) = 0;

        //Creates a pixel indexing map
        //If false is returned, map is not necessary - pixel array in tile is not sparse (1:1)
        //If rebuilding the map on master, skip value is no longer valid - map construction is different
        //If building on slave, numPixels is <=0, on master it should be set to returned pixel array size
        virtual bool GenPixMapIsNecessary(std::map<uint64_t, unsigned int>& m, const Tile* tile, int numPixels) = 0;

        //Takes sparse remote tile and expands it
        //E.g interpretes colors in the tile to respective pixels
        //virtual void ExpandSparseRemoteTile(const Tile* tile, unsigned int LOD, const std::vector<Vector4>& inColors, Vector4* outColors) = 0;

		unsigned int SamplesLODs(void) { return samples.size(); }
		unsigned int SubSamplesLODs(void) { return subSamples.size(); }
		unsigned int LODForSamples(int samplesPerPixel)
		{
			// Calculate required number of samples for a tile
			unsigned int requiredSampleCount = 0;
			if (samplesPerPixel == 0)
			{
				requiredSampleCount = TILE_AREA;
			}
			else if (samplesPerPixel > 0)
			{
				requiredSampleCount = TILE_AREA * samplesPerPixel;
			}
			else
			{
				requiredSampleCount = IntDivCeil(TILE_AREA, -samplesPerPixel);
			}
			// Iterate through sample and sub-sample buffers and calculate LOD
			unsigned int lod = 0;
			unsigned int sampleCount = 0;
			for (auto &sample : samples)
			{
				sampleCount += sample.size();
				++lod;
				if (sampleCount >= requiredSampleCount)
				{
					return lod;
				}
			}
			for (auto &subSample : subSamples)
			{
				sampleCount += subSample.size();
				++lod;
				if (sampleCount >= requiredSampleCount)
				{
					return lod;
				}
			}
			// Not enought subsamples, but at least return max LOD
			return lod;
		}

		friend class PathRenderer;
	};

	typedef std::pair<Vector2, int> HilbertPair;

	struct HilbertComparator {
		bool operator() (const HilbertPair & a, const HilbertPair & b) { return a.second < b.second; }
	};

}

#endif /* __ABSTRACT_SAMPLER_H__ */
