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

use raw::{ProgramPipelineId, create_program_pipeline, use_program_stage, PipelineShaderStage,
          ShaderType, bind_program_pipeline, delete_program_pipeline};

pub struct Pipeline<'a> {
    id: ProgramPipelineId,

    pub vertex_shader: Option<&'a Shader>,
    pub tessellation_control_shader: Option<&'a Shader>,
    pub tessellation_evaluation_shader: Option<&'a Shader>,
    pub geometry_shader: Option<&'a Shader>,
    pub fragment_shader: Option<&'a Shader>,
}

pub struct PipelineBuilder<'a> {
    pub vertex_shader: Option<&'a Shader>,
    pub tessellation_control_shader: Option<&'a Shader>,
    pub tessellation_evaluation_shader: Option<&'a Shader>,
    pub geometry_shader: Option<&'a Shader>,
    pub fragment_shader: Option<&'a Shader>,

    // not ideal but for the time being suffices
    vertex_ok: bool,
    tessellation_control_ok: bool,
    tessellation_evaluation_ok: bool,
    geometry_ok: bool,
    fragment_ok: bool,
}

impl<'a> Pipeline<'a> {
    #[allow(unused_must_use)]
    pub fn new(
        vertex_shader: Option<&'a Shader>,
        tessellation_control_shader: Option<&'a Shader>,
        tessellation_evaluation_shader: Option<&'a Shader>,
        geometry_shader: Option<&'a Shader>,
        fragment_shader: Option<&'a Shader>,
    ) -> Pipeline<'a> {
        let id: ProgramPipelineId;

        unsafe {
            id = create_program_pipeline();

            if let Some(ref shader) = vertex_shader {
                use_program_stage(id, PipelineShaderStage::Vertex, shader.get_id());
            }

            if let Some(ref shader) = tessellation_control_shader {
                use_program_stage(
                    id,
                    PipelineShaderStage::TessellationControl,
                    shader.get_id(),
                );
            }

            if let Some(ref shader) = tessellation_evaluation_shader {
                use_program_stage(
                    id,
                    PipelineShaderStage::TessellationEvaluation,
                    shader.get_id(),
                );
            }

            if let Some(ref shader) = geometry_shader {
                use_program_stage(id, PipelineShaderStage::Geometry, shader.get_id());
            }

            if let Some(ref shader) = fragment_shader {
                use_program_stage(id, PipelineShaderStage::Fragment, shader.get_id());
            }
        }

        Pipeline {
            id: id,
            vertex_shader: vertex_shader,
            tessellation_control_shader: tessellation_control_shader,
            tessellation_evaluation_shader: tessellation_evaluation_shader,
            geometry_shader: geometry_shader,
            fragment_shader: fragment_shader,
        }
    }

    pub fn bind(&self) {
        unsafe {
            bind_program_pipeline(self.id);
        }
    }
}

impl<'a> Drop for Pipeline<'a> {
    fn drop(&mut self) {
        unsafe {
            delete_program_pipeline(self.id);
        }
    }
}
impl<'a> PipelineBuilder<'a> {
    pub fn new() -> PipelineBuilder<'a> {
        PipelineBuilder {
            vertex_shader: None,
            tessellation_control_shader: None,
            tessellation_evaluation_shader: None,
            geometry_shader: None,
            fragment_shader: None,

            vertex_ok: true,
            tessellation_control_ok: true,
            tessellation_evaluation_ok: true,
            geometry_ok: true,
            fragment_ok: true,
        }
    }

    pub fn vertex_shader(mut self, shader: &'a Shader) -> PipelineBuilder {
        if shader.shader_type != ShaderType::Vertex {
            self.vertex_ok = false;
        }

        self.vertex_shader = Some(shader);
        self
    }


    pub fn tessellation_control_shader(mut self, shader: &'a Shader) -> PipelineBuilder {
        if shader.shader_type != ShaderType::TessellationControl {
            self.tessellation_control_ok = false;
        }

        self.tessellation_control_shader = Some(shader);
        self
    }

    pub fn tessellation_evaluation_shader(mut self, shader: &'a Shader) -> PipelineBuilder {
        if shader.shader_type != ShaderType::TessellationEvaluation {
            self.tessellation_evaluation_ok = false;
        }

        self.tessellation_evaluation_shader = Some(shader);
        self
    }

    pub fn geometry_shader(mut self, shader: &'a Shader) -> PipelineBuilder {
        if shader.shader_type != ShaderType::Geometry {
            self.geometry_ok = false;
        }

        self.geometry_shader = Some(shader);
        self
    }

    pub fn fragment_shader(mut self, shader: &'a Shader) -> PipelineBuilder {
        if shader.shader_type != ShaderType::Fragment {
            self.fragment_ok = false;
        }

        self.fragment_shader = Some(shader);
        self
    }

    pub fn build(self) -> Result<Pipeline<'a>> {
        if !self.vertex_ok {
            bail!("Wrong type of shader supplied, expected vertex");
        }
        if !self.tessellation_control_ok {
            bail!("Wrong type of shader supplied, expected tessellation control");
        }
        if !self.tessellation_evaluation_ok {
            bail!("Wrong type of shader supplied, expected tessellation evaluation");
        }
        if !self.geometry_ok {
            bail!("Wrong type of shader supplied, expected geometry");
        }
        if !self.fragment_ok {
            bail!("Wrong type of shader supplied, expected fragment");
        }

        Ok(Pipeline::new(
            self.vertex_shader,
            self.tessellation_control_shader,
            self.tessellation_evaluation_shader,
            self.geometry_shader,
            self.fragment_shader,
        ))
    }
}
