using System;

namespace Volpe.Cafe.Generic
{
    /// <summary>
    /// Represents an object that stores modeling data for each fuel type.
    /// <br/>
    /// This object supports and operates on all fuel types as defined by <see cref="FuelTypes.List"/>.
    /// </summary>
    [Serializable]
    public class FTValue<T> : ICloneable
    {
        #region <<< Constructors >>>

        /// <summary>
        /// Initializes a new instance of the <see cref="FTValue{T}"/> class.
        /// </summary>
        public FTValue() : this(new T[FuelTypes.List.Length]) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="FTValue{T}"/> class, using the specified initializeT method
        /// for instantiating each element of type T to a specific initial value.
        /// </summary>
        /// <param name="initializeT">
        ///     Encapsulates a method that will handle instantiation for each element of type T within this instance.
        /// </param>
        public FTValue(Func<T> initializeT) : this()
        {
            GenericsHelper.Initialize(this.Items, initializeT);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="FTValue{T}"/> class using the specified values.
        /// </summary>
        public FTValue(T gasoline, T ethanol85, T diesel, T electricity, T hydrogen, T cng)
            : this(new T[] { gasoline, ethanol85, diesel, electricity, hydrogen, cng }) { }

        FTValue(T[] items)
        {
            this.Items = items;
        }

        #endregion

        #region <<< Properties >>>

        /// <summary>Gets or sets the modeling data value based on the given <see cref="Cafe.FuelType"/>.</summary>
        /// <exception cref="IndexOutOfRangeException">
        ///     The fuelType specified does not represent a valid <see cref="Cafe.FuelType"/>.
        /// </exception>
        public T this[FuelType fuelType]
        {
            get => this.Items[GetIndex(fuelType)];
            set => this.Items[GetIndex(fuelType)] = value;
        }

        /// <summary>
        /// Gets or sets the modeling data value associated with the <see cref="Cafe.FuelType.Gasoline"/> fuel type.
        /// </summary>
        public T Gasoline
        {
            get => this.Items[FuelTypes.GasolineIndex];
            set => this.Items[FuelTypes.GasolineIndex] = value;
        }

        /// <summary>
        /// Gets or sets the modeling data value associated with the <see cref="Cafe.FuelType.Ethanol85"/> fuel type.
        /// </summary>
        public T Ethanol85
        {
            get => this.Items[FuelTypes.Ethanol85Index];
            set => this.Items[FuelTypes.Ethanol85Index] = value;
        }

        /// <summary>
        /// Gets or sets the modeling data value associated with the <see cref="Cafe.FuelType.Diesel"/> fuel type.
        /// </summary>
        public T Diesel
        {
            get => this.Items[FuelTypes.DieselIndex];
            set => this.Items[FuelTypes.DieselIndex] = value;
        }

        /// <summary>
        /// Gets or sets the modeling data value associated with the <see cref="Cafe.FuelType.Electricity"/> fuel type.
        /// </summary>
        public T Electricity
        {
            get => this.Items[FuelTypes.ElectricityIndex];
            set => this.Items[FuelTypes.ElectricityIndex] = value;
        }

        /// <summary>
        /// Gets or sets the modeling data value associated with the <see cref="Cafe.FuelType.Hydrogen"/> fuel type.
        /// </summary>
        public T Hydrogen
        {
            get => this.Items[FuelTypes.HydrogenIndex];
            set => this.Items[FuelTypes.HydrogenIndex] = value;
        }

        /// <summary>
        /// Gets or sets the modeling data value associated with the <see cref="Cafe.FuelType.CNG"/> fuel type.
        /// </summary>
        public T Cng
        {
            get => this.Items[FuelTypes.CngIndex];
            set => this.Items[FuelTypes.CngIndex] = value;
        }

        /// <summary>
        /// Gets whether this <see cref="FTValue{T}"/> instance has actual (non-default) values for more then one
        /// component.
        /// </summary>
        public bool IsMultiFuel
        {
            get
            {
                int f = (int)this.FuelType;
                return ((f & (f - 1)) != 0);
            }
        }

        /// <summary>
        /// Gets the fuel type that represents all componenets of this <see cref="FTValue{T}"/> instance.
        /// </summary>
        public FuelType FuelType
        {
            get
            {
                FuelType fuelType = FuelType.None;
                for (int i = 0; i < this.Items.Length; i++)
                {
                    if (this.Items[i] != null && !this.Items[i].Equals(default(T)))
                    {
                        fuelType |= FuelTypes.List[i];
                    }
                }
                return fuelType;
            }
        }

        /// <summary>Gets an array of modeling data specified by <see cref="Cafe.FuelType"/>.</summary>
        public T[] Items { get; private set; }

        #endregion

        #region <<< Methods >>>

        #region < Overloaded Operators >

        /// <summary>
        /// Determines whether the specified two values are equal.
        /// </summary>
        /// <param name="value1">The first value to compare.</param>
        /// <param name="value2">The second value to compare.</param>
        /// <returns>true, if the two values are equal; false, otherwise.</returns>
        public static bool operator ==(FTValue<T> value1, FTValue<T> value2)
        {
            return Equals(value1, value2);
        }
        /// <summary>
        /// Determines whether the specified two values are not equal.
        /// </summary>
        /// <param name="value1">The first value to compare.</param>
        /// <param name="value2">The second value to compare.</param>
        /// <returns>true, if the two values are not equal; false, otherwise.</returns>
        public static bool operator !=(FTValue<T> value1, FTValue<T> value2)
        {
            return !Equals(value1, value2);
        }

        #endregion

        #region < ICloneable Members and Other Clone Methods >

        /// <summary>
        /// Creates a shallow copy of the current <see cref="FTValue{T}"/> instance.
        /// </summary>
        /// <returns>A new object that is a copy of this <see cref="FTValue{T}"/>.</returns>
        object ICloneable.Clone()
        {
            return this.Clone(null);
        }
        /// <summary>
        /// Creates a shallow copy of the current <see cref="FTValue{T}"/> instance.
        /// </summary>
        /// <returns>A new object that is a copy of this <see cref="FTValue{T}"/>.</returns>
        public FTValue<T> Clone()
        {
            return this.Clone(null);
        }
        /// <summary>
        /// Creates a copy of the current <see cref="FTValue{T}"/> instance, optionally using the specified method for
        /// cloning each element of type T within this instance.
        /// </summary>
        /// <param name="cloneT">
        ///     Encapsulates a method that will handle cloning for each element of type T within this instance.
        /// </param>
        /// <returns>A new object that is a copy of this <see cref="FTValue{T}"/>.</returns>
        public FTValue<T> Clone(Func<T, T> cloneT)
        {
            FTValue<T> value = new FTValue<T>();
            this.CloneInternal(this, value, cloneT);
            return value;
        }

        /// <summary>
        /// Copies all elements from source to destination, optionally using the specified method for cloning each
        /// element of type T from one instance to the other.
        /// </summary>
        /// <param name="source">The source of the clone operation.</param>
        /// <param name="destination">The destination of the clone operation.</param>
        /// <param name="cloneT">Encapsulates a method that will handle cloning for each element of type T.</param>
        protected void CloneInternal(FTValue<T> source, FTValue<T> destination, Func<T, T> cloneT)
        {
            GenericsHelper.Clone(source.Items, destination.Items, cloneT);
        }

        #endregion

        #region < Overriden from object >

        /// <summary>
        /// Returns the string representation of this <see cref="FTValue{T}"/> instance.
        /// </summary>
        /// <returns>The string representation of the <see cref="FTValue{T}"/> instance.</returns>
        public override string ToString()
        {
            return GenericsHelper.CalcString(this.Items, FuelTypes.Names);
        }

        /// <summary>
        /// Serves as a hash function for a particular type, suitable for use in hashing algorithms and data structures
        /// like a hash table.
        /// </summary>
        /// <returns>A hash code for the current <see cref="FTValue{T}"/>.</returns>
        public override int GetHashCode()
        {
            return GenericsHelper.CalcHashCode(this.Items);
        }

        /// <summary>
        /// Determines whether the specified <see cref="object"/> is equal to the current instance.
        /// </summary>
        /// <param name="obj">The <see cref="object"/> to compare with the current instance.</param>
        /// <returns>
        /// true, if the specified <see cref="object"/> is equal to the current instance; false, otherwise.
        /// </returns>
        public override bool Equals(object obj)
        {
            return (obj is FTValue<T>) ? this.Equals((FTValue<T>)obj) : base.Equals(obj);
        }
        /// <summary>
        /// Determines whether the specified value is equal to the current instance.
        /// </summary>
        /// <param name="value">The value to compare with the current instance.</param>
        /// <returns>true, if the specified value is equal to the current instance; false, otherwise.</returns>
        public bool Equals(FTValue<T> value)
        {
            return Equals(this, value);
        }
        /// <summary>
        /// Determines whether the specified two values are equal.
        /// </summary>
        /// <param name="value1">The first value to compare.</param>
        /// <param name="value2">The second value to compare.</param>
        /// <returns>true, if the specified two values are equal; false, otherwise.</returns>
        public static bool Equals(FTValue<T> value1, FTValue<T> value2)
        {
            return GenericsHelper.Equals(value1?.Items, value2?.Items);
        }

        #endregion

        /// <summary>
        /// Resets all members of this instance to their default values (0, false, or null), or optionally using the
        /// specified clearT method to reset each element of type T to a specific initial value.
        /// </summary>
        /// <param name="clearT">
        ///     Encapsulates a method that will handle clearing for each element of type T within this instance.
        /// </param>
        public virtual void Clear(Action<T> clearT = null)
        {
            GenericsHelper.Clear(this.Items, clearT);
        }

        /// <summary>
        /// Returns the index corresponding to the specified <see cref="Cafe.FuelType"/>, as defined within the context
        /// of this <see cref="FTValue{T}"/>.
        /// </summary>
        /// <returns>The index corresponding to the specified <see cref="Cafe.FuelType"/>.</returns>
        public static int GetIndex(FuelType fuelType)
        {
            return fuelType.GetListIndex();
        }

        #endregion
    }
}
