use super::*;
use super::errors::*;

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct FramebufferId(u32);

pub const DEFAULT_FRAMEBUFFER_ID: FramebufferId = FramebufferId(0);

impl From<FramebufferId> for u32 {
    fn from(framebuffer_id: FramebufferId) -> Self {
        framebuffer_id.0
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum FramebufferTarget {
    DrawFramebuffer,
    ReadFramebuffer,
    Framebuffer,
}

impl From<FramebufferTarget> for gl::types::GLenum {
    fn from(framebuffer_target: FramebufferTarget) -> Self {
        match framebuffer_target {
            FramebufferTarget::DrawFramebuffer => gl::DRAW_FRAMEBUFFER,
            FramebufferTarget::ReadFramebuffer => gl::READ_FRAMEBUFFER,
            FramebufferTarget::Framebuffer => gl::FRAMEBUFFER,
        }
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum FramebufferDrawBuffer {
    None,
    Front,
    FrontBack,
    FrontLeft,
    FrontRight,
    Back,
    BackLeft,
    BackRight,
    ColorAttachment0,
    ColorAttachment1,
    ColorAttachment2,
    ColorAttachment3,
    ColorAttachment4,
    ColorAttachment5,
    ColorAttachment6,
    ColorAttachment7,
}

impl From<FramebufferDrawBuffer> for gl::types::GLenum {
    fn from(framebuffer_target: FramebufferDrawBuffer) -> Self {
        match framebuffer_target {
            FramebufferDrawBuffer::Front => gl::FRONT,
            FramebufferDrawBuffer::Back => gl::BACK,
            FramebufferDrawBuffer::FrontBack => gl::FRONT_AND_BACK,
            FramebufferDrawBuffer::None => gl::NONE,
            FramebufferDrawBuffer::FrontLeft => gl::FRONT_LEFT,
            FramebufferDrawBuffer::FrontRight => gl::FRONT_RIGHT,
            FramebufferDrawBuffer::BackLeft => gl::BACK_LEFT,
            FramebufferDrawBuffer::BackRight => gl::BACK_RIGHT,
            FramebufferDrawBuffer::ColorAttachment0 => gl::COLOR_ATTACHMENT0,
            FramebufferDrawBuffer::ColorAttachment1 => gl::COLOR_ATTACHMENT1,
            FramebufferDrawBuffer::ColorAttachment2 => gl::COLOR_ATTACHMENT2,
            FramebufferDrawBuffer::ColorAttachment3 => gl::COLOR_ATTACHMENT3,
            FramebufferDrawBuffer::ColorAttachment4 => gl::COLOR_ATTACHMENT4,
            FramebufferDrawBuffer::ColorAttachment5 => gl::COLOR_ATTACHMENT5,
            FramebufferDrawBuffer::ColorAttachment6 => gl::COLOR_ATTACHMENT6,
            FramebufferDrawBuffer::ColorAttachment7 => gl::COLOR_ATTACHMENT7,
        }
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum FramebufferCompleteness {
    Complete,
    Undefined,
    IncompleteAttachment,
    IncompleteMissingAttachment,
    IncompleteDrawBuffer,
    IncompleteReadBuffer,
    Unsupported,
    IncompleteMultisample,
    IncompleteLayerTargets,
    Unknown,
}

impl From<FramebufferCompleteness> for gl::types::GLenum {
    fn from(completeness: FramebufferCompleteness) -> Self {
        match completeness {
            FramebufferCompleteness::Complete => gl::FRAMEBUFFER_COMPLETE,
            FramebufferCompleteness::Undefined => gl::FRAMEBUFFER_UNDEFINED,
            FramebufferCompleteness::IncompleteAttachment => gl::FRAMEBUFFER_INCOMPLETE_ATTACHMENT,
            FramebufferCompleteness::IncompleteMissingAttachment => {
                gl::FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT
            }
            FramebufferCompleteness::IncompleteDrawBuffer => gl::FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER,
            FramebufferCompleteness::IncompleteReadBuffer => gl::FRAMEBUFFER_INCOMPLETE_READ_BUFFER,
            FramebufferCompleteness::Unsupported => gl::FRAMEBUFFER_UNSUPPORTED,
            FramebufferCompleteness::IncompleteMultisample => {
                gl::FRAMEBUFFER_INCOMPLETE_MULTISAMPLE
            }
            FramebufferCompleteness::IncompleteLayerTargets => {
                gl::FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS
            }
            FramebufferCompleteness::Unknown => 0,
        }
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum FramebufferAttachment {
    DepthAttachment,
    StencilAttachment,
    DepthStencilAttachment,
    ColorAttachment0,
    ColorAttachment1,
    ColorAttachment2,
    ColorAttachment3,
    ColorAttachment4,
    ColorAttachment5,
    ColorAttachment6,
    ColorAttachment7,
}

impl From<FramebufferAttachment> for gl::types::GLenum {
    fn from(framebuffer_attachment: FramebufferAttachment) -> Self {
        match framebuffer_attachment {
            FramebufferAttachment::DepthAttachment => gl::DEPTH_ATTACHMENT,
            FramebufferAttachment::StencilAttachment => gl::STENCIL_ATTACHMENT,
            FramebufferAttachment::DepthStencilAttachment => gl::DEPTH_STENCIL_ATTACHMENT,
            FramebufferAttachment::ColorAttachment0 => gl::COLOR_ATTACHMENT0,
            FramebufferAttachment::ColorAttachment1 => gl::COLOR_ATTACHMENT1,
            FramebufferAttachment::ColorAttachment2 => gl::COLOR_ATTACHMENT2,
            FramebufferAttachment::ColorAttachment3 => gl::COLOR_ATTACHMENT3,
            FramebufferAttachment::ColorAttachment4 => gl::COLOR_ATTACHMENT4,
            FramebufferAttachment::ColorAttachment5 => gl::COLOR_ATTACHMENT5,
            FramebufferAttachment::ColorAttachment6 => gl::COLOR_ATTACHMENT6,
            FramebufferAttachment::ColorAttachment7 => gl::COLOR_ATTACHMENT7,
        }
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ClearBuffers {
    None,
    Color,
    Depth,
    Stencil,
    ColorDepth,
    ColorStencil,
    DepthStencil,
    ColorDepthStencil,
}

impl From<ClearBuffers> for gl::types::GLenum {
    fn from(buffers: ClearBuffers) -> Self {
        match buffers {
            ClearBuffers::None => 0,
            ClearBuffers::Color => gl::COLOR_BUFFER_BIT,
            ClearBuffers::Depth => gl::DEPTH_BUFFER_BIT,
            ClearBuffers::Stencil => gl::STENCIL_BUFFER_BIT,
            ClearBuffers::ColorDepth => gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT,
            ClearBuffers::ColorStencil => gl::COLOR_BUFFER_BIT | gl::STENCIL_BUFFER_BIT,
            ClearBuffers::DepthStencil => gl::DEPTH_BUFFER_BIT | gl::STENCIL_BUFFER_BIT,
            ClearBuffers::ColorDepthStencil => {
                gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT | gl::STENCIL_BUFFER_BIT
            }
        }
    }
}

pub unsafe fn clear(buffers: ClearBuffers) {
    gl::Clear(buffers.into());
}

pub unsafe fn create_framebuffer() -> FramebufferId {
    let mut framebuffer_id: u32 = 0;
    let framebuffer_id_ptr: *mut u32 = &mut framebuffer_id;

    gl::CreateFramebuffers(1, framebuffer_id_ptr);

    FramebufferId(framebuffer_id)
}

pub unsafe fn delete_framebuffer(id: FramebufferId) {
    gl::DeleteFramebuffers(1, &id.into());
}

pub unsafe fn bind_framebuffer(
    target: FramebufferTarget,
    framebuffer_id: FramebufferId,
) -> Result<()> {
    gl::BindFramebuffer(target.into(), framebuffer_id.into());

    get_error(())
}

pub unsafe fn named_framebuffer_draw_buffer(
    id: FramebufferId,
    buffer: FramebufferDrawBuffer,
) -> Result<()> {
    gl::NamedFramebufferDrawBuffer(id.into(), buffer.into());

    get_error(())
}

pub unsafe fn named_framebuffer_draw_buffers(
    id: FramebufferId,
    buffers: &[FramebufferDrawBuffer],
) -> Result<()> {
    let gl_buffers: Vec<gl::types::GLenum> = buffers.iter().map(|b| (*b).into()).collect::<_>();
    let gl_buffers_ptr: *const u32 = gl_buffers.as_ptr();

    gl::NamedFramebufferDrawBuffers(id.into(), buffers.len() as i32, gl_buffers_ptr);

    get_error(())
}

pub unsafe fn named_framebuffer_texture(
    id: FramebufferId,
    attachment: FramebufferAttachment,
    texture: TextureId,
    level: u32,
) -> Result<()> {
    gl::NamedFramebufferTexture(id.into(), attachment.into(), texture.into(), level as i32);

    get_error(())
}

pub unsafe fn named_framebuffer_texture_layer(
    id: FramebufferId,
    attachment: FramebufferAttachment,
    texture: TextureId,
    level: u32,
    layer: u32,
) -> Result<()> {
    gl::NamedFramebufferTextureLayer(
        id.into(),
        attachment.into(),
        texture.into(),
        level as i32,
        layer as i32,
    );

    get_error(())
}

pub unsafe fn check_named_framebuffer_status(
    id: FramebufferId,
    target: FramebufferTarget,
) -> Result<FramebufferCompleteness> {
    let result: gl::types::GLenum = gl::CheckNamedFramebufferStatus(id.into(), target.into());

    get_error(match result {
        gl::FRAMEBUFFER_COMPLETE => FramebufferCompleteness::Complete,
        gl::FRAMEBUFFER_UNDEFINED => FramebufferCompleteness::Undefined,
        gl::FRAMEBUFFER_INCOMPLETE_ATTACHMENT => FramebufferCompleteness::IncompleteAttachment,
        gl::FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT => {
            FramebufferCompleteness::IncompleteMissingAttachment
        }
        gl::FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER => FramebufferCompleteness::IncompleteDrawBuffer,
        gl::FRAMEBUFFER_INCOMPLETE_READ_BUFFER => FramebufferCompleteness::IncompleteReadBuffer,
        gl::FRAMEBUFFER_UNSUPPORTED => FramebufferCompleteness::Unsupported,
        gl::FRAMEBUFFER_INCOMPLETE_MULTISAMPLE => FramebufferCompleteness::IncompleteMultisample,
        gl::FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS => FramebufferCompleteness::IncompleteLayerTargets,
        _ => FramebufferCompleteness::Unknown,
    })
}
