/* Orx - Portable Game Engine
 *
 * Copyright (c) 2008-2017 Orx-Project
 *
 * This software is provided 'as-is', without any express or implied
 * warranty. In no event will the authors be held liable for any damages
 * arising from the use of this software.
 *
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 *    1. The origin of this software must not be misrepresented; you must not
 *    claim that you wrote the original software. If you use this software
 *    in a product, an acknowledgment in the product documentation would be
 *    appreciated but is not required.
 *
 *    2. Altered source versions must be plainly marked as such, and must not be
 *    misrepresented as being the original software.
 *
 *    3. This notice may not be removed or altered from any source
 *    distribution.
 */

/**
 * @file orx.h
 * @date 02/09/2005
 * @author iarwain@orx-project.org
 *
 * @todo
 */

/**
 * @addtogroup Orx
 *
 * Main orx include, execution convenience helpers, freely modifiable by users
 *
 * @{
 */

#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */

#ifndef _orx_H_
#define _orx_H_

#define __orxEXTERN__ /* Not compiling orx library */


#include "orxInclude.h"
#include "orxKernel.h"
#include "orxUtils.h"

#ifndef __orxPLUGIN__

/***************************************************************************
 * Static variables                                                        *
 ***************************************************************************/

/** Should stop execution by default event handling?
 */
static orxBOOL sbStopByEvent = orxFALSE;


/***************************************************************************
 * Public functions                                                        *
 ***************************************************************************/

/** Orx default basic event handler
 * @param[in]   _pstEvent                     Sent event
 * @return      orxSTATUS_SUCCESS if handled / orxSTATUS_FAILURE otherwise
 */
static orxSTATUS orxFASTCALL orx_DefaultEventHandler(const orxEVENT *_pstEvent)
{
  orxSTATUS eResult = orxSTATUS_SUCCESS;

  /* Checks */
  orxASSERT(_pstEvent->eType == orxEVENT_TYPE_SYSTEM);

  /* Depending on event ID */
  switch(_pstEvent->eID)
  {
    /* Close event */
    case orxSYSTEM_EVENT_CLOSE:
    {
      /* Updates status */
      sbStopByEvent = orxTRUE;

      break;
    }

    default:
    {
      break;
    }
  }

  /* Done! */
  return eResult;
}

/** Default main setup (module dependencies)
*/
static void orxFASTCALL orx_MainSetup()
{
  /* Adds module dependencies */
  orxModule_AddDependency(orxMODULE_ID_MAIN, orxMODULE_ID_PARAM);
  orxModule_AddDependency(orxMODULE_ID_MAIN, orxMODULE_ID_CLOCK);
  orxModule_AddDependency(orxMODULE_ID_MAIN, orxMODULE_ID_CONFIG);
  orxModule_AddDependency(orxMODULE_ID_MAIN, orxMODULE_ID_INPUT);
  orxModule_AddDependency(orxMODULE_ID_MAIN, orxMODULE_ID_EVENT);
  orxModule_AddDependency(orxMODULE_ID_MAIN, orxMODULE_ID_FILE);
  orxModule_AddDependency(orxMODULE_ID_MAIN, orxMODULE_ID_LOCALE);
  orxModule_AddDependency(orxMODULE_ID_MAIN, orxMODULE_ID_PLUGIN);
  orxModule_AddDependency(orxMODULE_ID_MAIN, orxMODULE_ID_OBJECT);
  orxModule_AddDependency(orxMODULE_ID_MAIN, orxMODULE_ID_RENDER);

  orxModule_AddOptionalDependency(orxMODULE_ID_MAIN, orxMODULE_ID_CONSOLE);
  orxModule_AddOptionalDependency(orxMODULE_ID_MAIN, orxMODULE_ID_PROFILER);
  orxModule_AddOptionalDependency(orxMODULE_ID_MAIN, orxMODULE_ID_SCREENSHOT);

  return;
}

#ifdef __orxIOS__

  #ifdef __orxOBJC__

#import <UIKit/UIKit.h>

/** Orx application interface
 */
@interface orxAppDelegate : NSObject <UIAccelerometerDelegate>
{
  UIWindow           *poWindow;
  orxViewController  *poViewController;
}

@property (nonatomic, retain) UIWindow         *poWindow;
@property (nonatomic, retain) UIViewController *poViewController;

- (void)  MainLoop;

@end

extern orxSTATUS (orxFASTCALL *spfnRun)();

/** Orx main execution function
 * @param[in]   _u32NbParams                  Main function parameters number (argc)
 * @param[in]   _azParams                     Main function parameter list (argv)
 * @param[in]   _pfnInit                      Main init function (should init all the main stuff and register the main event handler to override the default one)
 * @param[in]   _pfnRun                       Main run function (will be called once per frame, should return orxSTATUS_SUCCESS to continue processing)
 * @param[in]   _pfnExit                      Main exit function (should clean all the main stuff)
 */
static orxINLINE void orx_Execute(orxU32 _u32NbParams, orxSTRING _azParams[], const orxMODULE_INIT_FUNCTION _pfnInit, const orxMODULE_RUN_FUNCTION _pfnRun, const orxMODULE_EXIT_FUNCTION _pfnExit)
{
  /* Inits the Debug System */
  orxDEBUG_INIT();

  /* Checks */
  orxASSERT(_u32NbParams > 0);
  orxASSERT(_azParams != orxNULL);
  orxASSERT(_pfnRun != orxNULL);

  /* Registers main module */
  orxModule_Register(orxMODULE_ID_MAIN, "MAIN", orx_MainSetup, _pfnInit, _pfnExit);

  /* Stores run callback */
  spfnRun = _pfnRun;

  /* Sends the command line arguments to orxParam module */
  if(orxParam_SetArgs(_u32NbParams, _azParams) != orxSTATUS_FAILURE)
  {
    NSAutoreleasePool *poPool;

    /* Allocates memory pool */
    poPool = [[NSAutoreleasePool alloc] init];

    /* Launches application */
    UIApplicationMain(_u32NbParams, _azParams, nil, @"orxAppDelegate");

    /* Releases memory pool */
    [poPool release];
  }

  /* Done! */
  return;
}

  #endif /* __orxOBJC__ */

#else /* __orxIOS__ */

  #if defined(__orxANDROID__) || defined(__orxANDROID_NATIVE__)

#include "main/orxAndroid.h"

/** Orx main execution function
 * @param[in]   _u32NbParams                  Main function parameters number (argc)
 * @param[in]   _azParams                     Main function parameter list (argv)
 * @param[in]   _pfnInit                      Main init function (should init all the main stuff and register the main event handler to override the default one)
 * @param[in]   _pfnRun                       Main run function (will be called once per frame, should return orxSTATUS_SUCCESS to continue processing)
 * @param[in]   _pfnExit                      Main exit function (should clean all the main stuff)
 */
static orxINLINE void orx_Execute(orxU32 _u32NbParams, orxSTRING _azParams[], const orxMODULE_INIT_FUNCTION _pfnInit, const orxMODULE_RUN_FUNCTION _pfnRun, const orxMODULE_EXIT_FUNCTION _pfnExit)
{
  /* Inits the Debug System */
  orxDEBUG_INIT();

  /* Checks */
  orxASSERT(_pfnRun != orxNULL);

  /* Registers main module */
  orxModule_Register(orxMODULE_ID_MAIN, "MAIN", orx_MainSetup, _pfnInit, _pfnExit);

  /* Sends the command line arguments to orxParam module */
  if(orxParam_SetArgs(_u32NbParams, _azParams) != orxSTATUS_FAILURE)
  {
    /* Inits the engine */
    if(orxModule_Init(orxMODULE_ID_MAIN) != orxSTATUS_FAILURE)
    {
      orxSYSTEM_EVENT_PAYLOAD stPayload;
      orxSTATUS               eClockStatus, eMainStatus;
      orxBOOL                 bStop;

      /* Registers default event handler */
      orxEvent_AddHandler(orxEVENT_TYPE_SYSTEM, orx_DefaultEventHandler);

      /* Clears payload */
      orxMemory_Zero(&stPayload, sizeof(orxSYSTEM_EVENT_PAYLOAD));

      /* Main loop */
      for(bStop = orxFALSE, sbStopByEvent = orxFALSE;
          bStop == orxFALSE;
          bStop = ((sbStopByEvent != orxFALSE) || (eMainStatus == orxSTATUS_FAILURE) || (eClockStatus == orxSTATUS_FAILURE)) ? orxTRUE : orxFALSE)
      {
        orxAndroid_PumpEvents();

        /* Sends frame start event */
        orxEVENT_SEND(orxEVENT_TYPE_SYSTEM, orxSYSTEM_EVENT_GAME_LOOP_START, orxNULL, orxNULL, &stPayload);

        /* Runs the engine */
        eMainStatus = _pfnRun();

        /* Updates clock system */
        eClockStatus = orxClock_Update();

        /* Sends frame stop event */
        orxEVENT_SEND(orxEVENT_TYPE_SYSTEM, orxSYSTEM_EVENT_GAME_LOOP_STOP, orxNULL, orxNULL, &stPayload);

        /* Updates frame counter */
        stPayload.u32FrameCounter++;
      }
    }

    /* Removes event handler */
    orxEvent_RemoveHandler(orxEVENT_TYPE_SYSTEM, orx_DefaultEventHandler);

    /* Exits from engine */
    orxModule_Exit(orxMODULE_ID_MAIN);
  }

  /* Exits from the Debug system */
  orxDEBUG_EXIT();
}

  #else /* __orxANDROID__ || __orxANDROID_NATIVE__ */

/** Orx main execution function
 * @param[in]   _u32NbParams                  Main function parameters number (argc)
 * @param[in]   _azParams                     Main function parameter list (argv)
 * @param[in]   _pfnInit                      Main init function (should init all the main stuff and register the main event handler to override the default one)
 * @param[in]   _pfnRun                       Main run function (will be called once per frame, should return orxSTATUS_SUCCESS to continue processing)
 * @param[in]   _pfnExit                      Main exit function (should clean all the main stuff)
 */
static orxINLINE void orx_Execute(orxU32 _u32NbParams, orxSTRING _azParams[], const orxMODULE_INIT_FUNCTION _pfnInit, const orxMODULE_RUN_FUNCTION _pfnRun, const orxMODULE_EXIT_FUNCTION _pfnExit)
{
  /* Inits the Debug System */
  orxDEBUG_INIT();

  /* Checks */
  orxASSERT(_u32NbParams > 0);
  orxASSERT(_azParams != orxNULL);
  orxASSERT(_pfnRun != orxNULL);

  /* Registers main module */
  orxModule_Register(orxMODULE_ID_MAIN, "MAIN", orx_MainSetup, _pfnInit, _pfnExit);

  /* Sends the command line arguments to orxParam module */
  if(orxParam_SetArgs(_u32NbParams, _azParams) != orxSTATUS_FAILURE)
  {
    /* Inits the engine */
    if(orxModule_Init(orxMODULE_ID_MAIN) != orxSTATUS_FAILURE)
    {
      orxSYSTEM_EVENT_PAYLOAD stPayload;
      orxSTATUS               eClockStatus, eMainStatus;
      orxBOOL                 bStop;

      /* Registers default event handler */
      orxEvent_AddHandler(orxEVENT_TYPE_SYSTEM, orx_DefaultEventHandler);

      /* Clears payload */
      orxMemory_Zero(&stPayload, sizeof(orxSYSTEM_EVENT_PAYLOAD));

      /* Main loop */
      for(bStop = orxFALSE, sbStopByEvent = orxFALSE;
          bStop == orxFALSE;
          bStop = ((sbStopByEvent != orxFALSE) || (eMainStatus == orxSTATUS_FAILURE) || (eClockStatus == orxSTATUS_FAILURE)) ? orxTRUE : orxFALSE)
      {
        /* Sends frame start event */
        orxEVENT_SEND(orxEVENT_TYPE_SYSTEM, orxSYSTEM_EVENT_GAME_LOOP_START, orxNULL, orxNULL, &stPayload);

        /* Runs the engine */
        eMainStatus = _pfnRun();

        /* Updates clock system */
        eClockStatus = orxClock_Update();

        /* Sends frame stop event */
        orxEVENT_SEND(orxEVENT_TYPE_SYSTEM, orxSYSTEM_EVENT_GAME_LOOP_STOP, orxNULL, orxNULL, &stPayload);

        /* Updates frame counter */
        stPayload.u32FrameCounter++;
      }
    }

    /* Removes event handler */
    orxEvent_RemoveHandler(orxEVENT_TYPE_SYSTEM, orx_DefaultEventHandler);

    /* Exits from engine */
    orxModule_Exit(orxMODULE_ID_MAIN);
  }

  /* Exits from the Debug system */
  orxDEBUG_EXIT();
}

    #ifdef __orxMSVC__

/** Orx main execution function (console-less windows application)
 * @param[in]   _pfnInit                      Main init function (should init all the main stuff and register the main event handler to override the default one)
 * @param[in]   _pfnRun                       Main run function (will be called once per frame, should return orxSTATUS_SUCCESS to continue processing)
 * @param[in]   _pfnExit                      Main exit function (should clean all the main stuff)
 */
static orxINLINE void orx_WinExecute(const orxMODULE_INIT_FUNCTION _pfnInit, const orxMODULE_RUN_FUNCTION _pfnRun, const orxMODULE_EXIT_FUNCTION _pfnExit)
{
  #define orxMAX_ARGS 256

  int   argc;
  char *argv[orxMAX_ARGS];
  char *pcToken, *pcNextToken, *pcFirstDelimiters;
  LPSTR lpFullCmdLine;

  /* Gets full command line */
  lpFullCmdLine = GetCommandLineA();

  /* Starts with a double quote? */
  if(*orxString_SkipWhiteSpaces(lpFullCmdLine) == '"')
  {
    /* Gets first delimiters */
    pcFirstDelimiters = "\"";
  }
  else
  {
    /* Gets first delimiters */
    pcFirstDelimiters = " ";
  }

  /* Process command line */
  for(argc = 0, pcNextToken = NULL, pcToken = strtok_s(lpFullCmdLine, pcFirstDelimiters, &pcNextToken);
      pcToken && (argc < orxMAX_ARGS);
      pcToken = strtok_s(NULL, " ", &pcNextToken))
  {
    argv[argc++] = pcToken;
  }

  /* Inits and executes orx */
  orx_Execute(argc, argv, _pfnInit, _pfnRun, _pfnExit);
}

    #endif /* __orxMSVC__ */

  #endif /* __orxANDROID__ || __orxANDROID_NATIVE__ */

#endif /* __orxIOS__ */

#endif /* __orxPLUGIN__ */

/** Logs all user-generated active structures
 * @return      orxSTATUS_SUCCESS / orxSTATUS_FAILURE
 */
static orxINLINE orxSTATUS orx_LogAllStructures()
{
#define orx_MAX_NAME_LENGTH                 20
#define orx_DECLARE_STRUCTURE_INFO(ID, FN)  {orxSTRUCTURE_ID_##ID, (orxSTRUCTURE_GETNAME)FN}

  typedef orxSTRING(orxFASTCALL *orxSTRUCTURE_GETNAME)(const void *_pStructure);

  static struct
  {
    orxSTRUCTURE_ID       eID;
    orxSTRUCTURE_GETNAME  pfnGetName;
  }
  sastStructureInfoList[] =
  {
    orx_DECLARE_STRUCTURE_INFO(OBJECT,    orxObject_GetName),
    orx_DECLARE_STRUCTURE_INFO(FONT,      orxFont_GetName),
    orx_DECLARE_STRUCTURE_INFO(VIEWPORT,  orxViewport_GetName),
    orx_DECLARE_STRUCTURE_INFO(ANIMSET,   orxAnimSet_GetName),
    orx_DECLARE_STRUCTURE_INFO(ANIM,      orxAnim_GetName),
    orx_DECLARE_STRUCTURE_INFO(BODY,      orxBody_GetName),
    orx_DECLARE_STRUCTURE_INFO(CAMERA,    orxCamera_GetName),
    orx_DECLARE_STRUCTURE_INFO(CLOCK,     orxClock_GetName),
    orx_DECLARE_STRUCTURE_INFO(FX,        orxFX_GetName),
    orx_DECLARE_STRUCTURE_INFO(GRAPHIC,   orxGraphic_GetName),
    orx_DECLARE_STRUCTURE_INFO(SHADER,    orxShader_GetName),
    orx_DECLARE_STRUCTURE_INFO(SOUND,     orxSound_GetName),
    orx_DECLARE_STRUCTURE_INFO(SPAWNER,   orxSpawner_GetName),
    orx_DECLARE_STRUCTURE_INFO(TEXT,      orxText_GetName),
    orx_DECLARE_STRUCTURE_INFO(TEXTURE,   orxTexture_GetName),
    orx_DECLARE_STRUCTURE_INFO(TIMELINE,  orxNULL)
  };
  orxU32        i;
  orxHASHTABLE *pstTable;
  orxSTATUS     eResult = orxSTATUS_FAILURE;

  /* Creates table */
  pstTable = orxHashTable_Create(1024, orxHASHTABLE_KU32_FLAG_NONE, orxMEMORY_TYPE_TEMP);

  /* Valid? */
  if(pstTable != orxNULL)
  {
    orxCHAR acBuffer[1024] = {0};
    orxU32  u32DebugFlags;

    /* Backups debug flags */
    u32DebugFlags = orxDEBUG_GET_FLAGS();

    /* Sets new debug flags */
    orxDEBUG_SET_FLAGS(orxDEBUG_KU32_STATIC_FLAG_NONE,
                       orxDEBUG_KU32_STATIC_FLAG_TIMESTAMP | orxDEBUG_KU32_STATIC_FLAG_TYPE);

    /* Logs header */
    orxLOG("*** BEGIN STRUCTURE LOG ***\n");

    /* For all IDs */
    for(i = 0; i < orxARRAY_GET_ITEM_COUNT(sastStructureInfoList); i++)
    {
      orxSTRUCTURE *pstStructure;

      /* Checks */
      orxASSERT(orxStructure_GetStorageType(sastStructureInfoList[i].eID) == orxSTRUCTURE_STORAGE_TYPE_LINKLIST);

      /* For all structures */
      for(pstStructure = orxStructure_GetFirst(sastStructureInfoList[i].eID);
          pstStructure != orxNULL;
          pstStructure = orxStructure_GetNext(pstStructure))
      {
        /* Is owner-less? */
        if(orxStructure_GetOwner(pstStructure) == orxNULL)
        {
          orxSTRUCTURE **ppstBucket;

          /* Retrieves hashtable bucket */
          ppstBucket = (orxSTRUCTURE **)orxHashTable_Retrieve(pstTable, orxStructure_GetGUID(pstStructure));

          /* Valid and not already logged? */
          if((ppstBucket != orxNULL) && (*ppstBucket == orxNULL))
          {
            /* Adds it to table */
            *ppstBucket = pstStructure;

            /* Logs it */
            if(sastStructureInfoList[i].pfnGetName != orxNULL)
            {
              orxU32 u32Offset;
              u32Offset = orxString_NPrint(acBuffer, sizeof(acBuffer) - 1, "%-8s \"%s\"", orxStructure_GetIDString(sastStructureInfoList[i].eID), sastStructureInfoList[i].pfnGetName(pstStructure));
              orxLOG("%s%*s[% 016llX]\n", acBuffer, orxMAX(0, orx_MAX_NAME_LENGTH + 12 - u32Offset), orxSTRING_EMPTY, pstStructure->u64GUID);
            }
            else
            {
              orxLOG("%-8s %*s [%016llX]\n", orxStructure_GetIDString(sastStructureInfoList[i].eID), orx_MAX_NAME_LENGTH + 2, orxSTRING_EMPTY, pstStructure->u64GUID);
            }

            //! TODO: Print branch owned by this structure
          }
        }
      }
    }

    /* Logs footer */
    orxLOG("*** END STRUCTURE LOG ***");

    /* Restores debug flags */
    orxDEBUG_SET_FLAGS(u32DebugFlags, orxDEBUG_KU32_STATIC_MASK_USER_ALL);

    /* Updates result */
    eResult = orxSTATUS_SUCCESS;

    /* Deletes table */
    orxHashTable_Delete(pstTable);
  }

#undef orx_DECLARE_STRUCTURE_INFO
#undef orx_MAX_NAME_LENGTH

  /* Done! */
  return eResult;
}

#endif /*_orx_H_*/

#ifdef __cplusplus
}
#endif /* __cplusplus */

/** @} */
