using System;

using Org.BouncyCastle.Crypto.Parameters;

namespace Org.BouncyCastle.Crypto.Engines
{
	/**
	* HC-256 is a software-efficient stream cipher created by Hongjun Wu. It 
	* generates keystream from a 256-bit secret key and a 256-bit initialization 
	* vector.
	* <p>
	* http://www.ecrypt.eu.org/stream/p3ciphers/hc/hc256_p3.pdf
	* </p><p>
	* Its brother, HC-128, is a third phase candidate in the eStream contest.
	* The algorithm is patent-free. No attacks are known as of today (April 2007). 
	* See
	* 
	* http://www.ecrypt.eu.org/stream/hcp3.html
	* </p>
	*/
	public class HC256Engine
		: IStreamCipher
	{
		private uint[] p = new uint[1024];
		private uint[] q = new uint[1024];
		private uint cnt = 0;

		private uint Step()
		{
			uint j = cnt & 0x3FF;
			uint ret;
			if (cnt < 1024)
			{
				uint x = p[(j - 3 & 0x3FF)];
				uint y = p[(j - 1023 & 0x3FF)];
				p[j] += p[(j - 10 & 0x3FF)]
					+ (RotateRight(x, 10) ^ RotateRight(y, 23))
					+ q[((x ^ y) & 0x3FF)];

				x = p[(j - 12 & 0x3FF)];
				ret = (q[x & 0xFF] + q[((x >> 8) & 0xFF) + 256]
					+ q[((x >> 16) & 0xFF) + 512] + q[((x >> 24) & 0xFF) + 768])
					^ p[j];
			}
			else
			{
				uint x = q[(j - 3 & 0x3FF)];
				uint y = q[(j - 1023 & 0x3FF)];
				q[j] += q[(j - 10 & 0x3FF)]
					+ (RotateRight(x, 10) ^ RotateRight(y, 23))
					+ p[((x ^ y) & 0x3FF)];

				x = q[(j - 12 & 0x3FF)];
				ret = (p[x & 0xFF] + p[((x >> 8) & 0xFF) + 256]
					+ p[((x >> 16) & 0xFF) + 512] + p[((x >> 24) & 0xFF) + 768])
					^ q[j];
			}
			cnt = cnt + 1 & 0x7FF;
			return ret;
		}

		private byte[] key, iv;
		private bool initialised;

		private void Init()
		{
			if (key.Length != 32)
				throw new ArgumentException("The key must be 256 bit long");

			cnt = 0;

			uint[] w = new uint[2560];

			for (int i = 0; i < 32; i++)
			{
				w[i >> 3] |= ((uint)key[i] << (i & 0x7));
			}

			for (int i = 0; i < iv.Length && i < 32; i++)
			{
				w[(i >> 3) + 8] |= ((uint)iv[i] << (i & 0x7));
			}

			for (uint i = 16; i < 2560; i++)
			{
				uint x = w[i - 2];
				uint y = w[i - 15];
				w[i] = (RotateRight(x, 17) ^ RotateRight(x, 19) ^ (x >> 10))
					+ w[i - 7]
					+ (RotateRight(y, 7) ^ RotateRight(y, 18) ^ (y >> 3))
					+ w[i - 16] + i;
			}

			Array.Copy(w, 512, p, 0, 1024);
			Array.Copy(w, 1536, q, 0, 1024);

			for (int i = 0; i < 4096; i++)
			{
				Step();
			}

			cnt = 0;
		}

		public string AlgorithmName
		{
			get { return "HC-256"; }
		}

		/**
		* Initialise a HC-256 cipher.
		*
		* @param forEncryption whether or not we are for encryption. Irrelevant, as
		*                      encryption and decryption are the same.
		* @param params        the parameters required to set up the cipher.
		* @throws ArgumentException if the params argument is
		*                                  inappropriate (ie. the key is not 256 bit long).
		*/
		public void Init(
			bool				forEncryption,
			ICipherParameters	parameters)
		{
			ICipherParameters keyParam = parameters;

			if (parameters is ParametersWithIV)
			{
				iv = ((ParametersWithIV)parameters).GetIV();
				keyParam = ((ParametersWithIV)parameters).Parameters;
			}
			else
			{
				iv = new byte[0];
			}

			if (keyParam is KeyParameter)
			{
				key = ((KeyParameter)keyParam).GetKey();
				Init();
			}
			else
			{
				throw new ArgumentException(
					"Invalid parameter passed to HC256 init - " + parameters.GetType().Name,
					"parameters");
			}

			initialised = true;
		}

		private byte[] buf = new byte[4];
		private int idx = 0;

		private byte GetByte()
		{
			if (idx == 0)
			{
				uint step = Step();
				buf[3] = (byte)step;
				step >>= 8;
				buf[2] = (byte)step;
				step >>= 8;
				buf[1] = (byte)step;
				step >>= 8;
				buf[0] = (byte)step;
			}
			byte ret = buf[idx];
			idx = idx + 1 & 0x3;
			return ret;
		}

		public void ProcessBytes(
			byte[]	input,
			int		inOff,
			int		len,
			byte[]	output,
			int		outOff)
		{
			if (!initialised)
				throw new InvalidOperationException(AlgorithmName + " not initialised");
			if ((inOff + len) > input.Length)
				throw new DataLengthException("input buffer too short");
			if ((outOff + len) > output.Length)
				throw new DataLengthException("output buffer too short");

			for (int i = 0; i < len; i++)
			{
				output[outOff + i] = (byte)(input[inOff + i] ^ GetByte());
			}
		}

		public void Reset()
		{
			idx = 0;
			Init();
		}

		public byte ReturnByte(byte input)
		{
			return (byte)(input ^ GetByte());
		}

		private static uint RotateRight(uint x, int bits)
		{
			return (x >> bits) | (x << -bits);
		}
	}
}
