﻿using System;

namespace Oni.Imaging
{
	internal static class Dxt1
	{
		public static Surface Decompress(Surface src, SurfaceFormat dstFormat)
		{
            var dst = new Surface(src.Width, src.Height, dstFormat);
			var colors = new Color[4];
			int srcOffset = 0;

			for (int y = 0; y < dst.Height; y += 4)
			{
				for (int x = 0; x < dst.Width; x += 4)
				{
					colors[0] = Color.ReadBgr565(src.Data, srcOffset);
					srcOffset += 2;

					colors[1] = Color.ReadBgr565(src.Data, srcOffset);
					srcOffset += 2;

					if (colors[0].ToBgr565() > colors[1].ToBgr565())
					{
						colors[2] = Color.Lerp(colors[0], colors[1], 1.0f / 3.0f);
						colors[3] = Color.Lerp(colors[0], colors[1], 2.0f / 3.0f);
					}
					else
					{
						colors[2] = Color.Lerp(colors[0], colors[1], 0.5f);
						colors[3] = Color.Transparent;
					}

					for (int y2 = 0; y2 < 4; y2++)
					{
						int packedLookup = src.Data[srcOffset++];

						for (int x2 = 0; x2 < 4; x2++)
						{
							dst[x + x2, y + y2] = colors[packedLookup & 3];
							packedLookup >>= 2;
						}
					}
				}
			}

			return dst;
		}

		public static Surface Compress(Surface src)
		{
			var dst = new Surface(Utils.Align4(src.Width), Utils.Align4(src.Height), SurfaceFormat.DXT1);

			var block = new Vector3[16];
			var colors = new Vector3[4];
			var lookup = new int[16];
			int dstOffset = 0;
            int height = dst.Height;
            int width = dst.Width;

			for (int y = 0; y < height; y += 4)
			{
				for (int x = 0; x < width; x += 4)
				{
					for (int by = 0; by < 4; by++)
					{
						for (int bx = 0; bx < 4; bx++)
							block[by * 4 + bx] = src[x + bx, y + by].ToVector3();
					}

					CompressBlock(block, lookup, colors);

					Color.WriteBgr565(new Color(colors[0]), dst.Data, dstOffset);
					dstOffset += 2;

					Color.WriteBgr565(new Color(colors[1]), dst.Data, dstOffset);
					dstOffset += 2;

					for (int by = 0; by < 4; by++)
					{
						int packedLookup = 0;

						for (int bx = 3; bx >= 0; bx--)
							packedLookup = (packedLookup << 2) | lookup[by * 4 + bx];

						dst.Data[dstOffset++] = (byte)packedLookup;
					}
				}
			}

			return dst;
		}

		private static void CompressBlock(Vector3[] block, int[] lookup, Vector3[] colors)
		{
			colors[0] = block[0];
			colors[1] = block[0];

			for (int i = 1; i < block.Length; i++)
			{
				colors[0] = Vector3.Min(colors[0], block[i]);
				colors[1] = Vector3.Max(colors[1], block[i]);
			}

			int maxColor;

			if (new Color(colors[0]).ToBgr565() > new Color(colors[1]).ToBgr565())
			{
				colors[2] = Vector3.Lerp(colors[0], colors[1], 1.0f / 3.0f);
				colors[3] = Vector3.Lerp(colors[0], colors[1], 2.0f / 3.0f);
				maxColor = 4;
			}
			else
			{
				colors[2] = Vector3.Lerp(colors[0], colors[1], 0.5f);
				maxColor = 3;
			}

			for (int i = 0; i < block.Length; i++)
			{
				lookup[i] = LookupNearest(colors, block[i], maxColor);
			}
		}

		private static int LookupNearest(Vector3[] colors, Vector3 pixel, int maxColor)
		{
			int index = 0;
			float ds = Vector3.DistanceSquared(pixel, colors[0]);

			for (int i = 1; i < maxColor; i++)
			{
				float newDs = Vector3.DistanceSquared(pixel, colors[i]);

				if (newDs < ds)
				{
					ds = newDs;
					index = i;
				}
			}

			return index;
		}
	}
}
