using System;

namespace SpriteWave
{
	public class ColorPattern : ColorTable
	{
		// RGBA pattern
		private byte[] _rgbaOrder, _rgbaOrderInv;
		private byte[] _rgbaDepth;

		/*
			RGBA Order And Depth: A formula descriptor for how to arrange different formats of RGBA.
			The input parameter as passed as the integer 0xrgbaRGBA where:
				r = position of red channel (0-3)
				g = position of green channel (0-3)
				b = position of blue channel (0-3)
				a = position of alpha channel (0-3)
				R = depth (number of bits) for the red channel (0-8)
				G = depth for the green channel (0-8)
				B = depth for the blue channel (0-8)
				A = depth for the alpha channel (0-8)
			Each digit takes up 4 bits in the input parameter.
		*/
		public ColorPattern(uint rgbaOrderAndDepth, uint[] defSel)
		{
			_rgbaOrder = new byte[4];
			_rgbaOrderInv = new byte[4];
			_rgbaDepth = new byte[4];

			uint cCfg = rgbaOrderAndDepth;

			// Here we take each of the 8 hex digits inside 'cCfg' and place them in their appropriate elements
			for (int i = 0; i < 4; i++)
			{
				int shift = (4 + (3 - i)) * 4;
				uint order = Utils.GetBits(cCfg, 4, shift);
				if (order > 3)
					throw new ArgumentException("RGBA Order Error:\nInvalid order index " + order + " for the " + Utils.RGBANames[i] + " channel (" + i + ")");
	
				uint depth = Utils.GetBits(cCfg, 4, shift - 16);
				if (depth > 8)
					throw new ArgumentException("RGBA Depth Error:\nInvalid bit depth " + depth + " for the " + Utils.RGBANames[i] + " channel (" + i + ")");
	
				_rgbaOrder[i] = (byte)order;
				_rgbaDepth[i] = (byte)depth;
			}

			// We make sure that _rgbaOrder is bijective (1:1 correspondence) to the set {0, 1, 2, 3}
			// This means _rgbaOrder will be bijective with its inverted set (_rgbaOrderInv), a fact we will rely upon later
			for (int i = 0; i < 4; i++)
			{
				int chn = -1, freq = 0;
				for (int j = 0; j < 4; j++)
				{
					if (_rgbaOrder[j] == i)
					{
						chn = j;
						freq++;
					}
				}

				if (chn < 0)
					throw new ArgumentException("RGBA Order Error:\nNo channel for position " + i + " was given");
				if (freq != 1)
					throw new ArgumentException("RGBA Order Error:\nMultiple channels were attempted to placed in the same position");

				_rgbaOrderInv[i] = (byte)chn;
			}

			_defSel = defSel;
		}

		public override uint NativeToRGBA(uint idx)
		{
			/*
				We turn our 'index' into an RGBA color using our 'rgbaOrderAndDepth' formula descriptor.
				This is something I came up with, so a bit of explanation is always nice.
				I've noticed there are two common attributes of RGBA color that are
				often tailored to particular pixel formats: order of channels and bits per channel.
				'_rgbaOrder' tells us the order of channels as they appear in 'idx', and
				'_rgbaDepth' tells us how many bits are used for the R, G, B and A channels respectively.
			*/

			// This for loop takes the channel values out of 'idx' and places them in RGBA order in the process
			int[] chVals = new int[4];
			for (int i = 3; i >= 0; i--)
			{
				int which = _rgbaOrderInv[i];
				int depth = _rgbaDepth[which];
				chVals[which] = (int)idx & ((1 << depth) - 1);
				idx >>= depth;
			}

			/*
				This loop takes each channel value from above and pads it out as evenly as possible,
				while placing it in the final RGBA value we return.
			*/
			uint rgba = 0;
			for (int i = 0; i < 4; i++)
			{
				int depth = _rgbaDepth[i];
				if (depth == 0)
				{
					if (i != 3)
						rgba <<= 8;

					continue;
				}

				/*
					If we continuously pad the remaining bits with the existing bits, it produces the smoothest pattern.
					Eg.
						00 -> 00000000, 01 -> 01000000, 10 -> 10000000, 11 -> 11000000
					is not as smooth/even as
						00 -> 00000000, 01 -> 01010101, 10 -> 10101010, 11 -> 11111111
					, the latter of which this while loop achieves.
				*/
				int val = 0;
				int left = 8;
				while (left > 0)
				{
					int pad = chVals[i];
					if (left < depth)
					{
						pad >>= (depth - left);
						depth = left;
					}

					val <<= depth;
					val |= pad;

					left -= depth;
				}
				val &= 0xff;

				// Place the new channel value in the final output
				rgba |= (uint)val;
				if (i != 3)
					rgba <<= 8;
			}

			return rgba;
		}

		public override uint RGBAToNative(uint rgba)
		{
			uint native = 0;
			for (int i = 0; i < 4; i++)
			{
				int which = _rgbaOrderInv[i];
				int depth = _rgbaDepth[which];

				uint channel = Utils.ColorAt(rgba, _rgbaOrder[3 - i] * 8);
				uint val = channel >> (8 - depth);

				native <<= depth;
				native |= val;
			}

			return native;
		}
	}
}