"""
HBA-GWO Hybrid Algorithm
========================

Hybrid algorithm combining Honey Badger Algorithm (HBA) with Grey Wolf
Optimizer (GWO) for balanced exploration and exploitation.

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 HBAGWOHybrid(BaseOptimizer):
    """
    Honey Badger Algorithm + Grey Wolf Optimizer Hybrid
    
    Combines:
    - HBA: Digging and honey searching behavior
    - GWO: Hierarchical hunting with alpha, beta, delta wolves
    
    Parameters
    ----------
    population_size : int, default=30
        Size of the population
    max_iterations : int, default=100
        Maximum iterations
    beta : float, default=6
        HBA ability parameter
    """
    
    def __init__(self, population_size=30, max_iterations=100, beta=6, **kwargs):
        super().__init__(population_size, max_iterations, **kwargs)
        self.algorithm_name = "HBA-GWO Hybrid"
        self.aliases = ["hba_gwo", "badger_wolf_hybrid"]
        self.beta = beta
    
    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 agents (wolves/badgers)
        agents = np.random.uniform(lb, ub, (self.population_size_, dimension))
        fitness = np.array([objective_function(a) for a in agents])
        
        # GWO hierarchy
        sorted_idx = np.argsort(fitness)
        alpha = agents[sorted_idx[0]].copy()
        beta_wolf = agents[sorted_idx[1]].copy() if len(sorted_idx) > 1 else alpha.copy()
        delta = agents[sorted_idx[2]].copy() if len(sorted_idx) > 2 else alpha.copy()
        best_fitness = fitness[sorted_idx[0]]
        global_fitness = []
        local_fitness = []
        local_positions = []
        
        for iteration in range(self.max_iterations_):
            # GWO parameter
            a_gwo = 2 * (1 - iteration / self.max_iterations_)
            
            # HBA parameter
            alpha_hba = 2 * np.exp(-iteration / self.max_iterations_)
            
            for i in range(self.population_size_):
                # Hybrid strategy selection
                if np.random.rand() < 0.5:
                    # HBA component
                    I = np.random.rand() * alpha_hba  # Smell concentration
                    density_factor = np.random.rand() * (ub - lb)
                    
                    if np.random.rand() < 0.5:
                        # Digging phase
                        r3 = np.random.rand()
                        distance = alpha - agents[i]
                        agents[i] = alpha + r3 * alpha_hba * density_factor * np.abs(distance)
                        
                        # Levy flight in digging
                        if np.random.rand() < 0.5:
                            levy_step = self._levy_flight(dimension)
                            agents[i] = agents[i] + levy_step * (alpha - agents[i])
                    else:
                        # Honey phase (exploitation)
                        r7 = np.random.rand()
                        agents[i] = agents[i] + r7 * I * agents[i]
                else:
                    # GWO component
                    # Position update using alpha, beta, delta
                    r1, r2 = np.random.rand(dimension), np.random.rand(dimension)
                    A1 = 2 * a_gwo * r1 - a_gwo
                    C1 = 2 * r2
                    D_alpha = np.abs(C1 * alpha - agents[i])
                    X1 = alpha - A1 * D_alpha
                    
                    r1, r2 = np.random.rand(dimension), np.random.rand(dimension)
                    A2 = 2 * a_gwo * r1 - a_gwo
                    C2 = 2 * r2
                    D_beta = np.abs(C2 * beta_wolf - agents[i])
                    X2 = beta_wolf - A2 * D_beta
                    
                    r1, r2 = np.random.rand(dimension), np.random.rand(dimension)
                    A3 = 2 * a_gwo * r1 - a_gwo
                    C3 = 2 * r2
                    D_delta = np.abs(C3 * delta - agents[i])
                    X3 = delta - A3 * D_delta
                    
                    agents[i] = (X1 + X2 + X3) / 3
                
                # Levy flight in late iterations
                if iteration > self.max_iterations_ * 0.6:
                    levy_step = self._levy_flight(dimension)
                    agents[i] = agents[i] + levy_step * (alpha - agents[i])
                
                # Boundary control
                agents[i] = np.clip(agents[i], lb, ub)
                fitness[i] = objective_function(agents[i])
            
            # Update hierarchy
            sorted_idx = np.argsort(fitness)
            alpha = agents[sorted_idx[0]].copy()
            beta_wolf = agents[sorted_idx[1]].copy() if len(sorted_idx) > 1 else alpha.copy()
            delta = agents[sorted_idx[2]].copy() if len(sorted_idx) > 2 else alpha.copy()
            best_fitness = fitness[sorted_idx[0]]
            
            global_fitness.append(best_fitness)
            # Note: Add local_fitness and local_positions tracking as needed
            
            if self.verbose_ and iteration % 10 == 0:
                print(f"HBA-GWO Iteration {iteration}: Best Fitness = {best_fitness:.6e}")
        
        return alpha, 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
