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

pub trait Framebuffer {
    fn get_id(&self) -> FramebufferId;

    fn state(&self) -> &FramebufferState;
    fn mut_state(&mut self) -> &mut FramebufferState;

    fn bind(&self) -> ();

    fn set_viewport<'a>(&'a mut self, viewport: Viewport) {
        self.mut_state().viewport = viewport;
    }

    fn set_enable<'a>(&'a mut self, option: EnableOption) {
        self.mut_state().options[usize::from(option)] = true;
    }

    fn set_disable<'a>(&'a mut self, option: EnableOption) {
        self.mut_state().options[usize::from(option)] = false;
    }

    fn set_stencil_test<'a>(&'a mut self, face: Face, stencil_test: StencilTest) {
        match face {
            Face::Front => self.mut_state().stencil_test[0] = stencil_test,
            Face::Back => self.mut_state().stencil_test[1] = stencil_test,
            Face::FrontBack => {
                self.mut_state().stencil_test[0] = stencil_test;
                self.mut_state().stencil_test[1] = stencil_test;
            }
        }
    }

    fn set_depth_test<'a>(&'a mut self, depth_test: DepthTest) {
        self.mut_state().depth_test = depth_test;
    }

    fn set_depth_mask<'a>(&'a mut self, depth_mask: DepthMask) {
        self.mut_state().depth_mask = depth_mask;
    }

    fn set_depth_range<'a>(&'a mut self, depth_range: DepthRange) {
        self.mut_state().depth_range = depth_range;
    }

    fn set_logic_operation<'a>(&'a mut self, logic_operation: LogicOperation) {
        self.mut_state().logic_operation = logic_operation;
    }

    fn set_blending_equation<'a>(
        &'a mut self,
        blending_equation_rgb: BlendingEquation,
        blending_equation_alpha: BlendingEquation,
    ) {
        self.mut_state().blending_equation_rgb = blending_equation_rgb;
        self.mut_state().blending_equation_alpha = blending_equation_alpha;
    }

    fn set_linear_blending_factors<'a>(
        &'a mut self,
        source_rgb: LinearBlendingFactor,
        destination_rgb: LinearBlendingFactor,
        source_alpha: LinearBlendingFactor,
        destination_alpha: LinearBlendingFactor,
    ) {
        self.mut_state().linear_blending_factors =
            Some(
                [source_rgb, destination_rgb, source_alpha, destination_alpha],
            );
    }

    fn set_face_orientation<'a>(&'a mut self, face_orientation: FaceOrientation) {
        self.mut_state().face_orientation = face_orientation;
    }

    fn set_cull_face<'a>(&'a mut self, cull_face: Face) {
        self.mut_state().cull_face = cull_face;
    }

    fn set_clear_color<'a>(&'a mut self, clear_color: ClearColor) {
        self.mut_state().clear_color = clear_color;
    }
}

#[derive(Debug)]
pub struct DefaultFramebuffer {
    state: FramebufferState,
}

impl DefaultFramebuffer {
    pub fn new() -> DefaultFramebuffer {
        DefaultFramebuffer { state: FramebufferState::default() }
    }
}

impl Framebuffer for DefaultFramebuffer {
    fn get_id(&self) -> FramebufferId {
        DEFAULT_FRAMEBUFFER_ID
    }

    fn state(&self) -> &FramebufferState {
        &self.state
    }

    fn mut_state(&mut self) -> &mut FramebufferState {
        &mut self.state
    }

    #[allow(unused_must_use)]
    fn bind(&self) {
        unsafe {
            bind_framebuffer(FramebufferTarget::Framebuffer, self.get_id());
        }
    }
}

#[derive(Debug)]
pub struct GeneralFramebuffer {
    id: FramebufferId,
    state: FramebufferState,
}

impl Framebuffer for GeneralFramebuffer {
    fn get_id(&self) -> FramebufferId {
        self.id
    }

    fn state(&self) -> &FramebufferState {
        &self.state
    }

    fn mut_state(&mut self) -> &mut FramebufferState {
        &mut self.state
    }

    #[allow(unused_must_use)]
    fn bind(&self) {
        unsafe {
            bind_framebuffer(FramebufferTarget::Framebuffer, self.id);
        }
    }
}

impl GeneralFramebuffer {
    pub fn new() -> GeneralFramebuffer {
        let id;

        unsafe {
            id = create_framebuffer();
        };

        GeneralFramebuffer {
            id: id,
            state: FramebufferState::default(),
        }
    }

    pub fn attach_texture(
        &self,
        attachment: FramebufferAttachment,
        texture_option: &DrawTextureAttachOption,
    ) -> Result<()> {
        unsafe {
            match *texture_option {
                DrawTextureAttachOption::AttachTexture(ref texture) => {
                    named_framebuffer_texture(self.id, attachment, texture.get_id(), 0)
                        .chain_err(|| "Could not attach texture to framebuffer")?;
                }
                DrawTextureAttachOption::AttachTextureLayer {
                    ref texture,
                    level,
                    layer,
                } => {
                    named_framebuffer_texture_layer(
                        self.id,
                        attachment,
                        texture.get_id(),
                        level,
                        layer,
                    ).chain_err(|| format!("Could not attach texture layer to framebuffer"))?;
                }
                DrawTextureAttachOption::None => {
                    named_framebuffer_texture(self.id, attachment, TextureId::empty(), 0)
                        .chain_err(|| "Could not detach texture from framebuffer")?;
                }
                DrawTextureAttachOption::Keep => {}
            }
        }

        Ok(())
    }

    pub fn attach_textures(&self, textures: &DrawTextureTarget) -> Result<()> {
        // Take all color attachments in attachments description
        let color_attachments = [
            &textures.color0,
            &textures.color1,
            &textures.color2,
            &textures.color3,
            &textures.color4,
            &textures.color5,
            &textures.color6,
            &textures.color7,
        ];

        // Enable all fragment shader outputs and match their location id to
        // the same attachment point id
        // layout(location = 0) => ColorAttachment0
        let mut draw_buffers = [
            FramebufferDrawBuffer::ColorAttachment0,
            FramebufferDrawBuffer::ColorAttachment1,
            FramebufferDrawBuffer::ColorAttachment2,
            FramebufferDrawBuffer::ColorAttachment3,
            FramebufferDrawBuffer::ColorAttachment4,
            FramebufferDrawBuffer::ColorAttachment5,
            FramebufferDrawBuffer::ColorAttachment6,
            FramebufferDrawBuffer::ColorAttachment7,
        ];

        // Iterate over each attachment point
        for (i, color_attachment_option) in color_attachments.iter().enumerate() {
            match **color_attachment_option {
                // If output is explicitly set to None, then let it be
                DrawTextureAttachOption::None => draw_buffers[i] = FramebufferDrawBuffer::None,
                _ => {}
            }
        }

        // Call to set draw buffers described above
        unsafe {
            named_framebuffer_draw_buffers(self.id, &draw_buffers)
                .chain_err(|| "Could not attach draw buffers")?;
        }

        // Attach Depth and Stencil textures
        match textures.depth_stencil {
            DepthStencilOption::Separate {
                ref depth,
                ref stencil,
            } => {
                self.attach_texture(FramebufferAttachment::DepthAttachment, &depth)
                    .chain_err(|| "Could not attach depth texture")?;
                self.attach_texture(FramebufferAttachment::StencilAttachment, &stencil)
                    .chain_err(|| "Could not attach stencil texture")?;;
            }
            DepthStencilOption::Together(ref depth_stencil) => {
                self.attach_texture(
                    FramebufferAttachment::DepthStencilAttachment,
                    &depth_stencil,
                ).chain_err(|| "Could not attach depth stencil texture")?;;
            }
        }

        let color_attachments_enums = [
            FramebufferAttachment::ColorAttachment0,
            FramebufferAttachment::ColorAttachment1,
            FramebufferAttachment::ColorAttachment2,
            FramebufferAttachment::ColorAttachment3,
            FramebufferAttachment::ColorAttachment4,
            FramebufferAttachment::ColorAttachment5,
            FramebufferAttachment::ColorAttachment6,
            FramebufferAttachment::ColorAttachment7,
        ];

        for (i, color_attachment_option) in color_attachments.iter().enumerate() {
            self.attach_texture(color_attachments_enums[i], color_attachment_option)
                .chain_err(|| format!("Could not attach color{} texture", i))?;
        }

        Ok(())
    }
}

impl Drop for GeneralFramebuffer {
    fn drop(&mut self) {
        unsafe {
            delete_framebuffer(self.id.into());
        }
    }
}
