namespace Ethanol.MalwareSonar.Fuzzy
{

    /// <summary>
    /// Represents a membership function that calculates the membership of data items based on their frequency,
    /// normalized between specified minimum and maximum frequency values.
    /// </summary>
    /// <typeparam name="TData">The type of data items the membership function operates on. Must be a non-nullable type.</typeparam>
    /// <remarks>
    /// This class uses a frequency dictionary to determine the relative frequency of each item.
    /// The membership value for each item is calculated based on its frequency, adjusted by the minimum
    /// and maximum frequency bounds.
    /// </remarks>
    public class FrequencyBasedMembership<TData> : IMembershipFunction<TData> where TData : notnull
    {
        // Minimum frequency threshold for normalization.
        private readonly int _minFrequency;

        // Maximum frequency threshold for normalization.
        private readonly int _maxFrequency;

        // Dictionary to store the frequency of each data item.
        private Dictionary<TData, int> _frequency;


        /// <summary>
        /// Initializes a new instance of the FrequencyBasedMembership class with the specified frequency data and explicit min/max frequency bounds.
        /// </summary>
        /// <param name="frequency">A dictionary representing the frequency of each data item.</param>
        /// <param name="minFrequency">The minimum frequency value for normalization.</param>
        /// <param name="maxFrequency">The maximum frequency value for normalization.</param>
        public FrequencyBasedMembership(IDictionary<TData, int> frequency, int minFrequency, int maxFrequency)
        {
            if (frequency is null)
            {
                throw new ArgumentNullException(nameof(frequency));
            }

            _frequency = new Dictionary<TData, int>(frequency);
            this._minFrequency = minFrequency;
            this._maxFrequency = maxFrequency;
        }

        /// <summary>
        /// Initializes a new instance of the FrequencyBasedMembership class with the specified frequency data as an enumerable and explicit min/max frequency bounds.
        /// </summary>
        /// <param name="frequency">An enumerable of key-value pairs representing the frequency of each data item.</param>
        /// <param name="minFrequency">The minimum frequency value for normalization.</param>
        /// <param name="maxFrequency">The maximum frequency value for normalization.</param>
        public FrequencyBasedMembership(IEnumerable<KeyValuePair<TData, int>> frequency, int minFrequency, int maxFrequency)
        {
            if (frequency is null)
            {
                throw new ArgumentNullException(nameof(frequency));
            }

            _frequency = new Dictionary<TData, int>(frequency);
            this._minFrequency = minFrequency;
            this._maxFrequency = maxFrequency;
        }

        /// <summary>
        /// Initializes a new instance of the FrequencyBasedMembership class with the specified frequency data as an enumerable.
        /// The min/max frequency bounds are automatically determined.
        /// </summary>
        /// <param name="frequency">An enumerable of key-value pairs representing the frequency of each data item.</param>
        public FrequencyBasedMembership(IEnumerable<KeyValuePair<TData, int>> frequency)
        {
            if (frequency is null)
            {
                throw new ArgumentNullException(nameof(frequency));
            }

            _frequency = new Dictionary<TData, int>(frequency);
            this._minFrequency = 0;
            this._maxFrequency = (_frequency.Count() > 0) ? _frequency.Max(x => x.Value) : 0;
        }

        /// <summary>
        /// Calculates the normalized membership value of a specific data item based on its frequency.
        /// </summary>
        /// <param name="x">The data item to evaluate the membership for.</param>
        /// <returns>
        /// The normalized membership value of the data item, calculated as (frequency - minFrequency) / maxFrequency.
        /// Returns 0 if the frequency dictionary is empty or the item is not found.
        /// </returns>
        public double GetMembership(TData x)
        {
            // Check if the frequency dictionary is not empty
            if (_frequency.Count > 0)
            {
                // Calculate the membership value based on frequency
                var freq = _frequency.TryGetValue(x, out var value) ? value : 0;
                return Math.Min(Math.Max(0, (freq - _minFrequency) / (double)_maxFrequency), 1);
            }
            else
            {
                // Return 0 if the frequency dictionary is empty
                return 0;
            }
        }

        public IEnumerable<TData> GetMembers()
        {
            return this._frequency.Select(x => x.Key);
        }
    }
}
