using Ethanol.MalwareSonar.Fuzzy;
/// <summary>
/// Provides a collection of operations related to fuzzy sets.
/// </summary>
public static class FuzzySetOperations
{
    /// <summary>
    /// Contains different T-norm functions for fuzzy set operations.
    /// </summary>
    public static class NormFunctions
    {
        /// <summary>
        /// Minimum T-norm function: Returns the minimum of two membership values.
        /// </summary>
        public static readonly Func<double, double, double> Minimum = (a, b) => Math.Min(a, b);

        /// <summary>
        /// Bounded Difference T-norm function: Returns the bounded difference of two membership values.
        /// </summary>
        public static readonly Func<double, double, double> BoundedDifference = (a, b) => Math.Max(0, a + b - 1);

        /// <summary>
        /// Drastic T-norm function: Returns the minimum value if one of the values is 1, otherwise returns 0.
        /// </summary>
        public static readonly Func<double, double, double> Drastic = (a, b) => Math.Max(a, b) == 1 ? Math.Min(a, b) : 0;

        /// <summary>
        /// Einstein T-norm function: Applies the Einstein product of two membership values.
        /// </summary>
        public static readonly Func<double, double, double> Einstein = (a, b) => (a * b) / (2 - (a + b - a * b));

        /// <summary>
        /// Hamacher T-norm function: Applies the Hamacher product of two membership values.
        /// </summary>
        public static readonly Func<double, double, double> Hamacher = (a, b) => (a * b) / (a + b - a * b);
    }

    /// <summary>
    /// Performs the AND operation on two fuzzy sets using a specified T-norm function.
    /// </summary>
    /// <param name="setA">The first fuzzy set.</param>
    /// <param name="setB">The second fuzzy set.</param>
    /// <param name="product">The T-norm function to use. If null, defaults to Minimum function.</param>
    /// <typeparam name="T">The type of elements in the fuzzy sets.</typeparam>
    /// <returns>A new fuzzy set containing the results of the AND operation.</returns>
    /// <exception cref="ArgumentNullException">Thrown when setA or setB is null.</exception>
    public static FuzzySet<T> AndOperation<T>(FuzzySet<T> setA, FuzzySet<T> setB, Func<double, double, double>? product = null) where T : notnull
    {
        if (setA is null)
        {
            throw new ArgumentNullException(nameof(setA));
        }

        if (setB is null)
        {
            throw new ArgumentNullException(nameof(setB));
        }

        if (product is null)
        {
            product = NormFunctions.Minimum;
        }

        var elements = new Dictionary<T, double>();
        foreach (var element in setA.Members)
        {
            if (setB.Members.Contains(element))
            {
                double membershipA = setA.GetMembership(element);
                double membershipB = setB.GetMembership(element);

                var mv = product(membershipA, membershipB);
                elements.Add(element, Math.Min(1, Math.Max(0, mv)));
            }
        }

        return new FuzzySet<T>(new ValueBasedMembership<T>(elements), elements.Keys);
    }

    /// <summary>
    /// Contains different T-conorm functions for fuzzy set OR operations.
    /// </summary>
    public static class ConormFunctions
    {
        /// <summary>
        /// Maximum T-conorm function: Returns the maximum of two membership values.
        /// </summary>
        public static readonly Func<double, double, double> Maximum = (a, b) => Math.Max(a, b);

        /// <summary>
        /// Probabilistic Sum T-conorm function: Returns the probabilistic sum of two membership values.
        /// </summary>
        public static readonly Func<double, double, double> Probabilistic = (a, b) => a + b - a * b;

        /// <summary>
        /// Bounded Sum T-conorm function: Returns the bounded sum of two membership values.
        /// </summary>
        public static readonly Func<double, double, double> Bounded = (a, b) => Math.Min(1, a + b);

        /// <summary>
        /// Drastic T-conorm function: Returns the maximum value if one of the values is 0, otherwise returns 1.
        /// </summary>
        public static readonly Func<double, double, double> Drastic = (a, b) => Math.Min(a, b) == 0 ? Math.Max(a, b) : 1;

        /// <summary>
        /// Einstein T-conorm function: Applies the Einstein sum of two membership values.
        /// </summary>
        public static readonly Func<double, double, double> Einstein = (a, b) => (a + b) / (1 + (a * b));

        /// <summary>
        /// Hamacher T-conorm function: Applies the Hamacher sum of two membership values.
        /// </summary>
        public static readonly Func<double, double, double> Hamacher = (a, b) => a + b - 2 * a * b / (1 - a * b);
    }

    /// <summary>
    /// Performs the OR operation on two fuzzy sets.
    /// </summary>
    /// <param name="setA">The first fuzzy set.</param>
    /// <param name="setB">The second fuzzy set.</param>
    /// <typeparam name="T">The type of elements in the fuzzy sets.</typeparam>
    /// <returns>A new fuzzy set containing the results of the OR operation.</returns>
    public static FuzzySet<T> OrOperation<T>(FuzzySet<T> setA, FuzzySet<T> setB, Func<double, double, double>? coproduct = null) where T : notnull
    {
        if (setA is null)
        {
            throw new ArgumentNullException(nameof(setA));
        }

        if (setB is null)
        {
            throw new ArgumentNullException(nameof(setB));
        }
        if (coproduct is null) coproduct = ConormFunctions.Maximum;

        var resultSet = new Dictionary<T, double>();

        // Add all elements from setA
        foreach (var element in setA.Members)
        {
            resultSet[element] = coproduct(setA.GetMembership(element), 0);
        }

        // Update the membership with the maximum value
        foreach (var element in setB.Members)
        {
            if (resultSet.ContainsKey(element))
            {                
                resultSet[element] = coproduct(setA.GetMembership(element), setB.GetMembership(element));
            }
            else
            {
                resultSet[element] = coproduct(0, setB.GetMembership(element));
            }
        }

        return new FuzzySet<T>(new ValueBasedMembership<T>(resultSet), resultSet.Keys);
    }

    /// <summary>
    /// Computes the difference between two fuzzy sets.
    /// </summary>
    /// <typeparam name="T">The type of elements in the fuzzy sets.</typeparam>
    /// <param name="setA">The first fuzzy set.</param>
    /// <param name="setB">The second fuzzy set, from which elements will be subtracted from the first set.</param>
    /// <param name="product">The T-norm function to use. If null, defaults to Minimum function.</param>
    /// <returns>A new fuzzy set representing the difference between set A and set B.</returns>
    /// <remarks>
    /// The difference between two fuzzy sets represents elements that are in set A
    /// but not in set B, according to their membership degrees.
    /// </remarks>
    /// <exception cref="ArgumentNullException">Thrown if either input set is null.</exception>
    public static FuzzySet<T> Difference<T>(FuzzySet<T> setA, FuzzySet<T> setB, Func<double, double, double>? product = null) where T : notnull
    {
        if (setA is null)
        {
            throw new ArgumentNullException(nameof(setA));
        }
        
              
        if (setB is null)
        {
            throw new ArgumentNullException(nameof(setB));
        }

        if (product is null) product = NormFunctions.Minimum;

        var resultSet = new Dictionary<T, double>();
        
        foreach (var a in setA.Members)
        { 
            if (setB.Members.Contains(a))
            {
                var v = Normalize(product(1 - setA.GetMembership(a), setB.GetMembership(a)));
                if (v > 0) resultSet[a] = v;
            }
            else
            {
                resultSet[a] = setA.GetMembership(a);
            }
        }
        return new FuzzySet<T>(new ValueBasedMembership<T>(resultSet), resultSet.Keys);
    }
    public static double Normalize(double x) => Math.Max(0, Math.Min(1, x));
}
