//! Stable OpenSim/Second Life update, rendering, and viewport orchestration.

#![allow(clippy::missing_errors_doc)]

use std::{
    sync::{
        Arc, Mutex,
        atomic::{AtomicBool, AtomicU64, Ordering},
        mpsc::{self, Receiver, RecvTimeoutError, SyncSender, TryRecvError},
    },
    thread,
    time::{Duration, Instant},
};

pub use metacrate_grid_world::{
    DirtyWorldItem, LibreMetaverseWorldEvents, RegionDescriptor, WorldEvent, WorldEventMetrics,
    WorldEventReceiver, WorldSnapshot, WorldState,
};

/// Slight scheduling margin keeps measured delivery at or above 10 FPS.
pub const DEFAULT_VIEWPORT_FRAME_INTERVAL: Duration = Duration::from_millis(90);

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct LoopConfig {
    pub update_interval: Duration,
    pub render_interval: Duration,
    pub max_update_catch_up: u32,
    pub max_events_per_iteration: usize,
    pub event_queue_capacity: usize,
}

impl Default for LoopConfig {
    fn default() -> Self {
        Self {
            update_interval: Duration::from_nanos(16_666_667),
            render_interval: Duration::from_millis(100),
            max_update_catch_up: 4,
            max_events_per_iteration: 1_024,
            event_queue_capacity: 4_096,
        }
    }
}

impl LoopConfig {
    #[must_use]
    pub const fn valid(self) -> bool {
        !self.update_interval.is_zero()
            && !self.render_interval.is_zero()
            && self.max_update_catch_up != 0
            && self.max_events_per_iteration != 0
            && self.event_queue_capacity != 0
    }
}

#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct LoopStep {
    pub updates: u32,
    pub render: bool,
    pub skipped_updates: u64,
    pub skipped_renders: u64,
}

#[derive(Clone, Copy, Debug)]
pub struct LoopSchedule {
    config: LoopConfig,
    next_update: Instant,
    next_render: Instant,
}

impl LoopSchedule {
    #[must_use]
    pub fn new(config: LoopConfig, started: Instant) -> Option<Self> {
        config.valid().then_some(Self {
            config,
            next_update: started + config.update_interval,
            next_render: started + config.render_interval,
        })
    }

    #[must_use]
    pub fn next_deadline(self) -> Instant {
        self.next_update.min(self.next_render)
    }

    pub fn advance(&mut self, now: Instant) -> LoopStep {
        let update_due = intervals_due(now, self.next_update, self.config.update_interval);
        let updates = u32::try_from(update_due.min(u64::from(self.config.max_update_catch_up)))
            .unwrap_or(self.config.max_update_catch_up);
        let skipped_updates = update_due.saturating_sub(u64::from(updates));
        if update_due != 0 {
            self.next_update = next_after(now, self.next_update, self.config.update_interval);
        }
        let render_due = intervals_due(now, self.next_render, self.config.render_interval);
        if render_due != 0 {
            self.next_render = next_after(now, self.next_render, self.config.render_interval);
        }
        LoopStep {
            updates,
            render: render_due != 0,
            skipped_updates,
            skipped_renders: render_due.saturating_sub(1),
        }
    }
}

fn intervals_due(now: Instant, deadline: Instant, interval: Duration) -> u64 {
    if now < deadline {
        return 0;
    }
    let intervals = now.duration_since(deadline).as_nanos() / interval.as_nanos() + 1;
    intervals.try_into().unwrap_or(u64::MAX)
}

fn next_after(now: Instant, deadline: Instant, interval: Duration) -> Instant {
    let due = intervals_due(now, deadline, interval);
    let Ok(due) = u32::try_from(due) else {
        return now + interval;
    };
    deadline
        .checked_add(interval.saturating_mul(due))
        .unwrap_or(now + interval)
}

#[derive(Debug)]
struct LatestValue<T> {
    sequence: u64,
    value: Option<Arc<T>>,
}

#[derive(Debug)]
pub struct Latest<T> {
    inner: Mutex<LatestValue<T>>,
}

impl<T> Default for Latest<T> {
    fn default() -> Self {
        Self {
            inner: Mutex::new(LatestValue {
                sequence: 0,
                value: None,
            }),
        }
    }
}

#[derive(Clone, Debug)]
pub struct Published<T> {
    pub sequence: u64,
    pub value: Arc<T>,
}

#[derive(Clone, Debug)]
pub struct ViewportFrame {
    pub sequence: u64,
    pub scene_sequence: u64,
    pub observed_unix_millis: u64,
    pub camera: metacrate_rendering_wgpu::Camera,
    pub width: u32,
    pub height: u32,
    pub rgba: Arc<[u8]>,
}

#[derive(Debug, Default)]
pub struct StableViewport {
    latest: Latest<ViewportFrame>,
}

impl StableViewport {
    pub fn publish(&self, frame: ViewportFrame) -> Result<u64, ViewportFrame> {
        let expected = usize::try_from(frame.width)
            .ok()
            .and_then(|width| {
                usize::try_from(frame.height)
                    .ok()
                    .and_then(|height| width.checked_mul(height))
            })
            .and_then(|pixels| pixels.checked_mul(4));
        if frame.sequence == 0 || expected != Some(frame.rgba.len()) {
            return Err(frame);
        }
        Ok(self.latest.publish(frame))
    }

    #[must_use]
    pub fn latest(&self) -> Option<Published<ViewportFrame>> {
        self.latest.load()
    }
}

/// Owns the renderer and publishes immutable completed frames. Readers never
/// observe the render target while Bevy is writing the next frame.
pub struct ViewportRuntime {
    renderer: metacrate_rendering_wgpu::Renderer,
    viewport: StableViewport,
    next_sequence: AtomicU64,
    loaded_scene: Mutex<Option<u64>>,
}

pub struct ViewportRender {
    pub frame: Published<ViewportFrame>,
    pub timings: metacrate_rendering_wgpu::RenderTimings,
}

#[derive(Clone, Debug)]
pub struct ViewportScene {
    pub sequence: u64,
    pub camera: metacrate_rendering_wgpu::Camera,
    pub renderables: Arc<[metacrate_rendering_wgpu::Renderable]>,
    pub textures: Arc<[metacrate_rendering_wgpu::Texture]>,
    pub background_srgb: [u8; 4],
    pub limits: metacrate_rendering_wgpu::RenderLimits,
}

#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct ViewportStats {
    pub completed_frames: u64,
    pub missed_frame_deadlines: u64,
    pub render_errors: u64,
    pub last_render: Duration,
}

/// Fixed-rate viewport producer. Scene submission is nonblocking and always
/// replaces stale pending input with the newest complete scene.
pub struct ViewportLoop {
    runtime: Arc<ViewportRuntime>,
    input: Arc<Latest<ViewportScene>>,
    stopped: Arc<AtomicBool>,
    thread: Mutex<Option<thread::JoinHandle<()>>>,
    completed_frames: Arc<AtomicU64>,
    missed_frame_deadlines: Arc<AtomicU64>,
    render_errors: Arc<AtomicU64>,
    last_render_nanos: Arc<AtomicU64>,
}

impl ViewportLoop {
    pub fn start(frame_interval: Duration) -> Result<Self, metacrate_rendering_wgpu::RenderError> {
        if frame_interval.is_zero() {
            return Err(metacrate_rendering_wgpu::RenderError::InvalidScene);
        }
        let runtime = Arc::new(ViewportRuntime::new_blocking()?);
        let input: Arc<Latest<ViewportScene>> = Arc::new(Latest::default());
        let stopped = Arc::new(AtomicBool::new(false));
        let completed_frames = Arc::new(AtomicU64::new(0));
        let missed_frame_deadlines = Arc::new(AtomicU64::new(0));
        let render_errors = Arc::new(AtomicU64::new(0));
        let last_render_nanos = Arc::new(AtomicU64::new(0));
        let worker_runtime = Arc::clone(&runtime);
        let worker_input = Arc::clone(&input);
        let worker_stopped = Arc::clone(&stopped);
        let worker_completed = Arc::clone(&completed_frames);
        let worker_missed = Arc::clone(&missed_frame_deadlines);
        let worker_errors = Arc::clone(&render_errors);
        let worker_last_render = Arc::clone(&last_render_nanos);
        let thread = thread::Builder::new()
            .name("metacrate-viewport-loop".into())
            .spawn(move || {
                let mut next_frame = Instant::now();
                while !worker_stopped.load(Ordering::Acquire) {
                    let now = Instant::now();
                    if now < next_frame {
                        thread::park_timeout(next_frame - now);
                        continue;
                    }
                    let due = intervals_due(now, next_frame, frame_interval);
                    worker_missed.fetch_add(due.saturating_sub(1), Ordering::Relaxed);
                    next_frame = next_after(now, next_frame, frame_interval);
                    let Some(scene) = worker_input.load() else {
                        continue;
                    };
                    let started = Instant::now();
                    let result = worker_runtime.render_scene(
                        scene.value.sequence,
                        scene.value.camera,
                        || Arc::clone(&scene.value.renderables),
                        Arc::clone(&scene.value.textures),
                        scene.value.background_srgb,
                        scene.value.limits,
                    );
                    worker_last_render.store(
                        started.elapsed().as_nanos().try_into().unwrap_or(u64::MAX),
                        Ordering::Relaxed,
                    );
                    if result.is_ok() {
                        worker_completed.fetch_add(1, Ordering::Relaxed);
                    } else {
                        worker_errors.fetch_add(1, Ordering::Relaxed);
                    }
                }
            })
            .map_err(|error| metacrate_rendering_wgpu::RenderError::Device(error.to_string()))?;
        Ok(Self {
            runtime,
            input,
            stopped,
            thread: Mutex::new(Some(thread)),
            completed_frames,
            missed_frame_deadlines,
            render_errors,
            last_render_nanos,
        })
    }

    pub fn submit(&self, scene: ViewportScene) -> u64 {
        let sequence = self.input.publish(scene);
        if let Some(thread) = lock(&self.thread).as_ref() {
            thread.thread().unpark();
        }
        sequence
    }

    #[must_use]
    pub fn latest(&self) -> Option<Published<ViewportFrame>> {
        self.runtime.latest()
    }

    #[must_use]
    pub fn stats(&self) -> ViewportStats {
        ViewportStats {
            completed_frames: self.completed_frames.load(Ordering::Relaxed),
            missed_frame_deadlines: self.missed_frame_deadlines.load(Ordering::Relaxed),
            render_errors: self.render_errors.load(Ordering::Relaxed),
            last_render: Duration::from_nanos(self.last_render_nanos.load(Ordering::Relaxed)),
        }
    }

    pub fn shutdown(&self) {
        self.stopped.store(true, Ordering::Release);
        if let Some(thread) = lock(&self.thread).take() {
            thread.thread().unpark();
            let _ = thread.join();
        }
    }
}

impl Drop for ViewportLoop {
    fn drop(&mut self) {
        self.shutdown();
    }
}

impl ViewportRuntime {
    pub fn new_blocking() -> Result<Self, metacrate_rendering_wgpu::RenderError> {
        Ok(Self {
            renderer: metacrate_rendering_wgpu::Renderer::new_blocking()?,
            viewport: StableViewport::default(),
            next_sequence: AtomicU64::new(0),
            loaded_scene: Mutex::new(None),
        })
    }

    pub fn render_scene<F>(
        &self,
        scene_sequence: u64,
        camera: metacrate_rendering_wgpu::Camera,
        renderables: F,
        textures: Arc<[metacrate_rendering_wgpu::Texture]>,
        background_srgb: [u8; 4],
        limits: metacrate_rendering_wgpu::RenderLimits,
    ) -> Result<ViewportRender, metacrate_rendering_wgpu::RenderError>
    where
        F: FnOnce() -> Arc<[metacrate_rendering_wgpu::Renderable]>,
    {
        let mut loaded_scene = lock(&self.loaded_scene);
        let mut timings = if *loaded_scene == Some(scene_sequence) {
            metacrate_rendering_wgpu::RenderTimings::default()
        } else {
            let renderables = renderables();
            let timings = self
                .renderer
                .load_scene_shared(camera, renderables, textures, limits)?;
            *loaded_scene = Some(scene_sequence);
            timings
        };
        let rendered = self.renderer.render_view(camera, background_srgb, limits)?;
        timings.clear_previous_frame += rendered.timings.clear_previous_frame;
        timings.scene_setup += rendered.timings.scene_setup;
        timings.capture += rendered.timings.capture;
        let sequence = self.next_sequence.fetch_add(1, Ordering::AcqRel) + 1;
        self.viewport
            .publish(ViewportFrame {
                sequence,
                scene_sequence,
                observed_unix_millis: unix_millis_now(),
                camera,
                width: limits.width,
                height: limits.height,
                rgba: rendered.rgba.into(),
            })
            .map_err(|_| metacrate_rendering_wgpu::RenderError::InvalidScene)?;
        Ok(ViewportRender {
            frame: self
                .viewport
                .latest()
                .ok_or(metacrate_rendering_wgpu::RenderError::Readback)?,
            timings,
        })
    }

    #[must_use]
    pub fn latest(&self) -> Option<Published<ViewportFrame>> {
        self.viewport.latest()
    }
}

impl<T> Latest<T> {
    pub fn publish(&self, value: T) -> u64 {
        self.publish_arc(Arc::new(value))
    }

    pub fn publish_arc(&self, value: Arc<T>) -> u64 {
        let mut latest = lock(&self.inner);
        latest.sequence = latest.sequence.saturating_add(1);
        latest.value = Some(value);
        latest.sequence
    }

    #[must_use]
    pub fn load(&self) -> Option<Published<T>> {
        let latest = lock(&self.inner);
        Some(Published {
            sequence: latest.sequence,
            value: Arc::clone(latest.value.as_ref()?),
        })
    }
}

#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub enum TimingPhase {
    Events,
    Update,
    Render,
    Publish,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct TimingSignal {
    pub phase: TimingPhase,
    pub duration: Duration,
    pub sequence: u64,
}

#[derive(Clone, Debug)]
pub struct TimingEmitter {
    sender: SyncSender<TimingSignal>,
    dropped: Arc<AtomicU64>,
}

#[derive(Debug)]
pub struct TimingReceiver {
    receiver: Receiver<TimingSignal>,
    dropped: Arc<AtomicU64>,
}

#[must_use]
pub fn timing_channel(capacity: usize) -> Option<(TimingEmitter, TimingReceiver)> {
    if capacity == 0 {
        return None;
    }
    let (sender, receiver) = mpsc::sync_channel(capacity);
    let dropped = Arc::new(AtomicU64::new(0));
    Some((
        TimingEmitter {
            sender,
            dropped: Arc::clone(&dropped),
        },
        TimingReceiver { receiver, dropped },
    ))
}

impl TimingEmitter {
    pub fn emit(&self, signal: TimingSignal) {
        if self.sender.try_send(signal).is_err() {
            self.dropped.fetch_add(1, Ordering::Relaxed);
        }
    }
}

impl TimingReceiver {
    pub fn recv_timeout(&self, timeout: Duration) -> Result<TimingSignal, RecvTimeoutError> {
        self.receiver.recv_timeout(timeout)
    }

    pub fn try_recv(&self) -> Result<TimingSignal, TryRecvError> {
        self.receiver.try_recv()
    }

    #[must_use]
    pub fn dropped(&self) -> u64 {
        self.dropped.load(Ordering::Relaxed)
    }
}

pub trait LoopDriver: Send + 'static {
    fn world_changed(&mut self, world: &WorldState, dirty: &[DirtyWorldItem]);
    fn update(&mut self, world: &WorldState, step: Duration);
    fn render(&mut self, world: &WorldState);
}

impl LoopDriver for () {
    fn world_changed(&mut self, _world: &WorldState, _dirty: &[DirtyWorldItem]) {}
    fn update(&mut self, _world: &WorldState, _step: Duration) {}
    fn render(&mut self, _world: &WorldState) {}
}

pub struct LoopHandle {
    stopped: Arc<AtomicBool>,
    thread: Option<thread::JoinHandle<()>>,
    world: Arc<Latest<WorldSnapshot>>,
    world_events: WorldEventMetrics,
}

#[derive(Clone)]
pub struct WorldReader {
    world: Arc<Latest<WorldSnapshot>>,
    world_events: WorldEventMetrics,
}

impl WorldReader {
    #[must_use]
    pub fn latest(&self) -> Option<Published<WorldSnapshot>> {
        self.world.load()
    }

    #[must_use]
    pub fn dropped_events(&self) -> u64 {
        self.world_events.dropped()
    }
}

impl LoopHandle {
    #[must_use]
    pub fn latest_world(&self) -> Option<Published<WorldSnapshot>> {
        self.world.load()
    }

    #[must_use]
    pub fn world_reader(&self) -> WorldReader {
        WorldReader {
            world: Arc::clone(&self.world),
            world_events: self.world_events.clone(),
        }
    }

    #[must_use]
    pub fn dropped_world_events(&self) -> u64 {
        self.world_events.dropped()
    }

    pub fn shutdown(mut self) -> thread::Result<()> {
        self.stop();
        self.thread.take().map_or(Ok(()), thread::JoinHandle::join)
    }

    fn stop(&self) {
        self.stopped.store(true, Ordering::Release);
        if let Some(thread) = &self.thread {
            thread.thread().unpark();
        }
    }
}

impl Drop for LoopHandle {
    fn drop(&mut self) {
        self.stop();
    }
}

#[must_use]
pub fn start<D: LoopDriver>(
    config: LoopConfig,
    events: WorldEventReceiver,
    mut driver: D,
    timings: Option<TimingEmitter>,
) -> Option<LoopHandle> {
    if !config.valid() {
        return None;
    }
    let stopped = Arc::new(AtomicBool::new(false));
    let world_events = events.metrics();
    let world = Arc::new(Latest::default());
    let published_world = Arc::clone(&world);
    let thread_stopped = Arc::clone(&stopped);
    let thread = thread::Builder::new()
        .name("metacrate-game-loop".into())
        .spawn(move || {
            let Some(mut schedule) = LoopSchedule::new(config, Instant::now()) else {
                return;
            };
            let mut sequence = 0_u64;
            let mut world = WorldState::default();
            while !thread_stopped.load(Ordering::Acquire) {
                let started = Instant::now();
                let mut event_count = 0;
                while event_count < config.max_events_per_iteration {
                    match events.try_recv() {
                        Ok(event) => {
                            world.apply(event);
                            event_count += 1;
                        }
                        Err(TryRecvError::Empty) => break,
                        Err(TryRecvError::Disconnected) => return,
                    }
                }
                if event_count != 0 {
                    let dirty = world.take_dirty();
                    driver.world_changed(&world, &dirty);
                    published_world.publish(world.snapshot());
                    emit(
                        timings.as_ref(),
                        TimingPhase::Events,
                        started.elapsed(),
                        sequence,
                    );
                }
                let step = schedule.advance(Instant::now());
                for _ in 0..step.updates {
                    let started = Instant::now();
                    driver.update(&world, config.update_interval);
                    sequence = sequence.saturating_add(1);
                    emit(
                        timings.as_ref(),
                        TimingPhase::Update,
                        started.elapsed(),
                        sequence,
                    );
                }
                if step.render {
                    let started = Instant::now();
                    driver.render(&world);
                    emit(
                        timings.as_ref(),
                        TimingPhase::Render,
                        started.elapsed(),
                        sequence,
                    );
                }
                let wait = schedule
                    .next_deadline()
                    .saturating_duration_since(Instant::now());
                if wait.is_zero() {
                    thread::yield_now();
                } else {
                    thread::park_timeout(wait);
                }
            }
        })
        .ok()?;
    Some(LoopHandle {
        stopped,
        thread: Some(thread),
        world,
        world_events,
    })
}

fn emit(emitter: Option<&TimingEmitter>, phase: TimingPhase, duration: Duration, sequence: u64) {
    if let Some(emitter) = emitter {
        emitter.emit(TimingSignal {
            phase,
            duration,
            sequence,
        });
    }
}

fn lock<T>(value: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {
    value
        .lock()
        .unwrap_or_else(std::sync::PoisonError::into_inner)
}

fn unix_millis_now() -> u64 {
    std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .map_or(0, |duration| {
            duration.as_millis().try_into().unwrap_or(u64::MAX)
        })
}

#[cfg(test)]
mod tests {
    use super::*;

    struct Driver(Arc<AtomicU64>);

    impl LoopDriver for Driver {
        fn world_changed(&mut self, _world: &WorldState, dirty: &[DirtyWorldItem]) {
            self.0.fetch_add(
                u64::try_from(dirty.len()).unwrap_or(u64::MAX),
                Ordering::Relaxed,
            );
        }
        fn update(&mut self, _world: &WorldState, _step: Duration) {
            self.0.fetch_add(1, Ordering::Relaxed);
        }
        fn render(&mut self, _world: &WorldState) {}
    }

    #[test]
    fn schedule_bounds_catch_up_latest_publishes_and_runner_emits_timings() {
        let config = LoopConfig {
            update_interval: Duration::from_millis(10),
            render_interval: Duration::from_millis(25),
            max_update_catch_up: 2,
            ..LoopConfig::default()
        };
        let started = Instant::now();
        let mut schedule = LoopSchedule::new(config, started).unwrap();
        let step = schedule.advance(started + Duration::from_millis(56));
        assert_eq!(step.updates, 2);
        assert_eq!(step.skipped_updates, 3);
        assert!(step.render);
        assert_eq!(step.skipped_renders, 1);

        let latest = Latest::default();
        assert_eq!(latest.publish("old"), 1);
        assert_eq!(latest.publish("new"), 2);
        let published = latest.load().unwrap();
        assert_eq!(published.sequence, 2);
        assert_eq!(*published.value, "new");

        let viewport = StableViewport::default();
        let camera = metacrate_rendering_wgpu::Camera::look_at(
            [0.0; 3],
            [1.0, 0.0, 0.0],
            [0.0, 0.0, 1.0],
            60.0,
        )
        .unwrap();
        assert!(
            viewport
                .publish(ViewportFrame {
                    sequence: 1,
                    scene_sequence: 1,
                    observed_unix_millis: 1,
                    camera,
                    width: 2,
                    height: 1,
                    rgba: vec![0; 8].into(),
                })
                .is_ok()
        );
        assert_eq!(viewport.latest().unwrap().value.sequence, 1);

        let count = Arc::new(AtomicU64::new(0));
        let (timings, receiver) = timing_channel(32).unwrap();
        let (events, event_receiver) = metacrate_grid_world::world_event_channel(32).unwrap();
        events.send(WorldEvent::RegionChanged(None));
        let handle = start(
            config,
            event_receiver,
            Driver(Arc::clone(&count)),
            Some(timings),
        )
        .unwrap();
        thread::sleep(Duration::from_millis(40));
        handle.shutdown().unwrap();
        assert!(count.load(Ordering::Relaxed) >= 2);
        assert!(receiver.try_recv().is_ok());
    }
}
