from __future__ import annotations

import os
import sys

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

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

from .. import config
from ..math_util import *
from .. import audio

from ..glbase import gl_misc
from ..tkgui import tk_screenshot_path

from . import level as mod_level
from .marble import MARBLE, Marble
from . import particles
from . import render_advanced
from . import render_basic
from . import render_common



MODE_RETURN         = -1
MODE_PLAYING        = 1
MODE_PAUSED         = 2
MODE_EXPLODING      = 3
MODE_LVL_SUCCESS    = 4
MODE_LVL_EXPIRED    = 5
MODE_GAME_LOST      = 6
MODE_GAME_COMPLETED = 7
mode      = MODE_PLAYING
mode_time = 0.0

def set_mode( new_mode:int ):
	global mode, mode_time
	mode      = new_mode
	mode_time = 0.0



level:mod_level.Level|None = None

points:int = -1
lives :int = -1
_points_prev:int = -1
_lives_prev :int = -1

time_bonus:float = -1.0



def update_time_bonus( remaining_t:float ):
	global time_bonus
	time_bonus = rndint( 100.0 * remaining_t )

def on_collect_gem    ():
	global points
	audio.sounds.item_gem.start()
	points += 500
def on_collect_star   ():
	global points
	audio.sounds.item_star.start()
	points += 2500
def on_collect_life_up():
	global lives
	audio.sounds.item_lifeup.start()
	lives += 1
def on_collect_time( amount:float ):
	audio.sounds.ticking.start()
	level.time_left += amount

def on_exploded_evil( marble:Marble ):
	global points
	if   marble.type == MARBLE.EVIL_HENCH:
		points += 10_000
	elif marble.type == MARBLE.EVIL_KING:
		points += 50_000
	else:
		assert False
def on_exploded_self():
	set_mode(MODE_EXPLODING)



def level_load():
	global level
	global _points_prev, _lives_prev

	audio.stop_all()
	particles.clear()

	_points_prev, _lives_prev = points, lives

	path = f"{config.MAIN_DIR}data/level/lvl{config.LEVEL_CURRENT}.txt"
	level = mod_level.Level(path)

	if config.render_advanced:
		render_advanced.renderer.prepare_level()
	else:
		render_basic.prepare_level()

	render_common.camera.set_center(level.marble_self.center)

def level_restart():
	global points, lives
	points, lives = _points_prev, _lives_prev

	level_load()

def level_advance():
	if config.LEVEL_CURRENT+1 <= config.LEVELS_COUNT:
		config.LEVEL_CURRENT += 1
		config.level_max = max( config.level_max, config.LEVEL_CURRENT )

		level_load()

	else:
		set_mode(MODE_GAME_COMPLETED)



def game_input( dt:float ):
	""" Gets the user input during main game loop.

	The controls are:
		[CLOSE], ESC          quit game
		LEFT,  A              marble left
		RIGHT, D              marble right
		DOWN,  S              marble down
		UP,    W              marble up
		P                     toggle pause
		CTRL+D                die instantly
		CTRL+F                toggle framerate display
		CTRL+R                restart level
		CTRL+S                take screenshot

		(debug mode only):
			[LCLICK]+[DRAG]   camera orbit
			KEYPAD-4          camera orbit left
			KEYPAD-6          camera orbit right
			KEYPAD-2          camera orbit down
			KEYPAD-8          camera orbit up
			PGUP              camera zoom in
			PGDN              camera zoom out
			[SCROLLWHEEL]     camera zoom in/out
			END               camera reset
			CTRL+N            go to next level
			CTRL+W            win level by magically completing all objectives

		(debug mode + advanced renderer only):

			L                 cycle lighting mode: enabled, vis, disabled
			F5                reload renderer (and in particular, shaders)
	"""

	keys = pygame.key.get_pressed()
	mouse_buttons = pygame.mouse.get_pressed()
	mouse_rel     = pygame.mouse.get_rel    ()

	ctrl = pygame.key.get_mods() & (pygame.KMOD_LCTRL|pygame.KMOD_RCTRL)
	for event in pygame.event.get():
		if   event.type == pygame.QUIT:
			set_mode(MODE_RETURN)
		elif event.type == pygame.KEYDOWN:
			if   event.key == pygame.K_ESCAPE:
				set_mode(MODE_RETURN)
			elif event.key==pygame.K_d and ctrl:
				if level.marble_self != None: level.kill_marble(level.marble_self)
			elif event.key==pygame.K_f and ctrl:
				config.render_fps = not config.render_fps
			elif event.key==pygame.K_s and ctrl:
				surf = gl_misc.get_screenshot(False)
				path = tk_screenshot_path.main_gui()
				if path != None:
					pygame.image.save( surf, path )
			elif event.key == pygame.K_p:
				if   mode == MODE_PLAYING: set_mode(MODE_PAUSED )
				elif mode == MODE_PAUSED : set_mode(MODE_PLAYING)
			elif event.key==pygame.K_r and ctrl:
				level_restart()
			elif config.is_debugging:
				if   event.key==pygame.K_n and ctrl:
					level_advance()
				elif event.key==pygame.K_w and ctrl:
					level.complete()
				elif event.key == pygame.K_END:
					render_common.camera.reset()

				if config.render_advanced:
					if   event.key == pygame.K_l:
						config.RENDERADV_DISPLAY_MODE = ( config.RENDERADV_DISPLAY_MODE + 1 ) % 2
					elif event.key == pygame.K_F5:
						render_advanced.ensure_init()
						render_advanced.renderer.reload_shaders()

		elif event.type == pygame.MOUSEBUTTONDOWN:
			if config.is_debugging:
				if   event.button==4: render_common.camera.radius*=0.9
				elif event.button==5: render_common.camera.radius/=0.9

		elif event.type == pygame.WINDOWRESIZED:
			config.window_res = [ event.x, event.y ]

	#View
	if config.is_debugging:
		if keys[pygame.K_KP8]: render_common.camera.angles[1] += 60.0*dt
		if keys[pygame.K_KP2]: render_common.camera.angles[1] -= 60.0*dt
		if keys[pygame.K_KP4]: render_common.camera.angles[0] -= 60.0*dt
		if keys[pygame.K_KP6]: render_common.camera.angles[0] += 60.0*dt
		if keys[pygame.K_PAGEUP  ]: render_common.camera.radius -= 7.0*dt
		if keys[pygame.K_PAGEDOWN]: render_common.camera.radius += 7.0*dt
		if mouse_buttons[0]:
			render_common.camera.angles[0] -= 0.5 * mouse_rel[0]
			render_common.camera.angles[1] += 0.5 * mouse_rel[1]
		render_common.camera.angles[1] = clamp( render_common.camera.angles[1], -89.0,89.0 )
		render_common.camera.angles[0] %= 360.0
		render_common.camera.radius = clamp( render_common.camera.radius, 0.1,100.0 )

	if level.marble_self != None:
		rot_accel = Vector3(0.0)
		if keys[pygame.K_LEFT ] or keys[pygame.K_a]: rot_accel.y-=config.rot_accel_self
		if keys[pygame.K_RIGHT] or keys[pygame.K_d]: rot_accel.y+=config.rot_accel_self
		if keys[pygame.K_DOWN ] or keys[pygame.K_s]: rot_accel.x+=config.rot_accel_self
		if keys[pygame.K_UP   ] or keys[pygame.K_w]: rot_accel.x-=config.rot_accel_self
		if rot_accel.x!=0.0 or rot_accel.y!=0.0:
			rot_accel = rot_accel.rotated_z( math.radians(render_common.camera.angles[0]+90.0) )
			level.marble_self.apply_rot_accel( rot_accel, dt )
		else:
			level.marble_self.apply_rot_brake( config.rot_accel_self_brake, dt )

	return True

def game_update( dt:float ):
	global mode_time
	global points, lives

	if mode != MODE_PAUSED:
		level.update(dt)

		particles.update(dt)

		if level.marble_self != None:
			render_common.camera.set_target(level.marble_self)
		render_common.camera.update(dt)

		if   mode == MODE_PLAYING:
			#level complete?
			if level.is_complete(): #ending level...
				update_time_bonus(level.time_left)
				points += time_bonus
				set_mode(MODE_LVL_SUCCESS)
			elif level.time_left <= 0.0:
				set_mode(MODE_LVL_EXPIRED)
			elif level.time_left <= 1.5:
				if not audio.sounds.ticking.is_playing():
					audio.sounds.ticking.start()

		elif mode in ( MODE_EXPLODING, MODE_LVL_EXPIRED ):
			if mode_time >= 3.0:
				if lives >= 1:
					lives -= 1
					set_mode(MODE_PLAYING)
					level_load() #Not restart; we lost a life
				else:
					set_mode(MODE_GAME_LOST)

		elif mode == MODE_LVL_SUCCESS:
			if mode_time >= 3.0:
				set_mode(MODE_PLAYING)
				level_advance()

		elif mode in ( MODE_GAME_LOST, MODE_GAME_COMPLETED ):
			if mode_time >= 3.0:
				set_mode(MODE_RETURN)

		else: assert mode == MODE_RETURN #set in handle input

	mode_time += dt



class FPS_Timer:
	__slots__ = ( "n","_fps_factor", "_clock", "_sum_ms","_data_ms","_data_ms_ind" )

	def __init__( self:Self, n:int ):
		self.n = n
		self._fps_factor = 1000.0 * self.n

		self._clock = pygame.time.Clock()

		self._sum_ms  = 16 * n
		#Ring buffer of tick time (in [ms])
		self._data_ms     = [ 16 for k in range(n) ]
		self._data_ms_ind = 0

	def get_ms( self:Self ):
		return self._sum_ms / self.n
	def get_fps( self:Self ):
		#return self._clock.get_fps()
		return self._fps_factor / max(self._sum_ms,1)

	def get_last_tick_dt( self:Self ):
		return 0.001 * self._clock.get_time()
	def tick( self:Self ):
		ms = self._clock.tick()

		self._sum_ms += ms - self._data_ms[self._data_ms_ind]
		self._data_ms[self._data_ms_ind] = ms
		self._data_ms_ind = ( self._data_ms_ind + 1 ) % self.n

		return ms

def main():
	global points, lives

	set_mode(MODE_PLAYING)

	points = config.INITIAL_POINTS
	lives  = config.initial_lives - 1

	if config.render_advanced:
		render_advanced.ensure_init()
	else:
		render_basic.ensure_init()

	level_load()

	if not config.is_debugging:
		pygame.mouse.set_visible(False)

	fps_timer = FPS_Timer(100)
	while mode != MODE_RETURN:
		dt = fps_timer.get_last_tick_dt()
		dt = min( dt, 1.0/60.0 )

		game_input(dt)
		game_update(dt)
		fps = fps_timer.get_fps()
		if config.render_advanced:
			render_advanced.render_frame(fps)
		else:
			render_basic.render_frame(fps)
		pygame.display.flip()

		fps_timer.tick()

	audio.stop_all()

	pygame.mouse.set_visible(True)

	return points



#def die():
#	global points, lives, self_speed
#	audio.sounds.explode.start()
#	self_speed = 0.0
#	Explosion_Frame = 1
#	SmokeParticles = []
#	global Self_Parts
#	for i in range(50):
#		Self_Parts.append(Self_Part())
#	while Explosion_Frame < 300:
#		move()
#		if Explosion_Frame > 100:
#			if Explosion_Frame % 3 == 0: #every third frame
#				SmokeParticles.append(Smoke_Particle(0.02,self_pos))
#				SmokeParticles.append(Smoke_Particle(0.02,self_pos))
#				SmokeParticles.append(Smoke_Particle(0.02,self_pos))
#		for selfpart in Self_Parts:
#			selfpart.x += (selfpart.speedx/100.0)
#			selfpart.y += (selfpart.speedy/100.0)
#			selfpart.z += (selfpart.speedz/100.0)
###            if Explosion_Frame % 2 == 0:
###                SmokeParticles.append(Smoke_Particle(0.0,[selfpart.x,selfpart.y,selfpart.z]))
#			for i in range(50):
#				tmp_y_pos = selfpart.y
#				selfpart.speedy -= 0.005
#				tmp_y_pos += (selfpart.speedy/100.0)
#				if tmp_y_pos < 0.0375:
#					selfpart.speedy = selfpart.speedy*(-0.75)
#					selfpart.speedx *= 0.9
#					selfpart.speedz *= 0.9
#			selfpart.speedx *= 0.99
#			selfpart.speedz *= 0.99
#			if abs(selfpart.speedx) <= .001:
#				selfpart.speedx = 0.0
#			if abs(selfpart.speedz) <= .001:
#				selfpart.speedz = 0.0
#		Draw.draw_dead(view_distance, view_angle,
#			self_pos, self_rot,
#			laser_flip,
#			PowerUps,
#			power_up_translate_y, power_up_rotate,
#			EvilMarbles,
#			Self_Parts,
#			SmokeParticles)
#		Explosion_Frame += 1
