#!/usr/bin/env python3
"""
Real-Time 3D Attractor Geometry Visualization
Shows the strange attractor structure in 3D phase space
"""

import re
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.animation import FuncAnimation
from matplotlib.gridspec import GridSpec
from collections import deque
import sys

# Configuration
LOG_FILE = "../logs/peer1.log"
UPDATE_INTERVAL = 500  # ms
TRAJECTORY_LENGTH = 2000  # Points to display

class Attractor3D:
    def __init__(self):
        # Store Re/Im components for phase space reconstruction
        self.trajectories = {
            're': [deque(maxlen=TRAJECTORY_LENGTH) for _ in range(8)],
            'im': [deque(maxlen=TRAJECTORY_LENGTH) for _ in range(8)],
        }
        self.metadata = {
            'evolution': deque(maxlen=TRAJECTORY_LENGTH),
            'omega': deque(maxlen=TRAJECTORY_LENGTH),
            'dn_values': [deque(maxlen=TRAJECTORY_LENGTH) for _ in range(8)],
        }
        self.last_position = 0
        self.current_state = None

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

    def extract_snapshot(self, lines):
        """Extract dimensional state from log chunk"""
        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
            match = re.search(r'D(\d): ([\d.e+]+) \[Dₙ:([\d.]+)\]', line)
            if match:
                dim_num = int(match.group(1)) - 1
                amplitude = float(match.group(2))
                dn_value = float(match.group(3))
                dims_data[dim_num] = (amplitude, dn_value)

        # Complete snapshot
        if current_evolution and len(dims_data) == 8:
            self.metadata['evolution'].append(current_evolution)
            self.metadata['omega'].append(current_omega)

            # Reconstruct Re/Im from amplitude and phase estimate
            # (Simplified: assume phase evolves continuously)
            for i in range(8):
                if i in dims_data:
                    amp, dn = dims_data[i]
                    # Estimate phase from evolution count and frequency
                    freq = 1.0 + i * 0.2  # Approximate
                    phase = (current_evolution * 0.01 * freq) % (2 * np.pi)

                    re = amp * np.cos(phase) if amp > 0 else 0
                    im = amp * np.sin(phase) if amp > 0 else 0

                    self.trajectories['re'][i].append(re)
                    self.trajectories['im'][i].append(im)
                    self.metadata['dn_values'][i].append(dn)

    def update(self):
        """Update trajectories"""
        lines = self.parse_log_chunk()
        if lines:
            self.extract_snapshot(lines)

def create_3d_visualization():
    """Create interactive 3D attractor view"""
    attractor = Attractor3D()

    fig = plt.figure(figsize=(18, 10))
    gs = GridSpec(2, 3, figure=fig, hspace=0.3, wspace=0.3)

    # Main 3D attractor plot
    ax_3d = fig.add_subplot(gs[:, :2], projection='3d')
    ax_3d.set_xlabel('D1 (Re)', fontsize=10)
    ax_3d.set_ylabel('D2 (Re)', fontsize=10)
    ax_3d.set_zlabel('D3 (Re)', fontsize=10)
    ax_3d.set_title('Strange Attractor (D1-D2-D3 Projection)', fontsize=14, fontweight='bold')

    # Color by time (recent = bright)
    colors = plt.cm.plasma(np.linspace(0, 1, TRAJECTORY_LENGTH))

    # Initialize trajectory line
    trajectory_lines = []
    for i in range(3):
        line, = ax_3d.plot([], [], [], linewidth=1.5, alpha=0.7)
        trajectory_lines.append(line)

    # Scatter for current position
    current_point = ax_3d.scatter([], [], [], c='cyan', s=100, marker='o',
                                  edgecolors='white', linewidths=2)

    # Side panels
    ax_decoupling = fig.add_subplot(gs[0, 2])
    ax_manifold = fig.add_subplot(gs[1, 2])

    # Decoupling timeline
    ax_decoupling.set_xlabel('Evolution (M)', fontsize=9)
    ax_decoupling.set_ylabel('Active Dimensions', fontsize=9)
    ax_decoupling.set_title('Dimensional Collapse', fontsize=11, fontweight='bold')
    ax_decoupling.set_ylim(0, 8.5)
    ax_decoupling.grid(True, alpha=0.3)

    dim_count_line, = ax_decoupling.plot([], [], linewidth=2, color='red')
    ax_decoupling.axhline(y=8, color='green', linestyle='--', alpha=0.5, label='Full 8D')
    ax_decoupling.axhline(y=4, color='orange', linestyle='--', alpha=0.5, label='Half 4D')
    ax_decoupling.legend(fontsize=8)

    # Manifold curvature (simplified)
    ax_manifold.set_xlabel('Dimension', fontsize=9)
    ax_manifold.set_ylabel('Log₁₀(Amplitude)', fontsize=9)
    ax_manifold.set_title('Manifold Structure', fontsize=11, fontweight='bold')
    ax_manifold.grid(True, alpha=0.3)

    dim_labels = [f'D{i+1}' for i in range(8)]
    manifold_bars = ax_manifold.bar(range(8), [0]*8, color=plt.cm.viridis(np.linspace(0, 1, 8)))
    ax_manifold.set_xticks(range(8))
    ax_manifold.set_xticklabels(dim_labels, fontsize=8)

    def init():
        return trajectory_lines + [current_point, dim_count_line] + list(manifold_bars)

    def update(frame):
        attractor.update()

        if not attractor.metadata['evolution']:
            return trajectory_lines + [current_point, dim_count_line] + list(manifold_bars)

        # Get trajectory data for D1, D2, D3
        d1_re = np.array(attractor.trajectories['re'][0])
        d2_re = np.array(attractor.trajectories['re'][1])
        d3_re = np.array(attractor.trajectories['re'][2])

        if len(d1_re) < 2:
            return trajectory_lines + [current_point, dim_count_line] + list(manifold_bars)

        # Normalize for display (log scale)
        d1_log = np.sign(d1_re) * np.log10(np.abs(d1_re) + 1e-10)
        d2_log = np.sign(d2_re) * np.log10(np.abs(d2_re) + 1e-10)
        d3_log = np.sign(d3_re) * np.log10(np.abs(d3_re) + 1e-10)

        # Update 3D trajectory
        trajectory_lines[0].set_data(d1_log, d2_log)
        trajectory_lines[0].set_3d_properties(d3_log)

        # Update current position
        current_point._offsets3d = ([d1_log[-1]], [d2_log[-1]], [d3_log[-1]])

        # Auto-scale 3D axes
        if len(d1_log) > 10:
            recent = -min(500, len(d1_log))
            margin = 1.2
            ax_3d.set_xlim(d1_log[recent:].min() * margin, d1_log[recent:].max() * margin)
            ax_3d.set_ylim(d2_log[recent:].min() * margin, d2_log[recent:].max() * margin)
            ax_3d.set_zlim(d3_log[recent:].min() * margin, d3_log[recent:].max() * margin)

        # Update decoupling timeline
        evolutions = np.array(attractor.metadata['evolution']) / 1e6
        active_counts = []

        for idx in range(len(evolutions)):
            active = 0
            for i in range(8):
                if len(attractor.trajectories['re'][i]) > idx:
                    amp = abs(attractor.trajectories['re'][i][idx])
                    if amp > 1e-5:
                        active += 1
            active_counts.append(active)

        if active_counts:
            dim_count_line.set_data(evolutions[-len(active_counts):], active_counts)
            ax_decoupling.set_xlim(max(0, evolutions[-1] - 50), evolutions[-1] + 1)

        # Update manifold structure (current amplitudes)
        amplitudes = []
        for i in range(8):
            if attractor.trajectories['re'][i]:
                amp = np.sqrt(attractor.trajectories['re'][i][-1]**2 +
                             attractor.trajectories['im'][i][-1]**2)
                amplitudes.append(amp)
            else:
                amplitudes.append(0)

        log_amps = [np.log10(a + 1e-10) for a in amplitudes]
        for bar, height in zip(manifold_bars, log_amps):
            bar.set_height(height)

        # Auto-scale manifold
        if log_amps:
            ax_manifold.set_ylim(min(log_amps) - 1, max(log_amps) + 1)

        # Rotate 3D view slowly
        ax_3d.view_init(elev=20, azim=frame % 360)

        return trajectory_lines + [current_point, dim_count_line] + list(manifold_bars)

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

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

    return fig, anim

if __name__ == '__main__':
    print("Starting 3D attractor visualization...")
    print(f"Monitoring: {LOG_FILE}")
    print("3D view will rotate automatically")
    print("Press Ctrl+C to stop\n")

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