"""
Jellyfish Search Algorithm (JS)
==============================

Jellyfish Search Algorithm inspired by the behavior of jellyfish
in the ocean currents.
"""

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


class JellyfishSearchAlgorithm(BaseOptimizer):
    """
    Jellyfish Search Algorithm (JS)
    
    Bio-inspired algorithm based on the behavior of jellyfish following
    ocean currents and swarm behaviors.
    """
    
    def __init__(self, population_size=30, max_iterations=100, **kwargs):
        super().__init__(population_size, max_iterations, **kwargs)
        self.algorithm_name = "Jellyfish Search Algorithm"
        self.aliases = ["js", "jellyfish", "jellyfish_search"]
    
    def _optimize(self, objective_function, X=None, y=None, **kwargs):
        # Levy flight available via: add_levy_flight_to_position()
        """
        Execute the Jellyfish Search Algorithm
        
        Args:
            objective_function: Function to optimize
            X: Feature matrix (optional)
            y: Target values (optional)
            **kwargs: Additional parameters
            
        Returns:
            Tuple containing (best_position, best_fitness, global_fitness, local_fitness, local_positions)
        """
        # 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 jellyfish population
        population = np.random.uniform(lb, ub, (self.population_size_, dimension))
        fitness = np.array([objective_function(individual) for individual in population])
        
        # Track best solution
        best_idx = np.argmin(fitness)
        best_position = population[best_idx].copy()
        best_fitness = fitness[best_idx]
        
        # History tracking
        global_fitness = [best_fitness]
        local_fitness = [fitness.copy()]
        local_positions = [population.copy()]
        
        # Algorithm parameters
        c0 = 0.5  # Cognitive component
        beta = 3  # Distribution control parameter
        gamma = 0.1  # Motion control parameter
        
        for iteration in range(self.max_iterations_):
            # Calculate time control function
            time_control = abs((2 * np.exp(np.random.random() * (1 - iteration / self.max_iterations_))) - 1)
            
            for i in range(self.population_size_):
                if time_control >= c0:
                    # Follow ocean current (passive motion)
                    if np.random.random() > (1 - c0):
                        # Move towards best location
                        population[i] = population[i] + np.random.random() * (best_position - beta * np.random.random() * population[i])
                    else:
                        # Random motion
                        population[i] = population[i] + gamma * np.random.random() * (ub - lb) * np.random.uniform(-1, 1, dimension)
                else:
                    # Active motion (swarm behavior)
                    if i > 0:
                        # Follow the jellyfish ahead
                        if fitness[i] > fitness[i-1]:
                            # Move towards better jellyfish
                            direction = population[i-1] - population[i]
                            population[i] = population[i] + np.random.random() * direction
                        else:
                            # Random exploration
                            population[i] = population[i] + (ub - lb) * np.random.uniform(-1, 1, dimension) * 0.1
                    else:
                        # First jellyfish moves randomly
                        population[i] = population[i] + (ub - lb) * np.random.uniform(-1, 1, dimension) * 0.1
                
                # Apply bounds
                population[i] = np.clip(population[i], lb, ub)
            
            # Evaluate new positions
            fitness = np.array([objective_function(individual) for individual in population])
            
            # Update best solution
            current_best_idx = np.argmin(fitness)
            if fitness[current_best_idx] < best_fitness:
                best_position = population[current_best_idx].copy()
                best_fitness = fitness[current_best_idx]
            
            # Track progress
            global_fitness.append(best_fitness)
            local_fitness.append(fitness.copy())
            local_positions.append(population.copy())
        
        return best_position, best_fitness, global_fitness, local_fitness, local_positions