use std::ffi::{CString, CStr};
use std::ptr;

#[allow(dead_code)]
#[allow(non_camel_case_types)]
#[allow(non_upper_case_globals)]
mod bindings;

pub enum ShaderType {
    Vertex,
    Fragment,
}

pub enum Target {
    OpenGl,
    OpenGles20,
    OpenGles30,
    Metal,
}

pub struct Context {
    ctx: *mut bindings::glslopt_ctx,
}

impl Context {
    pub fn new(target: Target) -> Self {
        let target = match target {
            Target::OpenGl => bindings::glslopt_target_kGlslTargetOpenGL,
            Target::OpenGles20 => bindings::glslopt_target_kGlslTargetOpenGLES20,
            Target::OpenGles30 => bindings::glslopt_target_kGlslTargetOpenGLES30,
            Target::Metal => bindings::glslopt_target_kGlslTargetMetal,
        };

        let ctx = unsafe { bindings::glslopt_initialize(target) };

        Self {
            ctx,
        }
    }

    pub fn optimize(&self, shader_type: ShaderType, source: String) -> Shader {
        let shader_type = match shader_type {
            ShaderType::Vertex => bindings::glslopt_shader_type_kGlslOptShaderVertex,
            ShaderType::Fragment => bindings::glslopt_shader_type_kGlslOptShaderFragment,
        };
        let source = CString::new(source).unwrap();

        let shader = unsafe { bindings::glslopt_optimize(self.ctx, shader_type, source.as_ptr(), 0) };
        assert_ne!(shader, ptr::null_mut());
        Shader {
            shader,
        }
    }
}

impl Drop for Context {
    fn drop(&mut self) {
        unsafe {
            bindings::glslopt_cleanup(self.ctx);
        }
    }
}

pub struct Shader {
    shader: *mut bindings::glslopt_shader,
}

impl Shader {
    pub fn get_status(&self) -> bool {
        unsafe { bindings::glslopt_get_status(self.shader) }
    }

    pub fn get_output(&self) -> Result<&str, ()> {
        unsafe {
            let cstr = bindings::glslopt_get_output(self.shader);
            if cstr == ptr::null() {
                Err(())
            } else {
                Ok(CStr::from_ptr(cstr).to_str().unwrap())
            }
        }
    }

    pub fn get_log(&self) -> &str {
        unsafe {
            let cstr = bindings::glslopt_get_log(self.shader);
            if cstr == ptr::null() {
                ""
            } else {
                CStr::from_ptr(cstr).to_str().unwrap()
            }
        }
    }
}

impl Drop for Shader {
    fn drop(&mut self) {
        unsafe {
            bindings::glslopt_shader_delete(self.shader);
        }
    }
}