import array
import os
import math
import sys
from typing import Iterable, overload

#"pip install pygame-ce"
with open( os.devnull, "w" ) as f:
	oldstdout=sys.stdout; sys.stdout=f; import pygame; sys.stdout=oldstdout

#"pip install PyOpenGL"
from OpenGL.GL  import *
from OpenGL.GLU import *

try:
	#(other basecode files, optional)
	from . import gl_buffer
	from . import gl_geom
	_GLMISC_HAS_GEOM = True
except ModuleNotFoundError:
	_GLMISC_HAS_GEOM = False

#(another basecode file, required)
from . import gl_texture



def set_gl2_projview_2D( res:tuple[int,int] ):
	glViewport( 0,0, res[0],res[1] )

	glMatrixMode(GL_PROJECTION)
	glLoadIdentity()
	glOrtho( 0.0,float(res[0]), 0.0,float(res[1]), -1.0,1.0 )

	glMatrixMode(GL_MODELVIEW)
	glLoadIdentity()

def set_gl2_projview_3D(
	res:tuple[int,int], fov_deg:float, clip_near:float,clip_far:float,
	pos:Iterable[float], cen:Iterable[float], up:Iterable[float]
):
	glViewport( 0,0, res[0],res[1] )

	glMatrixMode(GL_PROJECTION)
	glLoadIdentity()
	aspect = float(res[0]) / float(res[1])
	gluPerspective( fov_deg, aspect, clip_near,clip_far )

	glMatrixMode(GL_MODELVIEW)
	glLoadIdentity()

	gluLookAt( pos[0],pos[1],pos[2], cen[0],cen[1],cen[2], up[0],up[1],up[2] )

def gl2_mult_matrix( matr4x4:Iterable ):
	tmp = [
		matr4x4[0], matr4x4[4], matr4x4[ 8], matr4x4[12],
		matr4x4[1], matr4x4[5], matr4x4[ 9], matr4x4[13],
		matr4x4[2], matr4x4[6], matr4x4[10], matr4x4[14],
		matr4x4[3], matr4x4[7], matr4x4[11], matr4x4[15]
	]
	glMultMatrixf(tmp)



def get_screenshot( with_alpha:bool ):
	( x,y, w,h ) = glGetIntegerv(GL_VIEWPORT)
	if with_alpha:
		data:bytes = glReadPixels( 0,0,w,h, GL_RGBA,GL_UNSIGNED_BYTE,None, bytes )
		surf = pygame.image.frombytes( data, (w,h), "RGBA", True )
	else:
		data:bytes = glReadPixels( 0,0,w,h, GL_RGB ,GL_UNSIGNED_BYTE,None, bytes )
		surf = pygame.image.frombytes( data, (w,h), "RGB" , True )
	return surf



def get_msaa_locations():
	#GL 3.2 and later
	num_samples = glGetInteger(GL_SAMPLES)
	if num_samples == 0: return [ (0.5,0.5) ]
	positions:list[tuple[float,float]] = []
	for k in range(num_samples):
		pos = glGetMultisamplefv( GL_SAMPLE_POSITION, k )
		positions.append(( float(pos[0]), float(pos[1]) ))
	return positions
#TODO set https://registry.khronos.org/OpenGL/extensions/ARB/ARB_sample_locations.txt

def render_gl2_diagram_msaa_samples(
	x:float, y:float,
	scale:float,
	color_samples:Iterable[float]|None=(1,0,0,1), color_footprint:Iterable[float]|None=(1,1,1,1)
):
	locs = get_msaa_locations()

	glPushMatrix()
	glTranslatef( x,y, 0 )
	glScalef( scale, scale, scale )

	if color_footprint != None:
		glColor4f( *color_footprint )
		glBegin(GL_LINE_LOOP)
		glVertex3f(0,0,0); glVertex3f(1,0,0); glVertex3f(1,1,0); glVertex3f(0,1,0)
		glEnd()

	if color_samples != None:
		glColor4f( *color_samples )
		glBegin(GL_POINTS)
		for x, y in locs:
			glVertex2f( x, y )
		glEnd()

	glColor4f( 1,1,1, 1 )
	glPopMatrix()



if _GLMISC_HAS_GEOM:
	#Note can pass `-1` for a slot if you for some reason don't want it
	def get_mesh_latlong_sphere(
		N:int, rad:float=1.0, vert_slot:int=0,norm_slot:int=1,texc_slot:int=2
	):
		M=(N<<1)+1; N+=1 #Number of vertices from number of faces
		vertices:list[gl_geom.Vertex_V_N_X] = []
		for     i in range(M): #This order for better caching when drawing.
			for j in range(N):
				vertex = gl_geom.Vertex_V_N_X()
				vertex.texc = [ i/(M-1), j/(N-1) ]
				theta = vertex.texc[0] * math.tau
				phi   = vertex.texc[1] * math.pi
				#phi = (vertex.texc[1]*0.9+0.05) * math.pi
				vertex.norm = [
					math.cos(theta) * math.sin(phi),
					math.sin(theta) * math.sin(phi),
					                  math.cos(phi)
				]
				vertex.vert = [ rad*vertex.norm[0], rad*vertex.norm[1], rad*vertex.norm[2] ]
				vertices.append(vertex)
		vao = gl_geom.VAO()
		vbo_verts = gl_buffer.BufferGPU(sum( [ vertex.vert for vertex in vertices ], [] ))
		vao.set_attrib( gl_geom.VertAttribInfo(
			vert_slot, vbo_verts, 3,GL_FLOAT,12
		) )
		if norm_slot != -1:
			vbo_norms = gl_buffer.BufferGPU(sum( [ vertex.norm for vertex in vertices ], [] ))
			vao.set_attrib( gl_geom.VertAttribInfo(
				norm_slot, vbo_norms, 3,GL_FLOAT,12
			) )
		if texc_slot != -1:
			vbo_texcs = gl_buffer.BufferGPU(sum( [ vertex.texc for vertex in vertices ], [] ))
			vao.set_attrib( gl_geom.VertAttribInfo(
				texc_slot, vbo_texcs, 2,GL_FLOAT,8
			) )
		inds = []
		for i in range( M - 1 ):
			if i % 2 == 0: #Strip going down
				for j in range( 0, N, 1 ):
					inds.append( j + (i+1)*N )
					inds.append( j +  i   *N )
			else: #Strip going up
				for j in range( N-1, 0-1, -1 ):
					inds.append( j +  i   *N )
					inds.append( j + (i+1)*N )
			#Note includes poles as degenerate triangles
		vao.set_indices( gl_buffer.BufferGPU(array.array( "I", inds )) )
		vao.finalize()
		return gl_geom.Mesh( vao, GL_TRIANGLE_STRIP,len(inds) )

def render_gl2_sphere( N:int ):
	sphere = gluNewQuadric()
	gluQuadricTexture( sphere, GL_TRUE )
	gluSphere( sphere, 1.0, 2*N,N )
	gluDeleteQuadric(sphere)



def render_gl2_axes():
	glBegin(GL_LINES)
	glColor3f(1,0,0); glVertex3f(0,0,0); glVertex3f(1,0,0)
	glColor3f(0,1,0); glVertex3f(0,0,0); glVertex3f(0,1,0)
	glColor3f(0,0,1); glVertex3f(0,0,0); glVertex3f(0,0,1)
	glColor3f(1,1,1)
	glEnd()

def render_gl2_circle( r:float=1.0, z:float=0.0, N:int=100 ):
	glBegin(GL_TRIANGLE_FAN)
	for k in range(N):
		angle = math.tau * float(k)/N
		s=math.sin(angle); c=math.cos(angle)
		glVertex3f( r*c, r*s, z )
	glEnd()

@overload
def render_gl2_quad( x0:float,x1:float, y0:float,y1:float ) -> None: ...
@overload
def render_gl2_quad( lo:tuple[float,float], hi:tuple[float,float] ) -> None: ...
@overload
def render_gl2_quad( center:tuple[float,float], radius_x:float,radius_y:float ) -> None: ...
def render_gl2_quad( *args ):
	match args:
		case [ x0,x1, y0,y1 ]:
			pass
		case [ [x0,y0], [x1,y1] ]:
			pass
		case [ [cx,cy], rx, ry ]:
			x0=cx-rx; x1=cx+rx; y0=cy-ry; y1=cy+ry

	glBegin(GL_QUADS)
	glTexCoord2f( 0.0, 0.0 ); glVertex2f( x0, y0 )
	glTexCoord2f( 1.0, 0.0 ); glVertex2f( x1, y0 )
	glTexCoord2f( 1.0, 1.0 ); glVertex2f( x1, y1 )
	glTexCoord2f( 0.0, 1.0 ); glVertex2f( x0, y1 )
	glEnd()

def render_gl2_blit( tex:gl_texture.Texture2D, pos:tuple[float,float] ):
	gl_texture.TextureBase.bind(tex)
	render_gl2_quad( pos, (pos[0]+tex.w,pos[1]+tex.h) )
