namespace Volpe.Cafe.TechKeys
{
    //--------------------------------------------------------------------------------------------
    // Use the hash functions here to test hashing performance when updating the set of available
    // technologies for analysis and tech-combinations defined in the simulation database.
    // 
    // Currently, FNVMod is considered the optimal choice for TechKey128. Although OAT produces
    // significantly better key distribution (~1.8x better than FNVMod), it does so at the expense
    // of computation time (~1.5 times slower per hash). Consequently, no measurable performance
    // gains in the overall model runtime could be seen between FNVMod and OAT.
    //--------------------------------------------------------------------------------------------
    internal static class HashHelper
    {
        private const int BernsteinPrime = 33;
        private const int FNVPrime = 16777619;
        private const uint FNVOffset = 2166136261; // FNV offset basis for 32-bit hashes

        public static int FNV(ulong hi, ulong lo)
        {
            // Fowler-Noll-Vo (FNV) hash algorithm
            unchecked {
                int hash = (int)FNVOffset;
                hash = (hash * FNVPrime) ^ (int)(hi >> 32);
                hash = (hash * FNVPrime) ^ (int)hi;
                hash = (hash * FNVPrime) ^ (int)(lo >> 32);
                hash = (hash * FNVPrime) ^ (int)lo;
                return hash;
            }
        }

        public static int FNVMod(ulong hi, ulong lo)
        {
            // modified FNV hash algorithm
            // this version resembles the modified Bernstein algorithm, but using the FNV prime
            unchecked {
                int hash = (int)(hi >> 32);
                hash = (hash * FNVPrime) ^ (int)hi;
                hash = (hash * FNVPrime) ^ (int)(lo >> 32);
                hash = (hash * FNVPrime) ^ (int)lo;
                return hash;
            }
        }

        public static int Bernstein(ulong hi, ulong lo)
        {
            // Dan Bernstein's hash algorithm
            unchecked {
                int hash = (int)(hi >> 32);
                hash = (hash * BernsteinPrime) + (int)hi;
                hash = (hash * BernsteinPrime) + (int)(lo >> 32);
                hash = (hash * BernsteinPrime) + (int)lo;
                return hash;
            }
        }

        public static int BernsteinMod(ulong hi, ulong lo)
        {
            // modified Bernstein hash algorithm
            unchecked {
                int hash = (int)(hi >> 32);
                hash = (hash * BernsteinPrime) ^ (int)hi;
                hash = (hash * BernsteinPrime) ^ (int)(lo >> 32);
                hash = (hash * BernsteinPrime) ^ (int)lo;
                return hash;
            }
        }

        public static int Rotating(ulong hi, ulong lo)
        {
            unchecked {
                int hash = (int)(hi >> 32);
                hash = (hash << 4) ^ (hash >> 28) ^ (int)hi;
                hash = (hash << 4) ^ (hash >> 28) ^ (int)(lo >> 32);
                hash = (hash << 4) ^ (hash >> 28) ^ (int)lo;
                return hash;
            }
        }

        public static int ShiftAddXOR(ulong hi, ulong lo)
        {
            unchecked {
                int hash = (int)(hi >> 32);
                hash ^= (hash << 5) + (hash >> 2) + (int)hi;
                hash ^= (hash << 5) + (hash >> 2) + (int)(lo >> 32);
                hash ^= (hash << 5) + (hash >> 2) + (int)lo;
                return hash;
            }
        }

        public static int OAT(ulong hi, ulong lo)
        {
            // Bob Jenkins' One-at-a-Time hash algorithm
            unchecked {
                int hash = 0;

                hash += (int)(hi >> 32);
                hash += (hash << 10);
                hash ^= (hash >> 6);

                hash += (int)hi;
                hash += (hash << 10);
                hash ^= (hash >> 6);

                hash += (int)(lo >> 32);
                hash += (hash << 10);
                hash ^= (hash >> 6);

                hash += (int)lo;
                hash += (hash << 10);
                hash ^= (hash >> 6);

                // NOTE: this last part was omitted as it reduced key distribution during testing
                //hash += (hash << 3);
                //hash ^= (hash >> 11);
                //hash += (hash << 15);

                return hash;
            }
        }

    }
}
