"""
MPA-DE Hybrid Algorithm
=======================

Hybrid algorithm combining Marine Predators Algorithm (MPA) with
Differential Evolution (DE) for improved convergence.

Author: MHA Flow Development Team
License: MIT
"""

import math
import numpy as np
from ...base import BaseOptimizer
from ..levy_flight_universal import add_levy_flight_to_position


class MPADEHybrid(BaseOptimizer):
    """
    Marine Predators Algorithm + Differential Evolution Hybrid
    
    Combines:
    - MPA: Predator-prey interactions with Levy/Brownian motion
    - DE: Mutation and crossover operations
    
    Parameters
    ----------
    population_size : int, default=30
        Size of the population
    max_iterations : int, default=100
        Maximum iterations
    F : float, default=0.5
        DE mutation factor
    CR : float, default=0.9
        DE crossover rate
    P : float, default=0.5
        MPA probability parameter
    FADs : float, default=0.2
        Fish Aggregating Devices effect
    """
    
    def __init__(self, population_size=30, max_iterations=100, 
                 F=0.5, CR=0.9, P=0.5, FADs=0.2, **kwargs):
        super().__init__(population_size, max_iterations, **kwargs)
        self.algorithm_name = "MPA-DE Hybrid"
        self.aliases = ["mpa_de", "marine_de_hybrid"]
        self.F = F
        self.CR = CR
        self.P = P
        self.FADs = FADs
    
    def _optimize(self, objective_function, X=None, y=None, **kwargs):
        # Levy flight available via: add_levy_flight_to_position()
        # Get dimensions and bounds
        if X is not None:
            dimension = X.shape[1]
            lb, ub = 0.0, 1.0
        else:
            dimension = kwargs.get('dimensions', getattr(self, 'dimensions_', 10))
            lb = kwargs.get('lower_bound', getattr(self, 'lower_bound_', -100.0))
            ub = kwargs.get('upper_bound', getattr(self, 'upper_bound_', 100.0))
            if hasattr(lb, '__getitem__'):
                lb = lb[0]
            if hasattr(ub, '__getitem__'):
                ub = ub[0]
        
        
        # Initialize prey
        prey = np.random.uniform(lb, ub, (self.population_size_, dimension))
        fitness = np.array([objective_function(p) for p in prey])
        
        # Elite (top predator)
        best_idx = np.argmin(fitness)
        elite = prey[best_idx].copy()
        best_fitness = fitness[best_idx]
        global_fitness = []
        local_fitness = []
        local_positions = []
        
        for iteration in range(self.max_iterations_):
            # MPA control factor
            CF = (1 - iteration / self.max_iterations_) ** (2 * iteration / self.max_iterations_)
            
            for i in range(self.population_size_):
                # Hybrid strategy selection
                if np.random.rand() < 0.5:
                    # MPA component
                    phase = iteration / self.max_iterations_
                    RL = 0.05 * np.random.rand(dimension)
                    RB = np.random.randn(dimension)
                    
                    if phase < 1/3:  # High velocity ratio
                        if np.random.rand() < self.P:
                            step_size = RB * (elite - RB * prey[i])
                        else:
                            step_size = RB * (RB * elite - prey[i])
                        prey[i] = prey[i] + self.P * np.random.rand() * step_size
                    elif phase < 2/3:  # Unit velocity ratio
                        if np.random.rand() < self.P:
                            step_size = RB * (RB * elite - prey[i])
                        else:
                            step_size = CF * (elite - prey[i])
                        prey[i] = prey[i] + self.P * CF * step_size
                    else:  # Low velocity ratio
                        if np.random.rand() < self.P:
                            step_size = RB * (RB * elite - prey[i])
                        else:
                            step_size = CF * (elite - prey[i])
                        prey[i] = elite + self.P * CF * step_size
                    
                    # FADs effect
                    if np.random.rand() < self.FADs:
                        U = np.random.rand() < self.FADs
                        prey[i] = prey[i] + CF * (lb + np.random.rand() * (ub - lb)) * U
                else:
                    # DE component
                    # Select three random distinct individuals
                    indices = [j for j in range(self.population_size_) if j != i]
                    if len(indices) >= 3:
                        a, b, c = np.random.choice(indices, 3, replace=False)
                    else:
                        a = b = c = indices[0] if indices else i
                    
                    # Mutation
                    mutant = prey[a] + self.F * (prey[b] - prey[c])
                    mutant = np.clip(mutant, lb, ub)
                    
                    # Crossover
                    cross_points = np.random.rand(dimension) < self.CR
                    if not np.any(cross_points):
                        cross_points[np.random.randint(0, dimension)] = True
                    
                    trial = np.where(cross_points, mutant, prey[i])
                    prey[i] = trial
                
                # Levy flight for exploration in early phase
                if iteration < self.max_iterations_ * 0.5:
                    levy_step = self._levy_flight(dimension)
                    prey[i] = prey[i] + levy_step * (elite - prey[i])
                
                # Boundary control
                prey[i] = np.clip(prey[i], lb, ub)
                
                # Evaluate
                new_fitness = objective_function(prey[i])
                
                if new_fitness < fitness[i]:
                    fitness[i] = new_fitness
                    if new_fitness < best_fitness:
                        elite = prey[i].copy()
                        best_fitness = new_fitness
            
            global_fitness.append(best_fitness)
            # Note: Add local_fitness and local_positions tracking as needed
            
            if self.verbose_ and iteration % 10 == 0:
                print(f"MPA-DE Iteration {iteration}: Best Fitness = {best_fitness:.6e}")
        
        return elite, best_fitness, global_fitness, local_fitness, local_positions
    
    def _levy_flight(self, dimension):
        """Generate Levy flight step"""
        beta = 1.5
        sigma = (math.gamma(1 + beta) * np.sin(np.pi * beta / 2) / 
                (math.gamma((1 + beta) / 2) * beta * 2 ** ((beta - 1) / 2))) ** (1 / beta)
        u = np.random.randn(dimension) * sigma
        v = np.random.randn(dimension)
        step = u / (np.abs(v) ** (1 / beta))
        return 0.01 * step
