#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 128-bit technology key.
    /// </summary>
    [StructLayout(LayoutKind.Sequential)]
    [Serializable]
    public struct TechKey128 : ITechKey, IComparable, IComparable<TechKey128>
    {
        #region <<< Variables >>>

        //----- static variables -----
        /// <summary>
        /// Defines the number of supported keys.
        /// </summary>
        public const int KeyCount = 128;

        /// <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 128 pre-calculated values, where the last element is Keys[127]=2^127.
        /// </summary>
        public static readonly TechKey128[] Keys =
            (from i in Enumerable.Range(0, KeyCount)
             select NewKey(i >= 64 ? 1UL << i : 0,
                           i  < 64 ? 1UL << i : 0)).ToArray();

        //----- runtime variables -----
        private ulong _hi, _lo;

        #endregion

        #region <<< Properties >>>

        /// <summary>Gets an empty 128-bit technology key.</summary>
        public static TechKey128 Empty => NewKey(0UL, 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._hi |= key._hi;
                    this._lo |= key._lo;
                }
                else {
                    this._hi &= ~(key._hi);
                    this._lo &= ~(key._lo);
                }
            }
        }

        #endregion

        #region <<< Methods >>>

        #region < Overloaded Operators >

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

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

        public static TechKey128 operator |(TechKey128 value1, TechKey128 value2)
        {
            return NewKey(value1._hi | value2._hi,
                          value1._lo | value2._lo);
        }
        public static TechKey128 operator &(TechKey128 value1, TechKey128 value2)
        {
            return NewKey(value1._hi & value2._hi,
                          value1._lo & value2._lo);
        }
        public static TechKey128 operator ^(TechKey128 value1, TechKey128 value2)
        {
            return NewKey(value1._hi ^ value2._hi,
                          value1._lo ^ value2._lo);
        }
        public static TechKey128 operator ~(TechKey128 value) => NewKey(~value._hi, ~value._lo);

        #endregion

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

            ulong hi = 0UL, lo = 0UL;
            for (int i = 0; i < indexes.Length; i++) {
                hi |= Keys[indexes[i]]._hi;
                lo |= Keys[indexes[i]]._lo;
            }
            return NewKey(hi, lo);
        }

        /// <summary>
        /// Builds a 128-bit key from the specified array of key states.
        /// </summary>
        /// <param name="states">
        ///     An array of states, with a maximum length of 128, from which to compute a 128-bit key.
        /// </param>
        /// <returns>A 128-bit key.</returns>
        /// <exception cref="ArgumentOutOfRangeException">
        ///     Length of states array may not exceed 128 elements.
        /// </exception>
        public static TechKey128 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 hi = 0UL, lo = 0UL;
            for (int i = 0; i < states.Length; i++) {
                if (states[i]) {
                    hi |= Keys[i]._hi;
                    lo |= Keys[i]._lo;
                }
            }
            return NewKey(hi, lo);
        }

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

        /// <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._hi, this._lo);
        }

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

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

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

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

        private static int Compare(TechKey128 value1, TechKey128 value2)
        {
            return (value1._hi != value2._hi)
                 ? value1._hi.CompareTo(value2._hi)
                 : value1._lo.CompareTo(value2._lo);
        }

        private static TechKey128 NewKey(ulong hi, ulong lo)
        {
            return new TechKey128() { _hi = hi, _lo = lo };
        }

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

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

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

        #endregion
    }
}
