"""
helix_cdc/scheduler/wave_scheduler.py
=====================================

FibPi3D Wave-Front Scheduler

Maps FibPi3D N-dimensional coordinates to CPU core timing (ripple/engine piston pattern).

Core Insight:
- FibPi3D generates low-discrepancy spatial coordinates on unit sphere
- Each coordinate [x₀, x₁, ..., xₙ] maps to a point in time/space
- CPU cores fire in sequence based on spatial proximity (wave-front propagation)
- Like engine pistons (firing order) or water ripples (wave propagation)

Usage:
    from helix_cdc.scheduler import get_runtime_schedule, engine_firing_order

    # Get schedule for current system
    schedule = get_runtime_schedule()
    print(f"Cores: {schedule['num_cores']}")
    print(f"Firing order: {schedule['firing_order']}")
"""

import os
import math
from typing import List, Dict, Tuple, Optional

from .fibpi_nd import fibpi_nd_points


def get_cpu_count() -> int:
    """Get available CPU cores (respecting affinity/cgroups)."""
    try:
        affinity = len(os.sched_getaffinity(0))
        return affinity
    except Exception:
        return os.cpu_count() or 1


def fibpi_wave_schedule(
    num_cores: int,
    N: int = 4,
    seed: str = "helix_runtime",
    fib_scale: float = 1.0,
    fib_shift_deg: float = 0.0
) -> List[Dict[str, float]]:
    """
    Generate wave-front firing schedule for CPU cores using FibPi3D coordinates.

    Args:
        num_cores: Number of CPU cores to schedule
        N: Dimensionality of FibPi space (default 4D)
        seed: Deterministic seed for FibPi generation
        fib_scale: Wave speed multiplier (higher = faster wave)
        fib_shift_deg: Phase offset in degrees (rotates wave pattern)

    Returns:
        List of dicts with keys:
            - core_id: Core number (0..num_cores-1)
            - coords: FibPi3D coordinates [x₀, x₁, ..., xₙ]
            - fire_time: Relative time to fire (0.0 = first core)
            - phase_deg: Phase angle for this core (0-360°)
    """
    coords = fibpi_nd_points(N, num_cores, seed=seed)

    schedule = []
    for i, coord in enumerate(coords):
        distance = math.sqrt(sum(x*x for x in coord))
        fire_time = distance / fib_scale

        x, y = coord[0], coord[1] if len(coord) > 1 else 0.0
        phase_deg = (math.degrees(math.atan2(y, x)) + fib_shift_deg) % 360.0

        schedule.append({
            "core_id": i,
            "coords": coord,
            "fire_time": fire_time,
            "phase_deg": phase_deg,
            "distance": distance
        })

    schedule.sort(key=lambda s: s["fire_time"])

    min_time = schedule[0]["fire_time"]
    max_time = schedule[-1]["fire_time"]
    time_range = max_time - min_time if max_time > min_time else 1.0

    for s in schedule:
        s["fire_time_norm"] = (s["fire_time"] - min_time) / time_range

    return schedule


def ripple_pattern(
    num_cores: int,
    origin_core: int = 0,
    N: int = 4,
    seed: str = "helix_ripple"
) -> List[Tuple[int, float]]:
    """
    Generate ripple pattern: wave propagates from origin_core outward.
    Like dropping a stone in water - wave spreads from origin to neighbors.
    """
    coords = fibpi_nd_points(N, num_cores, seed=seed)
    origin = coords[origin_core]

    ripples = []
    for i, coord in enumerate(coords):
        dist = math.sqrt(sum((a - b)**2 for a, b in zip(coord, origin)))
        ripples.append((i, dist))

    ripples.sort(key=lambda r: r[1])
    return ripples


def engine_firing_order(
    num_cores: int,
    N: int = 4,
    seed: str = "helix_engine"
) -> List[int]:
    """
    Generate engine piston firing order using FibPi3D spatial sequencing.
    Like a V8 engine: ensures even distribution of load across time.
    """
    coords = fibpi_nd_points(N, num_cores, seed=seed)

    angles = []
    for i, coord in enumerate(coords):
        x, y = coord[0], coord[1] if len(coord) > 1 else 0.0
        angle = math.atan2(y, x)
        angles.append((i, angle))

    angles.sort(key=lambda a: a[1])
    return [core_id for core_id, _ in angles]


def zero_copy_handoff_schedule(
    num_cores: int,
    chunk_size: int,
    N: int = 4,
    seed: str = "helix_handoff"
) -> List[Dict]:
    """
    Generate zero-copy handoff schedule for CPU cores.
    Each core processes chunk_size items, then hands off to next core
    in FibPi3D spatial sequence (ensures cache locality).
    """
    schedule = fibpi_wave_schedule(num_cores, N=N, seed=seed)

    handoffs = []
    for i, entry in enumerate(schedule):
        next_core = schedule[(i + 1) % num_cores]["core_id"] if i < num_cores - 1 else None
        handoffs.append({
            "core_id": entry["core_id"],
            "chunk_start": i * chunk_size,
            "chunk_end": (i + 1) * chunk_size,
            "handoff_to": next_core,
            "coords": entry["coords"],
            "fire_time": entry["fire_time"]
        })

    return handoffs


def get_runtime_schedule(
    fib_scale: Optional[float] = None,
    fib_shift_deg: Optional[float] = None,
    N: int = 4,
    seed: str = "helix_runtime"
) -> Dict:
    """
    Get FibPi3D wave schedule for current system (auto-detect CPU cores).
    This is the main entry point for runtime integration.
    """
    num_cores = get_cpu_count()

    if fib_scale is None:
        fib_scale = float(os.getenv("HELIX_FIB_SCALE", "1.0"))
    if fib_shift_deg is None:
        fib_shift_deg = float(os.getenv("HELIX_FIB_SHIFT_DEG", "0.0"))

    schedule = fibpi_wave_schedule(
        num_cores=num_cores,
        N=N,
        seed=seed,
        fib_scale=fib_scale,
        fib_shift_deg=fib_shift_deg
    )

    firing = engine_firing_order(num_cores, N=N, seed=seed)
    ripple = ripple_pattern(num_cores, origin_core=0, N=N, seed=seed)

    return {
        "num_cores": num_cores,
        "N": N,
        "fib_scale": fib_scale,
        "fib_shift_deg": fib_shift_deg,
        "schedule": schedule,
        "firing_order": firing,
        "ripple_pattern": ripple,
        "seed": seed
    }
