// Copyright (c) 2016-2018 Bruce Stenning. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
//    contributors may be used to endorse or promote products derived
//    from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
// OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
// DAMAGE.

use std::ptr;
use std::mem;
use std::os::raw;
use std::any::Any;
use std::fmt;

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

use graphics::texture::*;

#[derive(Clone)]
pub struct TextureGl {
    pub texture_name: GLuint,
}

impl TextureGl {
    /// Construct a new texture
    ///
    /// width: The width of the texture
    /// height: The height of the texture
    /// depth: true if the buffer should be depth, false if it should be colour
    /// floating: true if the buffer should be floating point, false if it should be byte
    /// msaa: The multisample factor
    /// data: The image data, empty if just defining the texture not populating it
    pub fn new(width: u32, height: u32, depth: bool, floating: bool, msaa: u32, data: &Vec<u8>) -> TextureGl 

}

impl Texture for TextureGl {
    /// To facilitate downcasting back to a concrete type
    fn as_any(&self) -> &Any {
        self
    }
    fn as_any_mut(&mut self) -> &mut Any {
        self
    }

    /// Bind the texture as the specified active texture number
    ///
    /// num: The texture number to bind the texture to
    fn bind(&self, num: i32) {
        unsafe {
            gl::ActiveTexture(match num {
                1 => gl::TEXTURE1,
                _ => gl::TEXTURE0,
            });

            gl::BindTexture(gl::TEXTURE_2D, self.texture_name);
        }
    }
}

// Output the handle associated with the textures's image, for debug purposes
impl fmt::Debug for TextureGl {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{:08x}", self.texture_name)
    }
}

{
        let mut texture_name: GLuint = 0;

        let internal_format = if depth { gl::DEPTH_COMPONENT } else { gl::RGBA };
        let data_format = if depth { gl::DEPTH_COMPONENT } else { gl::RGBA };
        let data_type = if floating {
            gl::FLOAT
        } else {
            gl::UNSIGNED_BYTE
        };

        unsafe {
            gl::PixelStorei(gl::UNPACK_ALIGNMENT, 1);
            gl::GenTextures(1, &mut texture_name);

            gl::BindTexture(
                if msaa > 1 {
                    gl::TEXTURE_2D_MULTISAMPLE
                } else {
                    gl::TEXTURE_2D
                },
                texture_name,
            );
            if msaa > 1 {
                gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::REPEAT as GLint);
                gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::REPEAT as GLint);
                gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST as GLint);
                gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as GLint);
            } else {
                gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::REPEAT as GLint);
                gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::REPEAT as GLint);
                gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as GLint);
                gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as GLint);
            }

            let mut ptr: *const raw::c_void = ptr::null();
            if data.len() != 0 {
                ptr = mem::transmute(data.as_ptr());
            };

            if msaa > 1 {
                gl::TexImage2DMultisample(
                    gl::TEXTURE_2D_MULTISAMPLE,
                    msaa as GLsizei,
                    internal_format as GLuint,
                    width as GLint,
                    height as GLint,
                    gl::FALSE, // Fixed sample locations
                );
            } else {
                gl::TexImage2D(
                    gl::TEXTURE_2D,
                    0, // Mipmap level
                    internal_format as GLint,
                    width as GLint,
                    height as GLint,
                    0, // Border: must be zero
                    data_format,
                    data_type,
                    ptr,
                );
            }
        }

        TextureGl { texture_name: texture_name }
    }