using System;

namespace Compress.Utils
{
    public class CRC
    {
        public static readonly uint[] CRC32Lookup;
        private uint _crc;
        private long _totalBytesRead;

        static CRC()
        {
            const uint polynomial = 0xEDB88320;
            const int crcNumTables = 8;

            unchecked
            {
                CRC32Lookup = new uint[256 * crcNumTables];
                int i;
                for (i = 0; i < 256; i++)
                {
                    uint r = (uint)i;
                    for (int j = 0; j < 8; j++)
                    {
                        r = (r >> 1) ^ (polynomial & ~((r & 1) - 1));
                    }

                    CRC32Lookup[i] = r;
                }

                for (; i < 256 * crcNumTables; i++)
                {
                    uint r = CRC32Lookup[i - 256];
                    CRC32Lookup[i] = CRC32Lookup[r & 0xFF] ^ (r >> 8);
                }
            }
        }


        public CRC()
        {
            Reset();
        }

        public void Reset()
        {
            _totalBytesRead = 0;
            _crc = 0xffffffffu;
        }


        internal void UpdateCRC(int inCh)
        {
            _crc = (_crc >> 8) ^ CRC32Lookup[(byte)_crc ^ ((byte)inCh)];
        }

        public void SlurpBlock(byte[] block, int offset, int count)
        {
            _totalBytesRead += count;
            uint crc = _crc;

            for (; (offset & 7) != 0 && count != 0; count--)
                crc = (crc >> 8) ^ CRC32Lookup[(byte)crc ^ block[offset++]];

            if (count >= 8)
            {
                int end = (count - 8) & ~7;
                count -= end;
                end += offset;

                while (offset != end)
                {
                    crc ^= (uint)(block[offset] + (block[offset + 1] << 8) + (block[offset + 2] << 16) + (block[offset + 3] << 24));
                    uint high = (uint)(block[offset + 4] + (block[offset + 5] << 8) + (block[offset + 6] << 16) + (block[offset + 7] << 24));
                    offset += 8;

                    crc = CRC32Lookup[(byte)crc + 0x700]
                          ^ CRC32Lookup[(byte)(crc >>= 8) + 0x600]
                          ^ CRC32Lookup[(byte)(crc >>= 8) + 0x500]
                          ^ CRC32Lookup[ /*(byte)*/(crc >> 8) + 0x400]
                          ^ CRC32Lookup[(byte)high + 0x300]
                          ^ CRC32Lookup[(byte)(high >>= 8) + 0x200]
                          ^ CRC32Lookup[(byte)(high >>= 8) + 0x100]
                          ^ CRC32Lookup[ /*(byte)*/(high >> 8) + 0x000];
                }
            }

            while (count-- != 0)
            {
                crc = (crc >> 8) ^ CRC32Lookup[(byte)crc ^ block[offset++]];
            }

            _crc = crc;

        }

        public byte[] Crc32ResultB
        {
            get
            {
                byte[] result = BitConverter.GetBytes(~_crc);
                Array.Reverse(result);
                return result;
            }
        }
        public Int32 Crc32Result => unchecked((Int32)(~_crc));

        public uint Crc32ResultU => ~_crc;

        public Int64 TotalBytesRead => _totalBytesRead;

        public static uint CalculateDigest(byte[] data, uint offset, uint size)
        {
            CRC crc = new CRC();
            // crc.Init();
            crc.SlurpBlock(data, (int)offset, (int)size);
            return crc.Crc32ResultU;
        }

        public static bool VerifyDigest(uint digest, byte[] data, uint offset, uint size)
        {
            return (CalculateDigest(data, offset, size) == digest);
        }
    }
}