use {
    crate::{
        cmm::{
            cmm_description::{ColorDescription, LinearColorDescription},
            cmm_eotf::Eotf,
        },
        format::Format,
        gfx_api::{
            AcquireSync, AsyncShmGfxTextureCallback, FdSync, GfxApiOpt, GfxBlendBuffer, GfxError,
            GfxFramebuffer, GfxInternalFramebuffer, GfxStagingBuffer, PendingShmTransfer,
            ReleaseSync, ShmMemory,
        },
        gfx_apis::gl::{
            RenderError,
            gl::{
                frame_buffer::GlFrameBuffer,
                sys::{GL_COLOR_BUFFER_BIT, GL_FRAMEBUFFER},
            },
            handle_explicit_sync,
            renderer::context::GlRenderContext,
            run_ops,
            sys::{GL_ONE, GL_ONE_MINUS_SRC_ALPHA},
        },
        rect::Region,
        theme::Color,
    },
    std::{
        cell::Cell,
        fmt::{Debug, Formatter},
        rc::Rc,
    },
};

pub struct Framebuffer {
    pub(in crate::gfx_apis::gl) ctx: Rc<GlRenderContext>,
    pub(in crate::gfx_apis::gl) gl: GlFrameBuffer,
}

impl Debug for Framebuffer {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Framebuffer").finish_non_exhaustive()
    }
}

impl Framebuffer {
    pub fn copy_to_shm(&self, shm: &[Cell<u8>]) -> Result<(), RenderError> {
        let format = self.gl.rb.format;
        let Some(shm_info) = &format.shm_info else {
            return Err(RenderError::UnsupportedShmFormat(format.name));
        };
        let gles = self.ctx.ctx.dpy.gles;
        let _ = self.ctx.ctx.with_current(|| {
            unsafe {
                (gles.glBindFramebuffer)(GL_FRAMEBUFFER, self.gl.fbo);
                (gles.glViewport)(0, 0, self.gl.width, self.gl.height);
                (gles.glReadnPixels)(
                    0,
                    0,
                    self.gl.width,
                    self.gl.height,
                    shm_info.gl_format as _,
                    shm_info.gl_type as _,
                    shm.len() as _,
                    shm.as_ptr() as _,
                );
            }
            Ok(())
        });
        Ok(())
    }

    pub fn render(
        &self,
        acquire_sync: AcquireSync,
        ops: &[GfxApiOpt],
        clear: Option<&Color>,
    ) -> Result<Option<FdSync>, RenderError> {
        let gles = self.ctx.ctx.dpy.gles;
        self.ctx.ctx.with_current(|| {
            handle_explicit_sync(&self.ctx, self.gl.rb._img.as_ref(), &acquire_sync);
            unsafe {
                (gles.glBindFramebuffer)(GL_FRAMEBUFFER, self.gl.fbo);
                (gles.glViewport)(0, 0, self.gl.width, self.gl.height);
                if let Some(c) = clear {
                    let [r, g, b, a] = c.to_array(Eotf::Gamma22);
                    (gles.glClearColor)(r, g, b, a);
                    (gles.glClear)(GL_COLOR_BUFFER_BIT);
                }
                (gles.glBlendFunc)(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
            }
            let sync = run_ops(self, ops);
            if sync.is_none() {
                unsafe {
                    (gles.glFinish)();
                }
            }
            Ok(sync)
        })
    }
}

impl GfxFramebuffer for Framebuffer {
    fn physical_size(&self) -> (i32, i32) {
        (self.gl.width, self.gl.height)
    }

    fn render_with_region(
        self: Rc<Self>,
        acquire_sync: AcquireSync,
        _release_sync: ReleaseSync,
        _cd: &Rc<ColorDescription>,
        ops: &[GfxApiOpt],
        clear: Option<&Color>,
        _clear_cd: &Rc<LinearColorDescription>,
        _region: &Region,
        _blend_buffer: Option<&Rc<dyn GfxBlendBuffer>>,
        _blend_cd: &Rc<ColorDescription>,
    ) -> Result<Option<FdSync>, GfxError> {
        (*self)
            .render(acquire_sync, ops, clear)
            .map_err(|e| e.into())
    }

    fn format(&self) -> &'static Format {
        self.gl.rb.format
    }
}

impl GfxInternalFramebuffer for Framebuffer {
    fn stride(&self) -> i32 {
        self.gl.rb.stride
    }

    fn staging_size(&self) -> usize {
        0
    }

    fn download(
        self: Rc<Self>,
        _staging: &Rc<dyn GfxStagingBuffer>,
        _callback: Rc<dyn AsyncShmGfxTextureCallback>,
        mem: Rc<dyn ShmMemory>,
        _damage: Region,
    ) -> Result<Option<PendingShmTransfer>, GfxError> {
        let mut res = Ok(());
        mem.access(&mut |mem| res = self.copy_to_shm(mem))
            .map_err(RenderError::AccessFailed)?;
        res?;
        Ok(None)
    }
}
