//! Per-output presentation: swapchain + render on the main thread, KMS commit on
//! a dedicated thread. Replaces smithay's `DrmCompositor` (which fused render and
//! commit in one `&mut self`).
//!
//! `Presenter` (main thread) owns the swapchain, GLES-facing pieces and damage
//! tracker; `render` composes the scene onto a free swapchain buffer and
//! `prepare_flip` does the swapchain bookkeeping. `Committer` (KMS thread) owns
//! the page-flip and the in-flight buffers across their vblanks. Every
//! `Committer` field is `Send`, so it crosses to the KMS thread while the `!Send`
//! renderer state stays on the main thread. `Presenter::new` returns both halves;
//! they share the `DrmSurface` through an `Arc` (it is `Sync`), but only the
//! `Committer` ever mutates it (flip / modeset / reset).
//!
//! Module layout: `committer` is the KMS-thread half, `cursor` the hardware
//! cursor plane, `render` the compose/flip half of the `Presenter`.

mod committer;
mod cursor;
mod render;

pub use committer::Committer;
pub use cursor::{CursorFb, CursorUpdate};
use cursor::CursorPlane;


use std::os::fd::OwnedFd;
use std::sync::Arc;

use smithay::backend::allocator::dmabuf::{AsDmabuf, Dmabuf};
use smithay::backend::allocator::gbm::{GbmAllocator, GbmBuffer, GbmBufferFlags};
use smithay::backend::allocator::{Fourcc, Modifier, Slot, Swapchain};
use smithay::backend::drm::exporter::gbm::GbmFramebufferExporter;
use smithay::backend::drm::exporter::ExportBuffer;
use smithay::backend::drm::exporter::ExportFramebuffer;
use smithay::backend::drm::gbm::GbmFramebuffer;
use smithay::backend::drm::{
    DrmDeviceFd, DrmError, DrmSurface, PlaneClaim, PlaneConfig, PlaneState,
};
use smithay::backend::renderer::damage::OutputDamageTracker;
use smithay::backend::renderer::element::{
    Element, Id, Kind, RenderElement, RenderElementStates, UnderlyingStorage,
};
use smithay::backend::renderer::gles::GlesRenderer;
use smithay::backend::renderer::{Bind, Color32F};
use smithay::reexports::drm::control::{framebuffer, plane};
use smithay::utils::{Buffer as BufferCoord, Physical, Point, Rectangle, Size, Transform};
use smithay::wayland::shm;

use crate::drawing::ForgeElement;

type Allocator0 = GbmAllocator<DrmDeviceFd>;
type Exporter = GbmFramebufferExporter<DrmDeviceFd>;

/// Exported state for a swapchain slot, cached in the slot's userdata so it is
/// created once per buffer and reused on every render/flip.
struct SlotExport {
    fb: framebuffer::Handle,
    _gbm_fb: GbmFramebuffer,
    dmabuf: Dmabuf,
}
/// What a single commit should put on which planes. Either half may be absent:
/// a pure pointer move carries only `cursor`, and a scene change with a still
/// pointer carries only `primary`.
pub struct FlipPlan {
    pub primary: Option<(framebuffer::Handle, Slot<GbmBuffer>)>,
    pub cursor: Option<CursorUpdate>,
}

/// Result of a render pass: the buffer to flip and whether it changed.
pub struct Rendered {
    slot: Slot<GbmBuffer>,
    fb: framebuffer::Handle,
    damaged: bool,
    /// Exported fence for this render, fed to the frame pacer to time the next
    /// frame. `None` when the renderer can't export a sync fd.
    pub gpu_fence: Option<OwnedFd>,
    /// Per-element render states, used to drive client frame callbacks
    /// (`Windows::mark_presented`).
    pub states: RenderElementStates,
    /// Cursor-plane work for this frame, if the pointer changed.
    cursor: Option<CursorUpdate>,
}
pub struct Presenter {
    /// Shared with the `Committer` on the KMS thread. The `Presenter` only reads
    /// it (crtc, plane info, device fd, commit_pending); the `Committer` mutates.
    surface: Arc<DrmSurface>,
    swapchain: Swapchain<Allocator0>,
    exporter: Exporter,
    damage: OutputDamageTracker,
    /// Size, output scale and transform `damage` was built with. The tracker
    /// captures all three at construction and maps element geometry through
    /// them, so a scale or orientation change has to rebuild it or every
    /// damage rect lands in the wrong place — and, for the transform, the
    /// scene never rotates onto the scanout at all.
    damage_size: Size<i32, Physical>,
    damage_scale: f64,
    damage_transform: Transform,
    use_opaque: bool,
    /// Dmabuf of the last buffer composed this frame, exposed for the DMA
    /// capture paths (screencopy / export-dmabuf / image-copy) which used to
    /// read smithay's RenderFrameResult.
    last_dmabuf: Option<Dmabuf>,
    /// Mirrors the `Committer`'s modeset state for the main-thread skip decision:
    /// a frame must be flipped (not skipped) when damaged or when a modeset is
    /// still pending. Set on construction and on VT-switch reset; cleared when a
    /// flip is dispatched. The `Committer` keeps the authoritative copy for the
    /// commit-vs-page_flip choice; the next damaged frame resyncs the two.
    needs_modeset: bool,
    /// When true, the next render passes age=0 to the damage tracker so it does
    /// a full repaint, without touching the swapchain slots.  Set instead of
    /// calling `swapchain.reset_buffer_ages()`, which would leave count=1 slots
    /// in the Committer with open fds that never close.
    force_full_repaint: bool,
    /// CRTC mode size in real pixels — the blit destination rect for `blit_flip`.
    mode_size: Size<i32, Physical>,
    /// Consecutive `render`/`blit_flip` calls that found no free swapchain
    /// slot. Slots only return on the vblank of the frame holding them, so a
    /// long streak means in-flight frames are not completing.
    dry_streak: u32,
    /// `None` when the output has no usable cursor plane, in which case the
    /// pointer stays composited into the primary.
    cursor: Option<CursorPlane>,
    /// Cleared while mirrors are attached: they blit this output's composed
    /// buffer, so a pointer on the plane would be missing from every mirror.
    cursor_enabled: bool,
}
impl Presenter {
    /// Build a presenter for one already-configured `DrmSurface`. `code` +
    /// `modifiers` must be a format the renderer can draw to and the plane can
    /// scan out (same selection smithay's DrmCompositor does internally).
    /// Returns the main-thread half and the KMS-thread `Committer`.
    pub fn new(
        surface: DrmSurface,
        allocator: Allocator0,
        exporter: Exporter,
        code: Fourcc,
        modifiers: Vec<Modifier>,
        size: Size<i32, Physical>,
        scale: f64,
        cursor: Option<(GbmAllocator<DrmDeviceFd>, Exporter, Size<u32, BufferCoord>)>,
    ) -> (Self, Committer) {
        let mode = surface.pending_mode();
        let mode_size = Size::<i32, Physical>::from((mode.size().0 as i32, mode.size().1 as i32));
        let swapchain = Swapchain::new(
            allocator,
            mode.size().0 as u32,
            mode.size().1 as u32,
            code,
            modifiers,
        );
        let use_opaque = !surface
            .plane_info()
            .formats
            .iter()
            .any(|f| f.code == code);
        let damage = OutputDamageTracker::new(size, scale, Transform::Normal);
        let surface = Arc::new(surface);

        // A cursor plane is only usable on the atomic path: the legacy backend
        // rejects every plane that is not the primary.
        let cursor = cursor.filter(|_| !surface.is_legacy()).and_then(
            |(allocator, exporter, size)| {
                let info = surface.planes().cursor.first()?;
                let claim = surface.claim_plane(info.handle)?;
                Some(CursorPlane {
                    handle: info.handle,
                    _claim: claim,
                    allocator,
                    exporter,
                    size: Size::from((size.w as i32, size.h as i32)),
                    last_id: None,
                    last_dst: None,
                })
            },
        );
        let cursor_plane = cursor.as_ref().map(|c| c.handle);

        let committer = Committer::new(surface.clone(), mode_size, cursor_plane);
        let presenter = Self {
            surface,
            swapchain,
            exporter,
            damage,
            damage_size: size,
            damage_scale: scale,
            damage_transform: Transform::Normal,
            use_opaque,
            last_dmabuf: None,
            needs_modeset: true,
            force_full_repaint: true,
            mode_size,
            dry_streak: 0,
            cursor,
            cursor_enabled: true,
        };
        (presenter, committer)
    }
    /// Route the pointer through the cursor plane, or keep it in the composed
    /// scene. Off while mirrors are attached, since they copy this output's
    /// buffer and would otherwise show no pointer at all.
    pub fn set_cursor_plane_enabled(&mut self, enabled: bool) {
        self.cursor_enabled = enabled;
    }

    /// The dmabuf composed in the most recent `render`, for DMA captures.
    pub fn last_dmabuf(&self) -> Option<&Dmabuf> {
        self.last_dmabuf.as_ref()
    }

    pub fn crtc(&self) -> smithay::reexports::drm::control::crtc::Handle {
        self.surface.crtc()
    }

    pub fn surface(&self) -> &DrmSurface {
        &self.surface
    }

    /// Follow a live output-scale change. Rebuilds the damage tracker, which
    /// cannot be retuned in place, and forces one full repaint so the frame
    /// after the switch is not assembled from history recorded at the old
    /// scale.
    pub fn set_scale(&mut self, scale: f64) {
        if (self.damage_scale - scale).abs() < 0.001 {
            return;
        }
        self.damage_scale = scale;
        self.damage = OutputDamageTracker::new(self.damage_size, scale, self.damage_transform);
        self.force_full_repaint = true;
    }

    /// Follow a live orientation change. Like `set_scale`, rebuilds the damage
    /// tracker — the transform it was built with is what rotates the composed
    /// scene onto the scanout, so without this the primary never rotates.
    pub fn set_transform(&mut self, transform: Transform) {
        if self.damage_transform == transform {
            return;
        }
        self.damage_transform = transform;
        self.damage = OutputDamageTracker::new(self.damage_size, self.damage_scale, transform);
        self.force_full_repaint = true;
    }
}
