#!/usr/bin/env python3
"""
Real-Time Attractor Geometry Visualization
Monitors peer1.log and displays live dimensional dynamics
"""

import re
import time
import numpy as np
import matplotlib
matplotlib.use('TkAgg')  # Force GUI backend for Windows
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from matplotlib.gridspec import GridSpec
from collections import deque
import subprocess
import sys

# Configuration
LOG_FILE = "../logs/peer1.log"
UPDATE_INTERVAL = 1000  # ms
HISTORY_SIZE = 500  # Keep last 500 snapshots

class AttractorMonitor:
    def __init__(self):
        self.history = {
            'evolution': deque(maxlen=HISTORY_SIZE),
            'omega': deque(maxlen=HISTORY_SIZE),
            'amplitudes': [deque(maxlen=HISTORY_SIZE) for _ in range(8)],
            'dn_values': [deque(maxlen=HISTORY_SIZE) for _ in range(8)],
            'coupling_strength': [deque(maxlen=HISTORY_SIZE) for _ in range(8)],
        }
        self.last_position = 0

    def parse_log_chunk(self):
        """Read new lines from log file"""
        try:
            with open(LOG_FILE, 'r', encoding='utf-8', errors='ignore') as f:
                f.seek(self.last_position)
                lines = f.readlines()
                self.last_position = f.tell()
                return lines
        except FileNotFoundError:
            return []

    def extract_data(self, lines):
        """Extract evolution data from log lines"""
        current_evolution = None
        current_omega = None
        dims_data = {}

        for line in lines:
            # Evolution line
            match = re.search(r'Evolution: (\d+).*Ω: ([\d.]+)', line)
            if match:
                current_evolution = int(match.group(1))
                current_omega = float(match.group(2))

            # Dimension line: D1: 1.23e45 [Dₙ:123.4]
            match = re.search(r'D(\d): ([\d.e+]+) \[Dₙ:([\d.]+)\]', line)
            if match:
                dim_num = int(match.group(1)) - 1  # 0-indexed
                amplitude = float(match.group(2))
                dn_value = float(match.group(3))
                dims_data[dim_num] = (amplitude, dn_value)

        # If we got a complete snapshot
        if current_evolution and len(dims_data) == 8:
            self.history['evolution'].append(current_evolution)
            self.history['omega'].append(current_omega)

            for i in range(8):
                if i in dims_data:
                    amp, dn = dims_data[i]
                    self.history['amplitudes'][i].append(amp)
                    self.history['dn_values'][i].append(dn)

                    # Calculate coupling strength to D1
                    dn_diff = abs(dn - dims_data[0][1]) if 0 in dims_data else 0
                    coupling = np.exp(-dn_diff / 50.0)
                    self.history['coupling_strength'][i].append(coupling)

    def update(self):
        """Update history with new data"""
        lines = self.parse_log_chunk()
        if lines:
            self.extract_data(lines)

def create_visualization():
    """Create the real-time visualization"""
    monitor = AttractorMonitor()

    # Create figure with subplots
    fig = plt.figure(figsize=(16, 10))
    gs = GridSpec(3, 3, figure=fig, hspace=0.3, wspace=0.3)

    # Subplots
    ax_amp = fig.add_subplot(gs[0, :])  # Amplitude evolution
    ax_dn = fig.add_subplot(gs[1, :])   # Dₙ evolution
    ax_coupling = fig.add_subplot(gs[2, 0])  # Coupling matrix
    ax_phase = fig.add_subplot(gs[2, 1])     # Phase space (2D projection)
    ax_status = fig.add_subplot(gs[2, 2])    # Status text

    # Color scheme
    colors = plt.cm.tab10(np.linspace(0, 1, 8))
    dim_labels = [f'D{i+1}' for i in range(8)]

    # Initialize line objects
    amp_lines = [ax_amp.plot([], [], label=dim_labels[i], color=colors[i], linewidth=2)[0]
                 for i in range(8)]
    dn_lines = [ax_dn.plot([], [], label=dim_labels[i], color=colors[i], linewidth=2)[0]
                for i in range(8)]

    # Coupling matrix heatmap
    coupling_matrix = np.zeros((8, 8))
    im_coupling = ax_coupling.imshow(coupling_matrix, cmap='hot', vmin=0, vmax=1,
                                     interpolation='nearest', aspect='auto')
    ax_coupling.set_xticks(range(8))
    ax_coupling.set_yticks(range(8))
    ax_coupling.set_xticklabels(dim_labels)
    ax_coupling.set_yticklabels(dim_labels)
    ax_coupling.set_title('Coupling Matrix', fontsize=12, fontweight='bold')
    plt.colorbar(im_coupling, ax=ax_coupling, fraction=0.046, pad=0.04)

    # Phase space (D1 vs D2 projection)
    phase_scatter = ax_phase.scatter([], [], s=20, c='cyan', alpha=0.6, edgecolors='blue')
    ax_phase.set_xlabel('D1 Re', fontsize=10)
    ax_phase.set_ylabel('D2 Re', fontsize=10)
    ax_phase.set_title('Phase Space (D1-D2)', fontsize=12, fontweight='bold')
    ax_phase.grid(True, alpha=0.3)

    # Status panel
    ax_status.axis('off')
    status_text = ax_status.text(0.05, 0.95, '', transform=ax_status.transAxes,
                                 verticalalignment='top', fontfamily='monospace',
                                 fontsize=9)

    # Configure amplitude plot
    ax_amp.set_xlabel('Evolution Count (millions)', fontsize=10)
    ax_amp.set_ylabel('Amplitude (log scale)', fontsize=10)
    ax_amp.set_title('Dimensional Amplitudes - Real-Time', fontsize=14, fontweight='bold')
    ax_amp.set_yscale('log')
    ax_amp.grid(True, alpha=0.3)
    ax_amp.legend(loc='upper left', ncol=4, fontsize=8)

    # Configure Dₙ plot
    ax_dn.set_xlabel('Evolution Count (millions)', fontsize=10)
    ax_dn.set_ylabel('Dₙ(r) Value', fontsize=10)
    ax_dn.set_title('Geometric Resonance (Dₙ) - Real-Time', fontsize=14, fontweight='bold')
    ax_dn.grid(True, alpha=0.3)
    ax_dn.legend(loc='upper left', ncol=4, fontsize=8)

    def init():
        """Initialize animation"""
        return amp_lines + dn_lines + [im_coupling, phase_scatter, status_text]

    def update(frame):
        """Update animation frame"""
        monitor.update()

        if not monitor.history['evolution']:
            return amp_lines + dn_lines + [im_coupling, phase_scatter, status_text]

        # Get data arrays
        evolutions = np.array(monitor.history['evolution']) / 1e6  # Convert to millions

        # Update amplitude lines
        for i in range(8):
            if monitor.history['amplitudes'][i]:
                amps = np.array(monitor.history['amplitudes'][i])
                # Replace zeros with small value for log plot
                amps = np.where(amps > 0, amps, 1e-10)
                amp_lines[i].set_data(evolutions[-len(amps):], amps)

        # Update Dₙ lines
        for i in range(8):
            if monitor.history['dn_values'][i]:
                dns = np.array(monitor.history['dn_values'][i])
                dn_lines[i].set_data(evolutions[-len(dns):], dns)

        # Update axes limits
        if len(evolutions) > 0:
            ax_amp.set_xlim(max(0, evolutions[-1] - 50), evolutions[-1] + 1)
            ax_dn.set_xlim(max(0, evolutions[-1] - 50), evolutions[-1] + 1)

            # Auto-scale y-axis for amplitudes
            all_amps = []
            for i in range(8):
                if monitor.history['amplitudes'][i]:
                    amps = [a for a in monitor.history['amplitudes'][i] if a > 0]
                    all_amps.extend(amps[-50:])  # Last 50 points
            if all_amps:
                ax_amp.set_ylim(min(all_amps) * 0.1, max(all_amps) * 10)

            # Auto-scale Dₙ
            all_dns = []
            for i in range(8):
                if monitor.history['dn_values'][i]:
                    all_dns.extend(list(monitor.history['dn_values'][i])[-50:])
            if all_dns:
                ax_dn.set_ylim(0, max(all_dns) * 1.1)

        # Update coupling matrix
        if monitor.history['dn_values'][0]:
            latest_dns = [list(monitor.history['dn_values'][i])[-1] if monitor.history['dn_values'][i]
                         else 0 for i in range(8)]
            for i in range(8):
                for j in range(8):
                    if i == j:
                        coupling_matrix[i, j] = 1.0
                    else:
                        dn_diff = abs(latest_dns[i] - latest_dns[j])
                        coupling_matrix[i, j] = np.exp(-dn_diff / 50.0)
            im_coupling.set_data(coupling_matrix)

        # Update phase space (last 100 points of D1 Re vs D2 Re)
        # For now, use amplitude as proxy (real implementation would need Re/Im from log)
        if len(monitor.history['amplitudes'][0]) > 0 and len(monitor.history['amplitudes'][1]) > 0:
            d1_amp = list(monitor.history['amplitudes'][0])[-100:]
            d2_amp = list(monitor.history['amplitudes'][1])[-100:]
            min_len = min(len(d1_amp), len(d2_amp))
            if min_len > 0:
                # Normalize for display
                d1_norm = np.log10(np.array(d1_amp[:min_len]) + 1e-10)
                d2_norm = np.log10(np.array(d2_amp[:min_len]) + 1e-10)
                phase_scatter.set_offsets(np.c_[d1_norm, d2_norm])
                ax_phase.set_xlim(d1_norm.min() * 1.1, d1_norm.max() * 1.1)
                ax_phase.set_ylim(d2_norm.min() * 1.1, d2_norm.max() * 1.1)

        # Update status text
        if monitor.history['evolution']:
            latest_evolution = monitor.history['evolution'][-1]
            latest_omega = monitor.history['omega'][-1]

            # Count active dimensions (amplitude > threshold)
            active_dims = 0
            dim_status = []
            for i in range(8):
                if monitor.history['amplitudes'][i]:
                    amp = monitor.history['amplitudes'][i][-1]
                    dn = monitor.history['dn_values'][i][-1]

                    if amp > 1e-5:
                        status = '✓ ACTIVE'
                        active_dims += 1
                    elif amp > 1e-10:
                        status = '⚠ WEAK'
                    else:
                        status = '✗ DEAD'

                    dim_status.append(f'{dim_labels[i]}: {status:8} (Dₙ:{dn:7.1f})')

            status_str = f"""
╔═══════════════════════════╗
║   ATTRACTOR STATUS        ║
╚═══════════════════════════╝

Evolution: {latest_evolution:,}
Ω:         {latest_omega:.2f}
Active:    {active_dims}/8 dimensions

DIMENSIONS:
{chr(10).join(dim_status)}

COUPLING THRESHOLD:
  Strong: Dₙ diff < 100
  Weak:   Dₙ diff < 500
  None:   Dₙ diff > 1000
            """
            status_text.set_text(status_str)

        return amp_lines + dn_lines + [im_coupling, phase_scatter, status_text]

    # Create animation
    anim = FuncAnimation(fig, update, init_func=init, interval=UPDATE_INTERVAL,
                        blit=False, cache_frame_data=False)

    plt.suptitle('Analog Mainnet V4.2 - Live Attractor Geometry',
                 fontsize=16, fontweight='bold')

    return fig, anim

if __name__ == '__main__':
    print("Starting real-time attractor visualization...")
    print(f"Monitoring: {LOG_FILE}")
    print("Press Ctrl+C to stop\n")

    try:
        fig, anim = create_visualization()
        plt.show()
    except KeyboardInterrupt:
        print("\n\nVisualization stopped by user.")
        sys.exit(0)
    except Exception as e:
        print(f"\nError: {e}")
        import traceback
        traceback.print_exc()
        sys.exit(1)
