/**
 * Qualitative Capacity Fusion Rules
 * 
 * Implements the fusion operations for qualitative capacities as described in the research paper.
 * The core operations are:
 * 1. Dempster-like Maxmin Conjunctive Rule (⊗)
 * 2. Normalized Conjunctive Rule (⊗̂)
 * 3. Disjunctive Rule (⊕)
 * 
 * All operations work on the Qualitative Möbius Transform (QMT) representation.
 */

import { QualitativeCapacity } from './QualitativeCapacity.js';

export class QualitativeFusion {
  
  /**
   * Dempster-like Maxmin Conjunctive Rule (⊗)
   * 
   * Given two QMTs ρ₁ and ρ₂, compute the unnormalized combination:
   * ρ_raw(A) = max_{E₁ ∩ E₂ = A} min(ρ₁(E₁), ρ₂(E₂))
   * 
   * @param {QualitativeCapacity} capacity1 - First capacity
   * @param {QualitativeCapacity} capacity2 - Second capacity
   * @returns {Map} Raw QMT result (unnormalized)
   */
  static dempsterLikeConjunctive(capacity1, capacity2) {
    if (!capacity1.scale.equals(capacity2.scale)) {
      throw new Error('Capacities must use the same qualitative scale');
    }
    
    const scale = capacity1.scale;
    const stateSpace = capacity1.stateSpace;
    const qmt1 = capacity1.getQMTMap();
    const qmt2 = capacity2.getQMTMap();
    
    const resultQMT = new Map();
    
    // For each pair of focal sets from both capacities
    for (const [E1, v1] of qmt1) {
      for (const [E2, v2] of qmt2) {
        // Compute intersection
        const intersection = new Set([...E1].filter(x => E2.has(x)));
        
        // Compute min value
        const minValue = scale.min(v1, v2);
        
        // Update result: max of existing value and new min value
        const existingValue = resultQMT.get(intersection) || scale.bottom;
        const newValue = scale.max(existingValue, minValue);
        
        if (newValue !== scale.bottom) {
          resultQMT.set(intersection, newValue);
        } else {
          resultQMT.delete(intersection);
        }
      }
    }
    
    return resultQMT;
  }
  
  /**
   * Normalized Conjunctive Rule (⊗̂)
   * 
   * Applies the Dempster-like rule followed by normalization:
   * 1. Compute raw combination using ⊗
   * 2. Bottom normalization: remove empty set entry
   * 3. Top normalization: ensure at least one focal set has weight 1
   * 
   * @param {Array<QualitativeCapacity>} capacities - Array of capacities to combine
   * @returns {QualitativeCapacity} Normalized combined capacity
   */
  static normalizedConjunctive(capacities) {
    if (!Array.isArray(capacities) || capacities.length === 0) {
      throw new Error('At least one capacity is required');
    }
    
    if (capacities.length === 1) {
      return capacities[0];
    }
    
    // Start with the first capacity
    let resultQMT = capacities[0].getQMTMap();
    const scale = capacities[0].scale;
    const stateSpace = capacities[0].stateSpace;
    
    // Apply conjunctive rule iteratively
    for (let i = 1; i < capacities.length; i++) {
      const tempCapacity = new QualitativeCapacity(stateSpace, scale, resultQMT);
      resultQMT = this.dempsterLikeConjunctive(tempCapacity, capacities[i]);
    }
    
    // Bottom normalization: remove empty set entry
    resultQMT.delete(new Set());
    
    // Top normalization: ensure at least one focal set has weight 1
    const maxValue = scale.maxAll(Array.from(resultQMT.values()));
    if (maxValue < scale.top) {
      resultQMT.set(new Set(stateSpace), scale.top);
    }
    
    // Create new capacity from normalized QMT
    const resultCapacity = new QualitativeCapacity(stateSpace, scale, resultQMT);
    
    // Convert to canonical QMT representation
    return this._convertToCanonicalQMT(resultCapacity);
  }
  
  /**
   * Disjunctive Rule (⊕)
   * 
   * For two capacities γ₁ and γ₂, the disjunctive combination is:
   * γ_⊕(A) = min(γ₁(A), γ₂(A))
   * 
   * @param {QualitativeCapacity} capacity1 - First capacity
   * @param {QualitativeCapacity} capacity2 - Second capacity
   * @returns {QualitativeCapacity} Disjunctive combination
   */
  static disjunctive(capacity1, capacity2) {
    if (!capacity1.scale.equals(capacity2.scale)) {
      throw new Error('Capacities must use the same qualitative scale');
    }
    
    const scale = capacity1.scale;
    const stateSpace = capacity1.stateSpace;
    
    // Generate all possible subsets
    const allSubsets = this._generateAllSubsets(stateSpace);
    const resultQMT = new Map();
    
    for (const subset of allSubsets) {
      const value1 = capacity1.getCapacity(subset);
      const value2 = capacity2.getCapacity(subset);
      const minValue = scale.min(value1, value2);
      
      if (minValue !== scale.bottom) {
        resultQMT.set(subset, minValue);
      }
    }
    
    const resultCapacity = new QualitativeCapacity(stateSpace, scale, resultQMT);
    
    // Convert to canonical QMT representation for consistency
    return this._convertToCanonicalQMT(resultCapacity);
  }
  
  /**
   * Convert a capacity to its canonical QMT representation
   * 
   * The canonical QMT γ#(E) = γ(E) if γ(E) > max_{B ⊂ E} γ(B), 0 otherwise
   * 
   * OPTIMIZED: Uses the fact that γ#(A) > 0 ⟺ γ(A) > max_{w∈A} γ(A∖{w})
   * This is much more efficient than checking all proper subsets.
   * 
   * @param {QualitativeCapacity} capacity - Capacity to convert
   * @returns {QualitativeCapacity} Capacity with canonical QMT
   */
  static _convertToCanonicalQMT(capacity) {
    const scale = capacity.scale;
    const stateSpace = capacity.stateSpace;
    const canonicalQMT = new Map();
    
    // Generate all subsets in order of increasing size
    const allSubsets = this._generateAllSubsets(stateSpace);
    allSubsets.sort((a, b) => a.size - b.size);
    
    for (const subset of allSubsets) {
      const capacityValue = capacity.getCapacity(subset);
      
      // OPTIMIZATION: Only check immediate proper subsets (size |A| - 1)
      // Due to monotonicity, if γ(A) > max_{w∈A} γ(A∖{w}), then γ(A) > max_{B⊂A} γ(B)
      let maxImmediateSubsetValue = scale.bottom;
      for (const element of subset) {
        const immediateSubset = new Set(subset);
        immediateSubset.delete(element);
        const subsetValue = capacity.getCapacity(immediateSubset);
        maxImmediateSubsetValue = scale.max(maxImmediateSubsetValue, subsetValue);
      }
      
      // If capacity value is greater than max of immediate proper subsets, it's a focal set
      if (scale.compare(capacityValue, maxImmediateSubsetValue) > 0) {
        canonicalQMT.set(subset, capacityValue);
      }
    }
    
    return new QualitativeCapacity(stateSpace, scale, canonicalQMT);
  }
  
  /**
   * Generate all possible subsets of a state space
   * @param {Array} stateSpace - Array of states
   * @returns {Array<Set>} Array of all possible subsets
   */
  static _generateAllSubsets(stateSpace) {
    const subsets = [];
    const n = stateSpace.length;
    
    // Generate all 2^n subsets
    for (let i = 0; i < (1 << n); i++) {
      const subset = new Set();
      for (let j = 0; j < n; j++) {
        if (i & (1 << j)) {
          subset.add(stateSpace[j]);
        }
      }
      subsets.push(subset);
    }
    
    return subsets;
  }
  
  /**
   * Check if A is a subset of B
   * @param {Set} A - First set
   * @param {Set} B - Second set
   * @returns {boolean} True if A ⊆ B
   */
  static _isSubset(A, B) {
    for (const element of A) {
      if (!B.has(element)) {
        return false;
      }
    }
    return true;
  }
  
  /**
   * Compute the Sugeno Integral
   * 
   * S_γ(f) = max_{A ⊆ W} min(γ(A), min_{w ∈ A} f(w))
   * 
   * @param {QualitativeCapacity} capacity - The capacity
   * @param {Map|Object} decisionFunction - Function f: W → L
   * @returns {number} Sugeno integral value
   */
  static sugenoIntegral(capacity, decisionFunction) {
    const scale = capacity.scale;
    const stateSpace = capacity.stateSpace;
    
    // Convert decision function to Map if needed
    const f = decisionFunction instanceof Map ? decisionFunction : new Map(Object.entries(decisionFunction));
    
    // Generate all subsets
    const allSubsets = this._generateAllSubsets(stateSpace);
    let maxValue = scale.bottom;
    
    for (const subset of allSubsets) {
      if (subset.size === 0) continue;
      
      // Compute min_{w ∈ A} f(w)
      let minFValue = scale.top;
      for (const state of subset) {
        const fValue = f.get(state) || scale.bottom;
        minFValue = scale.min(minFValue, fValue);
      }
      
      // Compute min(γ(A), min_{w ∈ A} f(w))
      const capacityValue = capacity.getCapacity(subset);
      const minValue = scale.min(capacityValue, minFValue);
      
      // Take maximum over all subsets
      maxValue = scale.max(maxValue, minValue);
    }
    
    return maxValue;
  }
  
  /**
   * Compute pessimistic counterpart γ⁻
   * γ⁻(A) = min(γ(A), ν(γ(A^c)))
   * 
   * @param {QualitativeCapacity} capacity - The capacity
   * @returns {QualitativeCapacity} Pessimistic counterpart
   */
  static pessimisticCounterpart(capacity) {
    const scale = capacity.scale;
    const stateSpace = capacity.stateSpace;
    const allSubsets = this._generateAllSubsets(stateSpace);
    const resultQMT = new Map();
    
    for (const subset of allSubsets) {
      const capacityValue = capacity.getCapacity(subset);
      const complement = new Set(stateSpace.filter(s => !subset.has(s)));
      const complementValue = capacity.getCapacity(complement);
      const negatedComplementValue = scale.negate(complementValue);
      
      const pessimisticValue = scale.min(capacityValue, negatedComplementValue);
      
      if (pessimisticValue !== scale.bottom) {
        resultQMT.set(subset, pessimisticValue);
      }
    }
    
    const resultCapacity = new QualitativeCapacity(stateSpace, scale, resultQMT);
    
    // Convert to canonical QMT representation for consistency
    return this._convertToCanonicalQMT(resultCapacity);
  }
  
  /**
   * Compute optimistic counterpart γ⁺
   * γ⁺(A) = max(γ(A), ν(γ(A^c)))
   * 
   * @param {QualitativeCapacity} capacity - The capacity
   * @returns {QualitativeCapacity} Optimistic counterpart
   */
  static optimisticCounterpart(capacity) {
    const scale = capacity.scale;
    const stateSpace = capacity.stateSpace;
    const allSubsets = this._generateAllSubsets(stateSpace);
    const resultQMT = new Map();
    
    for (const subset of allSubsets) {
      const capacityValue = capacity.getCapacity(subset);
      const complement = new Set(stateSpace.filter(s => !subset.has(s)));
      const complementValue = capacity.getCapacity(complement);
      const negatedComplementValue = scale.negate(complementValue);
      
      const optimisticValue = scale.max(capacityValue, negatedComplementValue);
      
      if (optimisticValue !== scale.bottom) {
        resultQMT.set(subset, optimisticValue);
      }
    }
    
    const resultCapacity = new QualitativeCapacity(stateSpace, scale, resultQMT);
    
    // Convert to canonical QMT representation for consistency
    return this._convertToCanonicalQMT(resultCapacity);
  }
}
