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

use std::ptr;
use std::ffi::CString;
use std::mem::uninitialized;

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

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

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ShaderParameter {
    ShaderType,
    DeleteStatus,
    CompileStatus,
    InfoLogLength,
    ShaderSourceLength,
}

impl From<ShaderParameter> for u32 {
    fn from(shader_parameter: ShaderParameter) -> Self {
        use self::ShaderParameter::*;
        match shader_parameter {
            ShaderType => gl::SHADER_TYPE,
            DeleteStatus => gl::DELETE_STATUS,
            CompileStatus => gl::COMPILE_STATUS,
            InfoLogLength => gl::INFO_LOG_LENGTH,
            ShaderSourceLength => gl::SHADER_SOURCE_LENGTH,
        }
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ShaderType {
    Vertex,
    Fragment,
    Geometry,
    TessellationEvaluation,
    TessellationControl,
    Compute,
}

impl From<ShaderType> for u32 {
    fn from(shader_type: ShaderType) -> Self {
        match shader_type {
            ShaderType::Vertex => gl::VERTEX_SHADER,
            ShaderType::Fragment => gl::FRAGMENT_SHADER,
            ShaderType::Geometry => gl::GEOMETRY_SHADER,
            ShaderType::TessellationEvaluation => gl::TESS_EVALUATION_SHADER,
            ShaderType::TessellationControl => gl::TESS_CONTROL_SHADER,
            ShaderType::Compute => gl::COMPUTE_SHADER,
        }
    }
}

pub unsafe fn create_shader(shader_type: ShaderType) -> ShaderId {
    ShaderId(gl::CreateShader(shader_type.into()))
}

pub unsafe fn shader_source(id: ShaderId, sources: &[&str]) {
    let source = sources.join("");
    let source_cstr = CString::new(source.as_bytes()).unwrap();

    gl::ShaderSource(id.into(), 1, [source_cstr.as_ptr()].as_ptr(), ptr::null());
}

pub unsafe fn compile_shader(id: ShaderId) {
    gl::CompileShader(id.into());
}

pub unsafe fn get_shader(id: ShaderId, parameter: ShaderParameter) -> i32 {
    let mut value: gl::types::GLint = uninitialized();

    gl::GetShaderiv(id.into(), parameter.into(), &mut value);

    value
}

pub unsafe fn get_shader_info_log(id: ShaderId) -> Result<String> {
    let mut error_log_size: gl::types::GLint =
        get_shader(id.into(), ShaderParameter::InfoLogLength);

    let mut error_log: Vec<u8> = Vec::with_capacity(error_log_size as usize);
    gl::GetShaderInfoLog(
        id.into(),
        error_log_size,
        &mut error_log_size,
        error_log.as_mut_ptr() as *mut gl::types::GLchar,
    );

    error_log.set_len(error_log_size as usize);
    let string = match String::from_utf8(error_log) {
        Ok(s) => s,
        Err(_) => bail!("Could not convert log"),
    };

    Ok(string)
}

pub unsafe fn delete_shader(id: ShaderId) {
    gl::DeleteShader(id.into())
}
