use crate::uses::*;

#[cfg(not(feature = "gl45"))]
pub const GL_VERSION: (u32, u32) = (3, 3);
#[cfg(feature = "gl45")]
pub const GL_VERSION: (u32, u32) = (4, 5);

#[cfg(not(feature = "gl45"))]
macro_rules! G {
	($g33: expr, $g45: expr) => {
		$g33
	};
}
#[cfg(feature = "gl45")]
macro_rules! G {
	($g33: expr, $g45: expr) => {
		$g45
	};
}

pub unsafe fn glCreateBuffer(obj: &mut u32) {
	G!(gl::GenBuffers(1, obj), gl::CreateBuffers(1, obj));
}
pub unsafe fn glCreateVao(obj: &mut u32) {
	G!(gl::GenVertexArrays(1, obj), gl::CreateVertexArrays(1, obj));
}
pub unsafe fn glCreateTexture(typ: GLenum, obj: &mut u32) {
	G!(gl::GenTextures(1, obj), gl::CreateTextures(typ, 1, obj));
}
pub unsafe fn glDeleteTexture(obj: &mut u32) {
	G!(
		{
			if *obj == *bound_tex33() {
				*bound_tex33() = 0;
			}
			gl::DeleteTextures(1, obj);
		},
		gl::DeleteTextures(1, obj)
	);
}
pub unsafe fn glCreateFramebuff(obj: &mut u32) {
	G!(gl::GenFramebuffers(1, obj), gl::CreateFramebuffers(1, obj));
}
pub unsafe fn glCreateRenderbuff(obj: &mut u32) {
	G!(gl::GenRenderbuffers(1, obj), gl::CreateRenderbuffers(1, obj));
}
pub unsafe fn glBufferStorage(_typ: GLenum, obj: u32, size: isize, data: *const GLvoid, usage: GLenum) {
	G!(
		{
			gl::BindBuffer(_typ, obj);
			gl::BufferData(_typ, size, data, gl::DYNAMIC_DRAW);
		},
		gl::NamedBufferStorage(obj, size, data, usage)
	);
}
pub unsafe fn glBufferSubData(_typ: GLenum, obj: u32, offset: isize, size: isize, data: *const GLvoid) {
	G!(
		{
			gl::BindBuffer(_typ, obj);
			gl::BufferSubData(_typ, offset, size, data);
		},
		gl::NamedBufferSubData(obj, offset, size, data)
	);
}
pub unsafe fn glMapBufferRange(_typ: GLenum, obj: u32, offset: isize, length: isize, access: GLbitfield) -> *mut GLvoid {
	G!(
		{
			gl::BindBuffer(_typ, obj);
			gl::MapBufferRange(_typ, offset, length, access)
		},
		gl::MapNamedBufferRange(obj, offset, length, access)
	)
}
pub unsafe fn glUnmapBuffer(_typ: GLenum, obj: u32) -> GLbool {
	G!(
		{
			gl::BindBuffer(_typ, obj);
			gl::UnmapBuffer(_typ)
		},
		gl::UnmapNamedBuffer(obj)
	)
}
pub unsafe fn glVaoElementBuffer(vao: u32, buf: u32) {
	G!(
		{
			gl::BindVertexArray(vao);
			gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, buf);
			gl::BindVertexArray(0);
		},
		gl::VertexArrayElementBuffer(vao, buf)
	);
}
pub unsafe fn glVertexAttribFormat(vao: u32, buf: u32, idx: u32, size: u32, typ: GLenum, normalized: GLbool, stride: u32, offset: u32, t_size: u32) {
	G!(
		{
			gl::BindVertexArray(vao);
			gl::BindBuffer(gl::ARRAY_BUFFER, buf);
			gl::EnableVertexAttribArray(idx);
			gl::VertexAttribPointer(idx, i32::to(size), typ, normalized, i32::to(stride), (offset * t_size) as *const GLvoid);
			gl::BindVertexArray(0);
		},
		{
			gl::EnableVertexArrayAttrib(vao, idx);
			gl::VertexArrayVertexBuffer(vao, idx, buf, 0, i32::to((size + stride) * t_size));
			gl::VertexArrayAttribFormat(vao, idx, i32::to(size), typ, normalized, offset * t_size);
			gl::VertexArrayAttribBinding(vao, idx, idx);
		}
	);
}
pub unsafe fn glTextureBuffer(_typ: GLenum, tex: u32, fmt: GLenum, buf: u32) {
	G!(
		{
			gl::BindTexture(_typ, tex);
			gl::TexBuffer(_typ, fmt, buf);
			gl::BindTexture(_typ, *bound_tex33());
		},
		gl::TextureBuffer(tex, fmt, buf)
	);
}
pub unsafe fn glTextureStorage1D(_typ: GLenum, tex: u32, levels: i32, fmt: GLenum, w: i32) {
	G!(
		{
			gl::BindTexture(_typ, tex);
			let mut w = w;
			for lvl in 0..levels {
				gl::TexImage1D(_typ, lvl, formatDepth45to33(fmt), w, 0, gl::RGBA, gl::FLOAT, 0 as *const GLvoid);
				w = 1.max(w / 2);
			}
			gl::BindTexture(_typ, *bound_tex33());
		},
		gl::TextureStorage1D(tex, levels, fmt, w)
	);
}
pub unsafe fn glTextureStorage2D(_typ: GLenum, tex: u32, levels: i32, fmt: GLenum, w: i32, h: i32) {
	G!(
		{
			gl::BindTexture(_typ, tex);
			let (mut w, mut h) = (w, h);
			for lvl in 0..levels {
				gl::TexImage2D(_typ, lvl, formatDepth45to33(fmt), w, h, 0, gl::RGBA, gl::FLOAT, 0 as *const GLvoid);
				w = 1.max(w / 2);
				h = 1.max(h / 2);
			}
			gl::BindTexture(_typ, *bound_tex33());
		},
		gl::TextureStorage2D(tex, levels, fmt, w, h)
	);
}
pub unsafe fn glTextureStorage3D(_typ: GLenum, tex: u32, levels: i32, fmt: GLenum, w: i32, h: i32, d: i32) {
	G!(
		{
			gl::BindTexture(_typ, tex);
			let (mut w, mut h, mut d) = (w, h, d);
			for lvl in 0..levels {
				gl::TexImage3D(_typ, lvl, formatDepth45to33(fmt), w, h, d, 0, gl::RGBA, gl::FLOAT, 0 as *const GLvoid);
				w = 1.max(w / 2);
				h = 1.max(h / 2);
				d = 1.max(d / 2);
			}
			gl::BindTexture(_typ, *bound_tex33());
		},
		gl::TextureStorage3D(tex, levels, fmt, w, h, d)
	);
}
pub unsafe fn glTextureSubImage1D(_typ: GLenum, tex: u32, lvl: i32, x: i32, w: i32, fmt: GLenum, t: GLenum, data: *const GLvoid) {
	G!(
		{
			gl::BindTexture(_typ, tex);
			gl::TexSubImage1D(_typ, lvl, x, w, fmt, t, data);
			gl::BindTexture(_typ, *bound_tex33());
		},
		gl::TextureSubImage1D(tex, lvl, x, w, fmt, t, data)
	);
}
pub unsafe fn glTextureSubImage2D(_typ: GLenum, tex: u32, lvl: i32, x: i32, y: i32, w: i32, h: i32, fmt: GLenum, t: GLenum, data: *const GLvoid) {
	G!(
		{
			gl::BindTexture(_typ, tex);
			gl::TexSubImage2D(_typ, lvl, x, y, w, h, fmt, t, data);
			gl::BindTexture(_typ, *bound_tex33());
		},
		gl::TextureSubImage2D(tex, lvl, x, y, w, h, fmt, t, data)
	);
}
pub unsafe fn glTextureSubImage3D(_typ: GLenum, tex: u32, lvl: i32, x: i32, y: i32, z: i32, w: i32, h: i32, d: i32, fmt: GLenum, t: GLenum, data: *const GLvoid) {
	G!(
		{
			gl::BindTexture(_typ, tex);
			gl::TexSubImage3D(_typ, lvl, x, y, z, w, h, d, fmt, t, data);
			gl::BindTexture(_typ, *bound_tex33());
		},
		gl::TextureSubImage3D(tex, lvl, x, y, z, w, h, d, fmt, t, data)
	);
}
pub unsafe fn glBindTextureUnit(_typ: GLenum, unit: u32, tex: u32) {
	G!(
		{
			gl::ActiveTexture(gl::TEXTURE0 + unit);
			gl::BindTexture(_typ, tex);
			*bound_tex33() = tex;
		},
		gl::BindTextureUnit(unit, tex)
	);
}
pub unsafe fn glGenMipmaps(_typ: GLenum, tex: u32) {
	G!(
		{
			gl::BindTexture(_typ, tex);
			gl::GenerateMipmap(_typ);
			gl::BindTexture(_typ, *bound_tex33());
		},
		gl::GenerateTextureMipmap(tex)
	);
}
pub unsafe fn glGetTexture(_typ: GLenum, tex: u32, lvl: i32, fmt: GLenum, t: GLenum, size: i32, data: *mut GLvoid) {
	G!(
		{
			gl::BindTexture(_typ, tex);
			gl::GetTexImage(_typ, lvl, fmt, t, data);
			gl::BindTexture(_typ, *bound_tex33());
		},
		gl::GetTextureImage(tex, lvl, fmt, t, size, data)
	);
}
pub unsafe fn glClearFramebuff(fb: u32, typ: GLenum, buffidx: i32, val: *const f32) {
	G!(
		{
			gl::BindFramebuffer(gl::FRAMEBUFFER, fb);
			gl::ClearBufferfv(typ, buffidx, val);
			gl::BindFramebuffer(gl::FRAMEBUFFER, 0);
		},
		gl::ClearNamedFramebufferfv(fb, typ, buffidx, val)
	);
}
pub unsafe fn glFramebuffTex(fb: u32, tex: u32, attach: GLenum) {
	G!(
		{
			gl::BindFramebuffer(gl::FRAMEBUFFER, fb);
			gl::FramebufferTexture(gl::FRAMEBUFFER, attach, tex, 0);
			gl::BindFramebuffer(gl::FRAMEBUFFER, 0);
		},
		gl::NamedFramebufferTexture(fb, attach, tex, 0)
	);
}
pub unsafe fn glFramebuffRenderbuff(fb: u32, rb: u32, attach: GLenum) {
	G!(
		{
			gl::BindFramebuffer(gl::FRAMEBUFFER, fb);
			gl::FramebufferRenderbuffer(gl::FRAMEBUFFER, attach, gl::RENDERBUFFER, rb);
			gl::BindFramebuffer(gl::FRAMEBUFFER, 0);
		},
		gl::NamedFramebufferRenderbuffer(fb, attach, gl::RENDERBUFFER, rb)
	);
}
pub unsafe fn glRenderbuffStorage(fb: u32, sampl: i32, fmt: GLenum, w: i32, h: i32) {
	G!(
		{
			gl::BindRenderbuffer(gl::RENDERBUFFER, fb);
			if sampl == 1 {
				gl::RenderbufferStorage(gl::RENDERBUFFER, fmt, w, h);
			} else {
				gl::RenderbufferStorageMultisample(gl::RENDERBUFFER, sampl, fmt, w, h);
			}
			gl::BindRenderbuffer(gl::RENDERBUFFER, 0);
		},
		if sampl == 1 {
			gl::NamedRenderbufferStorage(fb, fmt, w, h);
		} else {
			gl::NamedRenderbufferStorageMultisample(fb, sampl, fmt, w, h);
		}
	);
}

fn formatDepth45to33(fmt: GLenum) -> i32 {
	i32::to(if fmt == gl::DEPTH_COMPONENT32F || fmt == gl::DEPTH_COMPONENT24 || fmt == gl::DEPTH_COMPONENT16 {
		WARN!("Using unspecified GL_DEPTH_COMPONENT size");
		gl::DEPTH_COMPONENT
	} else {
		fmt
	})
}

fn bound_tex33() -> &'static mut u32 {
	static mut STATE: u32 = 0;
	unsafe { &mut STATE }
}
