#region Copyright 2013-2014 by Roger Knapp, Licensed under the Apache License, Version 2.0
/* Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#endregion
using System;
namespace CSharpTest.Net.IO
{
    ///  Provides a simple CRC64 checksum for a set of bytes using algorithm (CRC-64/XZ) 
    [System.Diagnostics.DebuggerDisplay("{Value:X8}")]
    public struct Crc64 : IEquatable, IEquatable
    {
        static readonly ulong[] Table;
        static Crc64()
        {
            Table = new ulong[256];
            for (uint i = 0; i < 256; i++)
            {
                ulong crc = i;
                for (uint j = 0; j < 8; j++)
                    crc = (crc >> 1) ^ (((crc & 1) == 1) ? 0xC96C5795D7870F42 : 0);
                //Note: table ordinal and value inverted to omit -1 start and end operation
                Table[~i & 0x0ff] = ~crc;
            }
        }
        ulong _crc64;
        ///  Resumes the computation of a CRC64 value 
        public Crc64(long crc)
        { _crc64 = unchecked((ulong)crc); }
        ///  Initailizes the Crc64 value to the checksum of the string as a series of 16-bit values 
        public Crc64(string text)
        { _crc64 = 0; Add(text); }
        ///  Initailizes the Crc64 value to the checksum of the bytes provided 
        public Crc64(byte[] bytes)
        { _crc64 = 0; Add(bytes, 0, bytes.Length); }
        ///  Returns the computed CRC64 value as a Hex string 
        public override string ToString() { return String.Format("{0:X16}", Value); }
        ///  Returns the computed CRC64 value 
        public long Value { get { return unchecked((long)_crc64); } }
        ///  Adds a byte to the checksum 
        public void Add(byte b)
        {
            _crc64 = (((~_crc64 >> 8) & 0x00FFFFFFFFFFFFFF) ^ Table[(_crc64 ^ b) & 0x0ff]);
        }
        ///  Adds a byte to the checksum 
        public static Crc64 operator +(Crc64 chksum, byte b)
        {
            chksum.Add(b);
            return chksum;
        }
        ///  Adds an entire array of bytes to the checksum 
        public void Add(byte[] bytes)
        { Add(bytes, 0, Check.NotNull(bytes).Length); }
        ///  Adds a range from an array of bytes to the checksum 
        public void Add(byte[] bytes, int start, int length)
        {
            Check.NotNull(bytes);
            int end = start + length;
            unchecked
            {
                for (int i = start; i < end; i++)
                    _crc64 = (((~_crc64 >> 8) & 0x00FFFFFFFFFFFFFF) ^ Table[(_crc64 ^ bytes[i]) & 0x0ff]);
            }
        }
        ///  Adds an entire array of bytes to the checksum 
        public static Crc64 operator +(Crc64 chksum, byte[] bytes)
        {
            chksum.Add(bytes, 0, bytes.Length);
            return chksum;
        }
        ///  Adds a string to the checksum as a series of 16-bit values (big endian) 
        public void Add(string text)
        {
            unchecked
            {
                foreach (char ch in Check.NotNull(text))
                {
                    _crc64 = (((~_crc64 >> 8) & 0x00FFFFFFFFFFFFFF) ^ Table[((int) _crc64 ^ ((byte) ch >> 8)) & 0x0ff]);
                    _crc64 = (((~_crc64 >> 8) & 0x00FFFFFFFFFFFFFF) ^ Table[(_crc64 ^ ((byte) ch)) & 0x0ff]);
                }
            }
        }
        ///  Adds a string to the checksum as a series of 16-bit values 
        public static Crc64 operator +(Crc64 chksum, string text)
        {
            chksum.Add(text);
            return chksum;
        }
        ///  Extracts the correct hash code 
        public override int GetHashCode() { return (int)(Value ^ (Value >> 32)); }
        ///  Returns true if the other object is equal to this one 
        public override bool Equals(object obj)
        {
            return 
                (obj is Crc64 && _crc64 == ((Crc64)obj)._crc64) ||
                (obj is long && Value == ((long)obj));
        }
        ///  Returns true if the other object is equal to this one 
        public bool Equals(Crc64 other) { return _crc64 == other._crc64; }
        ///  Returns true if the CRC64 provided is equal to this one 
        public bool Equals(long crc64) { return Value == crc64; }
        ///  Compares the two objects for equality 
        public static bool operator ==(Crc64 x, Crc64 y) { return x._crc64 == y._crc64; }
        ///  Compares the two objects for equality 
        public static bool operator !=(Crc64 x, Crc64 y) { return x._crc64 != y._crc64; }
        ///  Compares the two objects for equality 
        public static bool operator ==(Crc64 x, long y) { return x.Value == y; }
        ///  Compares the two objects for equality 
        public static bool operator !=(Crc64 x, long y) { return x.Value != y; }
    }
}