use std::fs::File;
use std::io::{Read, Seek};
use std::path::Path;

use imagefmt::{read_from, ColFmt, Error as ImageFmtError};

use crate::{
    error::Error,
    misc::{AsEnum, BindGuard, Bindable},
};

/* Texture */

pub struct Texture {
    id: gl::GLuint,
    target: Target,
    filter_min: FilterMin,
    filter_mag: FilterMag,
    wrap_s: Wrap,
    wrap_t: Wrap,
    wrap_r: Wrap,
}

impl Texture {
    pub fn new(target: Target) -> Result<Self, Error> {
        let mut id = 0;

        Error::checked(|| gl::create_textures(target.as_enum(), 1, &mut id))?;

        Ok(Self {
            id,
            target,
            filter_min: FilterMin::Nearest,
            filter_mag: FilterMag::Nearest,
            wrap_s: Wrap::Repeat,
            wrap_t: Wrap::Repeat,
            wrap_r: Wrap::Repeat,
        })
    }

    pub fn id(&self) -> gl::GLuint {
        self.id
    }

    pub fn upload(&mut self, data: &Data, generate_mipmap: bool) -> Result<(), Error> {
        let info = data.format.info();

        if info.gl_format == 0 || info.gl_format_data == 0 || info.gl_format_internal == 0 {
            return Err(Error::TextureUnsupportedFormat);
        }

        let target = self.target.as_enum();
        let _guard = BindGuard::new(&*self);

        Error::checked(|| {
            gl::tex_image_2d(
                target,
                0,
                info.gl_format_internal as gl::GLint,
                data.width as gl::GLint,
                data.height as gl::GLint,
                0,
                info.gl_format,
                info.gl_format_data,
                data.buffer.as_ptr() as *const _,
            )
        })?;

        if generate_mipmap {
            Error::checked(|| gl::generate_mipmap(target))?;
        }

        self.setup()
    }

    pub fn set_filter(&mut self, min: FilterMin, mag: FilterMag) -> Result<(), Error> {
        self.filter_min = min;
        self.filter_mag = mag;

        self.setup()
    }

    pub fn set_wrap(&mut self, s: Wrap, t: Wrap, r: Wrap) -> Result<(), Error> {
        self.wrap_s = s;
        self.wrap_t = t;
        self.wrap_r = r;

        self.setup()
    }

    fn setup(&self) -> Result<(), Error> {
        Error::checked(|| {
            gl::texture_parameter_i(
                self.id,
                gl::TEXTURE_MIN_FILTER,
                self.filter_min.as_enum() as gl::GLint,
            )
        })?;
        Error::checked(|| {
            gl::texture_parameter_i(
                self.id,
                gl::TEXTURE_MAG_FILTER,
                self.filter_mag.as_enum() as gl::GLint,
            )
        })?;
        Error::checked(|| {
            gl::texture_parameter_i(
                self.id,
                gl::TEXTURE_WRAP_S,
                self.wrap_s.as_enum() as gl::GLint,
            )
        })?;
        Error::checked(|| {
            gl::texture_parameter_i(
                self.id,
                gl::TEXTURE_WRAP_T,
                self.wrap_t.as_enum() as gl::GLint,
            )
        })?;
        Error::checked(|| {
            gl::texture_parameter_i(
                self.id,
                gl::TEXTURE_WRAP_R,
                self.wrap_r.as_enum() as gl::GLint,
            )
        })?;

        Ok(())
    }
}

impl Drop for Texture {
    fn drop(&mut self) {
        gl::delete_textures(1, &self.id);
    }
}

impl Bindable for Texture {
    fn bind(&self) {
        gl::bind_texture(self.target.as_enum(), self.id);
    }

    fn unbind(&self) {
        gl::bind_texture(self.target.as_enum(), 0);
    }
}

/* Data */

#[derive(Clone, Debug)]
pub struct Data {
    width: usize,
    height: usize,
    format: Format,
    buffer: Vec<u8>,
}

impl Data {
    pub fn from_parts(width: usize, height: usize, format: Format, buffer: Vec<u8>) -> Self {
        Self {
            width,
            height,
            format,
            buffer,
        }
    }

    pub fn from_reader<R>(reader: &mut R) -> Result<Self, Error>
    where
        R: Read + Seek,
    {
        let image = read_from(reader, ColFmt::Auto)?;

        Ok(Self {
            width: image.w,
            height: image.h,
            format: match image.fmt {
                ColFmt::Y => Format::Luminance8ub1,
                ColFmt::YA => Format::Luminance8Alpha8ub2,
                ColFmt::AY => Format::Alpha8Luminance8ub2,
                ColFmt::RGB => Format::RGB8ub3,
                ColFmt::RGBA => Format::RGBA8ub4,
                ColFmt::BGR => Format::BGR8ub3,
                ColFmt::BGRA => Format::BGRA8ub4,
                ColFmt::ARGB => Format::ARGB8ub4,
                ColFmt::ABGR => Format::ABGR8ub4,
                ColFmt::Auto => {
                    return Err(ImageFmtError::Internal("Unexpected Format: Auto").into())
                }
            },
            buffer: image.buf,
        })
    }

    pub fn from_file<P>(path: P) -> Result<Self, Error>
    where
        P: AsRef<Path>,
    {
        let mut file = File::open(path)?;

        Self::from_reader(&mut file)
    }

    pub fn format(&self) -> Format {
        self.format
    }

    pub fn width(&self) -> usize {
        self.width
    }

    pub fn height(&self) -> usize {
        self.height
    }

    pub fn convert<F>(&mut self, format: Format, func: F) -> Result<(), Error>
    where
        F: Fn(usize, usize, &mut PixelData),
    {
        let dst_info = format.info();

        let mut dst_descriptor = format.descriptor();
        let mut src_descriptor = self.format.descriptor();

        let mut tmp = Vec::with_capacity(self.width * self.height * dst_info.bits_per_pixel / 8);

        let mut src = self.buffer.as_slice();
        let mut dst = tmp.as_mut_slice();
        let mut data = PixelData::default();

        for x in 0..self.width {
            for y in 0..self.height {
                let b = src_descriptor.unmap(&mut data, src)?;
                src = &src[b..];

                func(x, y, &mut data);

                let b = dst_descriptor.map(&data, dst)?;
                dst = &mut dst[b..];
            }
        }

        self.buffer = tmp;

        Ok(())
    }

    pub fn convert_to(&mut self, format: Format) -> Result<(), Error> {
        if format == self.format {
            return Ok(());
        }

        if self.format.is_bit_equal(&format) {
            self.format = format;

            return Ok(());
        }

        let dst_info = format.info();
        let src_info = self.format.info();

        if src_info.same_size_channels(&dst_info) {
            self.convert(format, |_, _, _| {})
        } else {
            self.convert(format, |_, _, data| {
                data.r = data.r.map(|x| x * dst_info.r_max / src_info.r_max);
                data.g = data.g.map(|x| x * dst_info.g_max / src_info.g_max);
                data.b = data.b.map(|x| x * dst_info.b_max / src_info.b_max);
                data.a = data.a.map(|x| x * dst_info.a_max / src_info.a_max);
            })
        }
    }
}

/* FormatDescriptor */

pub trait FormatDescriptor {
    fn map(&mut self, data: &PixelData, buf: &mut [u8]) -> Result<usize, Error>;
    fn unmap(&mut self, data: &mut PixelData, buf: &[u8]) -> Result<usize, Error>;
}

struct Luminance8ub1Descriptor;

impl FormatDescriptor for Luminance8ub1Descriptor {
    fn map(&mut self, data: &PixelData, buf: &mut [u8]) -> Result<usize, Error> {
        check_len(&buf, 1)?;

        if data.has_rgb() {
            buf[0] = data.luminance() as u8;
        } else {
            buf[0] = data.a.unwrap_or(0) as u8;
        }

        Ok(1)
    }

    fn unmap(&mut self, data: &mut PixelData, buf: &[u8]) -> Result<usize, Error> {
        check_len(&buf, 1)?;

        data.r = Some(buf[0] as u32);
        data.g = Some(buf[0] as u32);
        data.b = Some(buf[0] as u32);
        data.a = None;

        Ok(1)
    }
}

struct Luminance8Alpha8ub2Descriptor;

impl FormatDescriptor for Luminance8Alpha8ub2Descriptor {
    fn map(&mut self, data: &PixelData, buf: &mut [u8]) -> Result<usize, Error> {
        check_len(&buf, 2)?;

        buf[0] = data.luminance() as u8;
        buf[1] = data.a.unwrap_or(255) as u8;

        Ok(2)
    }

    fn unmap(&mut self, data: &mut PixelData, buf: &[u8]) -> Result<usize, Error> {
        check_len(&buf, 2)?;

        data.r = Some(buf[0] as u32);
        data.g = Some(buf[0] as u32);
        data.b = Some(buf[0] as u32);
        data.a = Some(buf[1] as u32);

        Ok(2)
    }
}

struct Alpha8Luminance8ub2Descriptor;

impl FormatDescriptor for Alpha8Luminance8ub2Descriptor {
    fn map(&mut self, data: &PixelData, buf: &mut [u8]) -> Result<usize, Error> {
        check_len(&buf, 2)?;

        buf[0] = data.a.unwrap_or(255) as u8;
        buf[1] = data.luminance() as u8;

        Ok(2)
    }

    fn unmap(&mut self, data: &mut PixelData, buf: &[u8]) -> Result<usize, Error> {
        check_len(&buf, 2)?;

        data.r = Some(buf[1] as u32);
        data.g = Some(buf[1] as u32);
        data.b = Some(buf[1] as u32);
        data.a = Some(buf[0] as u32);

        Ok(2)
    }
}

struct RGB8ub3Descriptor;

impl FormatDescriptor for RGB8ub3Descriptor {
    fn map(&mut self, data: &PixelData, buf: &mut [u8]) -> Result<usize, Error> {
        check_len(&buf, 3)?;

        buf[0] = data.r.unwrap_or(0) as u8;
        buf[1] = data.g.unwrap_or(0) as u8;
        buf[2] = data.b.unwrap_or(0) as u8;

        Ok(3)
    }

    fn unmap(&mut self, data: &mut PixelData, buf: &[u8]) -> Result<usize, Error> {
        check_len(&buf, 3)?;

        data.r = Some(buf[0] as u32);
        data.g = Some(buf[1] as u32);
        data.b = Some(buf[2] as u32);
        data.a = None;

        Ok(3)
    }
}

struct RGBA8ub4Descriptor;

impl FormatDescriptor for RGBA8ub4Descriptor {
    fn map(&mut self, data: &PixelData, buf: &mut [u8]) -> Result<usize, Error> {
        check_len(&buf, 4)?;

        buf[0] = data.r.unwrap_or(0) as u8;
        buf[1] = data.g.unwrap_or(0) as u8;
        buf[2] = data.b.unwrap_or(0) as u8;
        buf[3] = data.a.unwrap_or(0) as u8;

        Ok(4)
    }

    fn unmap(&mut self, data: &mut PixelData, buf: &[u8]) -> Result<usize, Error> {
        check_len(&buf, 4)?;

        data.r = Some(buf[0] as u32);
        data.g = Some(buf[1] as u32);
        data.b = Some(buf[2] as u32);
        data.a = Some(buf[3] as u32);

        Ok(4)
    }
}

struct ARGB8ub4Descriptor;

impl FormatDescriptor for ARGB8ub4Descriptor {
    fn map(&mut self, data: &PixelData, buf: &mut [u8]) -> Result<usize, Error> {
        check_len(&buf, 4)?;

        buf[0] = data.a.unwrap_or(0) as u8;
        buf[1] = data.r.unwrap_or(0) as u8;
        buf[2] = data.g.unwrap_or(0) as u8;
        buf[3] = data.b.unwrap_or(0) as u8;

        Ok(4)
    }

    fn unmap(&mut self, data: &mut PixelData, buf: &[u8]) -> Result<usize, Error> {
        check_len(&buf, 4)?;

        data.a = Some(buf[0] as u32);
        data.r = Some(buf[1] as u32);
        data.g = Some(buf[2] as u32);
        data.b = Some(buf[3] as u32);

        Ok(4)
    }
}

struct BGR8ub3Descriptor;

impl FormatDescriptor for BGR8ub3Descriptor {
    fn map(&mut self, data: &PixelData, buf: &mut [u8]) -> Result<usize, Error> {
        check_len(&buf, 3)?;

        buf[0] = data.b.unwrap_or(0) as u8;
        buf[1] = data.g.unwrap_or(0) as u8;
        buf[2] = data.r.unwrap_or(0) as u8;

        Ok(3)
    }

    fn unmap(&mut self, data: &mut PixelData, buf: &[u8]) -> Result<usize, Error> {
        check_len(&buf, 3)?;

        data.b = Some(buf[1] as u32);
        data.g = Some(buf[2] as u32);
        data.r = Some(buf[3] as u32);
        data.a = None;

        Ok(3)
    }
}

struct BGRA8ub4Descriptor;

impl FormatDescriptor for BGRA8ub4Descriptor {
    fn map(&mut self, data: &PixelData, buf: &mut [u8]) -> Result<usize, Error> {
        check_len(&buf, 4)?;

        buf[0] = data.b.unwrap_or(0) as u8;
        buf[1] = data.g.unwrap_or(0) as u8;
        buf[2] = data.r.unwrap_or(0) as u8;
        buf[3] = data.a.unwrap_or(0) as u8;

        Ok(4)
    }

    fn unmap(&mut self, data: &mut PixelData, buf: &[u8]) -> Result<usize, Error> {
        check_len(&buf, 4)?;

        data.b = Some(buf[0] as u32);
        data.g = Some(buf[1] as u32);
        data.r = Some(buf[2] as u32);
        data.a = Some(buf[3] as u32);

        Ok(4)
    }
}

struct ABGR8ub4Descriptor;

impl FormatDescriptor for ABGR8ub4Descriptor {
    fn map(&mut self, data: &PixelData, buf: &mut [u8]) -> Result<usize, Error> {
        check_len(&buf, 4)?;

        buf[0] = data.a.unwrap_or(0) as u8;
        buf[1] = data.b.unwrap_or(0) as u8;
        buf[2] = data.g.unwrap_or(0) as u8;
        buf[3] = data.r.unwrap_or(0) as u8;

        Ok(4)
    }

    fn unmap(&mut self, data: &mut PixelData, buf: &[u8]) -> Result<usize, Error> {
        check_len(&buf, 4)?;

        data.a = Some(buf[0] as u32);
        data.b = Some(buf[1] as u32);
        data.g = Some(buf[2] as u32);
        data.r = Some(buf[3] as u32);

        Ok(4)
    }
}

fn check_len(buf: &[u8], len: usize) -> Result<(), Error> {
    if buf.len() < len {
        Err(Error::TextureBufferOverflow)
    } else {
        Ok(())
    }
}

/* PixelData */

#[derive(Default)]
pub struct PixelData {
    pub r: Option<u32>,
    pub g: Option<u32>,
    pub b: Option<u32>,
    pub a: Option<u32>,
}

impl PixelData {
    pub fn has_rgb(&self) -> bool {
        self.r.is_some() && self.g.is_some() && self.b.is_some()
    }

    pub fn luminance(&self) -> u32 {
        (LUMINANCE_WEIGHT_R * self.r.unwrap_or(0) as f32
            + LUMINANCE_WEIGHT_G * self.g.unwrap_or(0) as f32
            + LUMINANCE_WEIGHT_B * self.b.unwrap_or(0) as f32) as u32
    }
}

const LUMINANCE_WEIGHT_R: f32 = 0.30;
const LUMINANCE_WEIGHT_G: f32 = 0.59;
const LUMINANCE_WEIGHT_B: f32 = 0.11;

/* FormatInfo */

pub struct FormatInfo {
    pub gl_format: gl::GLenum,
    pub gl_format_data: gl::GLenum,
    pub gl_format_internal: gl::GLenum,

    pub format_gl_support: Format,
    pub format_with_alpha: Format,
    pub format_without_alpha: Format,

    pub bits_per_pixel: usize,

    pub r_max: u32,
    pub g_max: u32,
    pub b_max: u32,
    pub a_max: u32,

    pub r_bits: u32,
    pub g_bits: u32,
    pub b_bits: u32,
    pub a_bits: u32,
}

impl FormatInfo {
    pub fn same_size_channels(&self, other: &Self) -> bool {
        (self.r_bits == other.r_bits || self.r_bits == 0 || other.r_bits == 0)
            && (self.g_bits == other.g_bits || self.g_bits == 0 || other.g_bits == 0)
            && (self.b_bits == other.b_bits || self.b_bits == 0 || other.b_bits == 0)
            && (self.a_bits == other.a_bits || self.a_bits == 0 || other.a_bits == 0)
    }
}

static INFO_LUMINANCE8_UB1: FormatInfo = FormatInfo {
    gl_format: gl::LUMINANCE,
    gl_format_data: gl::UNSIGNED_BYTE,
    gl_format_internal: gl::LUMINANCE8,
    format_gl_support: Format::Luminance8ub1,
    format_with_alpha: Format::Luminance8Alpha8ub2,
    format_without_alpha: Format::Luminance8ub1,
    bits_per_pixel: 8,
    r_max: 255,
    g_max: 255,
    b_max: 255,
    a_max: 0,
    r_bits: 8,
    g_bits: 8,
    b_bits: 8,
    a_bits: 0,
};
static INFO_LUMINANCE8_ALPHA8_UB2: FormatInfo = FormatInfo {
    gl_format: gl::LUMINANCE_ALPHA,
    gl_format_data: gl::UNSIGNED_BYTE,
    gl_format_internal: gl::LUMINANCE8_ALPHA8,
    format_gl_support: Format::Luminance8Alpha8ub2,
    format_with_alpha: Format::Luminance8Alpha8ub2,
    format_without_alpha: Format::Luminance8ub1,
    bits_per_pixel: 16,
    r_max: 255,
    g_max: 255,
    b_max: 255,
    a_max: 255,
    r_bits: 8,
    g_bits: 8,
    b_bits: 8,
    a_bits: 8,
};
static INFO_ALPHA8_LUMINANCE8_UB2: FormatInfo = FormatInfo {
    gl_format: gl::LUMINANCE_ALPHA,
    gl_format_data: gl::UNSIGNED_BYTE,
    gl_format_internal: gl::LUMINANCE8_ALPHA8,
    format_gl_support: Format::Luminance8Alpha8ub2,
    format_with_alpha: Format::Alpha8Luminance8ub2,
    format_without_alpha: Format::Luminance8ub1,
    bits_per_pixel: 16,
    r_max: 255,
    g_max: 255,
    b_max: 255,
    a_max: 255,
    r_bits: 8,
    g_bits: 8,
    b_bits: 8,
    a_bits: 8,
};
static INFO_RGB8_UB3: FormatInfo = FormatInfo {
    gl_format: gl::RGB,
    gl_format_data: gl::UNSIGNED_BYTE,
    gl_format_internal: gl::RGB8,
    format_gl_support: Format::RGB8ub3,
    format_with_alpha: Format::RGBA8ub4,
    format_without_alpha: Format::RGB8ub3,
    bits_per_pixel: 24,
    r_max: 255,
    g_max: 255,
    b_max: 255,
    a_max: 0,
    r_bits: 8,
    g_bits: 8,
    b_bits: 8,
    a_bits: 0,
};
static INFO_RGBA8_UB4: FormatInfo = FormatInfo {
    gl_format: gl::RGBA,
    gl_format_data: gl::UNSIGNED_BYTE,
    gl_format_internal: gl::RGBA8,
    format_gl_support: Format::RGBA8ub4,
    format_with_alpha: Format::RGBA8ub4,
    format_without_alpha: Format::RGB8ub3,
    bits_per_pixel: 32,
    r_max: 255,
    g_max: 255,
    b_max: 255,
    a_max: 255,
    r_bits: 8,
    g_bits: 8,
    b_bits: 8,
    a_bits: 8,
};
static INFO_ARGB8_UB4: FormatInfo = FormatInfo {
    gl_format: gl::RGBA,
    gl_format_data: gl::UNSIGNED_INT_8_8_8_8_REV,
    gl_format_internal: gl::RGBA8,
    format_gl_support: Format::ARGB8ub4,
    format_with_alpha: Format::ARGB8ub4,
    format_without_alpha: Format::RGB8ub3,
    bits_per_pixel: 32,
    r_max: 255,
    g_max: 255,
    b_max: 255,
    a_max: 255,
    r_bits: 8,
    g_bits: 8,
    b_bits: 8,
    a_bits: 8,
};
static INFO_BGR8_UB3: FormatInfo = FormatInfo {
    gl_format: gl::BGR,
    gl_format_data: gl::UNSIGNED_BYTE,
    gl_format_internal: gl::RGB8,
    format_gl_support: Format::BGR8ub3,
    format_with_alpha: Format::BGRA8ub4,
    format_without_alpha: Format::BGR8ub3,
    bits_per_pixel: 24,
    r_max: 255,
    g_max: 255,
    b_max: 255,
    a_max: 0,
    r_bits: 8,
    g_bits: 8,
    b_bits: 8,
    a_bits: 0,
};
static INFO_BGRA8_UB4: FormatInfo = FormatInfo {
    gl_format: gl::BGR,
    gl_format_data: gl::UNSIGNED_BYTE,
    gl_format_internal: gl::RGBA8,
    format_gl_support: Format::BGRA8ub4,
    format_with_alpha: Format::BGRA8ub4,
    format_without_alpha: Format::BGR8ub3,
    bits_per_pixel: 32,
    r_max: 255,
    g_max: 255,
    b_max: 255,
    a_max: 255,
    r_bits: 8,
    g_bits: 8,
    b_bits: 8,
    a_bits: 8,
};
static INFO_ABGR8_UB4: FormatInfo = FormatInfo {
    gl_format: gl::BGR,
    gl_format_data: gl::UNSIGNED_INT_8_8_8_8_REV,
    gl_format_internal: gl::RGBA8,
    format_gl_support: Format::ABGR8ub4,
    format_with_alpha: Format::ABGR8ub4,
    format_without_alpha: Format::BGR8ub3,
    bits_per_pixel: 32,
    r_max: 255,
    g_max: 255,
    b_max: 255,
    a_max: 255,
    r_bits: 8,
    g_bits: 8,
    b_bits: 8,
    a_bits: 8,
};

/* Format */

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Format {
    /// [LLLL_LLLL]
    Luminance8ub1,
    /// [LLLL_LLLL][AAAA_AAAA]
    Luminance8Alpha8ub2,
    /// [AAAA_AAAA][LLLL_LLLL]
    Alpha8Luminance8ub2,

    /// [RRRR_RRRR][GGGG_GGGG][BBBB_BBBB]
    RGB8ub3,
    /// [RRRR_RRRR][GGGG_GGGG][BBBB_BBBB][AAAA_AAAA]
    RGBA8ub4,
    /// [AAAA_AAAA][RRRR_RRRR][GGGG_GGGG][BBBB_BBBB]
    ARGB8ub4,

    /// [BBBB_BBBB][GGGG_GGGG][RRRR_RRRR]
    BGR8ub3,
    /// [BBBB_BBBB][GGGG_GGGG][RRRR_RRRR][AAAA_AAAA]
    BGRA8ub4,
    /// [AAAA_AAAA][BBBB_BBBB][GGGG_GGGG][RRRR_RRRR]
    ABGR8ub4,
}

impl Format {
    pub fn info(&self) -> &'static FormatInfo {
        match self {
            Format::Luminance8ub1 => &INFO_LUMINANCE8_UB1,
            Format::Luminance8Alpha8ub2 => &INFO_LUMINANCE8_ALPHA8_UB2,
            Format::Alpha8Luminance8ub2 => &INFO_ALPHA8_LUMINANCE8_UB2,
            Format::RGB8ub3 => &INFO_RGB8_UB3,
            Format::RGBA8ub4 => &INFO_RGBA8_UB4,
            Format::ARGB8ub4 => &INFO_ARGB8_UB4,
            Format::BGR8ub3 => &INFO_BGR8_UB3,
            Format::BGRA8ub4 => &INFO_BGRA8_UB4,
            Format::ABGR8ub4 => &INFO_ABGR8_UB4,
        }
    }

    pub fn descriptor(&self) -> Box<dyn FormatDescriptor> {
        match self {
            Format::Luminance8ub1 => Box::new(Luminance8ub1Descriptor),
            Format::Luminance8Alpha8ub2 => Box::new(Luminance8Alpha8ub2Descriptor),
            Format::Alpha8Luminance8ub2 => Box::new(Alpha8Luminance8ub2Descriptor),
            Format::RGB8ub3 => Box::new(RGB8ub3Descriptor),
            Format::RGBA8ub4 => Box::new(RGBA8ub4Descriptor),
            Format::ARGB8ub4 => Box::new(ARGB8ub4Descriptor),
            Format::BGR8ub3 => Box::new(BGR8ub3Descriptor),
            Format::BGRA8ub4 => Box::new(BGRA8ub4Descriptor),
            Format::ABGR8ub4 => Box::new(ABGR8ub4Descriptor),
        }
    }

    pub fn is_bit_equal(&self, _other: &Self) -> bool {
        false
    }
}

/* Target */

pub enum Target {
    Texture1D,
    Texture2D,
    Texture3D,
    Texture1DArray,
    Texture2DArray,
    TextureRectangle,
    TextureCubeMap,
    TextureCubeMapArray,
    TextureBuffer,
    Texture2DMultisample,
    Texture2DMultisampleArray,
}

impl AsEnum for Target {
    fn as_enum(&self) -> gl::GLenum {
        match self {
            Self::Texture1D => gl::TEXTURE_1D,
            Self::Texture2D => gl::TEXTURE_2D,
            Self::Texture3D => gl::TEXTURE_3D,
            Self::Texture1DArray => gl::TEXTURE_1D_ARRAY,
            Self::Texture2DArray => gl::TEXTURE_2D_ARRAY,
            Self::TextureRectangle => gl::TEXTURE_RECTANGLE,
            Self::TextureCubeMap => gl::TEXTURE_CUBE_MAP,
            Self::TextureCubeMapArray => gl::TEXTURE_CUBE_MAP_ARRAY,
            Self::TextureBuffer => gl::TEXTURE_BUFFER,
            Self::Texture2DMultisample => gl::TEXTURE_2D_MULTISAMPLE,
            Self::Texture2DMultisampleArray => gl::TEXTURE_2D_MULTISAMPLE_ARRAY,
        }
    }
}

/* FilterMin */

pub enum FilterMin {
    /// Returns the value of the texture element that is nearest
    /// (in Manhattan distance) to the specified texture coordinates.
    Nearest,

    /// Returns the weighted average of the four texture elements
    /// that are closest to the specified texture coordinates.
    /// These can include items wrapped or repeated from other parts
    /// of a texture, depending on the values of GL_TEXTURE_WRAP_S
    /// and GL_TEXTURE_WRAP_T, and on the exact mapping.
    Linear,

    /// Chooses the mipmap that most closely matches the size of the
    /// pixel being textured and uses the GL_NEAREST criterion (the
    /// texture element closest to the specified texture coordinates)
    /// to produce a texture value.
    NearestMipmapNearest,

    /// Chooses the mipmap that most closely matches the size of the pixel
    /// being textured and uses the GL_LINEAR criterion (a weighted average
    /// of the four texture elements that are closest to the specified
    /// texture coordinates) to produce a texture value.
    LinearMipmapNearest,

    /// Chooses the two mipmaps that most closely match the size of the pixel
    /// being textured and uses the GL_NEAREST criterion (the texture element
    /// closest to the specified texture coordinates ) to produce a texture
    /// value from each mipmap. The final texture value is a weighted average
    /// of those two values.
    NearestMipmapLinear,

    /// Chooses the two mipmaps that most closely match the size of the pixel
    /// being textured and uses the GL_LINEAR criterion (a weighted average of
    /// the texture elements that are closest to the specified texture coordinates)
    /// to produce a texture value from each mipmap. The final texture value
    /// is a weighted average of those two values.
    LinearMipmapLinear,
}

impl AsEnum for FilterMin {
    fn as_enum(&self) -> gl::GLenum {
        match self {
            Self::Nearest => gl::NEAREST,
            Self::Linear => gl::LINEAR,
            Self::NearestMipmapNearest => gl::NEAREST_MIPMAP_NEAREST,
            Self::LinearMipmapNearest => gl::LINEAR_MIPMAP_NEAREST,
            Self::NearestMipmapLinear => gl::NEAREST_MIPMAP_LINEAR,
            Self::LinearMipmapLinear => gl::LINEAR_MIPMAP_LINEAR,
        }
    }
}

/* FilterMag */

pub enum FilterMag {
    /// Returns the value of the texture element that is nearest
    /// (in Manhattan distance) to the specified texture coordinates.
    Nearest,

    /// Returns the weighted average of the texture elements that are
    /// closest to the specified texture coordinates. These can include
    /// items wrapped or repeated from other parts of a texture, depending
    /// on the values of GL_TEXTURE_WRAP_S and GL_TEXTURE_WRAP_T, and on
    /// the exact mapping.
    Linear,
}

impl AsEnum for FilterMag {
    fn as_enum(&self) -> gl::GLenum {
        match self {
            Self::Nearest => gl::NEAREST,
            Self::Linear => gl::LINEAR,
        }
    }
}

/* Wrap */

pub enum Wrap {
    ClampToEdge,
    ClampToBorder,
    MirrorRepeat,
    Repeat,
    MirrorClampToEdge,
}

impl AsEnum for Wrap {
    fn as_enum(&self) -> gl::GLenum {
        match self {
            Self::ClampToEdge => gl::CLAMP_TO_EDGE,
            Self::ClampToBorder => gl::CLAMP_TO_BORDER,
            Self::MirrorRepeat => gl::MIRRORED_REPEAT,
            Self::Repeat => gl::REPEAT,
            Self::MirrorClampToEdge => gl::MIRROR_CLAMP_TO_EDGE,
        }
    }
}
