import math

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

from ..glbase import gl_font
from ..glbase import gl_buffer
from ..glbase import gl_geom
from ..glbase import gl_misc

from .. import config
from ..math_util import *

from . import level
from . import render_advanced
from . import render_basic
from . import render_common



_inited = False

def config_context():
	glEnable(GL_BLEND)
	glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA )

	glEnable(GL_DEPTH_TEST)
	glDepthFunc(GL_LEQUAL)

	#glEnable(GL_FRAMEBUFFER_SRGB)

	glClearColor( 0.0,0.0,0.0, 0.0 )
	glClearDepth(1.0)

	glEnable(GL_CULL_FACE)

	#print(glGetString(GL_RENDERER))
	#print(glGetString(GL_VERSION ))

def ensure_init  ():
	global _inited
	if _inited: return

	render_common.textures = render_common.Textures()

	if False:#os.path.exists(FILEPATH_FONTMAP):
		render_common.font = gl_font.Font.load_from_font_map(FILEPATH_FONTMAP)
		render_common.font.set_backing(pygame.font.Font( config.MAIN_DIR+"data/font.ttf", 20 ))
	else:
		render_common.font = gl_font.Font.create_from_font_file( config.MAIN_DIR+"data/font.ttf", 20 )
		#render_common.font = gl_font.Font.load_from_font_img( config.MAIN_DIR+"data/Font.bmp", (16,16), range(32,126+1,1), 0,3 )
		#render_common.font.crop_glyphs(None,None,True,True)#( 4,2, 8,12 ),None,False)
	#render_common.font.ensure("This is a test of the emergency broadcasting system.")
	#render_common.font.ensure("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
	#render_common.font.ensure("".join([ chr(k) for k in range(256) ]))
	#render_common.font.ensure("AaBb")
	#gl_font.Font.save_to_font_map( font, FILEPATH_FONTMAP )
	#gl_font.Font.load_and_save_font_map( FILEPATH_FONTMAP, FILEPATH_FONTMAP[:-4]+"2.png" )

	#Note don't initialize renderer(s) here.  If we aren't using the renderer, it's pointless (and
	#	for advanced renderer, maybe not supported by user's computer).  The game driver initializes
	#	the renderers, once the game is about to start and the user has committed to using one.

	_inited = True

def ensure_deinit():
	global _inited
	if not _inited: return

	render_basic   .ensure_deinit()
	render_advanced.ensure_deinit()

	#gl_font.Font.save_to_font_map( render_common.font, FILEPATH_FONTMAP )
	#gl_font.Font.save_to_font_map( render_common.font, config.MAIN_DIR+"data/_fontmap_autogen.png" )
	#gl_font.Font.save_to_font_img( render_common.font, config.MAIN_DIR+"data/_fontimg_autogen.png" )
	del render_common.textures
	del render_common.font

	_inited = False



def render_gl2_alpha_ring( N:int, r0:float,r1:float, a0:float,a1:float, z:float=0.0 ):
	k_to_angle = math.tau / N
	glBegin(GL_TRIANGLE_STRIP)
	for k in range( N + 1 ):
		angle = float(k) * k_to_angle
		s=math.sin(angle); c=math.cos(angle)
		glColor4f( 0.0,0.0,0.0, a0 ); glVertex3f( r0*c, r0*s, z )
		glColor4f( 0.0,0.0,0.0, a1 ); glVertex3f( r1*c, r1*s, z )
	glEnd()



assert len(level.TILE) == 11

def get_special_tile_texcs( tile_type:level.TILE ):
	if   tile_type == level.TILE.INFO1 : coord0=(1,3)
	elif tile_type == level.TILE.INFO2 : coord0=(2,3)
	elif tile_type == level.TILE.INFO3 : coord0=(3,3)
	elif tile_type == level.TILE.ARROWU: coord0=(1,2)
	elif tile_type == level.TILE.WARN  : coord0=(3,2)
	elif tile_type == level.TILE.R     : coord0=(0,1)
	elif tile_type == level.TILE.O     : coord0=(1,1)
	elif tile_type == level.TILE.Y     : coord0=(2,1)
	elif tile_type == level.TILE.G     : coord0=(3,1)
	elif tile_type == level.TILE.B     : coord0=(0,0)
	elif tile_type == level.TILE.P     : coord0=(1,0)
	else: assert False
	s0=coord0[0]*0.25; t0=coord0[1]*0.25; s1=s0+0.25; t1=t0+0.25
	#eps = 0.125 / textures.tiles.w #MSAA causes out-of-tile coordinates, so shift inwards 1/8th texel or so
	#s0+=eps; t0+=eps; s1-=eps; t1-=eps
	return s0,t0, s1,t1



WALL_GEOM_QUADS = [
	# Left Face
	( (0.0,1.0,0.0), (-1.0, 0.0,0.0), (0.0,0.5) ),
	( (0.0,0.0,0.0), (-1.0, 0.0,0.0), (1.0,0.5) ),
	( (0.0,0.0,0.5), (-1.0, 0.0,0.0), (1.0,1.0) ),
	( (0.0,1.0,0.5), (-1.0, 0.0,0.0), (0.0,1.0) ),
	# Right Face
	( (1.0,0.0,0.0), ( 1.0, 0.0,0.0), (0.0,0.5) ),
	( (1.0,1.0,0.0), ( 1.0, 0.0,0.0), (1.0,0.5) ),
	( (1.0,1.0,0.5), ( 1.0, 0.0,0.0), (1.0,1.0) ),
	( (1.0,0.0,0.5), ( 1.0, 0.0,0.0), (0.0,1.0) ),
	# Down Face
	( (0.0,0.0,0.0), ( 0.0,-1.0,0.0), (0.0,0.5) ),
	( (1.0,0.0,0.0), ( 0.0,-1.0,0.0), (1.0,0.5) ),
	( (1.0,0.0,0.5), ( 0.0,-1.0,0.0), (1.0,1.0) ),
	( (0.0,0.0,0.5), ( 0.0,-1.0,0.0), (0.0,1.0) ),
	# Up Face (not visible so can be disabled when drawing)
	( (1.0,1.0,0.0), ( 0.0, 1.0,0.0), (0.0,0.5) ),
	( (0.0,1.0,0.0), ( 0.0, 1.0,0.0), (1.0,0.5) ),
	( (0.0,1.0,0.5), ( 0.0, 1.0,0.0), (1.0,1.0) ),
	( (1.0,1.0,0.5), ( 0.0, 1.0,0.0), (0.0,1.0) ),
	# Top Face
	( (0.0,0.0,0.5), ( 0.0, 0.0,1.0), (0.0,0.0) ),
	( (1.0,0.0,0.5), ( 0.0, 0.0,1.0), (1.0,0.0) ),
	( (1.0,1.0,0.5), ( 0.0, 0.0,1.0), (1.0,1.0) ),
	( (0.0,1.0,0.5), ( 0.0, 0.0,1.0), (0.0,1.0) )
]

def render_gl2_wall_inner( wall:level.Wall ):
	def _vert( k:int ):
		vert, norm, texc = WALL_GEOM_QUADS[k]
		glNormal3f( *norm )
		glTexCoord2f( *texc )
		glVertex3f( wall.coord.x+vert[0], wall.coord.y+vert[1], vert[2] )
	def quad( k:int ):
		_vert( 4*k ); _vert( 4*k + 1 ); _vert( 4*k + 2 ); _vert( 4*k + 3 )

	if not wall.hidden_sides[0]: quad(0)
	if not wall.hidden_sides[1]: quad(1)
	if not wall.hidden_sides[2]: quad(2)
	#if not wall.hidden_sides[3]: quad(3) #not visible
	quad(4)



GEM_GEOM_TRIS:list[tuple[ Vector3, Vector3,Vector3,Vector3 ]] = []
GEM_COLOR = ( 1.0,0.1,0.1, 0.9 )

def _init_gem_geom():
	N = 8

	#Bottom
	bottom = Vector3(0.0,0.0,-0.18)

	#Ring around middle
	ring1:list[Vector3] = []
	for k in range(N):
		angle = math.tau * k*0.125
		ring1.append(Vector3( 0.15*math.cos(angle), 0.15*math.sin(angle), 0.0 ))

	#Ring around top
	ring2:list[Vector3] = []
	for k in range(N):
		angle = math.tau * (k+0.5)*0.125
		ring2.append(Vector3( 0.09*math.cos(angle), 0.09*math.sin(angle), 0.06 ))

	def _tri( normal:Vector3, v0:Vector3,v1:Vector3,v2:Vector3 ):
		GEM_GEOM_TRIS.append(( normal, v0,v1,v2 ))

	#Bottom cone
	for k in range(N):
		v1=ring1[(k+1)%N]; v2=ring1[k]
		normal = triangle_normal( bottom, v1, v2 )
		_tri( normal, bottom,v1,v2 )

	#Ring around top (lower triangles)
	for k in range(N):
		v0=ring1[k]; v1=ring1[(k+1)%N]; v2=ring2[k]
		normal = triangle_normal( v0, v1, v2 )
		_tri( normal, v0,v1,v2 )

	#Ring around top (upper triangles)
	for k in range(N):
		v0=ring1[k]; v1=ring2[k]; v2=ring2[(k-1)%N]
		normal = triangle_normal( v0, v1, v2 )
		_tri( normal, v0,v1,v2 )

	#Top plane
	up = Vector3( 0.0, 0.0, 1.0 )
	for k in range( 1, N-1 ):
		_tri( up, ring2[0],ring2[k],ring2[k+1] )

_init_gem_geom()

def get_mesh_gem():
	vertdata=[]; normdata=[]; vcoldata=[]
	for normal, v0,v1,v2 in GEM_GEOM_TRIS:
		for v in ( v0, v1, v2 ):
			vertdata += list(v)
			normdata += list(normal)
			vcoldata += list(GEM_COLOR)
	vao = gl_geom.VAO()
	vao.set_attrib( gl_geom.VertAttribInfo( 0, gl_buffer.BufferGPU(vertdata), 3,GL_FLOAT,12 ) )
	vao.set_attrib( gl_geom.VertAttribInfo( 1, gl_buffer.BufferGPU(normdata), 3,GL_FLOAT,12 ) )
	vao.set_attrib( gl_geom.VertAttribInfo( 3, gl_buffer.BufferGPU(vcoldata), 4,GL_FLOAT,16 ) )
	vao.finalize()
	return gl_geom.Mesh( vao, GL_TRIANGLES,len(vertdata)//3 )

def render_gl2_gem():
	glBegin(GL_TRIANGLES)
	for normal, v0,v1,v2 in GEM_GEOM_TRIS:
		glNormal3f( *normal )
		glVertex3f( *v0 ); glVertex3f( *v1 ); glVertex3f( *v2 )
	glEnd()

def render_gl2_gem_basic_shadow( z:float=0.0 ):
	r0 = 0.15 - 0.0125
	r1 = 0.15 + 0.0125

	glColor4f( 0.0,0.0,0.0, config.render_shadow_darkness )
	gl_misc.render_gl2_circle( r0, z, 8 )

	render_gl2_alpha_ring( 8, r0,r1, config.render_shadow_darkness,0.0, z )



STAR_GEOM_VERTS:list[tuple[ Vector3, Vector3 ]] = []
STAR_COLOR = ( 1.0,0.9,0.1, 1.0 ) #( 0.9,0.8,0.2, 1.0 )

def _init_star_geom():
	r_out=0.30; dr_out=0.14
	r_in =0.14; dr_in =0.07
	halfwidth = 0.02

	contour_outer = []
	contour_inner = []
	for k in range(5):
		angle = math.tau*0.2 * (k+1.25)
		c, s = math.cos(angle), math.sin(angle)
		contour_outer.append((  r_out        *c,  r_out        *s ))
		contour_inner.append(( (r_out-dr_out)*c, (r_out-dr_out)*s ))

		angle = math.tau*0.2 * (k+1.75)
		c, s = math.cos(angle), math.sin(angle)
		contour_outer.append((  r_in       *c,  r_in       *s ))
		contour_inner.append(( (r_in-dr_in)*c, (r_in-dr_in)*s ))

	transform = Matrix.get_rotation_deg( 90.0, 1.0,0.0,0.0 )
	def _vert( pos:tuple[float,float,float] ):
		nonlocal normal
		pos2    = ( transform * Vector4(*pos   ,1.0) ).xyz
		normal2 = ( transform * Vector4(*normal,0.0) ).xyz
		STAR_GEOM_VERTS.append(( pos2, normal2 ))
	def quad( v00, v10, v01, v11 ):
		_vert(v00); _vert(v10); _vert(v11)
		_vert(v00); _vert(v11); _vert(v01)

	#Back
	normal = Vector3( 0.0, 1.0, 0.0 )
	for k in range(10):
		v00=contour_outer[  k         ]; v00=( v00[0], halfwidth, v00[1] )
		v10=contour_inner[  k         ]; v10=( v10[0], halfwidth, v10[1] )
		v01=contour_outer[ (k+1) % 10 ]; v01=( v01[0], halfwidth, v01[1] )
		v11=contour_inner[ (k+1) % 10 ]; v11=( v11[0], halfwidth, v11[1] )
		quad( v00, v10, v01, v11 )

	#Front
	normal = Vector3( 0.0, -1.0, 0.0 )
	for k in range(10):
		v00 = contour_inner[  k         ]; v00=( v00[0], -halfwidth, v00[1] )
		v10 = contour_outer[  k         ]; v10=( v10[0], -halfwidth, v10[1] )
		v01 = contour_inner[ (k+1) % 10 ]; v01=( v01[0], -halfwidth, v01[1] )
		v11 = contour_outer[ (k+1) % 10 ]; v11=( v11[0], -halfwidth, v11[1] )
		quad( v00, v10, v01, v11 )

	#Outer sides
	for k in range(10):
		x0, z0 = contour_outer[  k    % 10 ]
		x1, z1 = contour_outer[ (k+1) % 10 ]
		normal = Vector3( z1-z0, 0.0, x0-x1 ).normalized() #Note flip to get perpendicular
		v00 = ( x0, -halfwidth, z0 )
		v10 = ( x0,  halfwidth, z0 )
		v01 = ( x1, -halfwidth, z1 )
		v11 = ( x1,  halfwidth, z1 )
		quad( v00, v10, v01, v11 )

	#Inner sides
	for k in range(10):
		x0, z0 = contour_inner[  k    % 10 ]
		x1, z1 = contour_inner[ (k+1) % 10 ]
		normal = Vector3( z0-z1, 0.0, x1-x0 ).normalized() #Note flip to get perpendicular
		v00 = ( x0,  halfwidth, z0 )
		v10 = ( x0, -halfwidth, z0 )
		v01 = ( x1,  halfwidth, z1 )
		v11 = ( x1, -halfwidth, z1 )
		quad( v00, v10, v01, v11 )

_init_star_geom()

def get_mesh_star():
	vertdata=[]; normdata=[]; vcoldata=[]
	for vert, normal in STAR_GEOM_VERTS:
		vertdata += list(vert)
		normdata += list(normal)
		vcoldata += list(STAR_COLOR)
	vao = gl_geom.VAO()
	vao.set_attrib( gl_geom.VertAttribInfo( 0, gl_buffer.BufferGPU(vertdata), 3,GL_FLOAT,12 ) )
	vao.set_attrib( gl_geom.VertAttribInfo( 1, gl_buffer.BufferGPU(normdata), 3,GL_FLOAT,12 ) )
	vao.set_attrib( gl_geom.VertAttribInfo( 3, gl_buffer.BufferGPU(vcoldata), 4,GL_FLOAT,16 ) )
	vao.finalize()
	return gl_geom.Mesh( vao, GL_TRIANGLES,len(vertdata)//3 )

def render_gl2_star():
	glBegin(GL_TRIANGLES)
	for vert, normal in STAR_GEOM_VERTS:
		glNormal3f( *normal )
		glVertex3f( *vert )
	glEnd()



def render_gl2_basic_shadow_circle( z:float=0.0 ):
	N = config.detail_shadow_circle

	glColor4f( 0.0,0.0,0.0, config.render_shadow_darkness )
	gl_misc.render_gl2_circle( 0.9, z, N )

	render_gl2_alpha_ring( N, 0.9,1.1, config.render_shadow_darkness,0.0, z )
