using System;
using System.Linq;

namespace Volpe.Cafe.Utils
{
    /// <summary>
    /// Provides a pre-built array of 64-bit keys, along with common helper methods for processing 64-bit keys.
    /// </summary>
    [Serializable]
    public static class KeyHelper
    {
        /// <summary>
        /// Defines the number of supported keys.
        /// </summary>
        public const int KeyCount = 64;

        /// <summary>
        /// Provides an array of pre-calculated keys corresponding to the expanded values of 2^n for each index in the
        /// array. That is:
        ///     Keys[0]=2^0 (1);
        ///     Keys[1]=2^1 (2);
        ///     Keys[2]=2^2 (4);
        ///     Keys[3]=2^3 (8);
        ///     Keys[4]=2^4 (16); and so on.
        /// The array contains a maximum of 64 pre-calculated values, where the last element is:
        ///     Keys[63]=2^63 (9,223,372,036,854,775,808).
        /// </summary>
        public static readonly ulong[] Keys = (from i in Enumerable.Range(0, KeyCount) select 1UL << i).ToArray();

        /// <summary>
        /// Builds a 64-bit key from the specified indexes.
        /// </summary>
        /// <param name="indexes">One or more indexes, ranged 0 to 63, from which to compute a 64-bit key.</param>
        /// <returns>A 64-bit key.</returns>
        /// <exception cref="IndexOutOfRangeException">
        ///     One or more of the indexes is out of range (must be between 0 and 63, inclusive).
        /// </exception>
        public static ulong BuildKey(params int[] indexes)
        {
            if (indexes == null || indexes.Length == 0) {
                return 0UL;
            }

            ulong key = 0UL;
            for (int i = 0; i < indexes.Length; i++) {
                key |= Keys[indexes[i]];
            }
            return key;
        }
        /// <summary>
        /// Builds a 64-bit key from the specified array of key states.
        /// </summary>
        /// <param name="states">
        ///     An array of states, with a maximum length of 64, from which to compute a 64-bit key.
        /// </param>
        /// <returns>A 64-bit key.</returns>
        /// <exception cref="ArgumentOutOfRangeException">
        ///     Length of states array may not exceed 64 elements.
        /// </exception>
        public static ulong BuildKey(bool[] states)
        {
            if (states == null || states.Length == 0) {
                return 0UL;
            }
            if (states.Length > KeyCount) {
                throw new ArgumentOutOfRangeException(
                    nameof(states), $"Length of states array may not exceed {KeyCount} elements.");
            }

            ulong key = 0UL;
            for (int i = 0; i < states.Length; i++) {
                if (states[i]) {
                    key |= Keys[i];
                }
            }
            return key;
        }

        /// <summary>
        /// Determines whether the given key contains a key with the specified index.
        /// </summary>
        /// <param name="key">The key to search.</param>
        /// <param name="index">The index to test.</param>
        /// <returns>
        /// <c>true</c>, if a key with the specified index is a subset of the given key; <c>false</c>, otherwise.
        /// </returns>
        /// <exception cref="IndexOutOfRangeException">
        ///     <paramref name="index"/> must be between 0 and 63, inclusive.
        /// </exception>
        public static bool ContainsKey(ulong key, int index)
        {
            return ContainsKey(key, Keys[index]);
        }
        /// <summary>
        /// Determines whether the given key contains a key with the specified value.
        /// </summary>
        /// <param name="key">The key to search.</param>
        /// <param name="value">The value to test.</param>
        /// <returns>
        /// <c>true</c>, if a key with the specified value is a subset of the given key; <c>false</c>, otherwise.
        /// </returns>
        public static bool ContainsKey(ulong key, ulong value)
        {
            return (key & value) == value;
        }

        /// <summary>
        /// Generates a hex string for the specified <see cref="ulong"/> value.
        /// </summary>
        /// <param name="value">The value for which to generate a hex string.</param>
        /// <returns>
        /// A string representation of the specified <see cref="ulong"/> value, in hexadecimal notation.
        /// </returns>
        public static string GetHexString(ulong value)
        {
            return ((ushort)(value >> 48)).ToString("X4") + "-"
                 + ((ushort)(value >> 32)).ToString("X4") + "-"
                 + ((ushort)(value >> 16)).ToString("X4") + "-"
                 + ((ushort)(value >>  0)).ToString("X4");
        }

    }
}
