use super::*;
use super::errors::*;
use raw::*;
use buffer::*;

use std::cell::Cell;

pub struct TextureBuffer<'a> {
    id: TextureId,

    inner_buffer: TextureBufferInner<'a>,

    bindless_id: TextureBindlessId,

    internal_format: TextureBufferInternalFormat,

    bindless_counter: Cell<usize>,
}

pub enum TextureBufferInner<'a> {
    OwnedImmutableBuffer(ImmutableBuffer),
    OwnedMutableBuffer(MutableBuffer),
    BorrowedImmutableBuffer(&'a ImmutableBuffer),
    ImmutablyBorrowedMutableBuffer(&'a MutableBuffer),
    MutablyBorrowedMutableBuffer(&'a mut MutableBuffer),
}

impl<'a> Texture for TextureBuffer<'a> {
    fn get_id(&self) -> TextureId {
        self.id
    }

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

    fn make_resident_texture(&self) -> ResidentTexture {
        ResidentTexture::new(self.get_bindless_texture_id(), &self.bindless_counter)
    }

    fn get_target(&self) -> TextureTarget {
        TextureTarget::TextureBuffer
    }
}

impl<'a> TextureBuffer<'a> {
    pub fn new(
        internal_format: TextureBufferInternalFormat,
        buffer: TextureBufferInner,
    ) -> Result<TextureBuffer> {
        unsafe {
            let id = create_texture(TextureTarget::TextureBuffer);

            let buffer_id = match buffer {
                TextureBufferInner::OwnedImmutableBuffer(ref buffer) => buffer.get_id(),
                TextureBufferInner::OwnedMutableBuffer(ref buffer) => buffer.get_id(),
                TextureBufferInner::BorrowedImmutableBuffer(ref buffer) => buffer.get_id(),
                TextureBufferInner::ImmutablyBorrowedMutableBuffer(ref buffer) => buffer.get_id(),
                TextureBufferInner::MutablyBorrowedMutableBuffer(ref buffer) => buffer.get_id(),
            };

            texture_buffer(id, internal_format, buffer_id).chain_err(
                || "Unable to attach buffer to texture",
            )?;

            let bindless_id = get_texture_handle(id.into()).chain_err(
                || "Could not create bindless id",
            )?;

            let texture = TextureBuffer {
                id: id,

                inner_buffer: buffer,

                bindless_id: bindless_id,

                internal_format: internal_format,

                bindless_counter: Cell::new(0),
            };

            Ok(texture)
        }
    }

    pub fn make_resident_image(
        &self,
        level: usize,
        internal_format: ImageInternalFormat,
        access: ImageAccess,
    ) -> Result<ResidentImage> {
        let bindless_id = unsafe {
            get_image_handle(self.id, level, Some(0), internal_format)
                .chain_err(|| "Could not create image handle")?
        };

        ResidentImage::new(bindless_id, access)
    }
}

impl<'a> Drop for TextureBuffer<'a> {
    fn drop(&mut self) {
        unsafe { delete_texture(self.id) };
    }
}
