using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace skyscraper8.Skyscraper.Security.Cryptography
{
	/// <summary>
	/// Inspired by https://github.com/tsduck/tsduck/blob/master/src/libtsduck/crypto/tsDVBCSA2.cpp
	/// </summary>
	internal class DvbCsa2
	{
		public const int KEY_BITS = 64;             //!< DVB CSA-2 control words size in bits.
		public const int KEY_SIZE = KEY_BITS / 8;   //!< DVB CSA-2 control words size in bytes.
		public const int BLOCK_SIZE = 8;
		public const int MAX_NBLOCKS = (184 / 8);

		public enum EntropyMode
		{
			FULL_CW,        //!< Keep the full 64-bit control word.
			REDUCE_ENTROPY  //!< Reduce the entropy of the control word to 48 bits.
		};

		public DvbCsa2(EntropyMode mode = EntropyMode.REDUCE_ENTROPY)
		{
			SetEntropyMode(mode);
		}

		static readonly int[] sbox1 = {
			2,0,1,1,2,3,3,0,
			3,2,2,0,1,1,0,3,
			0,3,3,0,2,2,1,1,
			2,2,0,3,1,1,3,0
		};

		static readonly int[] sbox2 = {
		3,1,0,2,2,3,3,0,
		1,3,2,1,0,0,1,2,
		3,1,0,3,3,2,0,2,
		0,0,1,2,2,1,3,1
	};

		static readonly int[] sbox3 = {
		2,0,1,2,2,3,3,1,
		1,1,0,3,3,0,2,0,
		1,3,0,1,3,0,2,2,
		2,0,1,2,0,3,3,1
	};

		static readonly int[] sbox4 = {
		3,1,2,3,0,2,1,2,
		1,2,0,1,3,0,0,3,
		1,0,3,1,2,3,0,3,
		0,3,2,0,1,2,2,1
	};

		static readonly int[] sbox5 = {
		2,0,0,1,3,2,3,2,
		0,1,3,3,1,0,2,1,
		2,3,2,0,0,3,1,1,
		1,0,3,2,3,1,0,2
	};

		static readonly int[] sbox6 = {
		0,1,2,3,1,2,2,0,
		0,1,3,0,2,3,1,3,
		2,3,0,2,3,0,1,1,
		2,1,1,2,0,3,3,0
	};

		static readonly int[] sbox7 = {
		0,3,2,2,3,0,0,1,
		3,0,1,3,1,2,2,1,
		1,0,3,3,0,1,1,2,
		2,3,1,0,2,3,0,2
	};
		
		public void SetEntropyMode(EntropyMode mode)
		{
			_mode = mode;
		}

		public EntropyMode GetEntropyMode()
		{
			return _mode;
		}

		public static void ReduceCW(Span<byte> cw)
		{
			cw[3] = (byte)(cw[0] + cw[1] + cw[2]);
			cw[7] = (byte)(cw[4] + cw[5] + cw[6]);
		}

		public static bool IsReducedCW(Span<byte> cw)
		{
			return cw[3] == (byte)(cw[0] + cw[1] + cw[2]) && cw[7] == (byte)(cw[4] + cw[5] + cw[6]);
		}

		protected static object Properties()
		{
			return new Tuple<string, int, int>("DVB-CSA2", BLOCK_SIZE, KEY_SIZE);
		}

		public bool SetKeyImpl(byte[] newKey)
		{
			// Only one possible key size.
			if (newKey.Length != KEY_SIZE)
			{
				return false;
			}

			// Preprocess control word
			_key = new byte[newKey.Length];
			Array.Copy(newKey, 0, _key, 0, newKey.Length);
			if (_mode == EntropyMode.REDUCE_ENTROPY)
			{
				ReduceCW(_key);
			}

			// Block cipher key schedule
			if (_block == null)
				_block = new DvbBlockCipher();
			_block.Init(_key);

			// Stream cipher initialization
			if (_stream == null)
				_stream = new DvbStreamCipher();
			_stream.Init(_key);

			// Mark as initialized
			_init = true;
			return true;
		}

		protected bool EncryptImpl(Span<byte> input, int inputLength, Span<byte> output, int size, ref int retsize)
		{
			if (size < inputLength)
				return false;

			size = inputLength;
			if (retsize != 0)
				retsize = size;

			if (input != output)
			{
				input.CopyTo(output);
			}

			Span<byte> data = output;
			int nblocks = size / 8;
			int rsize = size % 8;

			if (data == null || nblocks > MAX_NBLOCKS || !_init)
				return false;

			if (size < 8)
				return true;

			DvbStreamCipher stream_ctx;
			byte[] iblock = new byte[8];
			byte[][] ib = new byte[MAX_NBLOCKS + 1][];
			for (int i = 0; i < ib.Length; i++)
				ib[i] = new byte[8];
			byte[] ostream = new byte[8];

			Array.Clear(ib[nblocks]);
			for (int i = nblocks - 1; i >= 0; i--)
			{
				xor_8(iblock, data.Slice(8 * i), ib[i + 1]);
				_block.Encipher(iblock, ib[i]);
			}

			memcpy_8(data, ib[0]);

			stream_ctx = _stream;
			stream_ctx.Cipher(ib[0], ostream);

			for (int i = 1; i < nblocks; i++)
			{
				stream_ctx.Cipher(null, ostream);
				xor_8(data.Slice(8 * i), ib[i], ostream);
			}

			if (rsize > 0)
			{
				stream_ctx.Cipher(null, ostream);
				for (int i = 0; i < rsize; i++)
				{
					data[8 * nblocks + i] ^= ostream[i];
				}
			}

			return true;
		}

		public bool DecryptImpl(Span<byte> input, int inputLength, Span<byte> output, int size, ref int retsize)
		{
			if (size < inputLength)
				return false;

			size = inputLength;
			if (retsize != 0)
				retsize = size;

			if (input != output)
				input.CopyTo(output);

			Span<byte> data = output;
			int nblocks = size / 8;
			int rsize = size % 8;

			if (data == null || nblocks > MAX_NBLOCKS || !_init)
				return false;

			if (size < 8)
				return true;

			DvbStreamCipher stream_ctx;
			byte[] ostream = new byte[8];
			byte[] ib = new byte[8];
			byte[] oblock = new byte[8];

			stream_ctx = _stream;
			stream_ctx.Cipher(data, ib);

			for (int i = 1; i < nblocks; i++)
			{
				_block.Decipher(ib, oblock);
				stream_ctx.Cipher(null, ostream);
				xor_8(ib, data.Slice(8 * i), ostream);
				xor_8(data.Slice(8 * (i - 1)), ib, oblock);
			}

			_block.Decipher(ib, data.Slice(8 * (nblocks - 1)));

			if (rsize > 0)
			{
				stream_ctx.Cipher(null, ostream);
				for (int i = 0; i < rsize; i++)
				{
					data[8 * nblocks + 1] ^= ostream[i];
				}
			}

			return true;
		}

		private void xor_8(Span<byte> iblock, byte[] slice, byte[] ostream)
		{
			throw new NotImplementedException();
		}

		private void memcpy_8(Span<byte> data, byte[] bytes)
		{
			throw new NotImplementedException();
		}

		private void xor_8(byte[] res, Span<byte> a, byte[] b)
		{
			res[0] = (byte)(a[0] ^ b[0]);
		}

		private class DvbBlockCipher
		{
			private readonly byte[] key_perm = {
				0x12, 0x24, 0x09, 0x07, 0x2A, 0x31, 0x1D, 0x15,
				0x1C, 0x36, 0x3E, 0x32, 0x13, 0x21, 0x3B, 0x40,
				0x18, 0x14, 0x25, 0x27, 0x02, 0x35, 0x1B, 0x01,
				0x22, 0x04, 0x0D, 0x0E, 0x39, 0x28, 0x1A, 0x29,
				0x33, 0x23, 0x34, 0x0C, 0x16, 0x30, 0x1E, 0x3A,
				0x2D, 0x1F, 0x08, 0x19, 0x17, 0x2F, 0x3D, 0x11,
				0x3C, 0x05, 0x38, 0x2B, 0x0B, 0x06, 0x0A, 0x2C,
				0x20, 0x3F, 0x2E, 0x0F, 0x03, 0x26, 0x10, 0x37
			};

			// S-Box

			private readonly byte[] block_sbox = {
				0x3A, 0xEA, 0x68, 0xFE, 0x33, 0xE9, 0x88, 0x1A,
				0x83, 0xCF, 0xE1, 0x7F, 0xBA, 0xE2, 0x38, 0x12,
				0xE8, 0x27, 0x61, 0x95, 0x0C, 0x36, 0xE5, 0x70,
				0xA2, 0x06, 0x82, 0x7C, 0x17, 0xA3, 0x26, 0x49,
				0xBE, 0x7A, 0x6D, 0x47, 0xC1, 0x51, 0x8F, 0xF3,
				0xCC, 0x5B, 0x67, 0xBD, 0xCD, 0x18, 0x08, 0xC9,
				0xFF, 0x69, 0xEF, 0x03, 0x4E, 0x48, 0x4A, 0x84,
				0x3F, 0xB4, 0x10, 0x04, 0xDC, 0xF5, 0x5C, 0xC6,
				0x16, 0xAB, 0xAC, 0x4C, 0xF1, 0x6A, 0x2F, 0x3C,
				0x3B, 0xD4, 0xD5, 0x94, 0xD0, 0xC4, 0x63, 0x62,
				0x71, 0xA1, 0xF9, 0x4F, 0x2E, 0xAA, 0xC5, 0x56,
				0xE3, 0x39, 0x93, 0xCE, 0x65, 0x64, 0xE4, 0x58,
				0x6C, 0x19, 0x42, 0x79, 0xDD, 0xEE, 0x96, 0xF6,
				0x8A, 0xEC, 0x1E, 0x85, 0x53, 0x45, 0xDE, 0xBB,
				0x7E, 0x0A, 0x9A, 0x13, 0x2A, 0x9D, 0xC2, 0x5E,
				0x5A, 0x1F, 0x32, 0x35, 0x9C, 0xA8, 0x73, 0x30,

				0x29, 0x3D, 0xE7, 0x92, 0x87, 0x1B, 0x2B, 0x4B,
				0xA5, 0x57, 0x97, 0x40, 0x15, 0xE6, 0xBC, 0x0E,
				0xEB, 0xC3, 0x34, 0x2D, 0xB8, 0x44, 0x25, 0xA4,
				0x1C, 0xC7, 0x23, 0xED, 0x90, 0x6E, 0x50, 0x00,
				0x99, 0x9E, 0x4D, 0xD9, 0xDA, 0x8D, 0x6F, 0x5F,
				0x3E, 0xD7, 0x21, 0x74, 0x86, 0xDF, 0x6B, 0x05,
				0x8E, 0x5D, 0x37, 0x11, 0xD2, 0x28, 0x75, 0xD6,
				0xA7, 0x77, 0x24, 0xBF, 0xF0, 0xB0, 0x02, 0xB7,
				0xF8, 0xFC, 0x81, 0x09, 0xB1, 0x01, 0x76, 0x91,
				0x7D, 0x0F, 0xC8, 0xA0, 0xF2, 0xCB, 0x78, 0x60,
				0xD1, 0xF7, 0xE0, 0xB5, 0x98, 0x22, 0xB3, 0x20,
				0x1D, 0xA6, 0xDB, 0x7B, 0x59, 0x9F, 0xAE, 0x31,
				0xFB, 0xD3, 0xB6, 0xCA, 0x43, 0x72, 0x07, 0xF4,
				0xD8, 0x41, 0x14, 0x55, 0x0D, 0x54, 0x8B, 0xB9,
				0xAD, 0x46, 0x0B, 0xAF, 0x80, 0x52, 0x2C, 0xFA,
				0x8C, 0x89, 0x66, 0xFD, 0xB2, 0xA9, 0x9B, 0xC0
			};

			// Permutations

			private readonly byte[] block_perm = {
				0x00, 0x02, 0x80, 0x82, 0x20, 0x22, 0xA0, 0xA2,
				0x10, 0x12, 0x90, 0x92, 0x30, 0x32, 0xB0, 0xB2,
				0x04, 0x06, 0x84, 0x86, 0x24, 0x26, 0xA4, 0xA6,
				0x14, 0x16, 0x94, 0x96, 0x34, 0x36, 0xB4, 0xB6,
				0x40, 0x42, 0xC0, 0xC2, 0x60, 0x62, 0xE0, 0xE2,
				0x50, 0x52, 0xD0, 0xD2, 0x70, 0x72, 0xF0, 0xF2,
				0x44, 0x46, 0xC4, 0xC6, 0x64, 0x66, 0xE4, 0xE6,
				0x54, 0x56, 0xD4, 0xD6, 0x74, 0x76, 0xF4, 0xF6,
				0x01, 0x03, 0x81, 0x83, 0x21, 0x23, 0xA1, 0xA3,
				0x11, 0x13, 0x91, 0x93, 0x31, 0x33, 0xB1, 0xB3,
				0x05, 0x07, 0x85, 0x87, 0x25, 0x27, 0xA5, 0xA7,
				0x15, 0x17, 0x95, 0x97, 0x35, 0x37, 0xB5, 0xB7,
				0x41, 0x43, 0xC1, 0xC3, 0x61, 0x63, 0xE1, 0xE3,
				0x51, 0x53, 0xD1, 0xD3, 0x71, 0x73, 0xF1, 0xF3,
				0x45, 0x47, 0xC5, 0xC7, 0x65, 0x67, 0xE5, 0xE7,
				0x55, 0x57, 0xD5, 0xD7, 0x75, 0x77, 0xF5, 0xF7,

				0x08, 0x0A, 0x88, 0x8A, 0x28, 0x2A, 0xA8, 0xAA,
				0x18, 0x1A, 0x98, 0x9A, 0x38, 0x3A, 0xB8, 0xBA,
				0x0C, 0x0E, 0x8C, 0x8E, 0x2C, 0x2E, 0xAC, 0xAE,
				0x1C, 0x1E, 0x9C, 0x9E, 0x3C, 0x3E, 0xBC, 0xBE,
				0x48, 0x4A, 0xC8, 0xCA, 0x68, 0x6A, 0xE8, 0xEA,
				0x58, 0x5A, 0xD8, 0xDA, 0x78, 0x7A, 0xF8, 0xFA,
				0x4C, 0x4E, 0xCC, 0xCE, 0x6C, 0x6E, 0xEC, 0xEE,
				0x5C, 0x5E, 0xDC, 0xDE, 0x7C, 0x7E, 0xFC, 0xFE,
				0x09, 0x0B, 0x89, 0x8B, 0x29, 0x2B, 0xA9, 0xAB,
				0x19, 0x1B, 0x99, 0x9B, 0x39, 0x3B, 0xB9, 0xBB,
				0x0D, 0x0F, 0x8D, 0x8F, 0x2D, 0x2F, 0xAD, 0xAF,
				0x1D, 0x1F, 0x9D, 0x9F, 0x3D, 0x3F, 0xBD, 0xBF,
				0x49, 0x4B, 0xC9, 0xCB, 0x69, 0x6B, 0xE9, 0xEB,
				0x59, 0x5B, 0xD9, 0xDB, 0x79, 0x7B, 0xF9, 0xFB,
				0x4D, 0x4F, 0xCD, 0xCF, 0x6D, 0x6F, 0xED, 0xEF,
				0x5D, 0x5F, 0xDD, 0xDF, 0x7D, 0x7F, 0xFD, 0xFF
			};


			private int[] _kk;  //57 items
			public void Init(Span<byte> key)
			{
				_kk = new int[57];

				int i, j, k;
				int[] bit = new int[64];
				int[] newbit = new int[64];
				int[][] kb = new int[8][]; // original code was: int kb[9][8];
				for (int init = 0; init < kb.Length; init++)
				{
					kb[init] = new int[9];
				}

				// 56 steps
				// 56 key bytes kk(56)..kk(1) by key schedule from key

				// kb(7,1) .. kb(7,8) = key(1) .. key(8)
				kb[7][1] = key[0];
				kb[7][2] = key[1];
				kb[7][3] = key[2];
				kb[7][4] = key[3];
				kb[7][5] = key[4];
				kb[7][6] = key[5];
				kb[7][7] = key[6];
				kb[7][8] = key[7];

				// calculate kb[6] .. kb[1]
				for (i = 0; i < 7; i++)
				{
					// 64 bit perm on kb
					for (j = 0; j < 8; j++)
					{
						for (k = 0; k < 8; k++)
						{
							bit[j * 8 + k] = (kb[7 - i][1 + j] >> (7 - k)) & 1;
							newbit[key_perm[j * 8 + k] - 1] = bit[j * 8 + k];
						}
					}
					for (j = 0; j < 8; j++)
					{
						kb[6 - i][1 + j] = 0;
						for (k = 0; k < 8; k++)
						{
							kb[6 - i][1 + j] |= newbit[j * 8 + k] << (7 - k);
						}
					}
				}

				// xor to give kk
				for (i = 0; i < 7; i++)
				{
					for (j = 0; j < 8; j++)
					{
						_kk[1 + i * 8 + j] = kb[1 + i][1 + j] ^ i;
					}
				}
			}
			public void Encipher(Span<byte> bd, Span<byte> ib)
			{
				int i;
				int sbox_in;
				int sbox_out;
				int perm_out;
				int[] R = new int[9];
				int next_R1;

				R[1] = bd[0];
				R[2] = bd[1];
				R[3] = bd[2];
				R[4] = bd[3];
				R[5] = bd[4];
				R[6] = bd[5];
				R[7] = bd[6];
				R[8] = bd[7];

				// loop over kk[1]..kk[56]
				for (i = 1; i <= 56; i++)
				{
					sbox_in = _kk[i] ^ R[8];
					sbox_out = block_sbox[sbox_in];
					perm_out = block_perm[sbox_out];
					next_R1 = R[2];
					R[2] = R[3] ^ R[1];
					R[3] = R[4] ^ R[1];
					R[4] = R[5] ^ R[1];
					R[5] = R[6];
					R[6] = R[7] ^ perm_out;
					R[7] = R[8];
					R[8] = R[1] ^ sbox_out;
					R[1] = next_R1;
				}

				ib[0] = (byte)(R[1]);
				ib[1] = (byte)(R[2]);
				ib[2] = (byte)(R[3]);
				ib[3] = (byte)(R[4]);
				ib[4] = (byte)(R[5]);
				ib[5] = (byte)(R[6]);
				ib[6] = (byte)(R[7]);
				ib[7] = (byte)(R[8]);
			}
			public void Decipher(Span<byte> ib, Span<byte> bd)
			{
				int i;
				int sbox_in;
				int sbox_out;
				int perm_out;
				int[] R = new int[9];
				int next_R8;

				R[1] = ib[0];
				R[2] = ib[1];
				R[3] = ib[2];
				R[4] = ib[3];
				R[5] = ib[4];
				R[6] = ib[5];
				R[7] = ib[6];
				R[8] = ib[7];

				// loop over kk[56]..kk[1]
				for (i = 56; i > 0; i--)
				{
					sbox_in = _kk[i] ^ R[7];
					sbox_out = block_sbox[sbox_in];
					perm_out = block_perm[sbox_out];
					next_R8 = R[7];
					R[7] = R[6] ^ perm_out;
					R[6] = R[5];
					R[5] = R[4] ^ R[8] ^ sbox_out;
					R[4] = R[3] ^ R[8] ^ sbox_out;
					R[3] = R[2] ^ R[8] ^ sbox_out;
					R[2] = R[1];
					R[1] = R[8] ^ sbox_out;
					R[8] = next_R8;
				}

				bd[0] = (byte)(R[1]);
				bd[1] = (byte)(R[2]);
				bd[2] = (byte)(R[3]);
				bd[3] = (byte)(R[4]);
				bd[4] = (byte)(R[5]);
				bd[5] = (byte)(R[6]);
				bd[6] = (byte)(R[7]);
				bd[7] = (byte)(R[8]);
			}
		}

		private class DvbStreamCipher
		{
			private int[] A; //11 bytes
			private int[] B; //11 bytes
			private int X;
			private int Y;
			private int Z;
			private int D;
			private int E;
			private int F;
			private int p;
			private int q;
			private int r;

			public void Init(Span<byte> key)
			{
				A = new int[11];
				B = new int[11];

				A[1] = (key[0] >> 4) & 0x0F;
				A[2] = (key[0] >> 0) & 0x0F;
				A[3] = (key[1] >> 4) & 0x0F;
				A[4] = (key[1] >> 0) & 0x0F;
				A[5] = (key[2] >> 4) & 0x0F;
				A[6] = (key[2] >> 0) & 0x0F;
				A[7] = (key[3] >> 4) & 0x0F;
				A[8] = (key[3] >> 0) & 0x0F;
				A[9] = 0;
				A[10] = 0;

				B[1] = (key[4] >> 4) & 0x0F;
				B[2] = (key[4] >> 0) & 0x0F;
				B[3] = (key[5] >> 4) & 0x0F;
				B[4] = (key[5] >> 0) & 0x0F;
				B[5] = (key[6] >> 4) & 0x0F;
				B[6] = (key[6] >> 0) & 0x0F;
				B[7] = (key[7] >> 4) & 0x0F;
				B[8] = (key[7] >> 0) & 0x0F;
				B[9] = 0;
				B[10] = 0;

				X = 0;
				Y = 0;
				Z = 0;
				D = 0;
				E = 0;
				F = 0;
				p = 0;
				q = 0;
				r = 0;
			}

			public void Cipher(Span<byte> sb, Span<byte> cb)
			{
				bool init = sb != null;
				int i, j;
				int in1 = 0;  //  most significant nibble of input byte
				int in2 = 0;  // least significant nibble of input byte
				int op;
				int extra_B;
				int s1, s2, s3, s4, s5, s6, s7;
				int next_A1;
				int next_B1;
				int next_E;

				// 8 bytes per operation
				for (i = 0; i < 8; i++)
				{
					if (init)
					{
						in1 = (sb[i] >> 4) & 0x0F;
						in2 = sb[i] & 0x0F;
					}
					op = 0;
					// 2 bits per iteration
					for (j = 0; j < 4; j++)
					{
						// from A[1]..A[10], 35 bits are selected as inputs to 7 s-boxes
						// 5 bits input per s-box, 2 bits output per s-box
						s1 = sbox1[(((A[4] >> 0) & 1) << 4) |
									(((A[1] >> 2) & 1) << 3) |
									(((A[6] >> 1) & 1) << 2) |
									(((A[7] >> 3) & 1) << 1) |
									(((A[9] >> 0) & 1) << 0)];
						s2 = sbox2[(((A[2] >> 1) & 1) << 4) |
									(((A[3] >> 2) & 1) << 3) |
									(((A[6] >> 3) & 1) << 2) |
									(((A[7] >> 0) & 1) << 1) |
									(((A[9] >> 1) & 1) << 0)];
						s3 = sbox3[(((A[1] >> 3) & 1) << 4) |
									(((A[2] >> 0) & 1) << 3) |
									(((A[5] >> 1) & 1) << 2) |
									(((A[5] >> 3) & 1) << 1) |
									(((A[6] >> 2) & 1) << 0)];
						s4 = sbox4[(((A[3] >> 3) & 1) << 4) |
									(((A[1] >> 1) & 1) << 3) |
									(((A[2] >> 3) & 1) << 2) |
									(((A[4] >> 2) & 1) << 1) |
									(((A[8] >> 0) & 1) << 0)];
						s5 = sbox5[(((A[5] >> 2) & 1) << 4) |
									(((A[4] >> 3) & 1) << 3) |
									(((A[6] >> 0) & 1) << 2) |
									(((A[8] >> 1) & 1) << 1) |
									(((A[9] >> 2) & 1) << 0)];
						s6 = sbox6[(((A[3] >> 1) & 1) << 4) |
									(((A[4] >> 1) & 1) << 3) |
									(((A[5] >> 0) & 1) << 2) |
									(((A[7] >> 2) & 1) << 1) |
									(((A[9] >> 3) & 1) << 0)];
						s7 = sbox7[(((A[2] >> 2) & 1) << 4) |
									(((A[3] >> 0) & 1) << 3) |
									(((A[7] >> 1) & 1) << 2) |
									(((A[8] >> 2) & 1) << 1) |
									(((A[8] >> 3) & 1) << 0)];

						// use 4x4 xor to produce extra nibble for T3
						extra_B =
							(((B[3] & 1) << 3) ^
							  ((B[6] & 2) << 2) ^
							  ((B[7] & 4) << 1) ^
							  ((B[9] & 8) >> 0)) |
							(((B[6] & 1) << 2) ^
							  ((B[8] & 2) << 1) ^
							  ((B[3] & 8) >> 1) ^
							  ((B[4] & 4) >> 0)) |
							(((B[5] & 8) >> 2) ^
							  ((B[8] & 4) >> 1) ^
							  ((B[4] & 1) << 1) ^
							  ((B[5] & 2) >> 0)) |
							(((B[9] & 4) >> 2) ^
							  ((B[6] & 8) >> 3) ^
							  ((B[3] & 2) >> 1) ^
							  ((B[8] & 1) >> 0));

						// T1 = xor all inputs
						// in1, in2, D are only used in T1 during initialisation,
						// not generation
						next_A1 = A[10] ^ X;
						if (init)
						{
							next_A1 = next_A1 ^ D ^ ((j % 2) != 0 ? in2 : in1);
						}

						// T2 =  xor all inputs
						// in1,in2 are only used in T1 during initialisation, not generation
						// if p=0, use this, if p=1, rotate the result left
						next_B1 = B[7] ^ B[10] ^ Y;
						if (init)
						{
							next_B1 = next_B1 ^ ((j % 2) != 0 ? in1 : in2);
						}

						// if p=1, rotate left
						if (p != 0)
						{
							next_B1 = ((next_B1 << 1) | ((next_B1 >> 3) & 1)) & 0x0F;
						}

						// T3 = xor all inputs
						D = E ^ Z ^ extra_B;

						// T4 = sum, carry of Z + E + r
						next_E = F;
						if (q != 0)
						{
							F = Z + E + r;
							// r is the carry
							r = (F >> 4) & 1;
							F = F & 0x0F;
						}
						else
						{
							F = E;
						}
						E = next_E;

						A[10] = A[9];
						A[9] = A[8];
						A[8] = A[7];
						A[7] = A[6];
						A[6] = A[5];
						A[5] = A[4];
						A[4] = A[3];
						A[3] = A[2];
						A[2] = A[1];
						A[1] = next_A1;

						B[10] = B[9];
						B[9] = B[8];
						B[8] = B[7];
						B[7] = B[6];
						B[6] = B[5];
						B[5] = B[4];
						B[4] = B[3];
						B[3] = B[2];
						B[2] = B[1];
						B[1] = next_B1;

						X = ((s4 & 1) << 3) | ((s3 & 1) << 2) | (s2 & 2) | ((s1 & 2) >> 1);
						Y = ((s6 & 1) << 3) | ((s5 & 1) << 2) | (s4 & 2) | ((s3 & 2) >> 1);
						Z = ((s2 & 1) << 3) | ((s1 & 1) << 2) | (s6 & 2) | ((s5 & 2) >> 1);
						p = (s7 & 2) >> 1;
						q = (s7 & 1);

						// require 4 loops per output byte
						// 2 output bits are a function of the 4 bits of D
						// xor 2 by 2
						op = (op << 2) ^ ((((D ^ (D >> 1)) >> 1) & 2) | ((D ^ (D >> 1)) & 1));
					}
					// return input data during init
					cb[i] = init ? sb[i] : (byte)(op);
				}
			}
		}

		private bool _init = false;
		private EntropyMode _mode = EntropyMode.REDUCE_ENTROPY;
		private byte[] _key;	//KEY_SIZE elements;
		private DvbBlockCipher _block;
		private DvbStreamCipher _stream;
	}
}
