#region === Copyright (c) 2010 Pascal van der Heiden ===

using System;
using System.Drawing;

#endregion

namespace CodeImp.DoomBuilder.Plugins.VisplaneExplorer
{
	// This is a 64x64 tile in map space which holds the point data results.
	internal class Tile
	{
		// Constants
		public const int TILE_SIZE = 64;
		public static readonly int[] STATS_COMPRESSOR = new[] { 1, 2, 1, 160 };
		public const uint POINT_MAXRANGE = 254;
		public const uint POINT_OVERFLOW = 0xFEFEFEFE;
		public const uint POINT_VOID = 0xFFFFFFFF;
		public const byte POINT_OVERFLOW_B = 0xFE;
		public const byte POINT_VOID_B = 0xFF;
		
		// Members
		private Point position;
		private uint[][] points;
		private int nextindex;
		
		// Properties
		public Point Position { get { return position; } }
		public bool IsComplete { get { return nextindex == (TILE_SIZE * TILE_SIZE); } }

		// Constructor
		public Tile(Point lefttoppos)
		{
			// Make the jagged array
			// I use a jagged array because, allegedly, it performs better than a multidimensional array.
			points = new uint[TILE_SIZE][];
			for(int y = 0; y < TILE_SIZE; y++)
				points[y] = new uint[TILE_SIZE];
			
			position = lefttoppos;
		}

		// This receives a processed point
		public void StorePointData(PointData pd)
		{
			uint t;
			switch(pd.result)
			{
				case PointResult.OK:
					uint vp = (uint)Math.Min((pd.visplanes + (STATS_COMPRESSOR[(int)ViewStats.Visplanes] - 1)) / STATS_COMPRESSOR[(int)ViewStats.Visplanes], POINT_MAXRANGE);
					uint ds = (uint)Math.Min((pd.drawsegs + (STATS_COMPRESSOR[(int)ViewStats.Drawsegs] - 1)) / STATS_COMPRESSOR[(int)ViewStats.Drawsegs], POINT_MAXRANGE);
					uint ss = (uint)Math.Min((pd.solidsegs + (STATS_COMPRESSOR[(int)ViewStats.Solidsegs] - 1)) / STATS_COMPRESSOR[(int)ViewStats.Solidsegs], POINT_MAXRANGE);
					uint op = (uint)Math.Min((pd.openings + (STATS_COMPRESSOR[(int)ViewStats.Openings] - 1)) / STATS_COMPRESSOR[(int)ViewStats.Openings], POINT_MAXRANGE);
					t = MakePointValue(vp, ds, ss, op);
					break;

				case PointResult.BadZ:
					t = MakePointValue(1, 0, 0, 0);
					break;
					
				case PointResult.Void:
					t = POINT_VOID;
					break;

				case PointResult.Overflow:
				default:
					t = POINT_OVERFLOW;
					break;
			}

			FillPoints(ref pd.point, t);
		}

		// This fills points with the given tile data over the specified granularity
		private void FillPoints(ref TilePoint p, uint t)
		{
			int xs = p.x - position.X;
			int ys = p.y - position.Y;
			int xe = xs + p.granularity;
			int ye = ys + p.granularity;
			for(int x = xs; x < xe; x++)
				for(int y = ys; y < ye; y++)
					points[y][x] = t;
		}

		// This composes point values
		private static uint MakePointValue(uint vp, uint ds, uint ss, uint op)
		{
			unchecked
			{
				return vp + (ds << 8) + (ss << 16) + (op << 24);
			}
		}

		// This returns a point value for the heatmap
		public byte GetHeatmapByte(int x, int y, int stat)
		{
			byte b = GetPointByte(x, y, stat);
			if (stat == (int)ViewStats.Visplanes && b != 0 && b != POINT_VOID_B)
				b = General.Map.Config.StaticLimits.InterpolateVisplanes(b);
			return b;
		}

		// This returns a point value
		public byte GetPointByte(int x, int y, int stat)
		{
			unchecked
			{
				uint v = points[y][x];
				return (byte)((v >> (stat * 8)) & 0xFF);
			}
		}
		
		// This returns a point value
		public int GetPointValue(int x, int y, int stat)
		{
			byte b = GetPointByte(x, y, stat);
			return b * STATS_COMPRESSOR[stat];
		}

		// This returns the next point to process
		public TilePoint GetNextPoint()
		{
			TilePoint p = PointByIndex(nextindex++);
			p.x += position.X;
			p.y += position.Y;
			return p;
		}

		// Returns a position by index
		private static TilePoint PointByIndex(int index)
		{
			#if DEBUG
			if(index > (TILE_SIZE * TILE_SIZE))
				throw new IndexOutOfRangeException();
			#endif

			TilePoint p;
			
			// Would be nicer if this could be done without branching or looping...
			if(index == 0) p.granularity = 64;
			else if(index < 4) p.granularity = 32;
			else if(index < 16) p.granularity = 16;
			else if(index < 64) p.granularity = 8;
			else if(index < 256) p.granularity = 4;
			else if(index < 1024) p.granularity = 2;
			else p.granularity = 1;
			
			// this is a "butterfly" style sequence, which begins like:
			//    ( 0  0)  (32 32)  ( 0 32)  (32  0)
			//    (16 16)  (48 48)  (16 48)  (48 16)
			//    ( 0 16)  (32 48)  ( 0 48)  (32 16)
			//    (16  0)  (48 32)  (16 32)  (48  0)
			//    ( 8  8)  (40 40)  ( 8 40)  (40  8)
			//    etc....

			p.x = (index & 1) << 5;
			p.y = (((index >> 1) ^ index) & 1) << 5;

			index >>= 2;
			p.x += (index & 1) << 4;
			p.y += (((index >> 1) ^ index) & 1) << 4;

			index >>= 2;
			p.x |= (index & 1) << 3;
			p.y |= (((index >> 1) ^ index) & 1) << 3;

			index >>= 2;
			p.x |= (index & 1) << 2;
			p.y |= (((index >> 1) ^ index) & 1) << 2;

			index >>= 2;
			p.x |= (index & 1) << 1;
			p.y |= (((index >> 1) ^ index) & 1) << 1;

			index >>= 2;
			p.x |= index & 1;
			p.y |= ((index >> 1) ^ index) & 1;

			return p;
		}
	}
}
