Documentation
use core::ptr;
use core::mem;
use core::ops::Drop;

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

use super::{DataFormat, DataKind, Wrap, TextureKind, FilterMode};


#[derive(Debug, Clone, Hash)]
pub struct GLTexture {
    id: GLuint,
    kind: TextureKind,
    width: usize,
    height: usize,
    format: DataFormat,
    data_kind: DataKind,
    filter: FilterMode,
    wrap: Wrap,
    mipmap: bool,
}

impl GLTexture {

    #[inline(always)]
    pub fn new_2d<T>(
        width: usize,
        height: usize,
        format: DataFormat,
        data_kind: DataKind,
        filter: FilterMode,
        wrap: Wrap,
        generate_mipmap: bool,
        data: &[T]
    ) -> Self {
        let id = new_gl_texture();
        let mipmap = unsafe {
            gl::BindTexture(gl::TEXTURE_2D, id);
            Self::gl_2d(width, height, format, data_kind, data);
            Self::gl_wrap_filter_mipmap(width, height, filter, wrap, generate_mipmap)
        };

        GLTexture {
            id: id,
            kind: TextureKind::Texture2D,
            width: width,
            height: height,
            format: format,
            data_kind: data_kind,
            filter: filter,
            wrap: wrap,
            mipmap: mipmap,
        }
    }

    #[inline(always)]
    pub fn new_null_2d(
        width: usize,
        height: usize,
        format: DataFormat,
        data_kind: DataKind,
        filter: FilterMode,
        wrap: Wrap,
        generate_mipmap: bool
    ) -> Self {
        let id = new_gl_texture();
        let mipmap = unsafe {
            gl::BindTexture(gl::TEXTURE_2D, id);
            Self::gl_null_2d(width, height, format, data_kind);
            Self::gl_wrap_filter_mipmap(width, height, filter, wrap, generate_mipmap)
        };

        GLTexture {
            id: id,
            kind: TextureKind::Texture2D,
            width: width,
            height: height,
            format: format,
            data_kind: data_kind,
            filter: filter,
            wrap: wrap,
            mipmap: mipmap,
        }
    }

    #[inline(always)]
    pub fn resize_null_2d(&mut self, width: usize, height: usize) -> &Self {
        self.width = width;
        self.height = height;

        unsafe {
            gl::BindTexture(gl::TEXTURE_2D, self.id);
            Self::gl_null_2d(self.width, self.height, self.format, self.data_kind);

            if self.mipmap {
                Self::gl_wrap_filter_mipmap(self.width, self.height, self.filter, self.wrap, true);
            }
        }
        self
    }

    #[inline(always)]
    pub fn id(&self) -> GLuint { self.id }
    #[inline(always)]
    pub fn kind(&self) -> TextureKind { self.kind }
    #[inline(always)]
    pub fn width(&self) -> usize { self.width }
    #[inline(always)]
    pub fn height(&self) -> usize { self.height }
    #[inline(always)]
    pub fn data_kind(&self) -> DataKind { self.data_kind }
    #[inline(always)]
    pub fn format(&self) -> DataFormat { self.format }
    #[inline(always)]
    pub fn filter(&self) -> FilterMode { self.filter }
    #[inline(always)]
    pub fn wrap(&self) -> Wrap { self.wrap }
    #[inline(always)]
    pub fn mipmap(&self) -> bool { self.mipmap }

    #[inline]
    pub fn bind(&self) -> &Self {
        unsafe { gl::BindTexture(self.kind.to_gl(), self.id); }
        self
    }
    #[inline]
    pub fn unbind(&self) -> &Self {
        unsafe { gl::BindTexture(self.kind.to_gl(), 0); }
        self
    }

    #[inline]
    pub fn gl_wrap_filter_mipmap(
        width: usize,
        height: usize,
        filter: FilterMode,
        wrap: Wrap,
        generate_mipmap: bool
    ) -> bool {
        let is_power_of_two = width.is_power_of_two() && height.is_power_of_two();
        let (mag_filter, min_filter) = Self::mag_filter_min_filter(filter, is_power_of_two, generate_mipmap);

        unsafe {
            gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, mag_filter as GLint);
            gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, min_filter as GLint);

            let gl_wrap = wrap as GLint;
            gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl_wrap);
            gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl_wrap);

            if generate_mipmap && is_power_of_two {
                gl::GenerateMipmap(gl::TEXTURE_2D);
            }
        }

        generate_mipmap && is_power_of_two
    }

    #[inline]
    pub fn gl_2d<T>(
        width: usize,
        height: usize,
        format: DataFormat,
        data_kind: DataKind,
        data: &[T],
    ) {
        unsafe {
            gl::TexImage2D(
                gl::TEXTURE_2D,
                0,
                format.to_gl() as GLint,
                width as GLsizei,
                height as GLsizei,
                0,
                format.to_gl(),
                data_kind.to_gl(),
                mem::transmute(data.as_ptr())
            );
        }
    }

    #[inline]
    pub fn gl_null_2d(
        width: usize,
        height: usize,
        format: DataFormat,
        data_kind: DataKind
    ) {
        unsafe {
            gl::TexImage2D(
                gl::TEXTURE_2D,
                0,
                format.to_gl() as GLint,
                width as GLsizei,
                height as GLsizei,
                0,
                format.to_gl(),
                data_kind.to_gl(),
                ptr::null()
            );
        }
    }

    // (mag_filter, min_filter)
    #[inline]
    pub fn mag_filter_min_filter(
        filter: FilterMode,
        is_power_of_two: bool,
        generate_mipmap: bool
    ) -> (GLuint, GLuint) {
        match filter {
            FilterMode::None => (
                gl::NEAREST,
                if is_power_of_two && generate_mipmap {gl::LINEAR_MIPMAP_NEAREST} else {gl::NEAREST}
            ),
            _ => (
                gl::LINEAR,
                if is_power_of_two && generate_mipmap {gl::LINEAR_MIPMAP_LINEAR} else {gl::LINEAR}
            ),
        }
    }
}

impl Drop for GLTexture {
    #[inline]
    fn drop(&mut self) {
        if self.id != 0 {
            unsafe { gl::DeleteTextures(1, &self.id); }
        }
    }
}

#[inline(always)]
fn new_gl_texture() -> GLuint {
    let mut id = 0;
    unsafe { gl::GenTextures(1, &mut id); }
    id
}