using System;

using Org.BouncyCastle.Crypto.Parameters;

namespace Org.BouncyCastle.Crypto.Engines
{
	public class VmpcEngine
		: IStreamCipher
	{
		/*
		* variables to hold the state of the VMPC engine during encryption and
		* decryption
		*/
		protected byte n = 0;
		protected byte[] P = null;
		protected byte s = 0;

		protected byte[] workingIV;
		protected byte[] workingKey;

		public virtual string AlgorithmName
		{
			get { return "VMPC"; }
		}

		/**
		* initialise a VMPC cipher.
		* 
		* @param forEncryption
		*    whether or not we are for encryption.
		* @param params
		*    the parameters required to set up the cipher.
		* @exception ArgumentException
		*    if the params argument is inappropriate.
		*/
		public virtual void Init(
			bool				forEncryption,
			ICipherParameters	parameters)
		{
			if (!(parameters is ParametersWithIV))
				throw new ArgumentException("VMPC Init parameters must include an IV");

			ParametersWithIV ivParams = (ParametersWithIV) parameters;
			KeyParameter key = (KeyParameter) ivParams.Parameters;

			if (!(ivParams.Parameters is KeyParameter))
				throw new ArgumentException("VMPC Init parameters must include a key");

			this.workingIV = ivParams.GetIV();

			if (workingIV == null || workingIV.Length < 1 || workingIV.Length > 768)
				throw new ArgumentException("VMPC requires 1 to 768 bytes of IV");

			this.workingKey = key.GetKey();

			InitKey(this.workingKey, this.workingIV);
		}

		protected virtual void InitKey(
			byte[]	keyBytes,
			byte[]	ivBytes)
		{
			s = 0;
			P = new byte[256];
			for (int i = 0; i < 256; i++)
			{
				P[i] = (byte) i;
			}

			for (int m = 0; m < 768; m++)
			{
				s = P[(s + P[m & 0xff] + keyBytes[m % keyBytes.Length]) & 0xff];
				byte temp = P[m & 0xff];
				P[m & 0xff] = P[s & 0xff];
				P[s & 0xff] = temp;
			}
			for (int m = 0; m < 768; m++)
			{
				s = P[(s + P[m & 0xff] + ivBytes[m % ivBytes.Length]) & 0xff];
				byte temp = P[m & 0xff];
				P[m & 0xff] = P[s & 0xff];
				P[s & 0xff] = temp;
			}
			n = 0;
		}

		public virtual void ProcessBytes(
			byte[]	input,
			int		inOff,
			int		len,
			byte[]	output,
			int		outOff)
		{
			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++)
			{
				s = P[(s + P[n & 0xff]) & 0xff];
				byte z = P[(P[(P[s & 0xff]) & 0xff] + 1) & 0xff];
				// encryption
				byte temp = P[n & 0xff];
				P[n & 0xff] = P[s & 0xff];
				P[s & 0xff] = temp;
				n = (byte) ((n + 1) & 0xff);

				// xor
				output[i + outOff] = (byte) (input[i + inOff] ^ z);
			}
		}

		public virtual void Reset()
		{
			InitKey(this.workingKey, this.workingIV);
		}

		public virtual byte ReturnByte(
			byte input)
		{
			s = P[(s + P[n & 0xff]) & 0xff];
			byte z = P[(P[(P[s & 0xff]) & 0xff] + 1) & 0xff];
			// encryption
			byte temp = P[n & 0xff];
			P[n & 0xff] = P[s & 0xff];
			P[s & 0xff] = temp;
			n = (byte) ((n + 1) & 0xff);

			// xor
			return (byte) (input ^ z);
		}
	}
}
