"""
White Noise Generator - Agente 4
Generador de ruido blanco programático para efectos de transición y texturas.
"""
from __future__ import absolute_import, print_function, unicode_literals

import os
import wave
import struct
import random
import math
from typing import List, Tuple, Optional


class WhiteNoiseGenerator(object):
    """
    Generador de ruido blanco para crear efectos de transición en producción musical.
    
    Características:
    - Generación programática de ruido blanco (no requiere samples externos)
    - Aplicación de filtros (lowpass, highpass, sweep)
    - Envolventes de volumen (fade in/out, ADSR)
    - Exportación a WAV para uso en Ableton Live
    """
    
    def __init__(self, temp_dir=None):
        """
        Inicializa el generador de ruido blanco.
        
        Args:
            temp_dir: Directorio temporal para archivos generados (default: directorio del script)
        """
        if temp_dir is None:
            script_dir = os.path.dirname(os.path.abspath(__file__))
            self.temp_dir = os.path.join(os.path.dirname(script_dir), "generated_audio")
        else:
            self.temp_dir = temp_dir
        
        # Asegurar que el directorio existe
        if not os.path.exists(self.temp_dir):
            try:
                os.makedirs(self.temp_dir)
            except Exception:
                self.temp_dir = os.path.dirname(os.path.abspath(__file__))
    
    def generate_white_noise(self, duration, sample_rate=44100):
        """
        Genera ruido blanco básico.
        
        Args:
            duration: Duración en segundos
            sample_rate: Frecuencia de muestreo (default 44100 Hz)
            
        Returns:
            dict: Información del clip de audio generado incluyendo file_path
        """
        duration = float(duration)
        sample_rate = int(sample_rate)
        
        # Generar nombre de archivo único
        filename = "white_noise_{:.3f}s_{}hz.wav".format(duration, sample_rate)
        filepath = os.path.join(self.temp_dir, filename)
        
        # Número de muestras
        num_samples = int(duration * sample_rate)
        
        # Generar datos de audio (ruido blanco: valores aleatorios entre -1 y 1)
        audio_data = []
        for _ in range(num_samples):
            # Ruido blanco uniforme
            sample = random.uniform(-1.0, 1.0)
            # Aplicar pequeña atenuación para evitar clipping
            sample *= 0.7
            audio_data.append(sample)
        
        # Escribir archivo WAV
        self._write_wav_file(filepath, audio_data, sample_rate)
        
        return {
            "file_path": filepath,
            "duration": duration,
            "sample_rate": sample_rate,
            "num_samples": num_samples,
            "type": "white_noise"
        }
    
    def apply_filter_sweep(self, noise_clip, start_freq, end_freq):
        """
        Aplica un filtro sweep (lowpass) al ruido blanco.
        
        Args:
            noise_clip: Dict con información del clip (debe contener file_path)
            start_freq: Frecuencia de corte inicial en Hz
            end_freq: Frecuencia de corte final en Hz
            
        Returns:
            dict: Información del clip filtrado
        """
        input_path = noise_clip.get("file_path")
        if not input_path or not os.path.exists(input_path):
            raise ValueError("Invalid noise_clip: file_path not found")
        
        duration = noise_clip.get("duration", 4.0)
        sample_rate = noise_clip.get("sample_rate", 44100)
        
        # Generar nombre de archivo para la versión filtrada
        filename = "sweep_{}hz_to_{}hz_{:.3f}s.wav".format(
            int(start_freq), int(end_freq), duration
        )
        output_path = os.path.join(self.temp_dir, filename)
        
        # Leer el audio original
        audio_data = self._read_wav_file(input_path)
        
        # Aplicar filtro paso-bajo con frecuencia variable
        filtered_data = self._apply_lowpass_sweep(
            audio_data, sample_rate, float(start_freq), float(end_freq)
        )
        
        # Escribir archivo resultante
        self._write_wav_file(output_path, filtered_data, sample_rate)
        
        return {
            "file_path": output_path,
            "duration": duration,
            "sample_rate": sample_rate,
            "start_freq": float(start_freq),
            "end_freq": float(end_freq),
            "type": "filtered_sweep"
        }
    
    def apply_volume_envelope(self, noise_clip, envelope_points):
        """
        Aplica una envolvente de volumen al ruido blanco.
        
        Args:
            noise_clip: Dict con información del clip
            envelope_points: Lista de puntos [(time_ratio, volume), ...]
                            donde time_ratio es 0.0 al inicio y 1.0 al final
                            y volume es 0.0 a 1.0
                            
        Returns:
            dict: Información del clip con envolvente aplicada
        """
        input_path = noise_clip.get("file_path")
        if not input_path or not os.path.exists(input_path):
            raise ValueError("Invalid noise_clip: file_path not found")
        
        duration = noise_clip.get("duration", 4.0)
        sample_rate = noise_clip.get("sample_rate", 44100)
        
        # Generar nombre de archivo
        filename = "envelope_{:.3f}s.wav".format(duration)
        output_path = os.path.join(self.temp_dir, filename)
        
        # Leer audio original
        audio_data = self._read_wav_file(input_path)
        num_samples = len(audio_data)
        
        # Aplicar envolvente
        enveloped_data = []
        for i, sample in enumerate(audio_data):
            # Calcular posición relativa (0.0 a 1.0)
            t = float(i) / float(num_samples) if num_samples > 0 else 0.0
            
            # Interpolar volumen desde los puntos de la envolvente
            volume = self._interpolate_envelope(t, envelope_points)
            
            # Aplicar volumen
            enveloped_data.append(sample * volume)
        
        # Escribir archivo resultante
        self._write_wav_file(output_path, enveloped_data, sample_rate)
        
        return {
            "file_path": output_path,
            "duration": duration,
            "sample_rate": sample_rate,
            "envelope_points": envelope_points,
            "type": "enveloped_noise"
        }
    
    def create_riser_effect(self, duration=4.0, sample_rate=44100, 
                            start_freq=200, end_freq=8000):
        """
        Crea un efecto riser clásico (ruido blanco con sweep de filtro ascendente).
        
        Args:
            duration: Duración en segundos
            sample_rate: Frecuencia de muestreo
            start_freq: Frecuencia inicial del sweep
            end_freq: Frecuencia final del sweep
            
        Returns:
            dict: Información del clip de riser generado
        """
        # Generar ruido blanco base
        noise = self.generate_white_noise(duration, sample_rate)
        
        # Aplicar envolvente de fade in para el volumen
        envelope = [
            (0.0, 0.0),      # Silencio al inicio
            (0.1, 0.3),      # Subida rápida
            (0.5, 0.7),      # A mitad de camino
            (0.9, 1.0),      # Pico antes del final
            (1.0, 0.0)       # Corte brusco al final
        ]
        noise_with_env = self.apply_volume_envelope(noise, envelope)
        
        # Aplicar sweep de filtro ascendente
        riser = self.apply_filter_sweep(noise_with_env, start_freq, end_freq)
        
        riser["type"] = "riser_effect"
        riser["description"] = "Riser/buildup effect with filter sweep"
        
        return riser
    
    def create_downlifter_effect(self, duration=2.0, sample_rate=44100,
                                 start_freq=8000, end_freq=200):
        """
        Crea un efecto downlifter (ruido blanco con sweep de filtro descendente).
        
        Args:
            duration: Duración en segundos
            sample_rate: Frecuencia de muestreo
            start_freq: Frecuencia inicial del sweep
            end_freq: Frecuencia final del sweep
            
        Returns:
            dict: Información del clip de downlifter generado
        """
        # Generar ruido blanco base
        noise = self.generate_white_noise(duration, sample_rate)
        
        # Aplicar envolvente de fade out
        envelope = [
            (0.0, 1.0),      # Máximo al inicio
            (0.5, 0.7),      # Descenso gradual
            (0.9, 0.2),      # Casi silencio
            (1.0, 0.0)       # Silencio al final
        ]
        noise_with_env = self.apply_volume_envelope(noise, envelope)
        
        # Aplicar sweep de filtro descendente
        downlifter = self.apply_filter_sweep(noise_with_env, start_freq, end_freq)
        
        downlifter["type"] = "downlifter_effect"
        downlifter["description"] = "Downlifter effect with descending filter"
        
        return downlifter
    
    # -------------------------------------------------------------------------
    # Métodos auxiliares privados
    # -------------------------------------------------------------------------
    
    def _write_wav_file(self, filepath, audio_data, sample_rate):
        """Escribe datos de audio a un archivo WAV."""
        num_samples = len(audio_data)
        
        with wave.open(filepath, 'w') as wav_file:
            # Configurar parámetros: 1 canal, 2 bytes por muestra, sample_rate
            wav_file.setnchannels(1)
            wav_file.setsampwidth(2)
            wav_file.setframerate(sample_rate)
            
            # Convertir muestras float (-1.0 a 1.0) a int16
            for sample in audio_data:
                # Limitar a rango válido
                sample = max(-1.0, min(1.0, sample))
                # Convertir a int16
                int_sample = int(sample * 32767)
                # Empaquetar como little-endian 16-bit
                wav_file.writeframes(struct.pack('<h', int_sample))
    
    def _read_wav_file(self, filepath):
        """Lee datos de audio de un archivo WAV."""
        audio_data = []
        
        with wave.open(filepath, 'r') as wav_file:
            num_frames = wav_file.getnframes()
            
            for _ in range(num_frames):
                frame = wav_file.readframes(1)
                # Desempaquetar int16 little-endian
                int_sample = struct.unpack('<h', frame)[0]
                # Convertir a float -1.0 a 1.0
                sample = float(int_sample) / 32767.0
                audio_data.append(sample)
        
        return audio_data
    
    def _apply_lowpass_sweep(self, audio_data, sample_rate, start_freq, end_freq):
        """
        Aplica un filtro paso-bajo con frecuencia de corte variable (sweep).
        
        Implementa un filtro IIR simple de primer orden.
        """
        filtered_data = []
        num_samples = len(audio_data)
        
        # Variables del filtro
        prev_output = 0.0
        
        for i, sample in enumerate(audio_data):
            # Calcular frecuencia de corte en este punto (interpolación lineal)
            t = float(i) / float(num_samples) if num_samples > 0 else 0.0
            cutoff = start_freq + (end_freq - start_freq) * t
            
            # Calcular coeficiente del filtro basado en frecuencia de corte
            # Fórmula para filtro paso-bajo de primer orden
            rc = 1.0 / (2.0 * math.pi * max(cutoff, 1.0))  # Constante RC
            dt = 1.0 / float(sample_rate)  # Intervalo de tiempo
            alpha = dt / (rc + dt)  # Coeficiente de suavizado
            
            # Aplicar filtro
            output = prev_output + alpha * (sample - prev_output)
            filtered_data.append(output)
            prev_output = output
        
        return filtered_data
    
    def _interpolate_envelope(self, t, envelope_points):
        """
        Interpola el valor de volumen desde los puntos de la envolvente.
        
        Args:
            t: Posición temporal relativa (0.0 a 1.0)
            envelope_points: Lista de tuplas (time_ratio, volume)
            
        Returns:
            float: Volumen interpolado (0.0 a 1.0)
        """
        if not envelope_points:
            return 1.0
        
        # Asegurar que esté ordenado por tiempo
        sorted_points = sorted(envelope_points, key=lambda p: p[0])
        
        # Encontrar los puntos que rodean a t
        n = len(sorted_points)
        
        # Si t está antes del primer punto
        if t <= sorted_points[0][0]:
            return sorted_points[0][1]
        
        # Si t está después del último punto
        if t >= sorted_points[-1][0]:
            return sorted_points[-1][1]
        
        # Interpolar linealmente entre puntos
        for i in range(n - 1):
            t1, v1 = sorted_points[i]
            t2, v2 = sorted_points[i + 1]
            
            if t1 <= t <= t2:
                # Evitar división por cero
                if t2 - t1 < 0.0001:
                    return v1
                
                # Interpolación lineal
                ratio = (t - t1) / (t2 - t1)
                return v1 + (v2 - v1) * ratio
        
        # Fallback
        return sorted_points[-1][1]


def get_noise_generator(temp_dir=None):
    """
    Factory function para obtener una instancia del generador.
    
    Args:
        temp_dir: Directorio temporal opcional
        
    Returns:
        WhiteNoiseGenerator instance
    """
    return WhiteNoiseGenerator(temp_dir=temp_dir)
