import numpy as np
from time import perf_counter as pc
from time import sleep
import matplotlib.pyplot as plt
from datetime import datetime

# =====================
# CBHE-TD: Black Hole Timing Detector
# Uses adaptive timing to sense gravitational anomalies
# =====================

class SpacetimeDetector:
    def __init__(self, duration=3600, sample_rate=100):
        """
        duration: seconds to collect data (1 hour minimum for annual effects)
        sample_rate: Hz (must be ~100 for π-timing)
        """
        self.duration = duration
        self.sample_rate = sample_rate
        self.d = 0
        self.dl = []
        self.timestamps = []
        self.start_time = datetime.now()
        
    def collect_data(self):
        """Collect timing error data"""
        iterations = self.duration * self.sample_rate
        print(f"Collecting {iterations} samples over {self.duration}s...")
        
        for i in range(iterations):
            t = pc()
            sleep(np.pi/100 - self.d)
            pi = 100 * (pc() - t)
            err = np.pi - pi
            self.d -= 0.01 * err
            self.dl.append(self.d)
            self.timestamps.append((datetime.now() - self.start_time).total_seconds())
            
            if i % 10000 == 0:
                print(f"Progress: {100*i/iterations:.1f}%")
        
        return np.array(self.dl)
    
    def fft_analysis(self):
        """Perform FFT to find periodic gravitational signatures"""
        data = np.array(self.dl)
        
        # Remove DC component (mean)
        data = data - np.mean(data)
        
        # Apply window function
        window = np.hanning(len(data))
        data_windowed = data * window
        
        # FFT
        fft_result = np.fft.fft(data_windowed)
        freqs = np.fft.fftfreq(len(data), 1/self.sample_rate)
        
        # Only positive frequencies
        pos_mask = freqs > 0
        freqs_pos = freqs[pos_mask]
        magnitude = np.abs(fft_result[pos_mask])
        
        return freqs_pos, magnitude, data
    
    def identify_black_hole_signatures(self, freqs, magnitude):
        """Identify potential BH signatures in frequency domain"""
        signatures = []
        
        # Known frequencies of interest
        targets = {
            'Annual (Earth orbit)': 1/(365.25*24*3600),  # 1/year
            'Semi-annual': 2/(365.25*24*3600),
            'Lunar month': 1/(27.32*24*3600),
            'Solar day': 1/(24*3600),
            'Sidereal day': 1/(23.9345*3600),
            'PBH passage (0.001 Hz)': 0.001,
            'PBH passage (0.0001 Hz)': 0.0001,
            'Gravitational wave (LIGO)': 100,
        }
        
        print("\n" + "="*60)
        print("BLACK HOLE SIGNATURE ANALYSIS")
        print("="*60)
        
        for name, target_freq in targets.items():
            # Find closest frequency in spectrum
            idx = np.argmin(np.abs(freqs - target_freq))
            detected_freq = freqs[idx]
            power = magnitude[idx]
            
            # Signal detection threshold
            threshold = np.mean(magnitude) + 3*np.std(magnitude)
            
            status = "⚠ DETECTED" if power > threshold else "  normal"
            
            print(f"{name:30} | freq: {detected_freq:.2e} | power: {power:.2e} | {status}")
            
            if power > threshold:
                signatures.append({
                    'name': name,
                    'frequency': detected_freq,
                    'power': power,
                    'threshold': threshold
                })
        
        return signatures
    
    def time_domain_analysis(self, data):
        """Analyze time-domain for transient BH events"""
        print("\n" + "="*60)
        print("TIME-DOMAIN ANOMALY DETECTION")
        print("="*60)
        
        # Rolling statistics
        window = 1000
        rolling_mean = np.convolve(data, np.ones(window)/window, mode='same')
        rolling_std = np.array([np.std(data[max(0,i-window):i+1]) for i in range(len(data))])
        
        # Find anomalies (points > 3 std from mean)
        anomaly_mask = np.abs(data - rolling_mean) > 3 * rolling_std
        anomaly_indices = np.where(anomaly_mask)[0]
        
        print(f"Anomalous timing deviations detected: {len(anomaly_indices)}")
        
        if len(anomaly_indices) > 0:
            # Group consecutive anomalies
            groups = []
            current_group = [anomaly_indices[0]]
            
            for idx in anomaly_indices[1:]:
                if idx - current_group[-1] < 100:  # Within 1 second
                    current_group.append(idx)
                else:
                    groups.append(current_group)
                    current_group = [idx]
            groups.append(current_group)
            
            print(f"Possible BH passage events: {len(groups)}")
            
            for i, group in enumerate(groups[:5]):  # Show first 5
                center = group[len(group)//2]
                time_sec = self.timestamps[center] if center < len(self.timestamps) else 0
                hours = time_sec / 3600
                magnitude = np.mean(np.abs(data[group]))
                print(f"  Event {i+1}: t={hours:.2f}h, magnitude={magnitude:.4f}")
        
        # Spectral power in different bands
        print("\nSpectral Power Distribution:")
        
        bands = [
            ('Ultra-low (PBH) < 0.0001 Hz', 0, 0.0001),
            ('Very low (PBH) < 0.001 Hz', 0.0001, 0.001),
            ('Low (solar system) < 0.01 Hz', 0.001, 0.01),
            ('Medium (local) < 1 Hz', 0.01, 1),
            ('High (GW LIGO) < 100 Hz', 1, 100),
        ]
        
        freqs, magnitude, _ = self.fft_analysis()
        
        for name, f_min, f_max in bands:
            mask = (freqs >= f_min) & (freqs < f_max)
            power = np.sum(magnitude[mask]**2)
            print(f"  {name:40} | power: {power:.4e}")
        
        return anomaly_indices
    
    def plot_results(self, freqs, magnitude, data):
        """Generate comprehensive plots"""
        fig, axes = plt.subplots(3, 2, figsize=(14, 12))
        
        # 1. Time series
        ax1 = axes[0, 0]
        times_hours = np.array(self.timestamps) / 3600
        ax1.plot(times_hours, data, 'b-', alpha=0.5, linewidth=0.5)
        ax1.set_xlabel('Time (hours)')
        ax1.set_ylabel('Timing Deviation (d)')
        ax1.set_title('Timing Error Accumulation')
        ax1.grid(True, alpha=0.3)
        
        # 2. FFT magnitude
        ax2 = axes[0, 1]
        ax2.semilogy(freqs, magnitude, 'r-', linewidth=0.5)
        ax2.set_xlabel('Frequency (Hz)')
        ax2.set_ylabel('Magnitude')
        ax2.set_title('FFT: Frequency Domain Analysis')
        ax2.set_xscale('log')
        ax2.grid(True, alpha=0.3)
        
        # 3. Log-log FFT (reveals power laws)
        ax3 = axes[1, 0]
        valid = freqs > 0
        ax3.loglog(freqs[valid], magnitude[valid], 'g-', linewidth=0.5)
        ax3.set_xlabel('Frequency (Hz)')
        ax3.set_ylabel('Magnitude')
        ax3.set_title('Log-Log FFT (Power Law Detection)')
        ax3.grid(True, alpha=0.3)
        
        # 4. Spectrogram
        ax4 = axes[1, 1]
        window_size = len(data) // 50
        spectrogram = []
        for i in range(0, len(data) - window_size, window_size//2):
            segment = data[i:i+window_size]
            fft_seg = np.abs(np.fft.fft(segment * np.hanning(window_size)))
            spectrogram.append(fft_seg[:len(fft_seg)//2])
        
        spectrogram = np.array(spectrogram)
        im = ax4.imshow(spectrogram.T, aspect='auto', origin='lower',
                       extent=[0, len(data)/self.sample_rate/3600, 0, self.sample_rate/2])
        ax4.set_xlabel('Time (hours)')
        ax4.set_ylabel('Frequency (Hz)')
        ax4.set_title('Time-Frequency Spectrogram')
        plt.colorbar(im, ax=ax4)
        
        # 5. Histogram of deviations
        ax5 = axes[2, 0]
        ax5.hist(data, bins=100, alpha=0.7, color='purple', edgecolor='black')
        ax5.axvline(np.mean(data), color='red', linestyle='--', label=f'Mean: {np.mean(data):.4f}')
        ax5.axvline(np.mean(data) + 3*np.std(data), color='orange', linestyle='--', label='+3σ')
        ax5.axvline(np.mean(data) - 3*np.std(data), color='orange', linestyle='--', label='-3σ')
        ax5.set_xlabel('Timing Deviation')
        ax5.set_ylabel('Count')
        ax5.set_title('Distribution of Timing Errors')
        ax5.legend()
        
        # 6. Cumulative spectral power
        ax6 = axes[2, 1]
        sorted_idx = np.argsort(freqs)
        cumulative = np.cumsum(magnitude[sorted_idx]**2)
        cumulative = cumulative / cumulative[-1]  # Normalize
        ax6.plot(freqs[sorted_idx], cumulative, 'b-', linewidth=1)
        ax6.set_xlabel('Frequency (Hz)')
        ax6.set_ylabel('Cumulative Power')
        ax6.set_title('Cumulative Spectral Power')
        ax6.set_xscale('log')
        ax6.grid(True, alpha=0.3)
        
        plt.tight_layout()
        plt.savefig('black_hole_timing_analysis.png', dpi=150)
        plt.show()
        
        print("\nPlot saved to 'black_hole_timing_analysis.png'")


def run_detection_experiment(duration_seconds=60):
    """
    Run the black hole detection experiment
    
    duration: For meaningful results, run at least:
        - 1 hour for solar system effects
        - 24 hours for Earth rotation effects  
        - 1 week for annual effects
    """
    print("="*60)
    print("CBHE-TD: Conjugate Black Hole Timing Detector")
    print("Using Heisenberg Uncertainty for BH Detection")
    print("="*60)
    
    detector = SpacetimeDetector(duration=duration_seconds)
    
    # Collect data
    print("\n[1/4] Collecting timing data...")
    data = detector.collect_data()
    
    # FFT analysis
    print("\n[2/4] Performing FFT analysis...")
    freqs, magnitude, data_centered = detector.fft_analysis()
    
    # Identify signatures
    signatures = detector.identify_black_hole_signatures(freqs, magnitude)
    
    # Time-domain analysis
    print("\n[3/4] Time-domain anomaly detection...")
    anomalies = detector.time_domain_analysis(data)
    
    # Plot results
    print("\n[4/4] Generating plots...")
    detector.plot_results(freqs, magnitude, data)
    
    return detector, signatures, anomalies


# =====================
# Quick test / demonstration
# =====================
if __name__ == "__main__":
    # For demo, run 60 seconds
    # For real detection, use duration_seconds=3600 or more
    duration = 10  # seconds
    
    print(f"\nRunning {duration}s detection experiment...")
    print("Note: Longer runs (>1 hour) detect more phenomena\n")
    
    detector, sigs, anom = run_detection_experiment(duration)
    
    print("\n" + "="*60)
    print("DETECTION SUMMARY")
    print("="*60)
    
    if sigs:
        print("\n✓ POTENTIAL BLACK HOLE SIGNATURES DETECTED:")
        for s in sigs:
            print(f"  • {s['name']}: freq={s['frequency']:.2e} Hz, power={s['power']:.2e}")
    else:
        print("\n○ No strong BH signatures in this run.")
        print("  Try longer duration for better sensitivity.")
    
    if len(anom) > 0:
        print(f"\n✓ {len(anom)} ANOMALOUS TIMING DEVIATIONS:")
        print("  Could indicate transient BH passage or gravitational anomaly")
