"""
New Advanced MHA Algorithms Collection
======================================

This module contains 15 new state-of-the-art metaheuristic algorithms:

Normal Algorithms (10):
1. Jellyfish Search Algorithm (JS) - 2020
2. Honey Badger Algorithm (HBA) - 2021
3. Equilibrium Optimizer (EO) - 2020
4. Marine Predators Algorithm (MPA) - 2020
5. Slime Mould Algorithm (SMA) - 2020
6. Chimp Optimization Algorithm (ChOA) - 2020
7. Aquila Optimizer (AO) - 2021
8. Reptile Search Algorithm (RSA) - 2021
9. Arithmetic Optimization Algorithm (AOA) - 2021
10. Gorilla Troops Optimizer (GTO) - 2021

Hybrid Algorithms (5):
11. JS-PSO Hybrid
12. HBA-GWO Hybrid
13. MPA-DE Hybrid
14. SMA-GA Hybrid
15. AO-WOA Hybrid

All algorithms support Levy flight integration.

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

import numpy as np
from typing import Dict, Tuple, List, Optional, Callable
import math


class BaseNewAlgorithm:
    """Base class for new algorithms with Levy flight support"""
    
    def __init__(self, population_size: int = 30, max_iterations: int = 100):
        self.population_size = population_size
        self.max_iterations = max_iterations
        self._levy_engine = None
    
    def create_random_solution(self, bounds: Tuple[float, float], dimensions: int) -> np.ndarray:
        """Create random solution within bounds"""
        return np.random.uniform(bounds[0], bounds[1], dimensions)
    
    def clip_solution(self, solution: np.ndarray, bounds: Tuple[float, float]) -> np.ndarray:
        """Clip solution to bounds"""
        return np.clip(solution, bounds[0], bounds[1])
    
    def apply_levy_flight(self, position: np.ndarray, best: np.ndarray, 
                         bounds: Tuple[float, float], iteration: int) -> np.ndarray:
        """Apply Levy flight if available"""
        try:
            from .levy_flight_engine import integrate_levy_flight
            return integrate_levy_flight(
                self, position, best, bounds, iteration, self.max_iterations
            )
        except:
            return position


# ==================== NORMAL ALGORITHMS ====================

class JellyfishSearch(BaseNewAlgorithm):
    """
    Jellyfish Search Algorithm (JS) - Chou & Truong (2020)
    
    Inspired by jellyfish behavior in ocean currents.
    Jellyfish follow ocean currents (Type A) or move within swarm (Type B).
    """
    
    def __init__(self, population_size=30, max_iterations=100, c0=0.5):
        super().__init__(population_size, max_iterations)
        self.c0 = c0  # Control parameter
    
    def optimize(self, objective_func: Callable, bounds: Tuple[float, float], 
                dimensions: int) -> Dict:
        # Initialize jellyfish swarm
        jellyfish = [self.create_random_solution(bounds, dimensions) 
                    for _ in range(self.population_size)]
        fitness = [objective_func(j) for j in jellyfish]
        
        best_idx = np.argmin(fitness)
        best_jellyfish = jellyfish[best_idx].copy()
        best_fitness = fitness[best_idx]
        convergence_curve = []
        
        for iteration in range(self.max_iterations):
            # Time control parameter
            c = self.c0 * (1 - iteration / self.max_iterations)
            
            for i in range(self.population_size):
                # Ocean current direction
                trend = best_jellyfish - jellyfish[i]
                
                if np.random.rand() > (1 - c):  # Type A: Follow ocean current
                    # Move toward ocean current (best location)
                    mu = np.random.rand()
                    jellyfish[i] = jellyfish[i] + np.random.rand() * trend * mu
                else:  # Type B: Move in swarm
                    # Select random jellyfish
                    j = np.random.randint(0, self.population_size)
                    
                    # Direction between two jellyfish
                    if fitness[i] < fitness[j]:
                        direction = jellyfish[i] - jellyfish[j]
                    else:
                        direction = jellyfish[j] - jellyfish[i]
                    
                    jellyfish[i] = jellyfish[i] + np.random.rand() * direction
                
                # Apply Levy flight for exploration
                if iteration > self.max_iterations * 0.7:
                    jellyfish[i] = self.apply_levy_flight(
                        jellyfish[i], best_jellyfish, bounds, iteration
                    )
                
                # Clip to bounds
                jellyfish[i] = self.clip_solution(jellyfish[i], bounds)
                
                # Evaluate
                new_fitness = objective_func(jellyfish[i])
                if new_fitness < fitness[i]:
                    fitness[i] = new_fitness
                    if new_fitness < best_fitness:
                        best_jellyfish = jellyfish[i].copy()
                        best_fitness = new_fitness
            
            convergence_curve.append(best_fitness)
        
        return {
            'best_solution': best_jellyfish,
            'best_fitness': best_fitness,
            'convergence_curve': convergence_curve,
            'algorithm_name': 'JS'
        }


class HoneyBadgerAlgorithm(BaseNewAlgorithm):
    """
    Honey Badger Algorithm (HBA) - Hashim et al. (2021)
    
    Inspired by honey badger foraging behavior using smell and digging.
    """
    
    def __init__(self, population_size=30, max_iterations=100, beta=6):
        super().__init__(population_size, max_iterations)
        self.beta = beta  # Ability of honey badger to get food
    
    def optimize(self, objective_func: Callable, bounds: Tuple[float, float],
                dimensions: int) -> Dict:
        # Initialize population
        badgers = [self.create_random_solution(bounds, dimensions)
                  for _ in range(self.population_size)]
        fitness = [objective_func(b) for b in badgers]
        
        best_idx = np.argmin(fitness)
        best_badger = badgers[best_idx].copy()
        best_fitness = fitness[best_idx]
        convergence_curve = []
        
        for iteration in range(self.max_iterations):
            alpha = 2 * np.exp(-iteration / self.max_iterations)  # Decreasing factor
            
            # Get concentration of smell (intensity factor)
            I = np.random.rand() * alpha
            
            for i in range(self.population_size):
                # Update density factor
                density_factor = np.random.rand() * (bounds[1] - bounds[0])
                
                if np.random.rand() < 0.5:
                    # Digging phase
                    r3 = np.random.rand()
                    r4 = np.random.rand()
                    r5 = np.random.rand()
                    
                    # Digging equation
                    distance = best_badger - badgers[i]
                    new_position = best_badger + r3 * alpha * density_factor * np.abs(distance)
                    
                    # Apply Levy flight in digging
                    if r5 < 0.5:
                        new_position = self.apply_levy_flight(
                            new_position, best_badger, bounds, iteration
                        )
                else:
                    # Honey phase (exploitation)
                    r7 = np.random.rand()
                    new_position = badgers[i] + r7 * I * badgers[i]
                
                new_position = self.clip_solution(new_position, bounds)
                new_fitness = objective_func(new_position)
                
                if new_fitness < fitness[i]:
                    badgers[i] = new_position
                    fitness[i] = new_fitness
                    if new_fitness < best_fitness:
                        best_badger = new_position.copy()
                        best_fitness = new_fitness
            
            convergence_curve.append(best_fitness)
        
        return {
            'best_solution': best_badger,
            'best_fitness': best_fitness,
            'convergence_curve': convergence_curve,
            'algorithm_name': 'HBA'
        }


class EquilibriumOptimizer(BaseNewAlgorithm):
    """
    Equilibrium Optimizer (EO) - Faramarzi et al. (2020)
    
    Based on mass balance models to estimate equilibrium state.
    """
    
    def __init__(self, population_size=30, max_iterations=100, a1=2, a2=1, GP=0.5):
        super().__init__(population_size, max_iterations)
        self.a1 = a1
        self.a2 = a2
        self.GP = GP  # Generation probability
    
    def optimize(self, objective_func: Callable, bounds: Tuple[float, float],
                dimensions: int) -> Dict:
        # Initialize particles
        particles = [self.create_random_solution(bounds, dimensions)
                    for _ in range(self.population_size)]
        fitness = [objective_func(p) for p in particles]
        
        best_idx = np.argmin(fitness)
        best_particle = particles[best_idx].copy()
        best_fitness = fitness[best_idx]
        convergence_curve = []
        
        for iteration in range(self.max_iterations):
            # Compute equilibrium candidates
            sorted_idx = np.argsort(fitness)
            Ceq1 = particles[sorted_idx[0]].copy()
            Ceq2 = particles[sorted_idx[1]].copy()
            Ceq3 = particles[sorted_idx[2]].copy()
            Ceq4 = particles[sorted_idx[3]].copy()
            Ceq_ave = (Ceq1 + Ceq2 + Ceq3 + Ceq4) / 4
            
            # Equilibrium pool
            Ceq_pool = [Ceq1, Ceq2, Ceq3, Ceq4, Ceq_ave]
            
            # Time parameter
            t = (1 - iteration / self.max_iterations) ** (self.a2 * iteration / self.max_iterations)
            
            for i in range(self.population_size):
                # Select random equilibrium candidate
                Ceq = Ceq_pool[np.random.randint(0, len(Ceq_pool))]
                
                # Exponential term
                F = self.a1 * np.sign(np.random.rand() - 0.5) * (np.exp(-4 * t) - 1)
                
                # Generation rate
                r1 = np.random.rand()
                r2 = np.random.rand()
                GCP = 0.5 * r1 * r2 * (1 if r2 >= self.GP else 0)
                
                # Update concentration
                G0 = GCP * (Ceq - F * particles[i])
                G = G0 * F
                
                new_particle = Ceq + (particles[i] - Ceq) * F + G * (1 - F) / F
                
                # Apply Levy flight
                if np.random.rand() < 0.3:
                    new_particle = self.apply_levy_flight(
                        new_particle, best_particle, bounds, iteration
                    )
                
                new_particle = self.clip_solution(new_particle, bounds)
                new_fitness = objective_func(new_particle)
                
                if new_fitness < fitness[i]:
                    particles[i] = new_particle
                    fitness[i] = new_fitness
                    if new_fitness < best_fitness:
                        best_particle = new_particle.copy()
                        best_fitness = new_fitness
            
            convergence_curve.append(best_fitness)
        
        return {
            'best_solution': best_particle,
            'best_fitness': best_fitness,
            'convergence_curve': convergence_curve,
            'algorithm_name': 'EO'
        }


class MarinePredatorsAlgorithm(BaseNewAlgorithm):
    """
    Marine Predators Algorithm (MPA) - Faramarzi et al. (2020)
    
    Inspired by marine predator-prey interactions and Levy/Brownian foraging.
    """
    
    def __init__(self, population_size=30, max_iterations=100, P=0.5, FADs=0.2):
        super().__init__(population_size, max_iterations)
        self.P = P  # Probability for different phases
        self.FADs = FADs  # Fish Aggregating Devices effect
    
    def optimize(self, objective_func: Callable, bounds: Tuple[float, float],
                dimensions: int) -> Dict:
        # Initialize prey
        prey = [self.create_random_solution(bounds, dimensions)
               for _ in range(self.population_size)]
        fitness = [objective_func(p) for p in prey]
        
        # Elite (top predator)
        best_idx = np.argmin(fitness)
        elite = prey[best_idx].copy()
        best_fitness = fitness[best_idx]
        convergence_curve = []
        
        for iteration in range(self.max_iterations):
            # Compute CF (decreasing factor)
            CF = (1 - iteration / self.max_iterations) ** (2 * iteration / self.max_iterations)
            
            for i in range(self.population_size):
                # Random index
                RL = 0.05 * np.random.rand(dimensions)
                RB = np.random.randn(dimensions)
                
                # Phase selection based on iteration
                phase = iteration / self.max_iterations
                
                if phase < 1/3:  # Exploration: 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:  # Transition: Equal 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:  # Exploitation: 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 (Fish Aggregating Devices)
                if np.random.rand() < self.FADs:
                    U = np.random.rand() < self.FADs
                    prey[i] = prey[i] + CF * (bounds[0] + np.random.rand() * 
                                             (bounds[1] - bounds[0])) * U
                
                # Levy flight for exploration
                if iteration < self.max_iterations * 0.5:
                    prey[i] = self.apply_levy_flight(prey[i], elite, bounds, iteration)
                
                prey[i] = self.clip_solution(prey[i], bounds)
                new_fitness = objective_func(prey[i])
                
                if new_fitness < fitness[i]:
                    fitness[i] = new_fitness
                    if new_fitness < best_fitness:
                        elite = prey[i].copy()
                        best_fitness = new_fitness
            
            convergence_curve.append(best_fitness)
        
        return {
            'best_solution': elite,
            'best_fitness': best_fitness,
            'convergence_curve': convergence_curve,
            'algorithm_name': 'MPA'
        }


class SlimeMouldAlgorithm(BaseNewAlgorithm):
    """
    Slime Mould Algorithm (SMA) - Li et al. (2020)
    
    Inspired by oscillation mode of slime mould in nature.
    """
    
    def __init__(self, population_size=30, max_iterations=100, z=0.03):
        super().__init__(population_size, max_iterations)
        self.z = z  # Parameter
    
    def optimize(self, objective_func: Callable, bounds: Tuple[float, float],
                dimensions: int) -> Dict:
        # Initialize slime mould
        slime = [self.create_random_solution(bounds, dimensions)
                for _ in range(self.population_size)]
        fitness = [objective_func(s) for s in slime]
        
        best_idx = np.argmin(fitness)
        best_slime = slime[best_idx].copy()
        best_fitness = fitness[best_idx]
        convergence_curve = []
        
        # Weight for each slime
        weights = np.zeros(self.population_size)
        
        for iteration in range(self.max_iterations):
            # Calculate smell index (sorted fitness)
            sorted_idx = np.argsort(fitness)
            
            # Update smell index
            s = best_slime.copy()
            
            # Calculate a (smell factor)
            a = math.atanh(-(iteration / self.max_iterations) + 1)
            
            # Update W (weight)
            for i in range(self.population_size):
                if i < self.population_size // 2:
                    weights[sorted_idx[i]] = 1 + np.random.rand() * math.log10(
                        (best_fitness - fitness[sorted_idx[i]]) / 
                        (best_fitness - fitness[sorted_idx[-1]] + 1e-10) + 1
                    )
                else:
                    weights[sorted_idx[i]] = 1 - np.random.rand() * math.log10(
                        (best_fitness - fitness[sorted_idx[i]]) / 
                        (best_fitness - fitness[sorted_idx[-1]] + 1e-10) + 1
                    )
            
            # Update position
            for i in range(self.population_size):
                # Update slime mould
                p = math.tanh(abs(fitness[i] - best_fitness))
                vb = np.random.uniform(-a, a, dimensions)
                vc = np.random.uniform(-1, 1, dimensions)
                
                for j in range(dimensions):
                    r = np.random.rand()
                    
                    if r < self.z:
                        slime[i][j] = (bounds[1] - bounds[0]) * np.random.rand() + bounds[0]
                    else:
                        if r < p:
                            slime[i][j] = best_slime[j] + vb[j] * (
                                weights[i] * slime[sorted_idx[0]][j] - slime[i][j]
                            )
                        else:
                            slime[i][j] = vc[j] * slime[i][j]
                
                # Apply Levy flight
                if iteration % 10 == 0:
                    slime[i] = self.apply_levy_flight(
                        slime[i], best_slime, bounds, iteration
                    )
                
                slime[i] = self.clip_solution(slime[i], bounds)
                new_fitness = objective_func(slime[i])
                
                if new_fitness < fitness[i]:
                    fitness[i] = new_fitness
                    if new_fitness < best_fitness:
                        best_slime = slime[i].copy()
                        best_fitness = new_fitness
            
            convergence_curve.append(best_fitness)
        
        return {
            'best_solution': best_slime,
            'best_fitness': best_fitness,
            'convergence_curve': convergence_curve,
            'algorithm_name': 'SMA'
        }


class ChimpOptimization(BaseNewAlgorithm):
    """
    Chimp Optimization Algorithm (ChOA) - Khishe & Mosavi (2020)
    
    Inspired by chimp social behavior and hunting strategies.
    """
    
    def __init__(self, population_size=30, max_iterations=100, f=2.5, m=2):
        super().__init__(population_size, max_iterations)
        self.f = f  # Chaotic value
        self.m = m  # Multiplier
    
    def optimize(self, objective_func: Callable, bounds: Tuple[float, float],
                dimensions: int) -> Dict:
        # Initialize chimps
        chimps = [self.create_random_solution(bounds, dimensions)
                 for _ in range(self.population_size)]
        fitness = [objective_func(c) for c in chimps]
        
        # Identify alpha, beta, gamma, delta chimps
        sorted_idx = np.argsort(fitness)
        attacker = chimps[sorted_idx[0]].copy()
        barrier = chimps[sorted_idx[1]].copy()
        chaser = chimps[sorted_idx[2]].copy()
        driver = chimps[sorted_idx[3]].copy()
        
        best_fitness = fitness[sorted_idx[0]]
        convergence_curve = []
        
        for iteration in range(self.max_iterations):
            # Update a, c, f
            a = 2 * (1 - iteration / self.max_iterations)
            
            for i in range(self.population_size):
                # Generate chaotic values
                c1, c2, c3, c4 = [np.random.rand(dimensions) * self.f for _ in range(4)]
                
                # Calculate coefficients
                r1, r2 = np.random.rand(dimensions), np.random.rand(dimensions)
                A1 = 2 * a * r1 - a
                C1 = 2 * c1
                
                # Position update with respect to attacker
                D_attacker = np.abs(C1 * attacker - self.m * chimps[i])
                X1 = attacker - A1 * D_attacker
                
                # Position update with respect to barrier
                r3, r4 = np.random.rand(dimensions), np.random.rand(dimensions)
                A2 = 2 * a * r3 - a
                C2 = 2 * c2
                D_barrier = np.abs(C2 * barrier - self.m * chimps[i])
                X2 = barrier - A2 * D_barrier
                
                # Position update with respect to chaser
                r5, r6 = np.random.rand(dimensions), np.random.rand(dimensions)
                A3 = 2 * a * r5 - a
                C3 = 2 * c3
                D_chaser = np.abs(C3 * chaser - self.m * chimps[i])
                X3 = chaser - A3 * D_chaser
                
                # Position update with respect to driver
                r7, r8 = np.random.rand(dimensions), np.random.rand(dimensions)
                A4 = 2 * a * r7 - a
                C4 = 2 * c4
                D_driver = np.abs(C4 * driver - self.m * chimps[i])
                X4 = driver - A4 * D_driver
                
                # Update position
                chimps[i] = (X1 + X2 + X3 + X4) / 4
                
                # Apply Levy flight
                if iteration > self.max_iterations * 0.6:
                    chimps[i] = self.apply_levy_flight(
                        chimps[i], attacker, bounds, iteration
                    )
                
                chimps[i] = self.clip_solution(chimps[i], bounds)
                fitness[i] = objective_func(chimps[i])
            
            # Update leaders
            sorted_idx = np.argsort(fitness)
            attacker = chimps[sorted_idx[0]].copy()
            barrier = chimps[sorted_idx[1]].copy()
            chaser = chimps[sorted_idx[2]].copy()
            driver = chimps[sorted_idx[3]].copy()
            best_fitness = fitness[sorted_idx[0]]
            
            convergence_curve.append(best_fitness)
        
        return {
            'best_solution': attacker,
            'best_fitness': best_fitness,
            'convergence_curve': convergence_curve,
            'algorithm_name': 'ChOA'
        }


class AquilaOptimizer(BaseNewAlgorithm):
    """
    Aquila Optimizer (AO) - Abualigah et al. (2021)
    
    Inspired by Aquila hunting behavior with four strategies.
    """
    
    def __init__(self, population_size=30, max_iterations=100):
        super().__init__(population_size, max_iterations)
    
    def optimize(self, objective_func: Callable, bounds: Tuple[float, float],
                dimensions: int) -> Dict:
        # Initialize Aquila population
        aquilas = [self.create_random_solution(bounds, dimensions)
                  for _ in range(self.population_size)]
        fitness = [objective_func(a) for a in aquilas]
        
        best_idx = np.argmin(fitness)
        best_aquila = aquilas[best_idx].copy()
        best_fitness = fitness[best_idx]
        convergence_curve = []
        
        for iteration in range(self.max_iterations):
            # Average of solutions
            avg_pos = np.mean(aquilas, axis=0)
            
            for i in range(self.population_size):
                # Strategy selection based on iteration
                t = iteration / self.max_iterations
                
                if t <= 2/3:
                    if t <= 1/3:  # Expanded exploration (X1)
                        r1 = np.random.rand()
                        aquilas[i] = best_aquila * (1 - iteration / self.max_iterations) + \
                                    (avg_pos - best_aquila * r1)
                    else:  # Narrowed exploration (X2)
                        levy_step = self.levy_flight_step(dimensions)
                        aquilas[i] = best_aquila * levy_step + \
                                    np.random.rand(dimensions) * (avg_pos - best_aquila)
                else:
                    if np.random.rand() < 0.5:  # Expanded exploitation (X3)
                        alpha = 0.1
                        delta = 0.1
                        aquilas[i] = (best_aquila - avg_pos) * alpha - \
                                    np.random.rand() + \
                                    ((bounds[1] - bounds[0]) * np.random.rand() + bounds[0]) * delta
                    else:  # Narrowed exploitation (X4)
                        QF = iteration ** ((2 * np.random.rand() - 1) / (1 - self.max_iterations) ** 2)
                        levy_step = self.levy_flight_step(dimensions)
                        aquilas[i] = QF * best_aquila - \
                                    (np.random.rand(dimensions) * aquilas[i] - best_aquila) * levy_step
                
                aquilas[i] = self.clip_solution(aquilas[i], bounds)
                new_fitness = objective_func(aquilas[i])
                
                if new_fitness < fitness[i]:
                    fitness[i] = new_fitness
                    if new_fitness < best_fitness:
                        best_aquila = aquilas[i].copy()
                        best_fitness = new_fitness
            
            convergence_curve.append(best_fitness)
        
        return {
            'best_solution': best_aquila,
            'best_fitness': best_fitness,
            'convergence_curve': convergence_curve,
            'algorithm_name': 'AO'
        }
    
    def levy_flight_step(self, dimensions: int) -> np.ndarray:
        """Simple Levy flight implementation"""
        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(dimensions) * sigma
        v = np.random.randn(dimensions)
        step = u / (np.abs(v) ** (1 / beta))
        return 0.01 * step


class ReptileSearchAlgorithm(BaseNewAlgorithm):
    """
    Reptile Search Algorithm (RSA) - Abualigah et al. (2021)
    
    Inspired by crocodile hunting strategies: encircling, hunting cooperation,
    coordination, and cooperation.
    """
    
    def __init__(self, population_size=30, max_iterations=100):
        super().__init__(population_size, max_iterations)
    
    def optimize(self, objective_func: Callable, bounds: Tuple[float, float],
                dimensions: int) -> Dict:
        # Initialize reptiles
        reptiles = [self.create_random_solution(bounds, dimensions)
                   for _ in range(self.population_size)]
        fitness = [objective_func(r) for r in reptiles]
        
        best_idx = np.argmin(fitness)
        best_reptile = reptiles[best_idx].copy()
        best_fitness = fitness[best_idx]
        convergence_curve = []
        
        for iteration in range(self.max_iterations):
            # Compute parameters
            beta = np.random.randn()
            epsilon = 0.1
            eta = iteration / self.max_iterations
            
            for i in range(self.population_size):
                # Select two random reptiles
                r1, r2 = np.random.choice([j for j in range(self.population_size) if j != i], 2, replace=False)
                
                # Encircling behavior (exploration)
                if np.random.rand() < 0.5:
                    R = np.random.rand(dimensions) * best_reptile - reptiles[i]
                    reptiles[i] = best_reptile - eta * beta * R
                else:
                    # Hunting behavior (exploitation)
                    ES = 2 * np.random.rand() * (1 - iteration / self.max_iterations)
                    
                    if np.random.rand() < 0.1:  # Cooperation
                        reptiles[i] = best_reptile - ES * np.abs(
                            beta * best_reptile - reptiles[i]
                        )
                    else:  # Coordination
                        reptiles[i] = (reptiles[r1] + reptiles[r2]) / 2 - \
                                     eta * np.random.rand(dimensions)
                
                # Apply Levy flight
                if iteration % 5 == 0:
                    reptiles[i] = self.apply_levy_flight(
                        reptiles[i], best_reptile, bounds, iteration
                    )
                
                reptiles[i] = self.clip_solution(reptiles[i], bounds)
                new_fitness = objective_func(reptiles[i])
                
                if new_fitness < fitness[i]:
                    fitness[i] = new_fitness
                    if new_fitness < best_fitness:
                        best_reptile = reptiles[i].copy()
                        best_fitness = new_fitness
            
            convergence_curve.append(best_fitness)
        
        return {
            'best_solution': best_reptile,
            'best_fitness': best_fitness,
            'convergence_curve': convergence_curve,
            'algorithm_name': 'RSA'
        }


class ArithmeticOptimization(BaseNewAlgorithm):
    """
    Arithmetic Optimization Algorithm (AOA) - Abualigah et al. (2021)
    
    Based on arithmetic operators: multiplication, division, subtraction, addition.
    """
    
    def __init__(self, population_size=30, max_iterations=100, alpha=5, mu=0.5):
        super().__init__(population_size, max_iterations)
        self.alpha = alpha  # Sensitive parameter
        self.mu = mu  # Control parameter
    
    def optimize(self, objective_func: Callable, bounds: Tuple[float, float],
                dimensions: int) -> Dict:
        # Initialize population
        solutions = [self.create_random_solution(bounds, dimensions)
                    for _ in range(self.population_size)]
        fitness = [objective_func(s) for s in solutions]
        
        best_idx = np.argmin(fitness)
        best_solution = solutions[best_idx].copy()
        best_fitness = fitness[best_idx]
        convergence_curve = []
        
        for iteration in range(self.max_iterations):
            # Math Optimizer Accelerated (MOA)
            MOA = self.moa_func(iteration)
            
            # Math Optimizer Probability (MOP)
            MOP = 1 - iteration / self.max_iterations
            
            for i in range(self.population_size):
                for j in range(dimensions):
                    r1 = np.random.rand()
                    
                    if r1 > MOA:  # Exploration
                        r2 = np.random.rand()
                        if r2 > 0.5:  # Division (D)
                            solutions[i][j] = best_solution[j] / (MOP + 1e-10) * \
                                            ((bounds[1] - bounds[0]) * self.mu + bounds[0])
                        else:  # Multiplication (M)
                            solutions[i][j] = best_solution[j] * MOP * \
                                            ((bounds[1] - bounds[0]) * self.mu + bounds[0])
                    else:  # Exploitation
                        r3 = np.random.rand()
                        if r3 > 0.5:  # Subtraction (S)
                            solutions[i][j] = best_solution[j] - MOP * \
                                            ((bounds[1] - bounds[0]) * self.mu + bounds[0])
                        else:  # Addition (A)
                            solutions[i][j] = best_solution[j] + MOP * \
                                            ((bounds[1] - bounds[0]) * self.mu + bounds[0])
                
                # Apply Levy flight
                if iteration < self.max_iterations * 0.4:
                    solutions[i] = self.apply_levy_flight(
                        solutions[i], best_solution, bounds, iteration
                    )
                
                solutions[i] = self.clip_solution(solutions[i], bounds)
                new_fitness = objective_func(solutions[i])
                
                if new_fitness < fitness[i]:
                    fitness[i] = new_fitness
                    if new_fitness < best_fitness:
                        best_solution = solutions[i].copy()
                        best_fitness = new_fitness
            
            convergence_curve.append(best_fitness)
        
        return {
            'best_solution': best_solution,
            'best_fitness': best_fitness,
            'convergence_curve': convergence_curve,
            'algorithm_name': 'AOA'
        }
    
    def moa_func(self, iteration: int) -> float:
        """Math Optimizer Accelerated function"""
        return np.min([1, 1 + iteration / self.max_iterations * self.alpha])


class GorillaTroopsOptimizer(BaseNewAlgorithm):
    """
    Gorilla Troops Optimizer (GTO) - Abdollahzadeh et al. (2021)
    
    Inspired by gorilla social intelligence and life strategy.
    """
    
    def __init__(self, population_size=30, max_iterations=100, beta=3, p=0.03):
        super().__init__(population_size, max_iterations)
        self.beta = beta
        self.p = p  # Parameter p
    
    def optimize(self, objective_func: Callable, bounds: Tuple[float, float],
                dimensions: int) -> Dict:
        # Initialize gorilla troop
        gorillas = [self.create_random_solution(bounds, dimensions)
                   for _ in range(self.population_size)]
        fitness = [objective_func(g) for g in gorillas]
        
        best_idx = np.argmin(fitness)
        silverback = gorillas[best_idx].copy()  # Leader
        best_fitness = fitness[best_idx]
        convergence_curve = []
        
        for iteration in range(self.max_iterations):
            C = 1 - iteration / self.max_iterations  # Decreasing parameter
            
            for i in range(self.population_size):
                # Select random gorillas
                r = np.random.rand()
                
                if r < self.p:  # Exploration: Migration to unknown location
                    gorillas[i] = (bounds[1] - bounds[0]) * np.random.rand(dimensions) + bounds[0]
                else:
                    if r < 0.5:  # Movement toward other gorillas
                        # Select random gorilla
                        j = np.random.randint(0, self.population_size)
                        H = np.random.rand()
                        
                        # Update position
                        A = (2 * np.random.rand() - 1) * C
                        gorillas[i] = (np.random.rand() - A) * gorillas[j] + \
                                     H * (gorillas[i] - gorillas[j])
                    else:  # Movement toward silverback
                        # Silverback strength
                        H = np.random.rand()
                        
                        # Follow silverback
                        A = (2 * np.random.rand() - 1) * C
                        L = C * H * np.random.rand(dimensions)
                        
                        gorillas[i] = (np.random.rand() - A) * silverback + \
                                     L * (gorillas[i] - silverback)
                
                # Apply Levy flight
                if iteration % 7 == 0:
                    gorillas[i] = self.apply_levy_flight(
                        gorillas[i], silverback, bounds, iteration
                    )
                
                gorillas[i] = self.clip_solution(gorillas[i], bounds)
                new_fitness = objective_func(gorillas[i])
                
                if new_fitness < fitness[i]:
                    fitness[i] = new_fitness
                    if new_fitness < best_fitness:
                        silverback = gorillas[i].copy()
                        best_fitness = new_fitness
            
            convergence_curve.append(best_fitness)
        
        return {
            'best_solution': silverback,
            'best_fitness': best_fitness,
            'convergence_curve': convergence_curve,
            'algorithm_name': 'GTO'
        }


# ==================== HYBRID ALGORITHMS ====================

class JSPSOHybrid(BaseNewAlgorithm):
    """Jellyfish Search + PSO Hybrid"""
    
    def __init__(self, population_size=30, max_iterations=100, w=0.7, c1=1.5, c2=1.5):
        super().__init__(population_size, max_iterations)
        self.w = w
        self.c1 = c1
        self.c2 = c2
    
    def optimize(self, objective_func: Callable, bounds: Tuple[float, float],
                dimensions: int) -> Dict:
        # Initialize population with velocities (PSO component)
        positions = [self.create_random_solution(bounds, dimensions)
                    for _ in range(self.population_size)]
        velocities = [np.random.uniform(-1, 1, dimensions) 
                     for _ in range(self.population_size)]
        fitness = [objective_func(p) for p in positions]
        pbest = [p.copy() for p in positions]
        pbest_fitness = fitness.copy()
        
        best_idx = np.argmin(fitness)
        gbest = positions[best_idx].copy()
        best_fitness = fitness[best_idx]
        convergence_curve = []
        
        for iteration in range(self.max_iterations):
            c = 0.5 * (1 - iteration / self.max_iterations)
            
            for i in range(self.population_size):
                if np.random.rand() < 0.5:  # JS component
                    if np.random.rand() > (1 - c):
                        trend = gbest - positions[i]
                        positions[i] = positions[i] + np.random.rand() * trend
                    else:
                        j = np.random.randint(0, self.population_size)
                        if fitness[i] < fitness[j]:
                            direction = positions[i] - positions[j]
                        else:
                            direction = positions[j] - positions[i]
                        positions[i] = positions[i] + np.random.rand() * direction
                else:  # PSO component
                    r1, r2 = np.random.rand(), np.random.rand()
                    velocities[i] = (self.w * velocities[i] + 
                                    self.c1 * r1 * (pbest[i] - positions[i]) +
                                    self.c2 * r2 * (gbest - positions[i]))
                    positions[i] = positions[i] + velocities[i]
                
                # Apply Levy flight
                if iteration > self.max_iterations * 0.7:
                    positions[i] = self.apply_levy_flight(
                        positions[i], gbest, bounds, iteration
                    )
                
                positions[i] = self.clip_solution(positions[i], bounds)
                new_fitness = objective_func(positions[i])
                
                if new_fitness < fitness[i]:
                    fitness[i] = new_fitness
                    if new_fitness < pbest_fitness[i]:
                        pbest[i] = positions[i].copy()
                        pbest_fitness[i] = new_fitness
                    if new_fitness < best_fitness:
                        gbest = positions[i].copy()
                        best_fitness = new_fitness
            
            convergence_curve.append(best_fitness)
        
        return {
            'best_solution': gbest,
            'best_fitness': best_fitness,
            'convergence_curve': convergence_curve,
            'algorithm_name': 'JS_PSO'
        }


class HBAGWOHybrid(BaseNewAlgorithm):
    """Honey Badger Algorithm + Grey Wolf Optimizer Hybrid"""
    
    def __init__(self, population_size=30, max_iterations=100):
        super().__init__(population_size, max_iterations)
    
    def optimize(self, objective_func: Callable, bounds: Tuple[float, float],
                dimensions: int) -> Dict:
        # Initialize wolves/badgers
        agents = [self.create_random_solution(bounds, dimensions)
                 for _ in range(self.population_size)]
        fitness = [objective_func(a) for a in agents]
        
        # GWO hierarchy
        sorted_idx = np.argsort(fitness)
        alpha = agents[sorted_idx[0]].copy()
        beta = agents[sorted_idx[1]].copy()
        delta = agents[sorted_idx[2]].copy()
        best_fitness = fitness[sorted_idx[0]]
        convergence_curve = []
        
        for iteration in range(self.max_iterations):
            a = 2 * (1 - iteration / self.max_iterations)
            
            for i in range(self.population_size):
                if np.random.rand() < 0.5:  # HBA component
                    alpha_hba = 2 * np.exp(-iteration / self.max_iterations)
                    I = np.random.rand() * alpha_hba
                    
                    if np.random.rand() < 0.5:
                        r3 = np.random.rand()
                        agents[i] = alpha + r3 * alpha_hba * np.abs(alpha - agents[i])
                    else:
                        agents[i] = agents[i] + np.random.rand() * I * agents[i]
                else:  # GWO component
                    r1, r2 = np.random.rand(dimensions), np.random.rand(dimensions)
                    A1 = 2 * a * r1 - a
                    C1 = 2 * r2
                    D_alpha = np.abs(C1 * alpha - agents[i])
                    X1 = alpha - A1 * D_alpha
                    
                    r1, r2 = np.random.rand(dimensions), np.random.rand(dimensions)
                    A2 = 2 * a * r1 - a
                    C2 = 2 * r2
                    D_beta = np.abs(C2 * beta - agents[i])
                    X2 = beta - A2 * D_beta
                    
                    r1, r2 = np.random.rand(dimensions), np.random.rand(dimensions)
                    A3 = 2 * a * r1 - a
                    C3 = 2 * r2
                    D_delta = np.abs(C3 * delta - agents[i])
                    X3 = delta - A3 * D_delta
                    
                    agents[i] = (X1 + X2 + X3) / 3
                
                # Levy flight
                if iteration > self.max_iterations * 0.6:
                    agents[i] = self.apply_levy_flight(agents[i], alpha, bounds, iteration)
                
                agents[i] = self.clip_solution(agents[i], bounds)
                fitness[i] = objective_func(agents[i])
            
            # Update hierarchy
            sorted_idx = np.argsort(fitness)
            alpha = agents[sorted_idx[0]].copy()
            beta = agents[sorted_idx[1]].copy()
            delta = agents[sorted_idx[2]].copy()
            best_fitness = fitness[sorted_idx[0]]
            
            convergence_curve.append(best_fitness)
        
        return {
            'best_solution': alpha,
            'best_fitness': best_fitness,
            'convergence_curve': convergence_curve,
            'algorithm_name': 'HBA_GWO'
        }


class MPADEHybrid(BaseNewAlgorithm):
    """Marine Predators Algorithm + Differential Evolution Hybrid"""
    
    def __init__(self, population_size=30, max_iterations=100, F=0.5, CR=0.9):
        super().__init__(population_size, max_iterations)
        self.F = F  # DE mutation factor
        self.CR = CR  # DE crossover rate
    
    def optimize(self, objective_func: Callable, bounds: Tuple[float, float],
                dimensions: int) -> Dict:
        # Initialize prey
        prey = [self.create_random_solution(bounds, dimensions)
               for _ in range(self.population_size)]
        fitness = [objective_func(p) for p in prey]
        
        best_idx = np.argmin(fitness)
        elite = prey[best_idx].copy()
        best_fitness = fitness[best_idx]
        convergence_curve = []
        
        for iteration in range(self.max_iterations):
            CF = (1 - iteration / self.max_iterations) ** (2 * iteration / self.max_iterations)
            
            for i in range(self.population_size):
                if np.random.rand() < 0.5:  # MPA component
                    phase = iteration / self.max_iterations
                    RB = np.random.randn(dimensions)
                    
                    if phase < 1/3:
                        prey[i] = prey[i] + 0.5 * RB * (elite - RB * prey[i])
                    elif phase < 2/3:
                        prey[i] = prey[i] + 0.5 * CF * RB * (RB * elite - prey[i])
                    else:
                        prey[i] = elite + 0.5 * CF * RB * (RB * elite - prey[i])
                else:  # DE component
                    # Select three random distinct individuals
                    indices = [j for j in range(self.population_size) if j != i]
                    a, b, c = np.random.choice(indices, 3, replace=False)
                    
                    # Mutation
                    mutant = prey[a] + self.F * (prey[b] - prey[c])
                    mutant = self.clip_solution(mutant, bounds)
                    
                    # Crossover
                    cross_points = np.random.rand(dimensions) < self.CR
                    if not np.any(cross_points):
                        cross_points[np.random.randint(0, dimensions)] = True
                    
                    trial = np.where(cross_points, mutant, prey[i])
                    prey[i] = trial
                
                # Levy flight
                if iteration < self.max_iterations * 0.5:
                    prey[i] = self.apply_levy_flight(prey[i], elite, bounds, iteration)
                
                prey[i] = self.clip_solution(prey[i], bounds)
                new_fitness = objective_func(prey[i])
                
                if new_fitness < fitness[i]:
                    fitness[i] = new_fitness
                    if new_fitness < best_fitness:
                        elite = prey[i].copy()
                        best_fitness = new_fitness
            
            convergence_curve.append(best_fitness)
        
        return {
            'best_solution': elite,
            'best_fitness': best_fitness,
            'convergence_curve': convergence_curve,
            'algorithm_name': 'MPA_DE'
        }


class SMAGAHybrid(BaseNewAlgorithm):
    """Slime Mould Algorithm + Genetic Algorithm Hybrid"""
    
    def __init__(self, population_size=30, max_iterations=100, mutation_rate=0.1):
        super().__init__(population_size, max_iterations)
        self.mutation_rate = mutation_rate
    
    def optimize(self, objective_func: Callable, bounds: Tuple[float, float],
                dimensions: int) -> Dict:
        # Initialize population
        population = [self.create_random_solution(bounds, dimensions)
                     for _ in range(self.population_size)]
        fitness = [objective_func(p) for p in population]
        
        best_idx = np.argmin(fitness)
        best_solution = population[best_idx].copy()
        best_fitness = fitness[best_idx]
        convergence_curve = []
        
        for iteration in range(self.max_iterations):
            if np.random.rand() < 0.5:  # SMA component
                a = math.atanh(-(iteration / self.max_iterations) + 1)
                sorted_idx = np.argsort(fitness)
                weights = np.zeros(self.population_size)
                
                for i in range(self.population_size):
                    if i < self.population_size // 2:
                        weights[sorted_idx[i]] = 1 + np.random.rand()
                    else:
                        weights[sorted_idx[i]] = 1 - np.random.rand()
                
                for i in range(self.population_size):
                    p = math.tanh(abs(fitness[i] - best_fitness))
                    vb = np.random.uniform(-a, a, dimensions)
                    
                    if np.random.rand() < p:
                        population[i] = best_solution + vb * (
                            weights[i] * population[sorted_idx[0]] - population[i]
                        )
                    else:
                        population[i] = np.random.uniform(-1, 1, dimensions) * population[i]
            else:  # GA component
                # Selection (tournament)
                parents = []
                for _ in range(2):
                    idx1, idx2 = np.random.choice(self.population_size, 2, replace=False)
                    if fitness[idx1] < fitness[idx2]:
                        parents.append(population[idx1].copy())
                    else:
                        parents.append(population[idx2].copy())
                
                # Crossover
                alpha = np.random.rand()
                child = alpha * parents[0] + (1 - alpha) * parents[1]
                
                # Mutation
                if np.random.rand() < self.mutation_rate:
                    mutation_idx = np.random.randint(0, dimensions)
                    child[mutation_idx] = np.random.uniform(bounds[0], bounds[1])
                
                # Replace worst individual
                worst_idx = np.argmax(fitness)
                population[worst_idx] = child
                fitness[worst_idx] = objective_func(child)
            
            # Update best
            for i in range(self.population_size):
                population[i] = self.clip_solution(population[i], bounds)
                fitness[i] = objective_func(population[i])
                if fitness[i] < best_fitness:
                    best_solution = population[i].copy()
                    best_fitness = fitness[i]
            
            convergence_curve.append(best_fitness)
        
        return {
            'best_solution': best_solution,
            'best_fitness': best_fitness,
            'convergence_curve': convergence_curve,
            'algorithm_name': 'SMA_GA'
        }


class AOWOAHybrid(BaseNewAlgorithm):
    """Aquila Optimizer + Whale Optimization Algorithm Hybrid"""
    
    def __init__(self, population_size=30, max_iterations=100, b=1):
        super().__init__(population_size, max_iterations)
        self.b = b  # Spiral parameter
    
    def optimize(self, objective_func: Callable, bounds: Tuple[float, float],
                dimensions: int) -> Dict:
        # Initialize population
        agents = [self.create_random_solution(bounds, dimensions)
                 for _ in range(self.population_size)]
        fitness = [objective_func(a) for a in agents]
        
        best_idx = np.argmin(fitness)
        leader = agents[best_idx].copy()
        best_fitness = fitness[best_idx]
        convergence_curve = []
        
        for iteration in range(self.max_iterations):
            a = 2 * (1 - iteration / self.max_iterations)
            a2 = -1 + iteration * (-1 / self.max_iterations)
            
            for i in range(self.population_size):
                if np.random.rand() < 0.5:  # AO component
                    avg_pos = np.mean(agents, axis=0)
                    t = iteration / self.max_iterations
                    
                    if t <= 1/3:
                        agents[i] = leader * (1 - iteration / self.max_iterations) + \
                                   (avg_pos - leader * np.random.rand())
                    else:
                        levy_step = self.levy_flight_step(dimensions)
                        agents[i] = leader * levy_step + \
                                   np.random.rand(dimensions) * (avg_pos - leader)
                else:  # WOA component
                    r = np.random.rand()
                    A = 2 * a * r - a
                    C = 2 * r
                    l = np.random.uniform(-1, 1)
                    p = np.random.rand()
                    
                    if p < 0.5:
                        if np.abs(A) < 1:  # Encircling prey
                            D = np.abs(C * leader - agents[i])
                            agents[i] = leader - A * D
                        else:  # Search for prey
                            rand_idx = np.random.randint(0, self.population_size)
                            D = np.abs(C * agents[rand_idx] - agents[i])
                            agents[i] = agents[rand_idx] - A * D
                    else:  # Spiral updating
                        D_prime = np.abs(leader - agents[i])
                        agents[i] = D_prime * np.exp(self.b * l) * np.cos(2 * np.pi * l) + leader
                
                agents[i] = self.clip_solution(agents[i], bounds)
                new_fitness = objective_func(agents[i])
                
                if new_fitness < fitness[i]:
                    fitness[i] = new_fitness
                    if new_fitness < best_fitness:
                        leader = agents[i].copy()
                        best_fitness = new_fitness
            
            convergence_curve.append(best_fitness)
        
        return {
            'best_solution': leader,
            'best_fitness': best_fitness,
            'convergence_curve': convergence_curve,
            'algorithm_name': 'AO_WOA'
        }
    
    def levy_flight_step(self, dimensions: int) -> np.ndarray:
        """Simple Levy flight"""
        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(dimensions) * sigma
        v = np.random.randn(dimensions)
        step = u / (np.abs(v) ** (1 / beta))
        return 0.01 * step


# Registry for easy access
NEW_ALGORITHMS = {
    'JS': JellyfishSearch,
    'HBA': HoneyBadgerAlgorithm,
    'EO': EquilibriumOptimizer,
    'MPA': MarinePredatorsAlgorithm,
    'SMA': SlimeMouldAlgorithm,
    'ChOA': ChimpOptimization,
    'AO': AquilaOptimizer,
    'RSA': ReptileSearchAlgorithm,
    'AOA': ArithmeticOptimization,
    'GTO': GorillaTroopsOptimizer,
    'JS_PSO': JSPSOHybrid,
    'HBA_GWO': HBAGWOHybrid,
    'MPA_DE': MPADEHybrid,
    'SMA_GA': SMAGAHybrid,
    'AO_WOA': AOWOAHybrid
}


if __name__ == "__main__":
    print("=" * 70)
    print("New Advanced MHA Algorithms - Test Suite")
    print("=" * 70)
    print(f"\nTotal algorithms added: {len(NEW_ALGORITHMS)}")
    print("\nNormal Algorithms (10):")
    for i, name in enumerate(['JS', 'HBA', 'EO', 'MPA', 'SMA', 
                              'ChOA', 'AO', 'RSA', 'AOA', 'GTO'], 1):
        print(f"  {i}. {name}: {NEW_ALGORITHMS[name].__doc__.split('Inspired')[0].strip()}")
    
    print("\nHybrid Algorithms (5):")
    for i, name in enumerate(['JS_PSO', 'HBA_GWO', 'MPA_DE', 'SMA_GA', 'AO_WOA'], 1):
        print(f"  {i}. {name}")
    
    print("\n" + "=" * 70)
