use std::cell::RefCell;
use std::error::Error;
use std::rc::Rc;

use calloop::ping::Ping;
use smithay::backend::drm::{DrmDeviceFd, DrmNode};
use smithay::backend::renderer::ImportDma;
use smithay::backend::renderer::gles::GlesRenderer;
use smithay::backend::renderer::multigpu::GpuManager;
use smithay::backend::renderer::multigpu::gbm::GbmGlesBackend;
use smithay::backend::winit::WinitGraphicsBackend;
use smithay::backend::{allocator::Format, allocator::dmabuf::Dmabuf};

use crate::compositor::interaction::ResizeCtx;
use crate::compositor::root::Halley;
use crate::render::draw_debug_frame;
use std::cell::Cell;

pub(crate) trait BackendView {
    fn window_size_i32(&self) -> (i32, i32);
    fn request_redraw(&self);
}

pub(crate) trait RenderBackend: BackendView {
    fn draw_frame(
        &self,
        st: &mut Halley,
        resize_preview: Option<ResizeCtx>,
        hover_node: Option<halley_core::field::NodeId>,
        preview_hover_node: Option<halley_core::field::NodeId>,
    ) -> Result<(), Box<dyn Error>>;
}

pub(crate) trait DmabufImportBackend {
    fn dmabuf_formats(&self) -> Vec<Format>;
    fn import_dmabuf(&self, dmabuf: &Dmabuf) -> Result<(), Box<dyn Error>>;
}

pub(crate) struct CaptureDmabufResult {
    pub(crate) captured_at: std::time::SystemTime,
}

#[derive(Clone)]
pub(crate) struct TtyBackendHandle {
    size: Rc<Cell<(i32, i32)>>,
    redraw_ping: Rc<RefCell<Option<Rc<Ping>>>>,
}

impl TtyBackendHandle {
    pub(crate) fn new(width: i32, height: i32) -> Self {
        Self {
            size: Rc::new(Cell::new((width, height))),
            redraw_ping: Rc::new(RefCell::new(None)),
        }
    }

    pub(crate) fn set_size(&self, width: i32, height: i32) {
        self.size.set((width, height));
    }

    pub(crate) fn set_redraw_ping(&self, redraw_ping: Rc<Ping>) {
        *self.redraw_ping.borrow_mut() = Some(redraw_ping);
    }
}

impl BackendView for TtyBackendHandle {
    fn window_size_i32(&self) -> (i32, i32) {
        self.size.get()
    }

    fn request_redraw(&self) {
        if let Some(redraw_ping) = self.redraw_ping.borrow().as_ref() {
            redraw_ping.ping();
        }
    }
}

pub(crate) struct TtyDmabufImportBackend {
    gpu_manager: Rc<RefCell<GpuManager<GbmGlesBackend<GlesRenderer, DrmDeviceFd>>>>,
    primary_render_node: DrmNode,
}

impl TtyDmabufImportBackend {
    pub(crate) fn new(
        gpu_manager: Rc<RefCell<GpuManager<GbmGlesBackend<GlesRenderer, DrmDeviceFd>>>>,
        primary_render_node: DrmNode,
    ) -> Self {
        Self {
            gpu_manager,
            primary_render_node,
        }
    }
}

impl DmabufImportBackend for TtyDmabufImportBackend {
    fn dmabuf_formats(&self) -> Vec<Format> {
        let mut gpu_manager = self.gpu_manager.borrow_mut();
        gpu_manager
            .single_renderer(&self.primary_render_node)
            .map(|renderer| renderer.dmabuf_formats().iter().copied().collect())
            .unwrap_or_default()
    }

    fn import_dmabuf(&self, dmabuf: &Dmabuf) -> Result<(), Box<dyn Error>> {
        let mut gpu_manager = self.gpu_manager.borrow_mut();
        let mut renderer = gpu_manager.single_renderer(&self.primary_render_node)?;
        renderer.import_dmabuf(dmabuf, None)?;
        Ok(())
    }
}

#[derive(Clone)]
pub(crate) struct WinitBackendHandle {
    inner: Rc<RefCell<WinitGraphicsBackend<GlesRenderer>>>,
}

impl WinitBackendHandle {
    pub(crate) fn new(inner: Rc<RefCell<WinitGraphicsBackend<GlesRenderer>>>) -> Self {
        Self { inner }
    }
}

impl DmabufImportBackend for WinitBackendHandle {
    fn dmabuf_formats(&self) -> Vec<Format> {
        self.inner
            .borrow_mut()
            .renderer()
            .dmabuf_formats()
            .iter()
            .copied()
            .collect()
    }

    fn import_dmabuf(&self, dmabuf: &Dmabuf) -> Result<(), Box<dyn Error>> {
        self.inner
            .borrow_mut()
            .renderer()
            .import_dmabuf(dmabuf, None)?;
        Ok(())
    }
}

impl BackendView for WinitBackendHandle {
    fn window_size_i32(&self) -> (i32, i32) {
        let size = self.inner.borrow().window_size();
        (size.w, size.h)
    }

    fn request_redraw(&self) {
        self.inner.borrow().window().request_redraw();
    }
}

impl RenderBackend for WinitBackendHandle {
    fn draw_frame(
        &self,
        st: &mut Halley,
        resize_preview: Option<ResizeCtx>,
        hover_node: Option<halley_core::field::NodeId>,
        preview_hover_node: Option<halley_core::field::NodeId>,
    ) -> Result<(), Box<dyn Error>> {
        let mut backend = self.inner.borrow_mut();
        draw_debug_frame(
            &mut backend,
            st,
            resize_preview,
            hover_node,
            preview_hover_node,
        )
    }
}
