"""
Levify System - Algorithm Leveling Framework
=============================================

Converts exploration-based algorithms to exploitation and vice versa,
allowing flexible algorithm adaptation based on problem requirements.

Features:
- Bidirectional algorithm transformation
- Parameter adjustment for exploration/exploitation balance
- Algorithm characteristic profiling
- Adaptive algorithm selection
"""

import numpy as np
from typing import Dict, List, Tuple, Optional, Callable, Any
from dataclasses import dataclass
from enum import Enum
import json


class AlgorithmPhase(Enum):
    """Defines algorithm phases"""
    EXPLORATION = "exploration"
    EXPLOITATION = "exploitation"
    BALANCED = "balanced"


@dataclass
class AlgorithmCharacteristics:
    """Profile of an algorithm's characteristics"""
    name: str
    exploration_score: float  # 0-1: higher = more exploratory
    exploitation_score: float  # 0-1: higher = more exploitative
    convergence_speed: float  # 0-1: how fast it converges
    diversity_maintenance: float  # 0-1: maintains population diversity
    local_optima_avoidance: float  # 0-1: how well it avoids local optima
    phase: AlgorithmPhase
    parameter_count: int
    dimension_scalability: str  # 'low', 'medium', 'high'
    
    def to_dict(self) -> Dict:
        return {
            'name': self.name,
            'exploration_score': self.exploration_score,
            'exploitation_score': self.exploitation_score,
            'convergence_speed': self.convergence_speed,
            'diversity_maintenance': self.diversity_maintenance,
            'local_optima_avoidance': self.local_optima_avoidance,
            'phase': self.phase.value,
            'parameter_count': self.parameter_count,
            'dimension_scalability': self.dimension_scalability
        }


class LevifyTransformer:
    """
    Transforms algorithms between exploration and exploitation phases
    while maintaining algorithm integrity and performance characteristics.
    """
    
    def __init__(self):
        """Initialize the levify transformer with algorithm profiles"""
        self.algorithm_profiles = self._initialize_profiles()
        self.transformation_history = []
    
    def _initialize_profiles(self) -> Dict[str, AlgorithmCharacteristics]:
        """
        Initialize comprehensive profiles for common optimization algorithms
        """
        return {
            # Exploration-based algorithms
            'PSO': AlgorithmCharacteristics(
                name='PSO',
                exploration_score=0.75,
                exploitation_score=0.45,
                convergence_speed=0.6,
                diversity_maintenance=0.8,
                local_optima_avoidance=0.7,
                phase=AlgorithmPhase.EXPLORATION,
                parameter_count=4,
                dimension_scalability='high'
            ),
            'GA': AlgorithmCharacteristics(
                name='GA',
                exploration_score=0.8,
                exploitation_score=0.4,
                convergence_speed=0.5,
                diversity_maintenance=0.85,
                local_optima_avoidance=0.75,
                phase=AlgorithmPhase.EXPLORATION,
                parameter_count=3,
                dimension_scalability='high'
            ),
            'WOA': AlgorithmCharacteristics(
                name='WOA',
                exploration_score=0.76,
                exploitation_score=0.42,
                convergence_speed=0.65,
                diversity_maintenance=0.75,
                local_optima_avoidance=0.7,
                phase=AlgorithmPhase.EXPLORATION,
                parameter_count=2,
                dimension_scalability='high'
            ),
            'ACO': AlgorithmCharacteristics(
                name='ACO',
                exploration_score=0.78,
                exploitation_score=0.38,
                convergence_speed=0.55,
                diversity_maintenance=0.8,
                local_optima_avoidance=0.72,
                phase=AlgorithmPhase.EXPLORATION,
                parameter_count=5,
                dimension_scalability='medium'
            ),
            'BA': AlgorithmCharacteristics(
                name='BA',
                exploration_score=0.74,
                exploitation_score=0.44,
                convergence_speed=0.62,
                diversity_maintenance=0.76,
                local_optima_avoidance=0.68,
                phase=AlgorithmPhase.EXPLORATION,
                parameter_count=3,
                dimension_scalability='high'
            ),
            # Exploitation-based algorithms
            'DE': AlgorithmCharacteristics(
                name='DE',
                exploration_score=0.45,
                exploitation_score=0.78,
                convergence_speed=0.8,
                diversity_maintenance=0.6,
                local_optima_avoidance=0.55,
                phase=AlgorithmPhase.EXPLOITATION,
                parameter_count=3,
                dimension_scalability='high'
            ),
            'SA': AlgorithmCharacteristics(
                name='SA',
                exploration_score=0.65,
                exploitation_score=0.7,
                convergence_speed=0.7,
                diversity_maintenance=0.5,
                local_optima_avoidance=0.65,
                phase=AlgorithmPhase.EXPLOITATION,
                parameter_count=2,
                dimension_scalability='low'
            ),
            'GWO': AlgorithmCharacteristics(
                name='GWO',
                exploration_score=0.55,
                exploitation_score=0.8,
                convergence_speed=0.85,
                diversity_maintenance=0.5,
                local_optima_avoidance=0.5,
                phase=AlgorithmPhase.EXPLOITATION,
                parameter_count=1,
                dimension_scalability='high'
            ),
            'ABC': AlgorithmCharacteristics(
                name='ABC',
                exploration_score=0.6,
                exploitation_score=0.72,
                convergence_speed=0.75,
                diversity_maintenance=0.62,
                local_optima_avoidance=0.58,
                phase=AlgorithmPhase.EXPLOITATION,
                parameter_count=2,
                dimension_scalability='high'
            ),
            'FSA': AlgorithmCharacteristics(
                name='FSA',
                exploration_score=0.58,
                exploitation_score=0.75,
                convergence_speed=0.72,
                diversity_maintenance=0.55,
                local_optima_avoidance=0.6,
                phase=AlgorithmPhase.EXPLOITATION,
                parameter_count=3,
                dimension_scalability='medium'
            ),
            # Balanced algorithms
            'PSO_GA': AlgorithmCharacteristics(
                name='PSO_GA',
                exploration_score=0.68,
                exploitation_score=0.65,
                convergence_speed=0.68,
                diversity_maintenance=0.78,
                local_optima_avoidance=0.72,
                phase=AlgorithmPhase.BALANCED,
                parameter_count=6,
                dimension_scalability='high'
            ),
            'GWO_PSO': AlgorithmCharacteristics(
                name='GWO_PSO',
                exploration_score=0.62,
                exploitation_score=0.72,
                convergence_speed=0.78,
                diversity_maintenance=0.68,
                local_optima_avoidance=0.65,
                phase=AlgorithmPhase.BALANCED,
                parameter_count=4,
                dimension_scalability='high'
            ),
        }
    
    def get_algorithm_characteristics(self, algorithm_name: str) -> Optional[AlgorithmCharacteristics]:
        """Get characteristics of an algorithm"""
        return self.algorithm_profiles.get(algorithm_name.upper())
    
    def levify_to_exploration(self, algorithm_name: str, 
                             intensity: float = 0.5) -> Dict[str, Any]:
        """
        Transform an algorithm towards exploration phase.
        
        Parameters
        ----------
        algorithm_name : str
            Name of the algorithm to transform
        intensity : float
            Strength of transformation (0-1). 0.5 = moderate, 1.0 = extreme
        
        Returns
        -------
        Dict with transformation details and parameter adjustments
        """
        if algorithm_name.upper() not in self.algorithm_profiles:
            return {'error': f'Algorithm {algorithm_name} not found'}
        
        original = self.algorithm_profiles[algorithm_name.upper()]
        
        # Calculate target characteristics
        exploration_delta = (1.0 - original.exploration_score) * intensity
        exploitation_delta = (original.exploitation_score - 0.0) * intensity * 0.5
        
        transformed = AlgorithmCharacteristics(
            name=f"{algorithm_name}_LEVIFIED_EXPLORATION",
            exploration_score=min(0.95, original.exploration_score + exploration_delta),
            exploitation_score=max(0.0, original.exploitation_score - exploitation_delta),
            convergence_speed=original.convergence_speed * (1 - intensity * 0.2),
            diversity_maintenance=min(0.95, original.diversity_maintenance + intensity * 0.1),
            local_optima_avoidance=min(0.95, original.local_optima_avoidance + intensity * 0.15),
            phase=AlgorithmPhase.EXPLORATION,
            parameter_count=original.parameter_count,
            dimension_scalability=original.dimension_scalability
        )
        
        # Generate parameter adjustments
        adjustments = self._generate_parameter_adjustments(
            original, transformed, AlgorithmPhase.EXPLORATION
        )
        
        return {
            'original': original.to_dict(),
            'transformed': transformed.to_dict(),
            'intensity': intensity,
            'parameter_adjustments': adjustments,
            'strategy': 'increased_exploration'
        }
    
    def levify_to_exploitation(self, algorithm_name: str, 
                              intensity: float = 0.5) -> Dict[str, Any]:
        """
        Transform an algorithm towards exploitation phase.
        
        Parameters
        ----------
        algorithm_name : str
            Name of the algorithm to transform
        intensity : float
            Strength of transformation (0-1)
        
        Returns
        -------
        Dict with transformation details and parameter adjustments
        """
        if algorithm_name.upper() not in self.algorithm_profiles:
            return {'error': f'Algorithm {algorithm_name} not found'}
        
        original = self.algorithm_profiles[algorithm_name.upper()]
        
        # Calculate target characteristics
        exploitation_delta = (1.0 - original.exploitation_score) * intensity
        exploration_delta = (original.exploration_score - 0.0) * intensity * 0.5
        
        transformed = AlgorithmCharacteristics(
            name=f"{algorithm_name}_LEVIFIED_EXPLOITATION",
            exploration_score=max(0.0, original.exploration_score - exploration_delta),
            exploitation_score=min(0.95, original.exploitation_score + exploitation_delta),
            convergence_speed=min(0.95, original.convergence_speed + intensity * 0.15),
            diversity_maintenance=max(0.0, original.diversity_maintenance - intensity * 0.15),
            local_optima_avoidance=max(0.0, original.local_optima_avoidance - intensity * 0.2),
            phase=AlgorithmPhase.EXPLOITATION,
            parameter_count=original.parameter_count,
            dimension_scalability=original.dimension_scalability
        )
        
        # Generate parameter adjustments
        adjustments = self._generate_parameter_adjustments(
            original, transformed, AlgorithmPhase.EXPLOITATION
        )
        
        return {
            'original': original.to_dict(),
            'transformed': transformed.to_dict(),
            'intensity': intensity,
            'parameter_adjustments': adjustments,
            'strategy': 'increased_exploitation'
        }
    
    def levify_to_balanced(self, algorithm_name: str) -> Dict[str, Any]:
        """
        Transform an algorithm towards balanced exploration/exploitation.
        """
        if algorithm_name.upper() not in self.algorithm_profiles:
            return {'error': f'Algorithm {algorithm_name} not found'}
        
        original = self.algorithm_profiles[algorithm_name.upper()]
        
        # Target balanced characteristics
        target_exploration = 0.6
        target_exploitation = 0.6
        
        transformed = AlgorithmCharacteristics(
            name=f"{algorithm_name}_LEVIFIED_BALANCED",
            exploration_score=target_exploration,
            exploitation_score=target_exploitation,
            convergence_speed=0.7,
            diversity_maintenance=0.7,
            local_optima_avoidance=0.65,
            phase=AlgorithmPhase.BALANCED,
            parameter_count=original.parameter_count,
            dimension_scalability=original.dimension_scalability
        )
        
        adjustments = self._generate_parameter_adjustments(
            original, transformed, AlgorithmPhase.BALANCED
        )
        
        return {
            'original': original.to_dict(),
            'transformed': transformed.to_dict(),
            'intensity': 1.0,
            'parameter_adjustments': adjustments,
            'strategy': 'balanced_exploration_exploitation'
        }
    
    def _generate_parameter_adjustments(self, original: AlgorithmCharacteristics,
                                       transformed: AlgorithmCharacteristics,
                                       target_phase: AlgorithmPhase) -> Dict[str, Dict]:
        """
        Generate parameter adjustment recommendations based on algorithm type.
        """
        adjustments = {}
        
        algo_name = original.name.upper()
        
        # PSO adjustments
        if algo_name == 'PSO':
            if target_phase == AlgorithmPhase.EXPLORATION:
                adjustments = {
                    'inertia_weight': {
                        'original_range': [0.4, 0.9],
                        'new_range': [0.7, 0.95],
                        'reason': 'Higher inertia promotes exploration'
                    },
                    'cognitive_coeff': {
                        'original_range': [2.0, 2.0],
                        'new_range': [1.5, 1.8],
                        'reason': 'Reduce self-best attraction'
                    },
                    'social_coeff': {
                        'original_range': [2.0, 2.0],
                        'new_range': [1.5, 1.8],
                        'reason': 'Reduce swarm attraction'
                    }
                }
            elif target_phase == AlgorithmPhase.EXPLOITATION:
                adjustments = {
                    'inertia_weight': {
                        'original_range': [0.4, 0.9],
                        'new_range': [0.2, 0.4],
                        'reason': 'Lower inertia promotes exploitation'
                    },
                    'cognitive_coeff': {
                        'original_range': [2.0, 2.0],
                        'new_range': [2.5, 3.0],
                        'reason': 'Increase self-best attraction'
                    },
                    'social_coeff': {
                        'original_range': [2.0, 2.0],
                        'new_range': [2.5, 3.0],
                        'reason': 'Increase swarm attraction'
                    }
                }
        
        # GA adjustments
        elif algo_name == 'GA':
            if target_phase == AlgorithmPhase.EXPLORATION:
                adjustments = {
                    'mutation_rate': {
                        'original_range': [0.01, 0.1],
                        'new_range': [0.15, 0.3],
                        'reason': 'Higher mutation increases exploration'
                    },
                    'crossover_rate': {
                        'original_range': [0.7, 0.9],
                        'new_range': [0.5, 0.7],
                        'reason': 'Lower crossover allows more diversity'
                    },
                    'selection_pressure': {
                        'original_range': [2.0, 3.0],
                        'new_range': [1.2, 1.5],
                        'reason': 'Lower pressure maintains diversity'
                    }
                }
            elif target_phase == AlgorithmPhase.EXPLOITATION:
                adjustments = {
                    'mutation_rate': {
                        'original_range': [0.01, 0.1],
                        'new_range': [0.01, 0.05],
                        'reason': 'Lower mutation focuses search'
                    },
                    'crossover_rate': {
                        'original_range': [0.7, 0.9],
                        'new_range': [0.85, 0.95],
                        'reason': 'Higher crossover refines solutions'
                    },
                    'selection_pressure': {
                        'original_range': [2.0, 3.0],
                        'new_range': [3.5, 5.0],
                        'reason': 'Higher pressure focuses on best solutions'
                    }
                }
        
        # DE adjustments
        elif algo_name == 'DE':
            if target_phase == AlgorithmPhase.EXPLORATION:
                adjustments = {
                    'mutation_factor': {
                        'original_range': [0.5, 0.8],
                        'new_range': [0.8, 1.2],
                        'reason': 'Higher mutation scale promotes exploration'
                    },
                    'crossover_rate': {
                        'original_range': [0.7, 0.95],
                        'new_range': [0.5, 0.7],
                        'reason': 'Lower CR increases exploration'
                    },
                    'population_diversity': {
                        'original_range': [0.5, 0.7],
                        'new_range': [0.8, 1.0],
                        'reason': 'Increase initial diversity'
                    }
                }
            elif target_phase == AlgorithmPhase.EXPLOITATION:
                adjustments = {
                    'mutation_factor': {
                        'original_range': [0.5, 0.8],
                        'new_range': [0.3, 0.5],
                        'reason': 'Lower mutation refines solutions'
                    },
                    'crossover_rate': {
                        'original_range': [0.7, 0.95],
                        'new_range': [0.9, 0.99],
                        'reason': 'Higher CR preserves good traits'
                    }
                }
        
        # GWO adjustments
        elif algo_name == 'GWO':
            if target_phase == AlgorithmPhase.EXPLORATION:
                adjustments = {
                    'convergence_factor_a': {
                        'original_range': [2.0, 0.0],
                        'new_range': [3.0, 0.5],
                        'reason': 'Slower convergence increases exploration'
                    },
                    'position_update_range': {
                        'original_range': [0.5, 1.0],
                        'new_range': [1.0, 2.0],
                        'reason': 'Larger update range explores more'
                    }
                }
        
        # WOA adjustments
        elif algo_name == 'WOA':
            if target_phase == AlgorithmPhase.EXPLORATION:
                adjustments = {
                    'spiral_coefficient': {
                        'original_range': [1.0, 2.0],
                        'new_range': [2.0, 3.0],
                        'reason': 'Higher coefficient increases exploration'
                    },
                    'encircling_probability': {
                        'original_range': [0.5, 0.7],
                        'new_range': [0.3, 0.5],
                        'reason': 'Lower probability enables exploration'
                    }
                }
        
        return adjustments if adjustments else {
            'note': f'Generic adjustments for {algo_name}',
            'phase': target_phase.value,
            'recommendation': 'Algorithm-specific parameter tuning recommended'
        }
    
    def get_levify_recommendations(self, algorithm_name: str,
                                  problem_type: str = 'general',
                                  phase_preference: str = 'auto') -> Dict[str, Any]:
        """
        Get levify recommendations based on problem characteristics.
        
        Parameters
        ----------
        algorithm_name : str
            Algorithm to analyze
        problem_type : str
            Type of problem: 'multimodal', 'unimodal', 'constrained', 'discrete', 'continuous'
        phase_preference : str
            'exploration', 'exploitation', 'balanced', or 'auto'
        
        Returns
        -------
        Recommendations for algorithm levification
        """
        if algorithm_name.upper() not in self.algorithm_profiles:
            return {'error': f'Algorithm {algorithm_name} not found'}
        
        original = self.algorithm_profiles[algorithm_name.upper()]
        recommendations = []
        
        # Auto-determine best phase for problem type
        if phase_preference == 'auto':
            if problem_type == 'multimodal':
                phase_preference = 'exploration'
            elif problem_type == 'unimodal':
                phase_preference = 'exploitation'
            elif problem_type == 'constrained':
                phase_preference = 'exploitation'
            elif problem_type == 'discrete':
                phase_preference = 'balanced'
            else:
                phase_preference = 'balanced'
        
        # Generate recommendations
        if phase_preference == 'exploration':
            result = self.levify_to_exploration(algorithm_name, intensity=0.7)
            recommendations.append({
                'type': 'exploration_boost',
                'intensity': 0.7,
                'result': result
            })
        elif phase_preference == 'exploitation':
            result = self.levify_to_exploitation(algorithm_name, intensity=0.7)
            recommendations.append({
                'type': 'exploitation_boost',
                'intensity': 0.7,
                'result': result
            })
        elif phase_preference == 'balanced':
            result = self.levify_to_balanced(algorithm_name)
            recommendations.append({
                'type': 'balanced',
                'intensity': 1.0,
                'result': result
            })
        
        return {
            'algorithm': algorithm_name,
            'problem_type': problem_type,
            'phase_preference': phase_preference,
            'current_characteristics': original.to_dict(),
            'recommendations': recommendations
        }


class AdaptiveLevifyEngine:
    """
    Adaptive engine that applies levify transformations during optimization.
    Dynamically adjusts algorithm behavior based on real-time performance.
    """
    
    def __init__(self, base_algorithm, transformer: LevifyTransformer):
        """Initialize adaptive engine"""
        self.base_algorithm = base_algorithm
        self.transformer = transformer
        self.iteration_count = 0
        self.best_fitness_history = []
        self.convergence_rate = []
        self.current_phase = AlgorithmPhase.BALANCED
    
    def evaluate_phase_effectiveness(self, window_size: int = 10) -> str:
        """
        Evaluate current algorithm phase effectiveness.
        
        Returns recommendation: 'increase_exploration', 'increase_exploitation', or 'maintain'
        """
        if len(self.best_fitness_history) < window_size:
            return 'maintain'
        
        recent_improvements = np.diff(self.best_fitness_history[-window_size:])
        improvement_rate = np.mean(recent_improvements)
        improvement_variance = np.var(recent_improvements)
        
        # High variance in improvements suggests good exploration
        if improvement_variance > 0.1 and improvement_rate > 0:
            return 'maintain'
        # No improvements suggests need for exploration
        elif improvement_rate <= 0:
            return 'increase_exploration'
        # Consistent small improvements suggest good exploitation
        elif improvement_rate > 0 and improvement_variance < 0.05:
            return 'maintain_exploitation'
        
        return 'maintain'
    
    def adaptive_levify_step(self, iteration: int, total_iterations: int):
        """
        Adaptively apply levify transformations during optimization.
        """
        self.iteration_count = iteration
        progress = iteration / total_iterations
        
        # Early phase: encourage exploration
        if progress < 0.3:
            if self.current_phase != AlgorithmPhase.EXPLORATION:
                self.current_phase = AlgorithmPhase.EXPLORATION
                return self.transformer.levify_to_exploration(
                    self.base_algorithm, 
                    intensity=0.5 + progress
                )
        
        # Mid phase: balance or shift based on performance
        elif progress < 0.7:
            effectiveness = self.evaluate_phase_effectiveness()
            if effectiveness == 'increase_exploitation':
                if self.current_phase != AlgorithmPhase.EXPLOITATION:
                    self.current_phase = AlgorithmPhase.EXPLOITATION
                    return self.transformer.levify_to_exploitation(
                        self.base_algorithm,
                        intensity=0.6
                    )
        
        # Final phase: maximize exploitation
        else:
            if self.current_phase != AlgorithmPhase.EXPLOITATION:
                self.current_phase = AlgorithmPhase.EXPLOITATION
                return self.transformer.levify_to_exploitation(
                    self.base_algorithm,
                    intensity=0.8
                )
        
        return None


# Export the main classes
__all__ = ['LevifyTransformer', 'AdaptiveLevifyEngine', 'AlgorithmCharacteristics', 'AlgorithmPhase']
