//-----------------------------------------------------------------------------
// Torque Game Engine 
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platformMacCarb/platformMacCarb.h"
#include "platformMacCarb/macCarbEvents.h"
#include "platform/platformThread.h"
#include "platform/gameInterface.h"
#include "core/fileio.h"

//-----------------------------------------------------------------------------
// _MacCarbRunTorqueMain() starts the Game.main() loop
//-----------------------------------------------------------------------------
static void _MacCarbRunTorqueMain()
{
   platState.torqueThreadId = Thread::getCurrentThreadId();
   platState.windowSize.set(0,0);
   platState.lastTimeTick = Platform::getRealMilliseconds();
   platState.appReturn = Game->main(platState.argc, platState.argv);
   
   if(!platState.headless)
   {
      HICommand cmd;
      dMemset(&cmd, 0, sizeof(HICommand));
      cmd.commandID = kHICommandQuit;
      ProcessHICommand( &cmd );
   }
}
   
//-----------------------------------------------------------------------------
// Handler stub for bsd signals.
//-----------------------------------------------------------------------------
static void _MacCarbSignalHandler(int )
{
   // we send the torque thread a SIGALRM to wake it up from usleep()
   // when transitioning from background - forground.
}

//-----------------------------------------------------------------------------
//  Thread subclass, for running Torque in multithreaded mode
//-----------------------------------------------------------------------------
class TorqueMainThread : public Thread
{
public:
   TorqueMainThread() : Thread(NULL,0,false) { }

   virtual void run(S32 arg)
   {
      signal(SIGALRM, _MacCarbSignalHandler);
      _MacCarbRunTorqueMain();
   }
};

//-----------------------------------------------------------------------------
//  RunAppEventLoop Callback, for running Torque in single threaded mode
//-----------------------------------------------------------------------------
void _MacCarbRAELCallback(EventLoopTimerRef theTimer, void *userData)
{
   _MacCarbRunTorqueMain();
}

#pragma mark -

//-----------------------------------------------------------------------------
// command line arg processing
//-----------------------------------------------------------------------------
#define KEYISDOWN(key) ((((unsigned char *)currKeyState)[key>>3] >> (key & 7)) & 1)
static bool _MacCarbCheckProcessTxtFileArgs()
{
   // this is yucky, but the easiest way to ignore the cmd line args:
   KeyMap currKeyState;
   GetKeys(currKeyState);
   if (KEYISDOWN(0x38)) // check shift key -- actually LShift.
      return false;
   else
      return true;
}

//-----------------------------------------------------------------------------
static void _MacCarbGetTxtFileArgs(int &argc, char** argv, int maxargc)
{
   argc = 0;
   
   const U32 kMaxTextLen = 2048; // arbitrary
   U32 textLen;
   char* text = new char[kMaxTextLen];   

   // open the file, kick out if we can't
   File cmdfile;
   File::Status err = cmdfile.open("maccmdline.txt", cmdfile.Read);
   if(err != File::Ok)
      return;
   
   // read in the first kMaxTextLen bytes, kick out if we get errors or no data
   err = cmdfile.read(kMaxTextLen-1, text, &textLen);
   if(!((err == File::Ok || err == File::EOS) || textLen > 0))
   {
      cmdfile.close();
      return;
   }
   
   // null terminate
   text[textLen++] = '\0';
   // truncate to the 1st line of the file
   for(int i = 0; i < textLen; i++)
   {
      if( text[i] == '\n' || text[i] == '\r' )
      {
         text[i] = '\0';
         textLen = i+1;
         break;
      }
   }

   // tokenize the args with nulls, save them in argv, count them in argc
   char* tok;
   for(tok = dStrtok(text, " "); tok && argc < maxargc; tok = dStrtok(NULL, " "))
   {
      argv[argc++] = tok;
   }
}

//-----------------------------------------------------------------------------
static void _MacCarbFilterCmdLineArgs( int &argc, char** argv)
{
   // MacOSX gui apps get at least 2 args: the full path to their binary, 
   // and a process serial number, formed something like: "-psn_0_123456".
   // Torque doesnt want to see the psn arg, so we strip it out.
   int j = 0;
   for(int i = 0; i < argc; i++)
   {
      if(dStrncmp(argv[i], "-psn", 4) != 0)
         argv[j++] = argv[i];

      if(dStrncmp(argv[i], "-headless", 9) == 0)
      {
         printf("entering headless mode\n");
         platState.headless = true;
      }
   }
   argc = j;
}

#pragma mark -

//-----------------------------------------------------------------------------
// main() - the real one - this is the actual program entry point.
//-----------------------------------------------------------------------------
S32 main(S32 argc, const char **argv)
{
   const int kMaxCmdlineArgs = 32; // arbitrary

   // get the actual command line args
   S32   newArgc = argc;
   char* newArgv[kMaxCmdlineArgs];
   for(int i=0; i < argc && i < kMaxCmdlineArgs; i++)
      newArgv[i] = argv[i];

   if( _MacCarbCheckProcessTxtFileArgs() )
   {
      // get the text file args
      S32 textArgc;
      char* textArgv[kMaxCmdlineArgs];
      _MacCarbGetTxtFileArgs(textArgc, textArgv, kMaxCmdlineArgs);
      
      // merge them
      int i=0;
      while(i < textArgc && newArgc < kMaxCmdlineArgs)
         newArgv[newArgc++] = textArgv[i++];
   }
   
   // filter them
   _MacCarbFilterCmdLineArgs( newArgc, newArgv);
   
   // store them in platState
   platState.argc = newArgc;
   platState.argv = newArgv;
    
   MacCarbInit1020CompatInit();

   // Headless mode is for sitations where torque must run as a command line
   // tool, without a connection to the window server. Any windowing or event 
   // calls may crash the app if there is no window server, so we avoid them
   // in headless mode.
   if(!platState.headless)
   {
      InitCursor();
      
      FlushEvents( everyEvent, 0 );
      SetEventMask(everyEvent);

      // push us to the front, just to be sure
      ProcessSerialNumber psn = { 0, kCurrentProcess };
      SetFrontProcess(&psn);
   }
   
   // save away OS version info into platState.
   Gestalt(gestaltSystemVersion, (SInt32 *) &(platState.osVersion));

   // Update the current working directory.
   Platform::getWorkingDirectory();
    
   // now, we prepare to hand off execution to torque & macosx.
   platState.appReturn = 0;
   platState.firstThreadId = Thread::getCurrentThreadId();

#if !defined(TORQUE_MULTITHREAD)
   // Install a one-shot timer to run the game, then call RAEL to install
   // the default application handler (which can't be called directly).
   EventLoopTimerRef timer;
   InstallEventLoopTimer(GetCurrentEventLoop(), 0, 0, 
                     NewEventLoopTimerUPP(_MacCarbRAELCallback), NULL, &timer);
   RunApplicationEventLoop();
#else
   // Put the Torque application loop in one thread, and the event listener loop
   // in the other thread. The event loop must use the process's initial thread.

   // We need to cache a ref to the main event queue because GetMainEventQueue
   // is not thread safe pre 10.4 . 
   platState.mainEventQueue = GetMainEventQueue();

   // We need to install event handlers for interthread communication.
   // Events and some system calls must happen in the process's initial thread.
   MacCarbInstallTorqueCarbonEventHandlers();
   
   TorqueMainThread mainLoop;
   mainLoop.start();
   
   if(!platState.headless)
   {
      //printf("starting RAEL\n");
      RunApplicationEventLoop();
   }
   //printf("trying to join main loop...\n");
   mainLoop.join();
   //printf("main loop joined.\n");
#endif   
   InitCursor(); // don't leave it in a screwy state...
   
   //printf("exiting...\n");   
   return(platState.appReturn);
   
}