module vs.action.field;



private import std.random;
private import std.cstream;
private import gedo.video.alphameshlist;
private import gedo.video.staticmesh;
private import gedo.video.texture;
public import gedo.video.skybox;
private import gedo.video.color;
private import gedo.video.rect;
private import gedo.video.ggl;
private import gedo.math.vector;
private import gedo.text.parser;
private import gedo.file.path;
private import vs.action.task;



public class Field : ActionTask
{
public:
	this(string[] meshPathes, string zmapPath)
	{
		_meshes.length = meshPathes.length;

		foreach(i, inout mesh; _meshes)
		{
			mesh = StaticMesh(meshPathes[i]);

			mesh.texture.wrap(1);
		}

		auto parser = new Parser(cast(string).gedo.file.path.read(zmapPath));

		auto nx = parser.geti();
		auto ny = parser.geti();

		_tileSize = parser.getf();

		_scale = SIZE / (_tileSize * nx);

		_tileSize *= _scale;

		_zmap.length = nx;
		foreach(inout zline; _zmap)
		{
			zline.length = ny;
			foreach(inout z; zline)
			{
				z = parser.getf() * _scale;
			}
		}
	}


	struct MeshSource
	{
		string texturePathV;
		string texturePathH;
		float  bottom;
		float  top;
	}


	this(MeshSource[] sources)
	{
		generateZMap();

		_meshes.length = sources.length*2;

		foreach(i, source; sources)
		{
			_meshes[2*i+0] = createVerticalMesh(Texture(source.texturePathV), source.bottom, source.top);
			_meshes[2*i+1] = createHorizontalMesh(Texture(source.texturePathH), source.bottom, source.top);
		}

		foreach(i, inout mesh; _meshes)
		{
			mesh.texture.wrap(1);
		}
	}


	float size()
	{
		return SIZE;
	}


	bool clip(Vector pp, Vector pc, Vector s)
	{
		auto result = false;

		Vector pn = pp[];

		foreach(i, p; pc[0 .. 2])
		{
			int[2][2] a;

			if(p > pp[i])
			{
				a[i][0] = a[i][1] = toTileIndex(p + s[i]);
			}
			else
			{
				a[i][0] = a[i][1] = toTileIndex(p - s[i]);
			}

			auto j = i^1;

			a[j][0] = toTileIndex(pn[j] - s[j]);
			a[j][1] = toTileIndex(pn[j] + s[j]);

			if(isSinked(a, pn[2] - s[2]))
			{
				if(p > pp[i])
				{
					pn[i] = toTileEdge(toTileIndex(pp[i]) + 1) - s[i] - 1.0/128.0;
				}
				else
				{
					pn[i] = toTileEdge(toTileIndex(pp[i])) + s[i] + 1.0/128.0;
				}
				result = true;
			}
			else
			{
				pn[i] = p;
			}

			pn[i] = clipedPosition(pn[i]);
		}

		pn[2] = pc[2];
		if(pc[2] < pp[2])
		{
			Vector2D box_min;
			Vector2D box_max;

			box_min[0] = pn[0] - s[0];
			box_min[1] = pn[1] - s[1];
			box_max[0] = pn[0] + s[0];
			box_max[1] = pn[1] + s[1];

			float z;
			if(getBoundary(box_min, box_max, pp[2] - s[2], pc[2] - s[2], z))
			{
				pn[2] = z + s[2] + 1.0/128.0;
				result = true;
			}
		}
		if(pn[2] > 120.0)
		{
			pn[2] = 120.0;
		}

		pc[] = pn[];
		return result;
	}


	bool isSinked(Vector p)
	{
		auto x = toTileIndex(p[0]);
		auto y = toTileIndex(p[1]);
		auto z = zmap(x, y);

		return (p[2] <= z);
	}


	bool isSinked(int[2][2] a, float z)
	{
		for(auto x=a[0][0]; x<=a[0][1]; ++x)
		{
			for(auto y=a[1][0]; y<=a[1][1]; ++y)
			{
				if(zmap(x, y) > z)
				{
					return true;
				}
			}
		}

		return false;
	}


	bool isSinked(Vector p0, Vector p1, Vector p2)
	{
		p2[] = p0[];

		for(auto a=_zmap.length+_zmap[0].length; a>0; --a)
		{
			Vector v;

			.gedo.math.vector.subtract(v, p1, p2);

			auto t = 1.0;
			if(v[0] > 0.0)
			{
				auto e = toTileEdge(toTileIndex(p2[0])+1);

				auto d = e - p2[0];
				if(v[0] > d)
				{
					t = d / v[0];
				}
			}
			else if(v[0] < 0.0)
			{
				auto e = toTileEdge(toTileIndex(p2[0]));

				auto d = e - p2[0];
				if(v[0] < d)
				{
					t = d / v[0];
				}
			}

			if(v[1] > 0.0)
			{
				auto e = toTileEdge(toTileIndex(p2[1])+1);

				auto d = e - p2[1];
				if(v[1] > d)
				{
					auto t2 = d / v[1];
					if(t2 < t)
					{
						t = t2;
					}
				}
			}
			else if(v[1] < 0.0)
			{
				auto e = toTileEdge(toTileIndex(p2[1]));

				auto d = e - p2[1];
				if(v[1] < d)
				{
					auto t2 = d / v[1];
					if(t2 < t)
					{
						t = t2;
					}
				}
			}

			auto z = zmap(toTileIndex(p2[0]), toTileIndex(p2[1]));
			if(v[2] >= 0.0)
			{
				if(p2[2] < z)
				{
					return true;
				}
			}
			else
			{
				auto t2 = (z - p2[2])/v[2];
				if(t2 <= t)
				{
					if(t2 > 0.0)
					{
						.gedo.math.vector.multiply(v, t2);
						.gedo.math.vector.add(p2, v);
					}

					return true;
				}
			}

			t += 1.0/128.0;
			if(t >= 1.0)
			{
				return false;
			}
			else
			{
				.gedo.math.vector.multiply(v, t);
				.gedo.math.vector.add(p2, v);
			}
		}

		return false;
	}


	override void draw()
	{
		GGL_enableDepthBuffer(1);

		GGL_enableAlphaBlend(0);

		GGL_clearDepthBuffer();

		if(_skybox)
		{
			_skybox.draw();
		}

		GGL_translate(0.0, 0.0, 0.5);

		Rect(0, 0, SIZE, SIZE).frame(4.0, rgba(128, 0, 0, 255));

		GGL_translate(0.0, 0.0, -0.5);

		GGL_pushMatrix();
		GGL_scale(_scale, _scale, _scale);
		foreach(mesh; _meshes)
		{
			mesh.draw();
		}
		GGL_popMatrix();
	}


	void skybox(SkyBox box)
	{
		_skybox = box;
	}


	float height(Vector p, float r)
	{
		float h = -float.infinity;
		auto zp = p[2] - r;

		auto sx = toTileIndex(p[0]-r);
		auto ex = toTileIndex(p[0]+r);
		auto sy = toTileIndex(p[1]-r);
		auto ey = toTileIndex(p[1]+r);
		for(auto x=sx; x<=ex; ++x)
		{
			for(auto y=sy; y<=ey; ++y)
			{
				auto z = zmap(x, y);
				if(z < zp && z > h)
				{
					h = z;
				}
			}
		}

		return h;
	}


private:
	static const float SIZE = 360.0;

	StaticMesh[] _meshes;
	float[][]    _zmap;
	SkyBox       _skybox;
	float        _scale;
	float        _tileSize;


	static float clipedPosition(float x)
	{
		if(x < -0.5*SIZE)
		{
			return -0.5*SIZE;
		}
		else if(x > 0.5*SIZE)
		{
			return 0.5*SIZE;
		}
		else
		{
			return x;
		}
	}


	bool getBoundary(Vector2D min, Vector2D max, float z0, float z1, out float zo)
	{
		auto sx = toTileIndex(min[0]);
		auto ex = toTileIndex(max[0]);
		auto sy = toTileIndex(min[1]);
		auto ey = toTileIndex(max[1]);

		auto zmin = zmap(sx, sy);
		for(auto x=sx; x<=ex; ++x)
		{
			for(auto y=sy; y<=ey; ++y)
			{
				auto z = zmap(x, y);
				if(z1 < z && z <= z0)
				{
					zo = z;
					return true;
				}
				else if(z < zmin)
				{
					zmin = z;
				}
			}
		}

		if(z1 < zmin)
		{
			dout.writefln("x0, y0, z0, x1, y1, z1 = %f, %f, %f, %f, %f, %f", min[0], min[1], z0, max[0], max[1], z1);
			zo = zmin;
			return true;
		}

		return false;
	}


	int toTileIndex(float x)
	{
		auto f = (x/_tileSize + 0.5*_zmap.length);
		if(f >= 0.0)
		{
			return cast(int)f;
		}
		else
		{
			return -1;
		}
	}


	float toTileCenter(int i)
	{
		return (i - 0.5*(_zmap.length-1)) * _tileSize;
	}


	float toTileEdge(int i)
	{
		return (i - 0.5*_zmap.length) * _tileSize;
	}


	float zmap(int x, int y)
	{
		if(x >= 0 && x < _zmap.length)
		{
			if(y >= 0 && y < _zmap[x].length)
			{
				return _zmap[x][y];
			}
		}

		return 65536.0;
	}


	void generateZMap()
	{
		createMatrix!(float)(_zmap, 15, 15);

		auto m = new Maze(_zmap.length, _zmap[0].length);

		foreach(x, inout zline; _zmap)
		{
			auto ix = x/5;
			foreach(y, inout z; zline)
			{
				if(m.isRoad(x, y))
				{
					z = 0.0;
				}
				else
				{
					z = 24.0;
				}

				auto iy = y/5;

				if((ix + iy) & 1)
				{
					z += 16.0;
				}
			}
		}
		_zmap[0][] = 0.0;
		_zmap[$-1][] = 0.0;
		foreach(inout zline; _zmap[1 .. $-1])
		{
			zline[0] = zline[$-1] = 0.0;
		}

		_tileSize = SIZE / _zmap.length;

		_scale    = 1.0;
	}


	StaticMesh createVerticalMesh(Texture texture, float bottom, float top)
	{
		GGL_Vertex[] vertices;
		GGL_Face[]   faces;

		/*
                 +-----+
                 |     |
		   +-----+-----+
		 x |     |     |
		   +-----|     |
		   |     |     |
		   |     |     |
		   +-----+-----+
		x-1|     |     |
		   +-----+-----+
		   |     |     |
		     y-1    y
		*/
		for(auto x=1; x<_zmap.length; ++x)
		{
			auto yo = 0;
			for(auto y=1; y<_zmap[x].length; ++y)
			{
				if(createVerticalQuad(x, yo, x, y, bottom, top, 0, 1, vertices, faces))
				{
					yo = y;
				}
			}
		}

		for(auto y=1; y<_zmap[0].length; ++y)
		{
			auto xo = 0;
			for(auto x=1; x<_zmap.length; ++x)
			{
				if(createVerticalQuad(xo, y, x, y, bottom, top, 1, 0, vertices, faces))
				{
					xo = x;
				}
			}
		}

		return new StaticMesh(vertices, faces, texture);
	}


	StaticMesh createHorizontalMesh(Texture texture, float bottom, float top)
	{
		GGL_Vertex[] vertices;
		GGL_Face[]   faces;
		bool[][]     isDone;

		createMatrix!(bool)(isDone, _zmap.length, _zmap[0].length+1);

		foreach(x, zline; _zmap)
		{
			foreach(y, z; zline)
			{
				if(z < bottom || z >= top)
				{
					isDone[x][y] = true;
				}
				else
				{
					isDone[x][y] = false;
				}
			}
			isDone[x][zline.length] = true;
		}

		foreach(x0, zline; _zmap)
		{
			auto y0 = 0;
			for(auto y1=1; y1<=zline.length; ++y1)
			{
				if(!isDone[x0][y0])
				{
					if(isDone[x0][y1] || zline[y0] != zline[y1])
					{
						auto x1 = x0+1;
						while(x1 < _zmap.length && isZ(_zmap[x1][y0 .. y1], zline[y0]))
						{
							++x1;
						}

						createHorizontalQuad(x0, y0, x1, y1, zline[y0], vertices, faces);
						for(auto x=x0; x<x1; ++x)
						{
							for(auto y=y0; y<y1; ++y)
							{
								isDone[x][y] = true;
							}
						}
					}
				}

				if(isDone[x0][y0] != isDone[x0][y1])
				{
					y0 = y1;
				}
			}
		}

		return new StaticMesh(vertices, faces, texture);
	}


	bool isZ(float[] zline, float z)
	{
		foreach(zc; zline)
		{
			if(zc != z)
			{
				return false;
			}
		}

		return true;
	}


	bool createVerticalQuad(int x0, int y0, int x1, int y1, float bottom, float top, int xd, int yd, inout GGL_Vertex[] meshVertices, inout GGL_Face[] meshFaces)
	{
		if(_zmap[x1-1][y1-1] != _zmap[x1-1+xd][y1-1+yd] || _zmap[x1-xd][y1-yd] != _zmap[x1][y1])
		{
			if(_zmap[x1-1][y1-1] != _zmap[x1-xd][y1-yd])
			{
				float y0f = toTileEdge(y0);
				float y1f = toTileEdge(y1);
				float x0f = toTileEdge(x0);
				float x1f = toTileEdge(x1);
				float z0f = _zmap[x1-1][y1-1];
				float z1f = _zmap[x1-xd][y1-yd];

				float h   = z1f;
				if(h < z0f)
				{
					h = z0f;
				}

				if(h >= bottom && h < top)
				{
					GGL_Vertex[4] vertices;

					vertices[0].position[] = [x0f, y1f, z0f];
					vertices[1].position[] = [x1f, y0f, z0f];
					vertices[2].position[] = [x1f, y0f, z1f];
					vertices[3].position[] = [x0f, y1f, z1f];

					foreach(inout vertex; vertices)
					{
						vertex.texCoord[0] = vertex.position[yd] / _tileSize;
						vertex.texCoord[1] = vertex.position[2] / _tileSize;
					}

					GGL_Face[2] faces;

					faces[0].vertexIndices[0] = meshVertices.length;
					faces[0].vertexIndices[1] = faces[0].vertexIndices[0] + 1;
					faces[0].vertexIndices[2] = faces[0].vertexIndices[0] + 2;

					faces[1].vertexIndices[0] = faces[0].vertexIndices[0] + 2;
					faces[1].vertexIndices[1] = faces[0].vertexIndices[0] + 3;
					faces[1].vertexIndices[2] = faces[0].vertexIndices[0];

					meshVertices ~= vertices;
					meshFaces    ~= faces;
				}
			}

			return true;
		}

		return false;
	}


	void createHorizontalQuad(int x0, int y0, int x1, int y1, float z, inout GGL_Vertex[] meshVertices, inout GGL_Face[] meshFaces)
	{
		float x0f = toTileEdge(x0);
		float x1f = toTileEdge(x1);
		float y0f = toTileEdge(y0);
		float y1f = toTileEdge(y1);

		GGL_Vertex[4] vertices;

		vertices[0].position[] = [x0f, y0f, z];
		vertices[1].position[] = [x1f, y0f, z];
		vertices[2].position[] = [x1f, y1f, z];
		vertices[3].position[] = [x0f, y1f, z];

		foreach(inout vertex; vertices)
		{
			vertex.texCoord[0] = vertex.position[0] / _tileSize;
			vertex.texCoord[1] = vertex.position[1] / _tileSize;
		}

		GGL_Face[2] faces;

		faces[0].vertexIndices[0] = meshVertices.length;
		faces[0].vertexIndices[1] = faces[0].vertexIndices[0] + 1;
		faces[0].vertexIndices[2] = faces[0].vertexIndices[0] + 2;

		faces[1].vertexIndices[0] = faces[0].vertexIndices[0] + 2;
		faces[1].vertexIndices[1] = faces[0].vertexIndices[0] + 3;
		faces[1].vertexIndices[2] = faces[0].vertexIndices[0];

		meshVertices ~= vertices;
		meshFaces    ~= faces;
	}
}



private class Maze
{
public:
	this(int w, int h)
	{
		w |= 1;
		h |= 1;

		w += 2;
		h += 2;

		createMatrix!(bool)(_isRoadLines, w, h);

		_isRoadLines[0][] = true;
		_isRoadLines[$-1][] = true;
		foreach(inout isRoadLine; _isRoadLines[1 .. $-1])
		{
			isRoadLine[0] = isRoadLine[$-1] = true;
		}

		_isRoadLines[2][2] = true;

		dig(2, 2);

		bool[][] buffer;
		buffer.length = h;
		foreach(inout line; buffer)
		{
			line.length = w;

			line[] = false;
		}

		for(auto i=1; i<w-1; ++i)
		{
			for(auto j=1; j<h-1; ++j)
			{
				if(_isRoadLines[i][j])
				{
					buffer[i][j] = true;
				}
				else if(!buffer[i][j])
				{
					auto count = 0;
					auto id    = 0;

					foreach(e, d; D)
					{
						if(!_isRoadLines[i+d[0]][j+d[1]])
						{
							++count;
							id = e;
						}
					}

					if(count == 1)
					{
						buffer[i][j] = buffer[i+D[id][0]][j+D[id][1]] = true;
					}
				}
			}
		}

		_isRoadLines = buffer;
	}


	bool isRoad(int i, int j)
	{
		return _isRoadLines[i+1][j+1];
	}


private:
	bool[][] _isRoadLines;


	void dig(int i, int j)
	{
		IndexArray a;

		a.initialize();

		while(a.count > 0)
		{
			auto d = a.pop();

			auto ii = i + D[d][0]*2;
			auto jj = j + D[d][1]*2;

			if(!_isRoadLines[ii][jj])
			{
				_isRoadLines[ii][jj] = true;
				_isRoadLines[i+D[d][0]][j+D[d][1]] = true;

				dig(ii, jj);
			}
		}
	}
}



// 1 2 3 4
// 1 2 4 3
// 1 3 2 4
// 1 3 4 2
// 1 4 2 3
// 1 4 3 2

// 0 <= a < 24
// a/6 (a%6)/2 a%2
private struct IndexArray
{
	ushort r;
	ushort c;


	void initialize()
	{
		c = 4;

		r = .std.random.rand() % 24;
	}


	int pop()
	{
		--c;

		return patterns[r][c];
	}


	int count()
	{
		return c;
	}


	static int[4][24] patterns;


	static this()
	{
		for(auto r=0; r<24; ++r)
		{
			int[4] a = [0, 1, 2, 3];

			void swap(int i, int j)
			{
				auto b = a[i];

				a[i] = a[j];

				a[j] = b;
			}

			swap(0, r/6);
			swap(1, 1 + (r%6)/2);
			swap(2, 2 + (r%2));

			patterns[r][] = a[];
		}
	}
}



private const int[2][4] D = [[-1, 0], [1, 0], [0, -1], [0, 1]];



private void createMatrix(T)(out T[][] matrix, uint nx, uint ny)
{
	T[] buffer;

	buffer.length = nx*ny;

	matrix.length = nx;

	foreach(i, inout line; matrix)
	{
		line = buffer[i*ny .. (i+1)*ny];
	}
}
