import math
from typing import Self

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

from .. import config

from ..glbase import gl_geom
from ..glbase import gl_misc
from ..glbase import gl_program
from ..glbase import gl_texture

from . import driver
from . import level
from . import particles
from . import render_common
from . import render_lowlevel



class DisplayLists:
	def __init__( self:Self ):
		self.marble = gl_geom.DisplayList( lambda: gl_misc.render_gl2_sphere(config.detail_marble) )

		self.gem = gl_geom.DisplayList(render_lowlevel.render_gl2_gem)
		self.star = gl_geom.DisplayList(render_lowlevel.render_gl2_star)

		self.shadow_circle = gl_geom.DisplayList(render_lowlevel.render_gl2_basic_shadow_circle)
		self.shadow_gem = gl_geom.DisplayList(render_lowlevel.render_gl2_gem_basic_shadow)

		self.particle = gl_geom.DisplayList( lambda: gl_misc.render_gl2_sphere(config.detail_particle) )

		self.solid_static:gl_geom.DisplayList|None = None

		self.lasers:gl_geom.DisplayList|None = None



dls:DisplayLists|None = None

def ensure_init():
	global dls
	if not dls:
		dls = DisplayLists()

		glEnable(GL_LIGHT0)

		glEnable(GL_COLOR_MATERIAL)

		glEnable(GL_ALPHA_TEST)

		#glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE )
		#glTexEnvi( GL_POINT_SPRITE, GL_COORD_REPLACE, GL_TRUE )

		#glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST )

		#glShadeModel(GL_SMOOTH)

def ensure_deinit():
	global dls
	if dls:
		del dls
		dls = None



def prepare_level():
	def _render_lvl_solid_static():
		glEnable(GL_TEXTURE_2D)
		glColor4f( 1.0,1.0,1.0, 1.0 )

		#Floor
		gl_texture.TextureBase.bind(render_common.textures.tile_floor)
		glBegin(GL_QUADS)
		SC = config.FLOOR_RADIUS
		glTexCoord2f( -SC, -SC ); glVertex3f( -SC, -SC, 0.0 )
		glTexCoord2f(  SC, -SC ); glVertex3f(  SC, -SC, 0.0 )
		glTexCoord2f(  SC,  SC ); glVertex3f(  SC,  SC, 0.0 )
		glTexCoord2f( -SC,  SC ); glVertex3f( -SC,  SC, 0.0 )
		glEnd()

		#Special Floor Tiles
		glDisable(GL_DEPTH_TEST)
		gl_texture.TextureBase.bind(render_common.textures.tiles)
		for tile in driver.level.special_tiles:
			s0,t0, s1,t1 = render_lowlevel.get_special_tile_texcs(tile.type)
			glBegin(GL_QUADS)
			glTexCoord2f( s0, t0 ); glVertex3f( tile.coord.x    , tile.coord.y    , 0.0 )
			glTexCoord2f( s1, t0 ); glVertex3f( tile.coord.x+1.0, tile.coord.y    , 0.0 )
			glTexCoord2f( s1, t1 ); glVertex3f( tile.coord.x+1.0, tile.coord.y+1.0, 0.0 )
			glTexCoord2f( s0, t1 ); glVertex3f( tile.coord.x    , tile.coord.y+1.0, 0.0 )
			glEnd()
		glEnable(GL_DEPTH_TEST)

		#Hedge Walls
		gl_texture.TextureBase.bind(render_common.textures.wall_hedge)
		glBegin(GL_QUADS)
		for wall in driver.level.walls:
			if wall.type == level.WALL.HEDGE: render_lowlevel.render_gl2_wall_inner(wall)
		glEnd()

		#Metal Walls
		gl_texture.TextureBase.bind(render_common.textures.wall_metal)
		glBegin(GL_QUADS)
		for wall in driver.level.walls:
			if wall.type == level.WALL.METAL: render_lowlevel.render_gl2_wall_inner(wall)
		glEnd()

	def _render_lvl_lasers():
		glDisable(GL_TEXTURE_2D)
		glColor4f( 1.0,0.3,0.3, 1.0 )

		#Laser Walls
		glBegin(GL_QUADS)
		for wall in driver.level.walls:
			if wall.type == level.WALL.LASER: render_lowlevel.render_gl2_wall_inner(wall)
		glEnd()

	dls.solid_static = gl_geom.DisplayList(_render_lvl_solid_static)
	if driver.level.has_lasers:
		dls.lasers = gl_geom.DisplayList(_render_lvl_lasers)



def render_particle( particle:particles.Particle ):
	glPushMatrix()
	glTranslatef( particle.pos.x, particle.pos.y, particle.pos.z )
	#glRotatef( self.rot[1], 0.0,1.0,0.0 )
	#glRotatef( self.rot[0], 1.0,0.0,0.0 )
	glScalef( particle.radius, particle.radius, particle.radius )
	dls.particle.call()
	glPopMatrix()

def render_shadows():
	if not config.render_shadows: return

	glDisable(GL_DEPTH_TEST)
	glDisable(GL_TEXTURE_2D)
	glColor4f( 0.0,0.0,0.0, config.render_shadow_darkness )

	#Gem Shadows
	for item in driver.level.items:
		if item.type == level.ITEM.GEM:
			glPushMatrix()
			glTranslatef( item.pos.x, item.pos.y, 0.0 )
			glRotatef( math.degrees(item.rot_rad), 0.0,0.0,1.0 )
			dls.shadow_gem.call()
			glPopMatrix()

	#Star Shadows
	#glColor4f( 0.0,0.0,0.0, config.render_shadow_darkness )
	#for item in driver.level.items:
	#	if item.type == level.ITEM.STAR:
	#		glPushMatrix()
	#		glTranslatef( item.pos.x, item.pos.y, 0.0 )
	#		gl_misc.gl2_mult_matrix([
	#			1.0, 0.0, 0.0, 0.0,
	#			0.0, 1.0, 0.0, 0.0,
	#			0.0, 0.0, 0.0, 0.002,
	#			0.0, 0.0, 0.0, 1.0
	#		])
	#		glRotatef( math.degrees(item.rot_rad), 0.2,0.1,1.0 )
	#		dls.star.call()
	#		glPopMatrix()

	#Life-Up Shadows
	lifeup_radius = level.Item.LIFE_UP_SIZE_FACTOR * config.radius_self
	for item in driver.level.items:
		if item.type == level.ITEM.LIFE_UP:
			glPushMatrix()
			glTranslatef( item.pos.x, item.pos.y, 0.0 )
			glScalef( lifeup_radius, lifeup_radius, lifeup_radius )
			dls.shadow_circle.call()
			glPopMatrix()

	#Marble Shadows
	for marble in driver.level.marbles:
		glPushMatrix()
		glTranslatef( marble.center.x, marble.center.y, 0.0 )
		glScalef( marble.radius, marble.radius, marble.radius )
		dls.shadow_circle.call()
		glPopMatrix()

	#Marble Particle Shadows
	if config.renderbasic_shadows_particles:
		for particle in particles.particles:
			if particle.type in ( particles.PARTICLE.MARBLE_SELF, particles.PARTICLE.MARBLE_EVIL ):
				glPushMatrix()
				glTranslatef( particle.pos.x, particle.pos.y, 0.0 )
				glScalef( particle.radius, particle.radius, particle.radius )
				dls.shadow_circle.call()
				glPopMatrix()

	glEnable(GL_DEPTH_TEST)

def render_marbles_and_pieces():
	glEnable(GL_TEXTURE_2D)
	glColor4f( 1.0,1.0,1.0, 1.0 )

	def _draw_marble( marble:level.Marble ):
		glPushMatrix()
		glTranslatef( marble.center.x, marble.center.y, marble.center.z )
		gl_misc.gl2_mult_matrix( marble.rot.calc_matr4x4() )
		glScalef( marble.radius, marble.radius, marble.radius )
		dls.marble.call()
		glPopMatrix()

	gl_texture.TextureBase.bind(render_common.textures.marble_self)

	if driver.level.marble_self != None:
		_draw_marble(driver.level.marble_self)

	for particle in particles.particles:
		if particle.type == particles.PARTICLE.MARBLE_SELF:
			render_particle(particle)

	gl_texture.TextureBase.bind(render_common.textures.marble_evil)

	for marble in driver.level.marbles_evil:
		_draw_marble(marble)

	for particle in particles.particles:
		if particle.type == particles.PARTICLE.MARBLE_EVIL:
			render_particle(particle)

def render_items():
	#Life-Up items
	glColor4f( 1.0,1.0,1.0, 1.0 )
	glEnable(GL_TEXTURE_2D)
	glEnable(GL_LIGHTING)
	gl_texture.TextureBase.bind(render_common.textures.marble_self)
	lifeup_radius = level.Item.LIFE_UP_SIZE_FACTOR * config.radius_self
	for item in driver.level.items:
		if item.type != level.ITEM.LIFE_UP: continue
		glPushMatrix()
		glTranslatef( item.pos.x, item.pos.y, item.pos.z )
		glRotatef( math.degrees(item.rot_rad), 0.0,0.0,1.0 )
		glScalef( lifeup_radius, lifeup_radius, lifeup_radius )
		dls.marble.call()
		glPopMatrix()

	#Gem items
	glDisable(GL_TEXTURE_2D)
	glColor4f( *render_lowlevel.GEM_COLOR )
	for item in driver.level.items:
		if item.type != level.ITEM.GEM: continue
		glPushMatrix()
		glTranslatef( item.pos.x, item.pos.y, item.pos.z )
		glRotatef( math.degrees(item.rot_rad), 0.05,0.15,1.0 )
		dls.gem.call()
		glPopMatrix()

	#Star items
	glColor4f( *render_lowlevel.STAR_COLOR )
	for item in driver.level.items:
		if item.type != level.ITEM.STAR: continue
		glPushMatrix()
		glTranslatef( item.pos.x, item.pos.y, item.pos.z )
		#glRotatef( math.degrees(item.rot_rad), 0.2,0.1,1.0 )
		glRotatef( math.degrees(item.rot_rad), 0.0,0.0,1.0 )
		dls.star.call()
		glPopMatrix()

	#Time items
	glColor4f( 1.0,1.0,1.0, 1.0 )
	glDisable(GL_LIGHTING)
	glEnable(GL_TEXTURE_2D)
	gl_texture.TextureBase.bind(render_common.textures.tiles)
	for item in driver.level.items:
		if item.type != level.ITEM.TPLUS60: continue
		glBegin(GL_QUADS)
		glTexCoord2f( 0.00, 0.50 ); glVertex3f( item.pos.x-0.5, item.pos.y-0.5, 0.002 )
		glTexCoord2f( 0.25, 0.50 ); glVertex3f( item.pos.x+0.5, item.pos.y-0.5, 0.002 )
		glTexCoord2f( 0.25, 0.75 ); glVertex3f( item.pos.x+0.5, item.pos.y+0.5, 0.002 )
		glTexCoord2f( 0.00, 0.75 ); glVertex3f( item.pos.x-0.5, item.pos.y+0.5, 0.002 )
		glEnd()



def render_frame( fps:float ):
	assert dls

	render_common.camera.res = tuple(config.window_res)
	render_common.camera.set_gl2_projview_3D()
	glClearColor( 0.0,0.0,0.0, 1.0 )
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT )

	glLightfv( GL_LIGHT0, GL_AMBIENT , ( 0.5,0.5,0.5, 1.0 ) )
	glLightfv( GL_LIGHT0, GL_DIFFUSE , ( 1.0,1.0,1.0, 1.0 ) )
	#glLightfv( GL_LIGHT0, GL_SPECULAR, ( 0.0,0.0,0.0, 1.0 ) )
	glLightfv( GL_LIGHT0, GL_POSITION, ( 0.0,0.0,1000.0, 1.0 ) )

	#glMaterialfv( GL_FRONT_AND_BACK, GL_AMBIENT, ( 0.06,0.06,0.06, 1.0 ) )
	#glColorMaterial( GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE )
	#glMaterialfv( GL_FRONT_AND_BACK, GL_SPECULAR, ( 1.0,1.0,1.0, 1.0 ) )
	#glMaterialfv( GL_FRONT_AND_BACK, GL_SHININESS, 32.0 )

	#Solid static geometry
	dls.solid_static.call()

	#Shadows
	render_shadows()

	#Marbles and Marble Particles
	render_marbles_and_pieces()

	#Items
	render_items()

	#Misc. Particles
	glDisable(GL_LIGHTING)
	glColor4f( 0.75,0.75,0.75, 1.0 )
	for particle in particles.particles:
		if particle.type == particles.PARTICLE.SMOKE:
			render_particle(particle)
	glColor4f( 1.0,0.0,0.0, 1.0 )
	for particle in particles.particles:
		if particle.type == particles.PARTICLE.LASER:
			render_particle(particle)
	glEnable(GL_TEXTURE_2D)

	#Lasers
	if driver.level.lasers_on:
		glDisable(GL_TEXTURE_2D)
		glBlendFunc( GL_SRC_ALPHA, GL_ONE )
		dls.lasers.call()
		glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA )

	#glDisable(GL_TEXTURE_2D)
	#glColor4f( 1.0,1.0,1.0, 1.0 )
	#gl_misc.render_axes()

	#HUD
	gl_misc.set_gl2_projview_2D(config.window_res)
	glDisable(GL_DEPTH_TEST)
	render_common.render_hud(fps)
	gl_program.Program.use(None)
	glEnable(GL_DEPTH_TEST)
