using System;
namespace CUETools.Codecs
{
	public static class Crc16
	{
        const int GF2_DIM = 16;
        public static ushort[] table = new ushort[256];
        private static readonly ushort[,] combineTable = new ushort[GF2_DIM, GF2_DIM];
        private static readonly ushort[,] substractTable = new ushort[GF2_DIM, GF2_DIM];

		public static unsafe ushort ComputeChecksum(ushort crc, byte[] bytes, int pos, int count)
		{
			fixed (byte* bs = bytes)
				return ComputeChecksum(crc, bs + pos, count);
		}

        public static unsafe ushort ComputeChecksum(ushort crc, byte* bytes, int count)
		{
			fixed (ushort* t = table)
                for (int i = count; i > 0; i--)
                {
                    crc = (ushort)((crc << 8) ^ t[(crc >> 8) ^ *(bytes++)]);
                }
			return crc;
		}

        const ushort polynomial = 0x8005;
        const ushort reversePolynomial = 0x4003;

		static unsafe Crc16()
		{
            for (ushort i = 0; i < table.Length; i++)
            {
                int crc = i;
                for (int j = 0; j < GF2_DIM; j++)
                {
                    if ((crc & (1U << (GF2_DIM - 1))) != 0)
                        crc = ((crc << 1) ^ polynomial);
                    else
                        crc <<= 1;
                }
                table[i] = (ushort)(crc & ((1 << GF2_DIM) - 1));
            }

            combineTable[0, 0] = Crc16.Reflect(polynomial);
            substractTable[0, GF2_DIM - 1] = reversePolynomial;
            for (int n = 1; n < GF2_DIM; n++)
            {
                combineTable[0, n] = (ushort)(1 << (n - 1));
                substractTable[0, n - 1] = (ushort)(1 << n);
            }
            
            fixed (ushort* ct = &combineTable[0, 0], st = &substractTable[0, 0])
            {
                //for (int i = 0; i < GF2_DIM; i++)
                //	st[32 + i] = ct[i];
                //invert_binary_matrix(st + 32, st, GF2_DIM);

                for (int i = 1; i < GF2_DIM; i++)
                {
                    gf2_matrix_square(ct + i * GF2_DIM, ct + (i - 1) * GF2_DIM);
                    gf2_matrix_square(st + i * GF2_DIM, st + (i - 1) * GF2_DIM);
                }
            }
        }

        private static unsafe ushort gf2_matrix_times(ushort* mat, ushort uvec)
        {
            int vec = ((int) uvec) << 16;
            return (ushort)(
                (*(mat++) & ((vec << 15) >> 31)) ^
                (*(mat++) & ((vec << 14) >> 31)) ^
                (*(mat++) & ((vec << 13) >> 31)) ^
                (*(mat++) & ((vec << 12) >> 31)) ^
                (*(mat++) & ((vec << 11) >> 31)) ^
                (*(mat++) & ((vec << 10) >> 31)) ^
                (*(mat++) & ((vec << 09) >> 31)) ^
                (*(mat++) & ((vec << 08) >> 31)) ^
                (*(mat++) & ((vec << 07) >> 31)) ^
                (*(mat++) & ((vec << 06) >> 31)) ^
                (*(mat++) & ((vec << 05) >> 31)) ^
                (*(mat++) & ((vec << 04) >> 31)) ^
                (*(mat++) & ((vec << 03) >> 31)) ^
                (*(mat++) & ((vec << 02) >> 31)) ^
                (*(mat++) & ((vec << 01) >> 31)) ^
                (*(mat++) & (vec >> 31)));
        }

        private static unsafe void gf2_matrix_square(ushort* square, ushort* mat)
        {
            for (int n = 0; n < GF2_DIM; n++)
                square[n] = gf2_matrix_times(mat, mat[n]);
        }

        public static ushort Reflect(ushort crc)
        {
            return (ushort)Crc32.Reflect(crc, 16);
        }

        public static unsafe ushort Combine(ushort crc1, ushort crc2, long len2)
        {
            crc1 = Crc16.Reflect(crc1);
            crc2 = Crc16.Reflect(crc2);

            /* degenerate case */
            if (len2 == 0)
                return crc1;
            if (crc1 == 0)
                return crc2;
            if (len2 < 0)
                throw new ArgumentException("crc.Combine length cannot be negative", "len2");

            fixed (ushort* ct = &combineTable[0, 0])
            {
                int n = 3;
                do
                {
                    /* apply zeros operator for this bit of len2 */
                    if ((len2 & 1) != 0)
                        crc1 = gf2_matrix_times(ct + GF2_DIM * n, crc1);
                    len2 >>= 1;
                    n = (n + 1) & (GF2_DIM - 1);
                    /* if no more bits set, then done */
                } while (len2 != 0);
            }

            /* return combined crc */
            crc1 ^= crc2;
            crc1 = Crc16.Reflect(crc1);
            return crc1;
        }

        public static unsafe ushort Subtract(ushort crc1, ushort crc2, long len2)
        {
            crc1 = Crc16.Reflect(crc1);
            crc2 = Crc16.Reflect(crc2);
            /* degenerate case */
            if (len2 == 0)
                return crc1;
            if (len2 < 0)
                throw new ArgumentException("crc.Combine length cannot be negative", "len2");

            crc1 ^= crc2;

            fixed (ushort* st = &substractTable[0, 0])
            {
                int n = 3;
                do
                {
                    /* apply zeros operator for this bit of len2 */
                    if ((len2 & 1) != 0)
                        crc1 = gf2_matrix_times(st + GF2_DIM * n, crc1);
                    len2 >>= 1;
                    n = (n + 1) & (GF2_DIM - 1);
                    /* if no more bits set, then done */
                } while (len2 != 0);
            }

            /* return combined crc */
            crc1 = Crc16.Reflect(crc1);
            return crc1;
        }
    }
}
