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

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

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

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

        #endregion

        #region <<< Properties >>>

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

        #endregion

        #region <<< Methods >>>

        #region < Overloaded Operators >

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

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

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

        #endregion

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

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

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

        /// <summary>
        /// Returns the string representation of this <see cref="TechKey192"/> instance.
        /// </summary>
        /// <returns>The string representation of the <see cref="TechKey192"/> instance.</returns>
        public override string ToString()
        {
            return KeyHelper.GetHexString(this._hi) + "-"
                 + KeyHelper.GetHexString(this._md) + "-"
                 + 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()
        {
            // this is a (somewhat imperfect) workaround for using modified FNV hashing
            unchecked
            {
                int hash = HashHelper.FNVMod(this._hi, this._md);
                hash = (hash * 16777619) ^ (int)(this._lo >> 32);
                hash = (hash * 16777619) ^ (int)this._lo;
                return hash;
            }
        }

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

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

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

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

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

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

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

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

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

        #endregion
    }
}
