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

import re
import time
import numpy as np
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.animation import FuncAnimation
from collections import deque

LOG_FILE = "../logs/peer1.log"
TRAIL_LENGTH = 2000  # Number of points to show in the trail

class GeometryMonitor:
    def __init__(self):
        self.positions = deque(maxlen=TRAIL_LENGTH)
        self.last_position = 0
        self.current_omega = 0
        self.current_evolution = 0

    def update(self):
        """Read new dimensional positions from log"""
        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()

                current_evolution = None
                current_omega = None
                d1, d2, d3, d4, d5 = None, None, None, None, None

                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))

                    # Parse dimensions D1-D5 (the active core)
                    match = re.search(r'D1: ([\d.e+]+)', line)
                    if match:
                        d1 = float(match.group(1))
                    match = re.search(r'D2: ([\d.e+]+)', line)
                    if match:
                        d2 = float(match.group(1))
                    match = re.search(r'D3: ([\d.e+]+)', line)
                    if match:
                        d3 = float(match.group(1))
                    match = re.search(r'D4: ([\d.e+]+)', line)
                    if match:
                        d4 = float(match.group(1))
                    match = re.search(r'D5: ([\d.e+]+)', line)
                    if match:
                        d5 = float(match.group(1))

                    # If we have a complete snapshot, add to trajectory
                    if all(v is not None for v in [d1, d2, d3, d4, d5]):
                        # Normalize and add to positions
                        self.positions.append((d1, d2, d3, d4, d5))
                        self.current_evolution = current_evolution
                        self.current_omega = current_omega
                        d1, d2, d3, d4, d5 = None, None, None, None, None
        except Exception as e:
            print(f"Error reading log: {e}")

def normalize_trajectory(positions):
    """Normalize positions for visualization"""
    if len(positions) < 2:
        return np.array([]), np.array([]), np.array([])

    arr = np.array(positions)
    # Take log to compress huge dynamic range
    arr = np.log10(np.abs(arr) + 1e-100)

    # Normalize each dimension to [-1, 1] range
    for i in range(arr.shape[1]):
        col = arr[:, i]
        min_val, max_val = col.min(), col.max()
        if max_val > min_val:
            arr[:, i] = 2 * (col - min_val) / (max_val - min_val) - 1

    return arr[:, 0], arr[:, 1], arr[:, 2]

def animate(frame, monitor, ax, scatter, line, text_info):
    """Update 3D geometry"""
    monitor.update()

    if len(monitor.positions) < 2:
        return scatter, line, text_info

    # Get normalized trajectory
    x, y, z = normalize_trajectory(monitor.positions)

    if len(x) < 2:
        return scatter, line, text_info

    # Update trail line
    line.set_data(x, y)
    line.set_3d_properties(z)

    # Color points by age (newer = brighter)
    colors = np.linspace(0, 1, len(x))
    scatter._offsets3d = (x, y, z)
    scatter.set_array(colors)

    # Update info text
    omega_status = "🔴 SATURATED" if monitor.current_omega >= 999 else "🟢 GROWING"
    info_text = (f"Evolution: {monitor.current_evolution:,}\n"
                f"Ω: {monitor.current_omega:.1f} {omega_status}\n"
                f"Trail: {len(monitor.positions)} points")
    text_info.set_text(info_text)

    # Slowly rotate view
    ax.view_init(elev=20, azim=frame % 360)

    return scatter, line, text_info

def main():
    print("🌌 Starting 3D Attractor Geometry Visualization...")
    print(f"📊 Monitoring: {LOG_FILE}")
    print("🔄 Auto-rotating view (360°)")
    print("🎯 Press Ctrl+C to stop\n")

    monitor = GeometryMonitor()

    # Create 3D plot
    fig = plt.figure(figsize=(14, 10))
    ax = fig.add_subplot(111, projection='3d')

    fig.suptitle('🌀 Analog Codec V4.3 - Real-Time Strange Attractor Geometry',
                 fontsize=16, fontweight='bold')

    ax.set_xlabel('D1 (normalized)', fontsize=12)
    ax.set_ylabel('D2 (normalized)', fontsize=12)
    ax.set_zlabel('D3 (normalized)', fontsize=12)
    ax.set_xlim(-1.2, 1.2)
    ax.set_ylim(-1.2, 1.2)
    ax.set_zlim(-1.2, 1.2)

    # Create trail line (flowing path)
    line, = ax.plot([], [], [], color='cyan', alpha=0.3, linewidth=0.5, label='Trajectory trail')

    # Create scatter for current positions (gradient colored)
    scatter = ax.scatter([], [], [], c=[], cmap='plasma', s=2, alpha=0.8)

    # Info text box
    text_info = ax.text2D(0.02, 0.98, "", transform=ax.transAxes,
                         fontsize=11, verticalalignment='top',
                         bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.8))

    ax.legend(loc='upper right')
    ax.grid(True, alpha=0.2)

    # Set dark background for better contrast
    ax.set_facecolor('#1a1a2e')
    fig.patch.set_facecolor('#16213e')

    # Animate
    ani = FuncAnimation(fig, animate,
                       fargs=(monitor, ax, scatter, line, text_info),
                       interval=100,  # Update every 100ms for smooth rotation
                       blit=False,
                       cache_frame_data=False)

    try:
        plt.show()
    except KeyboardInterrupt:
        print("\n\n✅ Visualization stopped by user.")

if __name__ == "__main__":
    main()
