#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member

using System;
using System.Linq;
using System.Runtime.InteropServices;
using Volpe.Cafe.Utils;

namespace Volpe.Cafe.TechKeys
{
    /// <summary>
    /// Represents a 64-bit technology key.
    /// </summary>
    [StructLayout(LayoutKind.Sequential)]
    [Serializable]
    public struct TechKey64 : ITechKey, IComparable, IComparable<TechKey64>
    {
        #region <<< Variables >>>

        //----- static variables -----
        /// <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.
        /// </summary>
        public static readonly TechKey64[] Keys =
            (from i in Enumerable.Range(0, KeyCount)
             select NewKey(1UL << i)).ToArray();

        //----- runtime variables -----
        private ulong _value;

        #endregion

        #region <<< Properties >>>

        /// <summary>Gets an empty 64-bit technology key.</summary>
        public static TechKey64 Empty => NewKey(0UL);

        public bool IsEmpty => this.Equals(Empty);

        public bool this[int techIndex]
        {
            get => this.Contains(Keys[techIndex]);
            set {
                var key = Keys[techIndex];
                if (value) {
                    this._value |= key._value;
                }
                else {
                    this._value &= ~(key._value);
                }
            }
        }

        #endregion

        #region <<< Methods >>>

        #region < Overloaded Operators >

        public static bool operator ==(TechKey64 value1, TechKey64 value2) => value1.Equals(value2);
        public static bool operator !=(TechKey64 value1, TechKey64 value2) => !value1.Equals(value2);

        public static bool operator >(TechKey64 value1, TechKey64 value2) => Compare(value1, value2) > 0;
        public static bool operator <(TechKey64 value1, TechKey64 value2) => Compare(value1, value2) < 0;
        public static bool operator >=(TechKey64 value1, TechKey64 value2) => Compare(value1, value2) >= 0;
        public static bool operator <=(TechKey64 value1, TechKey64 value2) => Compare(value1, value2) <= 0;

        public static TechKey64 operator |(TechKey64 value1, TechKey64 value2) => NewKey(value1._value | value2._value);
        public static TechKey64 operator &(TechKey64 value1, TechKey64 value2) => NewKey(value1._value & value2._value);
        public static TechKey64 operator ^(TechKey64 value1, TechKey64 value2) => NewKey(value1._value ^ value2._value);
        public static TechKey64 operator ~(TechKey64 value) => NewKey(~value._value);

        #endregion

        /// <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 TechKey64 BuildKey(params int[] indexes)
        {
            if (indexes == null || indexes.Length == 0) {
                return Empty;
            }

            ulong key = 0UL;
            for (int i = 0; i < indexes.Length; i++) {
                key |= Keys[indexes[i]]._value;
            }
            return NewKey(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 TechKey64 BuildKey(bool[] states)
        {
            if (states == null || states.Length == 0) {
                return Empty;
            }
            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]._value;
                }
            }
            return NewKey(key);
        }

        /// <summary>
        /// Returns the string representation of this <see cref="TechKey64"/> instance.
        /// </summary>
        /// <returns>The string representation of the <see cref="TechKey64"/> instance.</returns>
        public override string ToString()
        {
            return KeyHelper.GetHexString(this._value);
        }

        /// <summary>
        /// Returns the hash code for this instance.
        /// </summary>
        /// <returns>A 32-bit signed integer hash code.</returns>
        public override int GetHashCode()
        {
            return HashHelper.FNVMod(this._value, 0UL);
        }

        /// <summary>
        /// Determines whether the specified <see cref="object"/> is equal to the current <see cref="TechKey64"/>
        /// value.
        /// </summary>
        /// <param name="obj">
        ///     The <see cref="object"/> to compare with the current <see cref="TechKey64"/> value.
        /// </param>
        /// <returns>
        /// true, if the specified <see cref="object"/> is equal to the current <see cref="TechKey64"/> value;
        /// false, otherwise.
        /// </returns>
        public override bool Equals(object obj)
        {
            return (obj is TechKey64 value) && this.Equals(value);
        }

        /// <summary>
        /// Determines whether the specified <see cref="TechKey64"/> value is equal to the current
        /// <see cref="TechKey64"/> value.
        /// </summary>
        /// <param name="value">
        ///     The <see cref="TechKey64"/> value to compare with the current <see cref="TechKey64"/> value.
        /// </param>
        /// <returns>
        /// true, if the specified <see cref="TechKey64"/> value is equal to the current <see cref="TechKey64"/> value;
        /// false, otherwise.
        /// </returns>
        public bool Equals(TechKey64 value)
        {
            return (this._value == value._value);
        }

        public bool Contains(int techIndex)
        {
            return this.Contains(Keys[techIndex]);
        }

        /// <inheritdoc cref="ITechKey.Contains(ITechKey)"/>
        public bool Contains(TechKey64 key)
        {
            return (this & key) == key;
        }

        private static int Compare(TechKey64 value1, TechKey64 value2)
        {
            return value1._value.CompareTo(value2._value);
        }

        private static TechKey64 NewKey(ulong value)
        {
            return new TechKey64() { _value = value };
        }

        //----- explicit interface implementation -----
        bool ITechKey.Contains(ITechKey key)
        {
            return this.Contains((TechKey64)key);
        }

        int IComparable.CompareTo(object value)
        {
            return (value == null) ? 1
                 : (value is TechKey64 key) ? Compare(this, key)
                 : throw new ArgumentException("Value must be TechKey64");
        }

        int IComparable<TechKey64>.CompareTo(TechKey64 value)
        {
            return Compare(this, value);
        }

        #endregion
    }
}
