#include <cmath>
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <windows.h>

using namespace std;

const int TRY_LIMIT = 1000;
const float FACTOR = 0.75f;

enum FORMAT
{
	FORMAT_SPACE_SEPARATED,
	FORMAT_COMMA_SEPARATED,
	FORMAT_CPP_CODE,
};

struct float2
{
	float x;
	float y;

	float2() { }
	float2(float x, float y) : x(x), y(y) { }
};

struct float3
{
	float x;
	float y;
	float z;

	float3() { }
	float3(float x, float y, float z) : x(x), y(y), z(z) { }
};

void Begin(int D, FORMAT Format)
{
	if (Format == FORMAT_CPP_CODE)
	{
		switch (D)
		{
		case 1:
			cout << "float PoissonDisc[] = {" << endl;
			break;
		case 2:
			cout << "VEC2 PoissonDisc[] = {" << endl;
			break;
		case 3:
			cout << "VEC3 PoissonDisc[] = {" << endl;
			break;
		}
	}
}

void End(int D, FORMAT Format, int C)
{
	if (Format == FORMAT_CPP_CODE)
	{
		cout << "};" << endl;
		cout << "const int POISSON_DISC_COUNT = " << C << ";" << endl;
	}
}

class Print_1D
{
private:
	FORMAT m_Format;

public:
	Print_1D(FORMAT Format) : m_Format(Format) { }

	void operator () (float x)
	{
		switch (m_Format)
		{
		case FORMAT_SPACE_SEPARATED:
		case FORMAT_COMMA_SEPARATED:
			cout << x << endl;
			break;
		case FORMAT_CPP_CODE:
			cout << "\t" << x << "f," << endl;
			break;
		}
	}
};

class Print_2D
{
private:
	FORMAT m_Format;

public:
	Print_2D(FORMAT Format) : m_Format(Format) { }

	void operator () (float2 x)
	{
		switch (m_Format)
		{
		case FORMAT_SPACE_SEPARATED:
			cout << x.x << " " << x.y << endl;
			break;
		case FORMAT_COMMA_SEPARATED:
			cout << x.x << "," << x.y << endl;
			break;
		case FORMAT_CPP_CODE:
			cout << "\tVEC2(" << x.x << "f, " << x.y << "f)," << endl;
			break;
		}
	}
};

class Print_3D
{
private:
	FORMAT m_Format;

public:
	Print_3D(FORMAT Format) : m_Format(Format) { }

	void operator () (float3 x)
	{
		switch (m_Format)
		{
		case FORMAT_SPACE_SEPARATED:
			cout << x.x << " " << x.y << " " << x.z << endl;
			break;
		case FORMAT_COMMA_SEPARATED:
			cout << x.x << "," << x.y << "," << x.z << endl;
			break;
		case FORMAT_CPP_CODE:
			cout << "\tVEC3(" << x.x << "f, " << x.y << "f, " << x.z << "f)," << endl;
			break;
		}
	}
};

class Pred_1D
{
private:
	float m_Point;
	float m_Epsilon;

public:
	Pred_1D(float Point, float Epsilon) : m_Point(Point), m_Epsilon(Epsilon) { }

	bool operator () (float x)
	{
		return (fabsf(x - m_Point) <= m_Epsilon);
	}
};

class Pred_2D
{
private:
	float2 m_Point;
	float m_Epsilon;

public:
	Pred_2D(float2 Point, float Epsilon) : m_Point(Point), m_Epsilon(Epsilon) { }

	bool operator () (float2 x)
	{
		float Distance = sqrtf(
			(m_Point.x - x.x)*(m_Point.x - x.x) +
			(m_Point.y - x.y)*(m_Point.y - x.y));
		return (Distance <= m_Epsilon);
	}
};

class Pred_3D
{
private:
	float3 m_Point;
	float m_Epsilon;

public:
	Pred_3D(float3 Point, float Epsilon) : m_Point(Point), m_Epsilon(Epsilon) { }

	bool operator () (float3 x)
	{
		float Distance = sqrtf(
			(m_Point.x - x.x)*(m_Point.x - x.x) +
			(m_Point.y - x.y)*(m_Point.y - x.y) +
			(m_Point.z - x.z)*(m_Point.z - x.z));
		return (Distance <= m_Epsilon);
	}
};

int Generate(int D, int C, FORMAT Format)
{
	srand(GetTickCount());

	if (D == 1)
	{
		std::vector<float> Points;
		int Tries;
		float NewPoint;
		float Epsilon = 0.5f;

		while ((int)Points.size() < C)
		{
			Tries = 0;

			do
			{
				NewPoint = (float)rand() / (float)RAND_MAX;
				Tries++;
			}
			while (std::find_if(Points.begin(), Points.end(), Pred_1D(NewPoint, Epsilon)) != Points.end() && Tries < TRY_LIMIT);

			if (Tries < TRY_LIMIT)
				Points.push_back(NewPoint);
			else
				Epsilon *= FACTOR;
		}

		Begin(D, Format);
		std::for_each(Points.begin(), Points.end(), Print_1D(Format));
		End(D, Format, C);
	}
	else if (D == 2)
	{
		std::vector<float2> Points;
		int Tries;
		float2 NewPoint;
		float Epsilon = 0.5f;

		while ((int)Points.size() < C)
		{
			Tries = 0;

			do
			{
				NewPoint = float2(
					(float)rand() / (float)RAND_MAX,
					(float)rand() / (float)RAND_MAX);
				Tries++;
			}
			while (std::find_if(Points.begin(), Points.end(), Pred_2D(NewPoint, Epsilon)) != Points.end() && Tries < TRY_LIMIT);

			if (Tries < TRY_LIMIT)
				Points.push_back(NewPoint);
			else
				Epsilon *= FACTOR;
		}

		Begin(D, Format);
		std::for_each(Points.begin(), Points.end(), Print_2D(Format));
		End(D, Format, C);
	}
	else if (D == 3)
	{
		std::vector<float3> Points;
		int Tries;
		float3 NewPoint;
		float Epsilon = 0.5f;

		while ((int)Points.size() < C)
		{
			Tries = 0;

			do
			{
				NewPoint = float3(
					(float)rand() / (float)RAND_MAX,
					(float)rand() / (float)RAND_MAX,
					(float)rand() / (float)RAND_MAX);
				Tries++;
			}
			while (std::find_if(Points.begin(), Points.end(), Pred_3D(NewPoint, Epsilon)) != Points.end() && Tries < TRY_LIMIT);

			if (Tries < TRY_LIMIT)
				Points.push_back(NewPoint);
			else
				Epsilon *= FACTOR;
		}

		Begin(D, Format);
		std::for_each(Points.begin(), Points.end(), Print_3D(Format));
		End(D, Format, C);
	}
	else
		return -1;

	return 0;
}

int main(int argc, char **argv)
{
	if (argc != 4)
	{
		cout << "Skadnia:" << endl;
		cout << "  PoissonDiscGenerator <d> <c> <f>" << endl;
		cout << endl;
		cout << "  d  Ile wymiarow: 1, 2, 3" << endl;
		cout << "  c  Ile punktow wygenerowac przyrostowo" << endl;
		cout << "  f  Format: space-separated, comma-separated, cpp-code" << endl;
		return -1;
	}

	FORMAT Format;
	if (argv[3] == string("space-separated"))
		Format = FORMAT_SPACE_SEPARATED;
	else if (argv[3] == string("comma-separated"))
		Format = FORMAT_COMMA_SEPARATED;
	else if (argv[3] == string("cpp-code"))
		Format = FORMAT_CPP_CODE;
	else
	{
		cout << "Invalid format." << endl;
		return -1;
	}

	int D = atoi(argv[1]);
	if (D < 1 || D > 3)
	{
		cout << "Invalid dimmension." << endl;
		return -1;
	}

	int C = atoi(argv[2]);
	if (C < 1)
	{
		cout << "Invalid point count." << endl;
		return -1;
	}

	return Generate(D, C, Format);
}
