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

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

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

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

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ShaderProgramParameter {
    DeleteStatus,
    LinkStatus,
    ValidateStatus,
    InfoLogLength,
    AttachedShaders,
    ActiveAtomicCounterBuffers,
    ActiveAttributes,
    ActiveAttributeMaxLength,
    ActiveUniforms,
    ActiveUniformBlocks,
    ActiveUniformBlockMaxNameLength,
    ActiveUniformMaxLength,
    ComputeWorkGroupSize,
    ProgramBinaryLength,
    TransformFeedbackBufferMode,
    TransformFeedbackVaryings,
    TransformFeedbackVaryingMaxLength,
    GeometryVerticesOut,
    GeometryInputType,
    GeometryOutputType,
}

impl From<ShaderProgramParameter> for u32 {
    fn from(parameter: ShaderProgramParameter) -> Self {
        use self::ShaderProgramParameter::*;
        match parameter {
            DeleteStatus => gl::DELETE_STATUS,
            LinkStatus => gl::LINK_STATUS,
            ValidateStatus => gl::VALIDATE_STATUS,
            InfoLogLength => gl::INFO_LOG_LENGTH,
            AttachedShaders => gl::ATTACHED_SHADERS,
            ActiveAtomicCounterBuffers => gl::ACTIVE_ATOMIC_COUNTER_BUFFERS,
            ActiveAttributes => gl::ACTIVE_ATTRIBUTES,
            ActiveAttributeMaxLength => gl::ACTIVE_ATTRIBUTE_MAX_LENGTH,
            ActiveUniforms => gl::ACTIVE_UNIFORMS,
            ActiveUniformBlocks => gl::ACTIVE_UNIFORM_BLOCKS,
            ActiveUniformBlockMaxNameLength => gl::ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH,
            ActiveUniformMaxLength => gl::ACTIVE_UNIFORM_MAX_LENGTH,
            ComputeWorkGroupSize => gl::COMPUTE_WORK_GROUP_SIZE,
            ProgramBinaryLength => gl::PROGRAM_BINARY_LENGTH,
            TransformFeedbackBufferMode => gl::TRANSFORM_FEEDBACK_BUFFER_MODE,
            TransformFeedbackVaryings => gl::TRANSFORM_FEEDBACK_VARYINGS,
            TransformFeedbackVaryingMaxLength => gl::TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH,
            GeometryVerticesOut => gl::GEOMETRY_VERTICES_OUT,
            GeometryInputType => gl::GEOMETRY_INPUT_TYPE,
            GeometryOutputType => gl::GEOMETRY_OUTPUT_TYPE,
        }
    }
}

pub unsafe fn create_shader_program<T: AsRef<str>>(
    gl_type: ShaderType,
    sources: &[T],
) -> Result<ShaderProgramId> {
    let mut source: String = String::new();
    for s in sources {
        source.push_str(s.as_ref());
    }
    let source_cstr = CString::new(source.as_bytes()).unwrap();

    let id = gl::CreateShaderProgramv(gl_type.into(), 1, [source_cstr.as_ptr()].as_ptr());

    get_error(ShaderProgramId(id))
}

pub unsafe fn delete_shader_program(id: ShaderProgramId) {
    gl::DeleteProgram(id.into());
}

pub unsafe fn get_shader_program(
    id: ShaderProgramId,
    parameter: ShaderProgramParameter,
) -> Result<i32> {
    let mut value: gl::types::GLint = uninitialized();

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

    get_error(value)
}

pub unsafe fn get_shader_program_info_log(id: ShaderProgramId) -> Result<String> {
    let mut error_log_size: gl::types::GLint =
        get_shader_program(id.into(), ShaderProgramParameter::InfoLogLength)
            .chain_err(|| "Could not get log length")?;

    let mut error_log: Vec<u8> = Vec::with_capacity(error_log_size as usize);
    gl::GetProgramInfoLog(
        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)
}
