mod errors;

mod texture_1d;
pub use self::texture_1d::*;

mod texture_2d;
pub use self::texture_2d::*;

mod texture_3d;
pub use self::texture_3d::*;

mod texture_cubemap;
pub use self::texture_cubemap::*;

mod texture_buffer;
pub use self::texture_buffer::*;

use std;
use std::cell::Cell;
use raw::*;

use self::errors::*;

pub trait Texture {
    fn get_id(&self) -> TextureId;
    fn get_bindless_texture_id(&self) -> TextureBindlessId;
    fn make_resident_texture(&self) -> ResidentTexture;
    fn get_target(&self) -> TextureTarget;
}

pub trait TextureDataPrimitive {}

impl TextureDataPrimitive for i8 {}
impl TextureDataPrimitive for i16 {}
impl TextureDataPrimitive for i32 {}

impl TextureDataPrimitive for u8 {}
impl TextureDataPrimitive for u16 {}
impl TextureDataPrimitive for u32 {}

impl TextureDataPrimitive for f32 {}


#[derive(Debug)]
pub struct ResidentTexture<'a> {
    bindless_id: TextureBindlessId,
    bindless_counter: &'a Cell<usize>,
}

impl<'a> ResidentTexture<'a> {
    // assuming bindless textures are available and counter works
    #[allow(unused_must_use)]
    pub fn new(
        bindless_id: TextureBindlessId,
        bindless_counter: &'a Cell<usize>,
    ) -> ResidentTexture<'a> {
        let counter = bindless_counter.get();
        if counter == 0 {
            unsafe {
                make_texture_handle_resident(bindless_id.into());
            }
        }
        bindless_counter.set(counter + 1);
        ResidentTexture {
            bindless_id: bindless_id,
            bindless_counter: bindless_counter,
        }
    }

    pub fn get_id(&self) -> TextureBindlessId {
        self.bindless_id
    }
}

impl<'a> Drop for ResidentTexture<'a> {
    // assuming counter works
    #[allow(unused_must_use)]
    fn drop(&mut self) {
        let counter = self.bindless_counter.get();
        if counter == 1 {
            unsafe {
                make_texture_handle_non_resident(self.bindless_id.into());
            }
        }

        self.bindless_counter.set(counter - 1);
    }
}

// highly unstable
// TODO needs collision checking
#[derive(Debug)]
pub struct ResidentImage {
    bindless_id: ImageBindlessId,
}

impl ResidentImage {
    #[allow(unused_must_use)]
    pub fn new(bindless_id: ImageBindlessId, access: ImageAccess) -> Result<ResidentImage> {
        use raw::get_error;

        unsafe {
            make_image_handle_resident(bindless_id.into(), access);
        }


        match get_error(()) {
            Ok(_) => {}
            Err(e) => Err(e).chain_err(|| "Could not make image handle resident")?,
        }

        Ok(ResidentImage { bindless_id: bindless_id })
    }

    pub fn get_id(&self) -> ImageBindlessId {
        self.bindless_id
    }
}

impl Drop for ResidentImage {
    #[allow(unused_must_use)]
    fn drop(&mut self) {
        unsafe {
            make_image_handle_non_resident(self.bindless_id.into());
        }
    }
}

fn discrete_floored_log2(mut n: usize) -> usize {
    let mut ret = 0;

    while n > 1 {
        n /= 2;
        ret += 1;
    }

    ret
}

// may panic if level is too high but Result<> is not necessary as it is for internal use only
fn texture_size_mipmap(size: usize, level: usize) -> usize {
    std::cmp::max(1, size / 2u32.pow(level as u32) as usize)
}
