#!/usr/bin/env python3
"""
Landour Memory Inducement - Telepathic Intelligence Transfer
Based on Q-CCT Water Intelligence and Skiss-Mathematics.

Implements:
- ODE system for two-agent memory/water coherence/toxicity.
- Real-time CPU temperature monitoring + structured load generation.
- Hypothesis: Structured heat (with lag features) reduces temperature vs random load.
"""

import numpy as np
import time
import argparse
import sys
import threading
from dataclasses import dataclass
from typing import Callable, Optional, Tuple, List

# For ODE solving
from scipy.integrate import solve_ivp

# For plotting
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

# For system monitoring (CPU temperature, load)
try:
    import psutil
    PSUTIL_AVAILABLE = True
except ImportError:
    PSUTIL_AVAILABLE = False
    print("Warning: psutil not installed. Real temperature monitoring disabled.")

# Platform-specific temperature reading
def get_cpu_temperature() -> Optional[float]:
    """Return CPU temperature in Celsius or None if not available."""
    if not PSUTIL_AVAILABLE:
        return None
    try:
        # psutil sensors_temperatures() returns dict
        temps = psutil.sensors_temperatures()
        if not temps:
            return None
        # Common keys: 'coretemp', 'cpu_thermal', 'k10temp', 'acpitz'
        for name, entries in temps.items():
            if entries and entries[0].current is not None:
                return entries[0].current
        return None
    except Exception:
        return None


# ----------------------------------------------------------------------
# ODE System (Landour Telepathy)
# ----------------------------------------------------------------------
@dataclass
class TelepathyParams:
    kappa: float = 1.2      # coupling strength
    rho: float = 0.8        # water coherence gain
    deltaQ: float = 0.25    # water decay
    psi: float = 0.5        # toxicity growth
    phi: float = 0.4        # toxicity effect on Q
    omega_tau: float = 0.3  # toxicity recovery
    eta_pulse: float = 0.7  # Landour pulse gain
    # Lag function parameters
    L0: float = 0.4
    AL: float = 0.3
    fL: float = 0.6
    # Toxicity gate for Landour
    tau_gate: float = 0.8

def lag_function(t: float, params: TelepathyParams) -> float:
    """Structured lag feature (sinusoidal modulation)."""
    L = params.L0 + params.AL * np.sin(2 * np.pi * params.fL * t)
    return np.clip(L, 0.0, 1.0)

def telepathy_ode(t: float, y: np.ndarray, params: TelepathyParams,
                  landour_pulse: float = 0.0) -> np.ndarray:
    """
    ODE system:
    y = [M_A, M_B, Q, tau]
    """
    MA, MB, Q, tau = y
    # Clamp to valid range
    MA = np.clip(MA, 0.0, 1.0)
    MB = np.clip(MB, 0.0, 1.0)
    Q = np.clip(Q, 0.0, 1.0)
    tau = np.clip(tau, 0.0, 1.0)

    L = lag_function(t, params)
    gate = 1.0 - tau * params.tau_gate
    if gate < 0:
        gate = 0

    # Landour term (external pulse)
    landour_term_A = params.eta_pulse * landour_pulse * (1 - MA) * gate
    landour_term_B = params.eta_pulse * landour_pulse * (1 - MB) * gate

    dMA = params.kappa * (MB - MA) * Q * L + landour_term_A
    dMB = params.kappa * (MA - MB) * Q * L + landour_term_B

    avg_mem = (MA + MB) / 2.0
    dQ = params.rho * L * avg_mem - params.deltaQ * Q - params.phi * tau * Q

    # Toxicity rate: grows when lag is low and memory changes rapidly
    # We approximate memory change rate using dMA,dMB (absolute)
    dTau = params.psi * (1 - L) * (abs(dMA) + abs(dMB)) - params.omega_tau * tau

    return np.array([dMA, dMB, dQ, dTau])


# ----------------------------------------------------------------------
# Simulation (pure ODE, no hardware)
# ----------------------------------------------------------------------
def run_simulation(duration: float = 35.0, dt_eval: float = 0.05,
                   pulse_time: float = 1.0, pulse_start: float = 5.0,
                   params: TelepathyParams = None,
                   initial_state: Tuple[float,float,float,float] = (0.9, 0.1, 0.3, 0.0)):
    """Run the ODE simulation and return time series."""
    if params is None:
        params = TelepathyParams()

    def pulse_func(t):
        return 1.0 if (pulse_start <= t < pulse_start + pulse_time) else 0.0

    t_span = (0.0, duration)
    t_eval = np.arange(0.0, duration, dt_eval)

    def ode_with_pulse(t, y):
        return telepathy_ode(t, y, params, pulse_func(t))

    sol = solve_ivp(ode_with_pulse, t_span, initial_state, t_eval=t_eval, method='RK45')
    return sol.t, sol.y


# ----------------------------------------------------------------------
# Real experiment: CPU load shaping with lag patterns
# ----------------------------------------------------------------------
class StructuredLoadExperiment:
    """
    Runs a real-time experiment applying either structured (sinusoidal) load
    or random load to the CPU, while monitoring temperature.
    """
    def __init__(self, params: TelepathyParams, duration_sec: float = 60.0,
                 pattern_type: str = 'structured', duty_cycle: float = 0.5,
                 base_load: float = 0.3):
        self.params = params
        self.duration = duration_sec
        self.pattern_type = pattern_type   # 'structured' or 'random'
        self.duty_cycle = duty_cycle       # fraction of time busy
        self.base_load = base_load         # background load fraction (0-1)
        self.running = False
        self.temp_history = []
        self.time_history = []

    def _busy_loop(self, duration_sec: float):
        """Busy loop consuming CPU for given duration."""
        start = time.perf_counter()
        while time.perf_counter() - start < duration_sec:
            _ = sum(i*i for i in range(1000))  # dummy computation

    def _run_pattern_structured(self, period: float = 2.0):
        """
        Structured load: sinusoidal CPU usage with lag features.
        We simulate by sleeping variable times.
        """
        start_time = time.time()
        while self.running and (time.time() - start_time) < self.duration:
            t = time.time() - start_time
            # Lag function from theory: L(t) determines "intensity" of structured heat
            L = lag_function(t, self.params)
            # Map L (0..1) to active fraction
            active_frac = self.base_load + (1 - self.base_load) * L
            active_frac = np.clip(active_frac, 0.0, 1.0)
            cycle_time = 0.1  # 100ms granularity
            busy_time = cycle_time * active_frac
            idle_time = cycle_time - busy_time
            if busy_time > 0:
                #self._busy_loop(busy_time)
                self._heavy_load(busy_time)
            if idle_time > 0:
                time.sleep(idle_time)
            # Record temperature
            temp = get_cpu_temperature()
            self.temp_history.append(temp)
            self.time_history.append(time.time() - start_time)

    def _run_pattern_random(self):
        """Random load: uniform random active fraction."""
        start_time = time.time()
        while self.running and (time.time() - start_time) < self.duration:
            active_frac = np.random.uniform(self.base_load, 1.0)
            cycle_time = 0.1
            busy_time = cycle_time * active_frac
            idle_time = cycle_time - busy_time
            if busy_time > 0:
                self._busy_loop(busy_time)
            if idle_time > 0:
                time.sleep(idle_time)
            temp = get_cpu_temperature()
            self.temp_history.append(temp)
            self.time_history.append(time.time() - start_time)

    def run(self):
        print(f"Starting {self.pattern_type} load experiment for {self.duration}s...")
        self.running = True
        if self.pattern_type == 'structured':
            self._run_pattern_structured()
        else:
            self._run_pattern_random()
        self.running = False
        print("Experiment finished.")

    def plot_results(self):
        if not self.time_history:
            print("No data to plot.")
            return
        plt.figure(figsize=(12,5))
        plt.subplot(1,2,1)
        plt.plot(self.time_history, self.temp_history, 'r-', alpha=0.7)
        plt.xlabel('Time (s)')
        plt.ylabel('CPU Temperature (°C)')
        plt.title(f'{self.pattern_type.capitalize()} Load Pattern')
        plt.grid(True)
        plt.subplot(1,2,2)
        # Compute lag function over time for reference
        t_vals = np.linspace(0, self.duration, 500)
        L_vals = [lag_function(t, self.params) for t in t_vals]
        plt.plot(t_vals, L_vals, 'b--', label='Lag L(t)')
        plt.xlabel('Time (s)')
        plt.ylabel('Lag feature L(t)')
        plt.title('Theoretical Structured Lag')
        plt.legend()
        plt.tight_layout()
        plt.show()

    def _run_pattern_structured_continuous(self):
        import threading
        import time
        stop_event = threading.Event()
        
        def worker(intensity_getter):
            # intensity_getter is a function returning current L(t)
            while not stop_event.is_set():
                intensity = intensity_getter()
                if intensity > 0:
                    # Busy loop for (intensity * 0.02) seconds
                    busy_until = time.time() + intensity * 0.02
                    while time.time() < busy_until:
                        _ = sum(i*i for i in range(1000))
                # Small sleep to prevent 100% busy when intensity is low
                time.sleep(0.005)
        
        def get_intensity():
            t = time.time() - start_time
            return lag_function(t, self.params)
        
        start_time = time.time()
        threads = [threading.Thread(target=worker, args=(get_intensity,)) 
                   for _ in range(psutil.cpu_count())]
        for t in threads:
            t.start()
        
        # Run for duration
        time.sleep(self.duration)
        stop_event.set()
        for t in threads:
            t.join()

# ----------------------------------------------------------------------
# Main CLI
# ----------------------------------------------------------------------
def main():
    parser = argparse.ArgumentParser(description='Landour Telepathy Simulator & CPU Experiment')
    parser.add_argument('--mode', choices=['simulate', 'experiment'], default='simulate',
                        help='Run ODE simulation or real CPU load experiment')
    parser.add_argument('--duration', type=float, default=35.0,
                        help='Duration in seconds')
    parser.add_argument('--pattern', choices=['structured', 'random'], default='structured',
                        help='For experiment: type of CPU load pattern')
    parser.add_argument('--plot', action='store_true', default=True,
                        help='Show plots')
    args = parser.parse_args()

    if args.mode == 'simulate':
        print("Running Landour Telepathy ODE Simulation...")
        t, y = run_simulation(duration=args.duration)
        MA, MB, Q, tau = y
        if args.plot:
            plt.figure(figsize=(12,8))
            plt.subplot(2,2,1)
            plt.plot(t, MA, label='Memory A (Sender)', color='blue')
            plt.plot(t, MB, label='Memory B (Receiver)', color='orange')
            plt.xlabel('Time (s)'); plt.ylabel('Memory')
            plt.legend(); plt.grid(True)
            plt.subplot(2,2,2)
            plt.plot(t, Q, label='Water Coherence Q', color='green')
            plt.xlabel('Time (s)'); plt.ylabel('Coherence')
            plt.grid(True)
            plt.subplot(2,2,3)
            plt.plot(t, tau, label='Toxicity τ', color='red')
            plt.xlabel('Time (s)'); plt.ylabel('Toxicity')
            plt.grid(True)
            plt.subplot(2,2,4)
            L_vals = [lag_function(ti, TelepathyParams()) for ti in t]
            plt.plot(t, L_vals, label='Lag L(t)', linestyle='--', color='purple')
            plt.xlabel('Time (s)'); plt.ylabel('Lag Feature')
            plt.grid(True)
            plt.suptitle('Landour Memory Inducement - Telepathic Transfer Simulation')
            plt.tight_layout()
            plt.show()
        else:
            print(f"Final: MA={MA[-1]:.3f}, MB={MB[-1]:.3f}, Q={Q[-1]:.3f}, tau={tau[-1]:.3f}")

    elif args.mode == 'experiment':
        if not PSUTIL_AVAILABLE:
            print("psutil not installed. Cannot read CPU temperature. Install with: pip install psutil")
            sys.exit(1)
        temp = get_cpu_temperature()
        if temp is None:
            print("Could not read CPU temperature. Ensure your system supports it.")
            print("On Linux: install lm-sensors and run 'sensors'")
            sys.exit(1)
        print(f"Current CPU temperature: {temp:.1f}°C")
        params = TelepathyParams()
        exp = StructuredLoadExperiment(params, duration_sec=args.duration,
                                       pattern_type=args.pattern)
        exp.run()
        if args.plot:
            exp.plot_results()
        # Also run ODE simulation for comparison
        print("\nRunning companion ODE simulation for the same parameters...")
        t, y = run_simulation(duration=args.duration)
        MA, MB, Q, tau = y
        print(f"Simulation final: MA={MA[-1]:.3f}, MB={MB[-1]:.3f}, Q={Q[-1]:.3f}, tau={tau[-1]:.3f}")

if __name__ == '__main__':
    main()
