Documentation
use std::mem;

use gl;
use gl::types::*;

use super::{Blending, CullFace, Depth, DrawMode, Error, IndexKind};

#[inline]
pub fn gl_set_defaults() {
    unsafe {
        gl::FrontFace(gl::CCW);

        if gl::PixelStorei::is_loaded() {
            gl::PixelStorei(gl::UNPACK_ALIGNMENT, 1);
        }
    }

    gl_set_viewport(0, 0, 1, 1);

    gl_set_clear_depth(1.0);
    gl_set_clear_stencil(0);

    gl_set_depth_write(true);
    gl_set_depth_range(0.0, 1.0);
    gl_set_line_width(1.0);

    gl_set_blending(Blending::Default);
    gl_set_cull_face(CullFace::Back);
    gl_set_depth_func(Depth::LessThanOrEqual);

    gl_set_clear_color(&[0.0, 0.0, 0.0, 1.0]);
    gl_clear(true, true, true);
}

#[inline(always)]
pub fn gl_get_error() -> Error {
    unsafe { gl::GetError().into() }
}

#[inline(always)]
pub fn gl_set_viewport(x: usize, y: usize, width: usize, height: usize) {
    unsafe {
        gl::Viewport(x as GLint, y as GLint, width as GLsizei, height as GLsizei);
    }
}

#[inline(always)]
pub fn gl_set_clear_depth(clear_depth: f64) {
    unsafe {
        if gl::ClearDepthf::is_loaded() {
            gl::ClearDepthf(clear_depth as f32);
        } else if gl::ClearDepth::is_loaded() {
            gl::ClearDepth(clear_depth);
        }
    }
}

#[inline(always)]
pub fn gl_set_clear_stencil(clear_stencil: isize) {
    unsafe {
        gl::ClearStencil(clear_stencil as GLint);
    }
}

#[inline(always)]
pub fn gl_set_depth_write(depth_write: bool) {
    unsafe {
        gl::DepthMask(if depth_write { gl::TRUE } else { gl::FALSE });
    }
}

#[inline(always)]
pub fn gl_set_depth_range(near: f64, far: f64) {
    unsafe {
        if gl::DepthRangef::is_loaded() {
            gl::DepthRangef(near as f32, far as f32);
        } else if gl::DepthRange::is_loaded() {
            gl::DepthRange(near, far);
        }
    }
}

#[inline(always)]
pub fn gl_set_line_width(line_width: f32) {
    unsafe {
        gl::LineWidth(line_width as GLfloat);
    }
}

#[inline]
pub fn gl_set_blending(blending: Blending) {
    match &blending {
        &Blending::Additive => unsafe {
            gl::Enable(gl::BLEND);
            gl::BlendEquation(gl::FUNC_ADD);
            gl::BlendFunc(gl::SRC_ALPHA, gl::ONE);
        },
        &Blending::Subtractive => unsafe {
            gl::Enable(gl::BLEND);
            gl::BlendEquation(gl::FUNC_ADD);
            gl::BlendFunc(gl::ZERO, gl::ONE_MINUS_SRC_COLOR);
        },
        &Blending::Multiply => unsafe {
            gl::Enable(gl::BLEND);
            gl::BlendEquation(gl::FUNC_ADD);
            gl::BlendFunc(gl::ZERO, gl::SRC_COLOR);
        },
        &Blending::Default => unsafe {
            gl::Enable(gl::BLEND);
            gl::BlendEquationSeparate(gl::FUNC_ADD, gl::FUNC_ADD);
            gl::BlendFuncSeparate(
                gl::SRC_ALPHA,
                gl::ONE_MINUS_SRC_ALPHA,
                gl::ONE,
                gl::ONE_MINUS_SRC_ALPHA,
            );
        },
        &Blending::None => unsafe {
            gl::Disable(gl::BLEND);
        },
    }
}

#[inline]
pub fn gl_set_cull_face(cull_face: CullFace) {
    match &cull_face {
        &CullFace::Back => unsafe {
            gl::Enable(gl::CULL_FACE);
            gl::CullFace(gl::BACK);
        },
        &CullFace::Front => unsafe {
            gl::Enable(gl::CULL_FACE);
            gl::CullFace(gl::FRONT);
        },
        &CullFace::FrontAndBack => unsafe {
            gl::Enable(gl::CULL_FACE);
            gl::CullFace(gl::FRONT_AND_BACK);
        },
        &CullFace::None => unsafe {
            gl::Disable(gl::CULL_FACE);
        },
    }
}

#[inline]
pub fn gl_set_depth_func(depth_func: Depth) {
    match &depth_func {
        &Depth::Never => unsafe {
            gl::Enable(gl::DEPTH_TEST);
            gl::DepthFunc(gl::NEVER);
        },
        &Depth::LessThan => unsafe {
            gl::Enable(gl::DEPTH_TEST);
            gl::DepthFunc(gl::LESS);
        },
        &Depth::Equal => unsafe {
            gl::Enable(gl::DEPTH_TEST);
            gl::DepthFunc(gl::EQUAL);
        },
        &Depth::LessThanOrEqual => unsafe {
            gl::Enable(gl::DEPTH_TEST);
            gl::DepthFunc(gl::LEQUAL);
        },
        &Depth::GreaterThan => unsafe {
            gl::Enable(gl::DEPTH_TEST);
            gl::DepthFunc(gl::GREATER);
        },
        &Depth::NotEqual => unsafe {
            gl::Enable(gl::DEPTH_TEST);
            gl::DepthFunc(gl::NOTEQUAL);
        },
        &Depth::GreaterThanOrEqual => unsafe {
            gl::Enable(gl::DEPTH_TEST);
            gl::DepthFunc(gl::GEQUAL);
        },
        &Depth::Always => unsafe {
            gl::Enable(gl::DEPTH_TEST);
            gl::DepthFunc(gl::ALWAYS);
        },
        &Depth::None => unsafe {
            gl::Disable(gl::DEPTH_TEST);
        },
    }
}

#[inline(always)]
pub fn gl_set_clear_color(color: &[f32; 4]) {
    unsafe {
        gl::ClearColor(color[0], color[1], color[2], color[3]);
    }
}

#[inline]
pub fn gl_clear(color: bool, depth: bool, stencil: bool) {
    let mut bits: GLbitfield = 0;

    if color {
        bits = bits | gl::COLOR_BUFFER_BIT;
    }
    if depth {
        bits = bits | gl::DEPTH_BUFFER_BIT;
    }
    if stencil {
        bits = bits | gl::STENCIL_BUFFER_BIT;
    }

    gl_clear_bits(bits);
}

#[inline(always)]
pub fn gl_clear_bits(bits: GLbitfield) {
    unsafe {
        gl::Clear(bits);
    }
}

#[inline]
pub fn gl_draw_arrays(mode: DrawMode, first: usize, count: usize) {
    unsafe {
        gl::DrawArrays(mode.into(), first as GLint, count as GLsizei);
    }
}
#[inline]
pub fn gl_draw_elements(mode: DrawMode, count: usize, kind: IndexKind, offset: GLint) {
    unsafe {
        gl::DrawElements(
            mode.into(),
            count as GLint,
            kind.into(),
            mem::transmute(offset as usize),
        );
    }
}