
////////////////
// LOGGING /////
////////////////

function SGR_Log( text )
{
	println( text );
}
function SGR_Fatal( text )
{
	SGR_Log( text );
	sys_exit( 1 );
}

SGR_Log( "SGR Engine" );
SGR_Log( "Started " $ os_date_string( "%c" ) );
SGR_Log( "Loading..." );


////////////////
// CONFIG //////
////////////////

include "gamefw";


if( !isset( _G, "DEBUG" ) )
	global DEBUG = false;


global SDLK__MB = 0xfffffff0;
global SDLK_LMB = SDLK__MB + 1;
global SDLK_RMB = SDLK__MB + 2;
global SDLK_MMB = SDLK__MB + 3;


global SOUNDTYPE_MUSIC = 1;
global SOUNDTYPE_SFX   = 2;
global SOUNDTYPE_VOICE = 3;


global gSGR_GameConfig =
{
	GameName = "<UNNAMED GAME>",
	AppDataDir = "<SGR_FIXME>",
	SplashScreen = null,
	ConfigFile = "config.cfg",
	LevelExt = "lev",
	LevelScriptExt = "sgs",
	StartupLevel = "startup",
};
global gSGR_Props =
{
	"game.root"               = "",
	"game.show_splash_screen" = "true",
	"window.width"            = "640",
	"window.height"           = "480",
	"renderer.fullscreen"     = "true",
	"renderer.vsync"          = "true",
	"sound.master_volume"     = "0.5",
	"sound.music_volume"      = "1",
	"sound.sfx_volume"        = "1",
	"sound.voice_volume"      = "1",
};
global gSGR_Props_NoSave =
{
	"game.root"=1,
	"game.show_splash_screen"=1,
	"game.startup_level"=1,
};
global gSGR_Keys = {}, gSGR_PrevKeys = {};

function SGR_PropSet( prop, val ){ gSGR_Props[ prop ] = tostring( val ); SGR_Log( "SET '" $ prop $ "' = '" $ tostring(val) $ "'" ); }
function SGR_PropSetDefault( prop, def ){ if( !isset( gSGR_Props, prop ) ) SGR_PropSet( prop, def ); }
function SGR_PropGet( prop, def ){ if( def === null ) def = ""; if( !isset( gSGR_Props, prop ) ) return tostring(def); return gSGR_Props[ prop ]; }
function SGR_PropGetBool( prop, def ){ return string_tolower( SGR_PropGet( prop, def ) ) == "true"; }
function SGR_PropGetInt( prop, def ){ return toint( SGR_PropGet( prop, def ) ); }
function SGR_PropGetReal( prop, def ){ return toreal( SGR_PropGet( prop, def ) ); }
function SGR_PropDisableSave( prop ){ gSGR_Props_NoSave[ prop ] = 1; }
function SGR_PropLoadFile( file )
{
	data = io_file_read( file );
	lines = string_explode( string_replace( data, ["\r\n","\r"], "\n" ), "\n" );
	foreach( line : lines )
	{
		kva = string_explode( line, "=" );
		if( kva.size < 2 ) continue;
		key = string_trim( kva.shift() );
		val = string_trim( string_implode( kva, "=" ) );
		SGR_PropSet( key, val );
	}
}
function SGR_PropSerialize()
{
	out = "";
	foreach( prop, val : gSGR_Props )
	{
		if( isset( gSGR_Props_NoSave, prop ) )
			continue;
		out $= prop $ "=" $ val $ "\r\n";
	}
	return out;
}
function SGR_PropSaveFile( file ){ return io_file_write( file, SGR_PropSerialize() ); }


////////////////
// LEVEL ///////
////////////////

function GameWorld.load_level( level )
{
	filename = gSGR_GameConfig.GameDir $ "/levels/" $ level $ "." $ gSGR_GameConfig.LevelExt;
	filename_script = gSGR_GameConfig.GameDir $ "/levels/" $ level $ "." $ gSGR_GameConfig.LevelScriptExt;
	
	global gSGR_Scene = this.scene = gSGR_SS3D.createScene();
	
	this.unload_level();
	this.cur_level = level;
	
	loadamount = 0;
	if( io_file_exists( filename ) )
	{
		data = io_file_read( filename );
		if( data )
		{
			data = unserialize( data );
			// TODO
		//	loadamount++;
		}
	}
	
	global gSGR_OnGlobalSerialize = function(){};
	global gSGR_OnGlobalUnserialize = function(){};
	
	if( io_file_exists( filename_script ) )
	{
		eval_file( filename_script );
		loadamount++;
	}
	
	return loadamount > 0;
}

function GameWorld.unload_level()
{
	this.remove_all_entities();
}

function GameWorld.reload_level()
{
	if( isset( this, "cur_level" ) )
		this.load_level( this.cur_level );
}


////////////////
// GAME ////////
////////////////

global Game =
{
	OverlayScreens = [],
	Running = true,
	OverlayScreenNeedsBgr = true,
	Paused = null,
	PrevPaused = null,
};

function Game.SetPaused( paused, force )
{
	paused = tobool( paused );
	if( this.Paused !== paused || force )
	{
		gSGR_Window.grab = !paused;
		SS_ShowCursor( paused );
		this.Running = !paused;
		this.Paused = paused;
	}
}

function Game.Start( level )
{
	if( !gSGR_World.load_level( level ) ){ return SGR_Log( "Failed to load map '"$level$"'!" ); }
	this.SetPaused( false, true );
}

function Game.End()
{
	gSGR_World.unload_level();
	this.SetPaused( true, true );
}

function Game.Restart()
{
	gSGR_World.reload_level();
	this.SetPaused( false, true );
}

function Game.AddOverlayScreen( name, params )
{
	this.OverlayScreens.push( name.create( params ) );
}

function Game.PopOverlayScreen()
{
	this.OverlayScreens.pop();
}

function Game.Run( delta )
{
	W = gSGR_Window.width;
	H = gSGR_Window.height;
	
	SS_Clear( color(0.04,0.04,0.04) );
	
	if( !this.Paused )
	{
		hx = floor( gSGR_Window.width / 2 );
		hy = floor( gSGR_Window.height / 2 );
		global gSGR_MouseMove = gSGR_MousePos - vec2( hx, hy );
		gSGR_Window.warpMouse( hx, hy );
	}
//	gSGR_MouseMove = vec2(0,0);
	
	global gSGR_Scene = gSGR_World.scene;
	gSGR_World.tick_nodraw( if( this.Running, delta, 0 ) );
	
	gSGR_SS3D.currentScene = gSGR_Scene;
	gSGR_SS3D.render();
	gSGR_SS3D.currentScene = null;
	
	SS_SetCameraUI( 0, W, 0, H );
	
	gSGR_World.draw_ui( W, H );
	
	if( this.OverlayScreens && this.OverlayScreenNeedsBgr )
	{
		SS_SetBlending( SS_BLENDOP_REVERSE_SUBTRACT, SS_BLEND_SRCALPHA, SS_BLEND_ONE );
		SS_DrawColorRect_WH( 0, 0, 9999, 9999, 1, 1, 1, 0.2 );
	}
	
	SS_SetBlending( SS_BLENDOP_ADD, SS_BLEND_SRCALPHA, SS_BLEND_INVSRCALPHA );
	
	if( this.OverlayScreens )
	{
		for( i = 0; i < this.OverlayScreens.size; ++i )
		{
			screen = this.OverlayScreens[ i ];
			remscr = screen.draw( delta );
			if( remscr )
			{
				this.OverlayScreens.erase( i-- );
			}
		}
	}
	
	global gSGR_PrevKeys = clone( gSGR_Keys );
	gSGR_Actions.advance();
	this.PrevPaused = this.Paused;
}

function Game.OnEvent( e )
{
	inhibited = false;
	for( i = this.OverlayScreens.size - 1; i >= 0; --i )
	{
		screen = this.OverlayScreens[ i ];
		if( screen.on_event( e ) === false )
			inhibited = true;
		if( inhibited )
			break;
	}
	if( inhibited )
		return;
	
	if( e.type == SDL_KEYDOWN || e.type == SDL_KEYUP )
	{
		// ACTION MAP
		if( !e.repeat )
		{
			gSGR_Actions.set( e.keycode, e.type == SDL_KEYDOWN );
			gSGR_Keys[ e.keycode ] = e.type == SDL_KEYDOWN;
		}
	}
	
	if( e.type == SDL_MOUSEBUTTONDOWN || e.type == SDL_MOUSEBUTTONUP )
	{
		// ACTION MAP
		gSGR_Actions.set( SDLK__MB + e.button, e.type == SDL_MOUSEBUTTONDOWN );
	}
	
	if( e.type == SDL_MOUSEMOTION )
	{
		global gSGR_MouseMove, gSGR_MousePos = vec2( e.x, e.y );
//		gSGR_MouseMove += vec2( e.xrel, e.yrel );
	}
}


////////////////
// SERIALIZE ///
////////////////

global SGR_GameState = null;
global gSGR_LastStateError = "";
function SGR_SerializeState()
{
	if( !gSGR_World.cur_level )
	{
		global gSGR_LastStateError = "level not loaded";
		return false;
	}
	
	outdata =
	{
		level = gSGR_World.cur_level,
		auto_id = gSGR_World.auto_id,
		entities = data_entities = [],
		globdata = gSGR_OnGlobalSerialize(),
	};
	
	foreach( ent : gSGR_World.entities )
	{
		if( @ent.serialize )
		{
			(type,params,data) = ent.serialize();
			outent =
			{
				id = ent.id,
				z_index = ent.z_index,
				type = type,
				params = params,
				data = data,
			};
			data_entities.push( outent );
		}
	}
	
	global SGR_GameState = serialize( outdata );
	return !!SGR_GameState;
}
function SGR_UnserializeState()
{
	global gSGR_LastStateError;
	if( gSGR_World.in_tick )
	{
		gSGR_LastStateError = "cannot unserialize inside tick";
	}
	if( !SGR_GameState )
	{
		gSGR_LastStateError = "state not set";
		return false;
	}
	if( !( data = @unserialize( SGR_GameState ) ) || !@data.level )
	{
		gSGR_LastStateError = "data unserialization failed";
		return false;
	}
	
	// change level if necessary
	if( gSGR_World.cur_level != data.level )
	{
		gSGR_World.load_level( data.level );
	}
	
	// find reusable entities
	S = data.entities;
	L = get_values( gSGR_World.entities );
	foreach( spec : S )
	{
		for( i = 0; i < L.size; ++i )
		{
			ent = L[i];
			if( @ent.can_reuse && ent.can_reuse( spec.type, spec.params, spec.data, spec ) )
			{
				spec.entity = ent;
				L.erase( i-- );
			}
		}
	}
	
	// remove unneeded entities
	foreach( ent : L )
		gSGR_World.remove_entity( ent.id );
	
	// create missing entities
	foreach( spec : S )
	{
		if( !@spec.entity )
		{
			spec.entity = _G[ "ENTITY_CREATE_" $ spec.type ]( spec.params );
			spec.entity.z_index = spec.z_index;
			gSGR_World.auto_id = spec.id;
			gSGR_World.add_entity( spec.entity );
		}
		else
		{
			spec.entity.id = spec.id;
			spec.entity.z_index = spec.z_index;
		}
	}
	
	// apply global state
	gSGR_World.auto_id = data.auto_id;
	gSGR_OnGlobalUnserialize( data.globdata );
	
	// unserialize entities that need it
	foreach( spec : S )
	{
		if( @spec.entity.unserialize )
			spec.entity.unserialize( spec.data, spec.params );
	}
	
	// 0-tick
	gSGR_World.tick_nodraw(0.0);
	return true;
}


/***********************************
* configure ************************
***********************************/

function configure()
{
	global sys_exit;
	
	for( i = 0; i < sys_args.size; ++i )
	{
		var arg = sys_args[ i ];
		if( arg == "-profile" )
		{
			enable_profiler();
			SGR_Log( "PROFILER [mode 1 - functions] ENABLED" );
		}
		else if( arg == "-profile-ops" )
		{
			enable_profiler2();
			SGR_Log( "PROFILER [mode 2 - instructions] ENABLED" );
		}
		else if( arg == "-profile-mem" )
		{
			enable_profiler(3);
			SGR_Log( "PROFILER [mode 3 - memory] ENABLED" );
		}
		else if( arg == "-game" )
			SGR_PropSet( "game.root", false );
		else if( arg == "-nosplash" )
			SGR_PropSet( "game.show_splash_screen", false );
		else if( arg == "-map" )
		{
			SGR_PropSet( "game.show_splash_screen", false );
			SGR_PropSet( "game.startup_level", sys_args[ ++i ] );
		}
	}
	
	// configure 3D renderer
	if( !@include( "ss3d" ) )
	{
		ERROR( "Please install DirectX to run the game" );
		sys_exit(1);
	}
	
	// configure game
	gamedir = SGR_PropGet( "game.root" );
	if( !gamedir )
	{
		SGR_Log( "No game selected, exiting..." );
		sys_exit = true;
	}
	
	include gamedir $ "/game";
	
	global gSGR_GameConfig = get_merged( gSGR_GameConfig, GameConfig );
	gSGR_GameConfig.GameDir = gamedir;
	SGR_PropLoadFile( gamedir $ "/" $ gSGR_GameConfig.ConfigFile );
	
	SGR_PropSetDefault( "game.startup_level", gSGR_GameConfig.StartupLevel );
}


/***********************************
* initialize ***********************
***********************************/

function initialize()
{
	SGR_Log( "Initializing core systems..." );
	global gSGR_Actions = ActionMap.create();
	global gSGR_World = GameWorld.create();
	foreach( is_real, entry : io_dir( gSGR_GameConfig.GameDir $ "/entities" ) )
	{
		if( is_real )
			include( gSGR_GameConfig.GameDir $ "/entities/" $ entry );
	}
	
	SGR_Log( "Initializing audio..." );
	include "sgs-audio";
	global gSGR_SoundSystem = sgs_audio_create();
	gSGR_SoundSystem.set_volume( SGR_PropGetReal( "sound.master_volume" ), 0 );
	gSGR_SoundSystem.set_volume( SGR_PropGetReal( "sound.music_volume" ), SOUNDTYPE_MUSIC );
	gSGR_SoundSystem.set_volume( SGR_PropGetReal( "sound.sfx_volume" ), SOUNDTYPE_SFX );
	gSGR_SoundSystem.set_volume( SGR_PropGetReal( "sound.voice_volume" ), SOUNDTYPE_VOICE );
	
	SGR_Log( "Initializing core video..." );
	global gSGR_Window = SS_CreateWindow
	(
		gSGR_GameConfig.GameName,
		SDL_WINDOWPOS_UNDEFINED,
		SDL_WINDOWPOS_UNDEFINED,
		SGR_PropGetInt( "window.width" ),
		SGR_PropGetInt( "window.height" ),
		SDL_WINDOW_RESIZABLE | ( SGR_PropGetBool( "renderer.fullscreen" ) * SDL_WINDOW_FULLSCREEN )
	);
	if( !gSGR_Window )
	{
		SGR_Fatal( "Failed to create a window; check platform support and resolution!" );
	}
	
	if( !gSGR_Window.initRenderer( SS_RENDERER_DIRECT3D9, SGR_PropGetBool( "renderer.vsync" ) * SS_RENDERER_VSYNC ) )
	{
		SGR_Fatal( "Failed to create renderer; check platform support and resolution!" );
	}
	
	SGR_Log( "Initializing default resources..." );
	SS_InitDrawFunctions();
	global gSGR_DefaultFont = SS_CreateFont( "fonts/lato-regular.ttf", 12 );
	
	SGR_Log( "Initializing 3D rendering..." );
	global gSGR_SS3D = SS3D_CreateRenderer( gSGR_Window.renderingAPI, gSGR_Window.rendererPtr );
	
	SGR_Log( "Initialized" );
	SGR_Log( "Starting..." );
	Game.Start( SGR_PropGet( "game.startup_level" ) );
}


/***********************************
* cleanup **************************
***********************************/

function cleanup()
{
	SGR_Log( "Finished" );
}


/***********************************
* update ***************************
***********************************/

global gSGR_MousePos = vec2(0,0);
global gSGR_MouseMove = vec2(0,0);

function real_update( delta, starttime )
{
	numallocs = sys_stat(4);
	
	Game.Run( delta );
	
	if( DEBUG )
	{
		SS_DrawTextLine( "allocs per frame: " $ ( sys_stat(4) - numallocs ), gSGR_DefaultFont, 4, 4, color(0.9,0.8,0.7,1) );
		SS_DrawTextLine( "memory usage: " $ ( sys_stat(3) / 1024 ) $ " kB", gSGR_DefaultFont, 4, 18, color(0.9,0.8,0.7,1) );
		tx = "frame time: " $ toint( ( ftime() - starttime ) * 1000.0 ) $ " ms";
		SS_DrawTextLine( tx, gSGR_DefaultFont, 5, 35, color(0,0,0,1) );
		SS_DrawTextLine( tx, gSGR_DefaultFont, 4, 34, color(0.9,0.8,0.7,1) );
	}
	
	SS_Present();
}

global update = GameLoop.create( real_update );


/***********************************
* on_event *************************
***********************************/

function on_event( e )
{
	if( e.type == SDL_QUIT )
		global sys_exit = true;
	SS3D_Event( gSGR_SS3D, e );
	Game.OnEvent( e );
	if( @gSGR_HandleEvent )
		gSGR_HandleEvent( e );
}
