//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------

function initServer()
{
   echo("\n--------- Initializing " @ $appName @ ": Server Scripts ---------");
   
   //load prefs

   //Force-load the defaults just so we don't have any mistakes
   exec( "./defaults.cs" );   
   
   //Then, if the user has saved preferences, we load those over-top the defaults
   %prefPath = getPrefpath();
   if ( isFile( %prefPath @ "/serverPrefs.cs" ) )
      exec( %prefPath @ "/serverPrefs.cs" );
   
   exec( "./audio.cs" );
   exec( "./commands.cs" );
   exec( "./kickban.cs" );
   exec( "./message.cs" );
   exec( "./levelDownload.cs" );
   exec( "./levelLoad.cs" );
   exec( "./levelInfo.cs" );
   exec( "./connectionToClient.cs" );

   // Server::Status is returned in the Game Info Query and represents the
   // current status of the server. This string sould be very short.
   $Server::Status = "Unknown";

   // Turn on testing/debug script functions
   $Server::TestCheats = false;

   // Specify where the mission files are.
   $Server::MissionFileSpec = "data/levels/*.mis";
   
   callOnModules("initServer");
   
   //Maybe this should be a pref for better per-project control
   //But many physically based/gameplay things utilize materials being detected
   //So we'll load on the server as well
   loadModuleMaterials();
}

//-----------------------------------------------------------------------------
function initDedicated()
{
   enableWinConsole(true);
   echo("\n--------- Starting Dedicated Server ---------");

   // Make sure this variable reflects the correct state.
   $Server::Dedicated = true;

   // The server isn't started unless a mission has been specified.
   if ($missionArg !$= "") {
      createServer("MultiPlayer", $missionArg);
   }
   else
      echo("No mission specified (use -mission filename)");
}

/// Attempt to find an open port to initialize the server with
function portInit(%port)
{
   %failCount = 0;
   while(%failCount < 10 && !setNetPort(%port))
   {
      echo("Port init failed on port " @ %port @ " trying next port.");
      %port++; %failCount++;
   }
}

/// Create a server of the given type, load the given level, and then
/// create a local client connection to the server.
//
/// @return true if successful.
function createAndConnectToLocalServer( %serverType, %levelAsset )
{
   if( !createServer( %serverType, %levelAsset ) )
      return false;
   
   %conn = new GameConnection( ServerConnection );
   RootGroup.add( ServerConnection );
   
   %conn.setConnectArgs( $pref::Player::Name );
   %conn.setJoinPassword( $Client::Password );
   
   %result = %conn.connectLocal();
   if( %result !$= "" )
   {
      %conn.delete();
      destroyServer();
         
      MessageBoxOK("Error starting local server!", "There was an error when trying to connect to the local server.");
      
      %mainMenuGUI = ProjectSettings.value("UI/mainMenuName");
      if (isObject( %mainMenuGUI ))
         Canvas.setContent( %mainMenuGUI );
      
      return false;
   }
   
   return true;
}

/// Create a server with either a "SinglePlayer" or "MultiPlayer" type
/// Specify the level to load on the server
function createServer(%serverType, %levelAsset)
{
   if($Game::firstTimeServerRun == true)
   {
      initServer();
      $Game::firstTimeServerRun = false;
   }
   // Increase the server session number.  This is used to make sure we're
   // working with the server session we think we are.
   $Server::Session++;
   
   if (%levelAsset $= "")
   {
      error("createServer(): level name unspecified");
      return false;
   }
   
   // Make sure our level name is relative so that it can send
   // across the network correctly
   //%level = makeRelativePath(%level, getWorkingDirectory());

   destroyServer();

   $missionSequence = 0;
   $Server::PlayerCount = 0;
   $Server::ServerType = %serverType;
   $Server::LoadFailMsg = "";
   $Physics::isSinglePlayer = true;
   
   // Setup for multi-player, the network must have been
   // initialized before now.
   if (%serverType $= "MultiPlayer")
   {
      $Physics::isSinglePlayer = false;
            
      echo("Starting multiplayer mode");

      // Make sure the network port is set to the correct pref.
      portInit($Pref::Server::Port);
      allowConnections(true);

      if ($pref::Net::DisplayOnMaster !$= "Never" )
         schedule(0,0,startHeartbeat);
   }
   
   callOnModules("onCreateGameServer", "Core");
   callOnModules("onCreateGameServer", "Game");
   
   // Let the game initialize some things now that the
   // the server has been created
   onServerCreated();

   loadMission(%levelAsset, true);
   
   $Game::running = true;
   
   return true;
}

function onServerCreated()
{
   // Server::GameType is sent to the master server.
   // This variable should uniquely identify your game and/or mod.
   $Server::GameType = $appName;

   // Server::MissionType sent to the master server.  Clients can
   // filter servers based on mission type.
  // $Server::MissionType = "Deathmatch";

   // GameStartTime is the sim time the game started. Used to calculated
   // game elapsed time.
   $Game::StartTime = 0;

   // Create the server physics world.
   physicsInitWorld( "server" );

   physicsStartSimulation("server");
   loadDatablockFiles( DatablockFilesList, true );
   
   callOnModules("onServerScriptExec", "Core");
   callOnModules("onServerScriptExec", "Game");   
   
   // Keep track of when the game started
   $Game::StartTime = $Sim::Time;
}

/// Shut down the server
function destroyServer()
{
   $Server::ServerType = "";
   $Server::Running = false;
   
   allowConnections(false);
   stopHeartbeat();
   $missionRunning = false;
   
   // End any running levels and shut down the physics sim
   onServerDestroyed();

   //physicsDestroy();

   // Delete all the server objects
   if (isObject(ServerGroup))
      ServerGroup.delete();

   // Delete all the connections:
   while (ClientGroup.getCount())
   {
      %client = ClientGroup.getObject(0);
      %client.delete();
   }

   $Server::GuidList = "";

   // Delete all the data blocks...
   deleteDataBlocks();
   
   //Get our modules so we can exec any specific server-side loading/handling
   callOnModules("onDestroyGameServer", "Game");
   callOnModules("onDestroyGameServer", "Core");
   
   // Save any server settings
   %prefPath = getPrefpath();
   echo( "Exporting server prefs..." );
   export( "$Pref::Server::*", %prefPath@"/serverPrefs.cs", false );
   
   BanList::Export(%prefPath@"/banlist.cs");

   // Increase the server session number.  This is used to make sure we're
   // working with the server session we think we are.
   $Server::Session++;
}

function onServerDestroyed()
{
   physicsStopSimulation("server");
   
   if (!isObject( getScene(0) ))
      return;

   echo("*** ENDING MISSION");
   
   // Inform the game code we're done.
   %hasGameMode = callGamemodeFunction("onMissionEnded");

   // Inform the clients
   for( %clientIndex = 0; %clientIndex < ClientGroup.getCount(); %clientIndex++ ) {
      // clear ghosts and paths from all clients
      %cl = ClientGroup.getObject( %clientIndex );
      %cl.endMission();
      %cl.resetGhosting();
      %cl.clearPaths();
   }
   
   // Delete everything
   getScene(0).delete();
   MissionCleanup.delete();
   
   clearServerPaths();
   
  if ($Pref::Server::EnableDatablockCache)
    resetDatablockCache();
   DatablockFilesList.empty();
}

/// Guid list maintenance functions
function addToServerGuidList( %guid )
{
   %count = getFieldCount( $Server::GuidList );
   for ( %i = 0; %i < %count; %i++ )
   {
      if ( getField( $Server::GuidList, %i ) == %guid )
         return;
   }

   $Server::GuidList = $Server::GuidList $= "" ? %guid : $Server::GuidList TAB %guid;
}

function removeFromServerGuidList( %guid )
{
   %count = getFieldCount( $Server::GuidList );
   for ( %i = 0; %i < %count; %i++ )
   {
      if ( getField( $Server::GuidList, %i ) == %guid )
      {
         $Server::GuidList = removeField( $Server::GuidList, %i );
         return;
      }
   }
}

/// When the server is queried for information, the value of this function is
/// returned as the status field of the query packet.  This information is
/// accessible as the ServerInfo::State variable.
function onServerInfoQuery()
{
   return "Doing Ok";
}