// This module defines the table of all functions callable from JS.
// it's required by the interpreter; we make use of the opportunity to
// document them all in one spot. we thus obviate having to dig through
// all the other headers. most of the functions are implemented here;
// as for the rest, we only link to their docs (duplication is bad).

#include "precompiled.h"

#include "ScriptGlue.h"
#include "JSConversions.h"
#include "GameEvents.h"

#include "graphics/GameView.h"
#include "graphics/LightEnv.h"
#include "graphics/MapWriter.h"
#include "graphics/Unit.h"
#include "graphics/UnitManager.h"
#include "graphics/scripting/JSInterface_Camera.h"
#include "graphics/scripting/JSInterface_LightEnv.h"
#include "gui/CGUI.h"
#include "lib/timer.h"
#include "maths/scripting/JSInterface_Vector3D.h"
#include "network/Client.h"
#include "network/Server.h"
#include "ps/CConsole.h"
#include "ps/CLogger.h"
#include "ps/CStr.h"
#include "ps/Game.h"
#include "ps/GameSetup/GameSetup.h"
#include "ps/Hotkey.h"
#include "ps/Interact.h"
#include "ps/ProfileViewer.h"
#include "ps/i18n.h"
#include "ps/scripting/JSCollection.h"
#include "ps/scripting/JSInterface_Console.h"
#include "ps/scripting/JSInterface_Selection.h"
#include "ps/scripting/JSInterface_VFS.h"
#include "renderer/Renderer.h"
#include "renderer/SkyManager.h"
#include "renderer/WaterManager.h"
#include "simulation/Entity.h"
#include "simulation/EntityFormation.h"
#include "simulation/EntityHandles.h"
#include "simulation/EntityManager.h"
#include "simulation/EntityTemplate.h"
#include "simulation/EntityTemplateCollection.h"
#include "simulation/FormationManager.h"
#include "simulation/LOSManager.h"
#include "simulation/Scheduler.h"
#include "simulation/Simulation.h"
#include "simulation/TechnologyCollection.h"
#include "simulation/TriggerManager.h"

#ifndef NO_GUI
#include "gui/scripting/JSInterface_IGUIObject.h"
#endif

extern bool g_TerrainModified;


// rationale: the function table is now at the end of the source file to
// avoid the need for forward declarations for every function.

// all normal function wrappers have the following signature:
//   JSBool func(JSContext* cx, JSObject* globalObject, uint argc, jsval* argv, jsval* rval);
// all property accessors have the following signature:
//   JSBool accessor(JSContext* cx, JSObject* globalObject, jsval id, jsval* vp);


//-----------------------------------------------------------------------------
// Output
//-----------------------------------------------------------------------------

// Write values to the log file.
// params: any number of any type.
// returns:
// notes:
// - Each argument is converted to a string and then written to the log.
// - Output is in NORMAL style (see LOG).
JSBool WriteLog(JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval)
{
    JSU_REQUIRE_PARAMS(1);

    CStr logMessage;

    for (int i = 0; i < (int)argc; i++)
    {
        try
        {
            CStr arg = g_ScriptingHost.ValueToString( argv[i] );
            logMessage += arg;
        }
        catch( PSERROR_Scripting_ConversionFailed )
        {
            // Do nothing.
        }
    }

    // We should perhaps unicodify (?) the logger at some point.

    LOG( NORMAL, "script", logMessage );

    *rval = JSVAL_TRUE;
    return JS_TRUE;
}


//-----------------------------------------------------------------------------
// Entity
//-----------------------------------------------------------------------------

// Retrieve the entity currently occupying the specified handle.
// params: handle [int]
// returns: entity
JSBool GetEntityByUnitID( JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval )
{
    JSU_REQUIRE_PARAMS(1);
    *rval = JSVAL_NULL;

    int uid;
    try
    {
        uid = ToPrimitive<int>( argv[0] );
    }
    catch( PSERROR_Scripting_ConversionFailed )
    {
        JS_ReportError( cx, "Invalid parameter" );
        return( JS_TRUE );
    }

    CUnit* unit = g_Game->GetWorld()->GetUnitManager().FindByID( uid );
    if( !unit || !unit->GetEntity() )
    {
        *rval = JSVAL_NULL;
        return( JS_TRUE );
    }

    *rval = OBJECT_TO_JSVAL( unit->GetEntity()->GetScript() );
    return( JS_TRUE );
}


// Look up an EntityTemplate by name.
// params: template name [wstring]
// returns: entity template object
JSBool GetEntityTemplate( JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval )
{
    JSU_REQUIRE_PARAM_RANGE(1, 2);
    *rval = JSVAL_NULL;

    CStrW templateName;
    CPlayer* player = 0;

    try
    {
        templateName = g_ScriptingHost.ValueToUCString( argv[0] );
        if( argc == 2 )
        {
            player = ToNative<CPlayer>( argv[1] );
        }
    }
    catch( PSERROR_Scripting_ConversionFailed )
    {
        JS_ReportError( cx, "Invalid template identifier" );
        return( JS_TRUE );
    }

    CEntityTemplate* v = g_EntityTemplateCollection.GetTemplate( templateName, player );
    if( !v )
    {
        JS_ReportError( cx, "No such template: %s", CStr(templateName).c_str() );
        return( JS_TRUE );
    }

    *rval = OBJECT_TO_JSVAL( v->GetScript() );
    return( JS_TRUE );
}

JSBool GetPlayerUnitCount( JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval )
{
    JSU_REQUIRE_PARAMS(2);
    int unitCount, playerNum = ToPrimitive<int>( argv[0] );
    CStrW unitName = ToPrimitive<CStrW>( argv[1] );

    unitCount = g_EntityManager.GetPlayerUnitCount((size_t)playerNum, unitName);
    *rval = ToJSVal( unitCount );
    return JS_TRUE;
}


//Used to create net messages for formations--msgList.front() is the original message. see IssueCommand
void CreateFormationMessage( std::vector<CNetMessage*>& msgList, CNetMessage* msg, CEntityList& formation )
{
    CNetMessage* retMsg;
    const int type = msg->GetType();

    if ( type == NMT_Goto )
    {
        //formationEnt->GetFormation()->BaseToMovement();
        CGoto* tmp = static_cast<CGoto*>(msg);
        retMsg = CNetMessage::CreatePositionMessage( formation, NMT_FormationGoto, 
                        CVector2D(tmp->m_TargetX, tmp->m_TargetY) );
    }
    else if( type == NMT_Run )
    {
        CGoto* tmp = static_cast<CGoto*>(msg);
        retMsg = CNetMessage::CreatePositionMessage( formation, NMT_FormationGoto, 
                        CVector2D(tmp->m_TargetX, tmp->m_TargetY) );
    }
    else if ( type == NMT_Generic )
    {
        CGeneric* tmp = static_cast<CGeneric*>(msg);
        retMsg = CNetMessage::CreateEntityIntMessage(formation, NMT_FormationGeneric, 
                    tmp->m_Target, tmp->m_Action);
    }
    else
        return;
    
    msgList.push_back(retMsg); 
}

// Issue a command (network message) to an entity or collection.
// params: either an entity- or entity collection object, message ID [int],
//   any further params needed by CNetMessage::CommandFromJSArgs
// returns: command in serialized form [string]
JSBool IssueCommand( JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval )
{
    // at least one for target object, one for isQueued, and then 1 or more for the CommandFromJSArgs
    JSU_REQUIRE_MIN_PARAMS(3);

    JSU_ASSERT(JSVAL_IS_OBJECT(argv[0]), "Argument 0 must be an entity collection.");
    *rval = JSVAL_NULL;
    
    CEntityList entities, msgEntities;

    if (JS_GetClass(cx, JSVAL_TO_OBJECT(argv[0])) == &CEntity::JSI_class)
        entities.push_back( (ToNative<CEntity>(argv[0])) ->me);
    else
        entities = *EntityCollection::RetrieveSet(cx, JSVAL_TO_OBJECT(argv[0]));

    std::map<int, CEntityList> entityStore;

    bool isQueued = ToPrimitive<bool>(argv[1]);

    //Destroy old notifiers if we're explicitly being reassigned
    for ( size_t i=0; i < entities.size(); i++)
    {
        if ( entities[i]->entf_get(ENTF_DESTROY_NOTIFIERS))
            entities[i]->DestroyAllNotifiers();
    }

    std::vector<CNetMessage*> messages;
    
    //Generate messages for formations
    for (size_t i=0; i < entities.size(); i++ )
    {
        if ( entities[i]->m_formation >= 0)
        {
            CEntityFormation* formation = entities[i]->GetFormation();
            bool duplicate = formation->IsDuplication();
            
            if ( formation->IsLocked() && !duplicate)
            {
                formation->SelectAllUnits();
                entityStore[entities[i]->m_formation] = formation->GetEntityList();
                formation->SetDuplication(true);
            }
        }
        else
            msgEntities.push_back( entities[i] );
    }
    CNetMessage* msg = CNetMessage::CommandFromJSArgs(msgEntities, cx, argc-2, argv+2, isQueued);
    if (!msg)
    {
        delete msg;
        return JS_TRUE;
    }
    messages.push_back(msg);
    
    for ( std::map<int, CEntityList>::iterator it=entityStore.begin(); it!=entityStore.end(); it++)
        CreateFormationMessage(messages, msg, it->second);
    
    for ( std::vector<CNetMessage*>::iterator it=messages.begin(); it != messages.end(); it++ )
    {
        g_Console->InsertMessage(L"IssueCommand: %hs", (*it)->GetString().c_str());
        *rval = g_ScriptingHost.UCStringToValue((*it)->GetString());
        g_Game->GetSimulation()->QueueLocalCommand(*it);
    }

    return JS_TRUE;
}


// Get the state of a given hotkey (from the hotkeys file)
JSBool isOrderQueued( JSContext* cx, JSObject* UNUSED(obj), uint argc, jsval* argv, jsval* rval )
{
    JSU_REQUIRE_NO_PARAMS();

    *rval = ToJSVal(hotkeys[HOTKEY_ORDER_QUEUE]);
    return JS_TRUE;
}


//-----------------------------------------------------------------------------
// formations
//-----------------------------------------------------------------------------

JSBool CreateEntityFormation( JSContext* cx, JSObject* UNUSED(obj), uint argc, jsval* argv, jsval* rval )
{
    JSU_REQUIRE_PARAMS(2);
    
    CEntityList entities = *EntityCollection::RetrieveSet(cx, JSVAL_TO_OBJECT(argv[0]));
    CStrW name = ToPrimitive<CStrW>( argv[1] );
    g_FormationManager.CreateFormation( entities, name );
    *rval = JSVAL_VOID;
    return JS_TRUE;

}

JSBool RemoveFromFormation( JSContext* cx, JSObject* UNUSED(obj), uint argc, jsval* argv, jsval* rval )
{
    JSU_REQUIRE_PARAMS(1);
    
    CEntityList entities;
    if (JS_GetClass(cx, JSVAL_TO_OBJECT(argv[0])) == &CEntity::JSI_class)
        entities.push_back( (ToNative<CEntity>(argv[0])) ->me);
    else
        entities = *EntityCollection::RetrieveSet(cx, JSVAL_TO_OBJECT(argv[0]));

    *rval = g_FormationManager.RemoveUnitList(entities) ? JS_TRUE : JS_FALSE;
    return JS_TRUE;
}

JSBool LockEntityFormation( JSContext* cx, JSObject* UNUSED(obj), uint argc, jsval* argv, jsval* rval )
{
    JSU_REQUIRE_PARAMS(1);

    CEntity* entity = ToNative<CEntity>( argv[0] );
    entity->GetFormation()->SetLock( ToPrimitive<bool>( argv[1] ) );
    *rval = JSVAL_VOID;
    return JS_TRUE;
}

JSBool IsFormationLocked( JSContext* cx, JSObject* UNUSED(obj), uint argc, jsval* argv, jsval* rval )
{
    JSU_REQUIRE_PARAMS(1);

    CEntity* entity = ToNative<CEntity>( argv[0] );
    *rval = entity->GetFormation()->IsLocked() ? JS_TRUE : JS_FALSE;
    return JS_TRUE;
}


//-----------------------------------------------------------------------------
// Techs
//-----------------------------------------------------------------------------

JSBool GetTechnology( JSContext* cx, JSObject* UNUSED(obj), uint argc, jsval* argv, jsval* rval )
{
    JSU_REQUIRE_PARAMS(2);
    
    CStrW name;
    CPlayer* player;
    
    try
    {
        name = g_ScriptingHost.ValueToUCString( argv[0] );
        player = ToNative<CPlayer>( argv[1] );
    }
    catch( PSERROR_Scripting_ConversionFailed )
    {
        JS_ReportError( cx, "Invalid parameters for GetTechnology (expected name and player)" );
        return( JS_TRUE );
    }

    *rval = JSVAL_NULL;

    CTechnology* tech = g_TechnologyCollection.GetTechnology( name, player );   
    if ( tech )
        *rval = ToJSVal( tech );
    else
        g_Console->InsertMessage( L"Warning: Invalid tech template name \"%ls\" passed for GetTechnology()", name.c_str() );

    return JS_TRUE;
}

//-----------------------------------------------------------------------------
// Events
//-----------------------------------------------------------------------------

// Register a global handler for the specified DOM event.
// params: event type name [wstring], handler [fragment or function]
// returns: whether it was actually newly registered [bool]
JSBool AddGlobalHandler( JSContext* cx, JSObject* UNUSED(obj), uint argc, jsval* argv, jsval* rval )
{
    JSU_REQUIRE_PARAMS(2);

    *rval = BOOLEAN_TO_JSVAL( g_JSGameEvents.AddHandlerJS( cx, argc, argv ) );
    return( JS_TRUE );
}


// Remove a previously registered global handler for the specified DOM event.
// params: event type name [wstring], handler [fragment or function]
// returns: whether it was successfully removed [bool]
JSBool RemoveGlobalHandler( JSContext* cx, JSObject* UNUSED(obj), uint argc, jsval* argv, jsval* rval )
{
    JSU_REQUIRE_PARAMS(2);

    *rval = BOOLEAN_TO_JSVAL( g_JSGameEvents.RemoveHandlerJS( cx, argc, argv ) );
    return( JS_TRUE );
}


//-----------------------------------------------------------------------------
// Timer
//-----------------------------------------------------------------------------

// Request a callback be executed after the specified delay.
// params: callback [fragment or function], delay in milliseconds [int]
// returns:
// notes:
// - Scripts and functions registered this way are called on the first
//   simulation frame after the specified period has elapsed. If this causes
//   multiple segments of code to be executed in the same frame,
//   relative timing is maintained. Delays of 0 milliseconds cause code to be
//   executed on the following simulation frame. If more than one script or
//   function is scheduled to execute in the same millisecond, the order of
//   execution is undefined. Code is scheduled in simulation time, and is
//   therefore suspended while the game is paused or frozen. Granularity of
//   timing is also limited to 1/(Simulation frame rate); currently 100ms.
//   The called function or script executes in the same scope as the
//   code that called SetTimeout (amongst other things, the
//   'this' reference is usually maintained)
JSBool SetTimeout( JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval )
{
    JSU_REQUIRE_PARAM_RANGE(2, 3);

    size_t delay;
    try
    {
        delay = ToPrimitive<int>( argv[1] );
    }
    catch( PSERROR_Scripting_ConversionFailed )
    {
        JS_ReportError( cx, "Invalid timer parameters" );
        return( JS_TRUE );
    }

    JSObject* scope;
    if( argc == 3 )
    {
        if( !JSVAL_IS_OBJECT( argv[2] ) )
        {
            JS_ReportError( cx, "Invalid timer parameters" );
            return( JS_TRUE );
        }
        scope = JSVAL_TO_OBJECT( argv[2] );
    }
    else
    {
        scope = JS_GetScopeChain( cx );
    }

    switch( JS_TypeOfValue( cx, argv[0] ) )
    {
    case JSTYPE_STRING:
    {
        CStrW fragment = g_ScriptingHost.ValueToUCString( argv[0] );
        int id = g_Scheduler.PushTime( delay, fragment, scope );
        *rval = INT_TO_JSVAL( id );
        return( JS_TRUE );
    }
    case JSTYPE_FUNCTION:
    {
        JSFunction* fn = JS_ValueToFunction( cx, argv[0] );
        int id = g_Scheduler.PushTime( delay, fn, scope );
        *rval = INT_TO_JSVAL( id );
        return( JS_TRUE );
    }
    default:
        JS_ReportError( cx, "Invalid timer script" );
        return( JS_TRUE );
    }
}

// Request a callback be executed periodically.
// params: callback [fragment or function], initial delay in ms [int], period in ms [int]
//   OR callback [fragment or function], period in ms [int] (initial delay = period)
// returns:
// notes:
// - SetTimeout's notes apply here as well.
JSBool SetInterval( JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval )
{
    JSU_REQUIRE_PARAM_RANGE(2, 3);

    size_t first, interval;
    try
    {
        first = ToPrimitive<int>( argv[1] );
        if( argc == 3 )
        {
            // toDo, first, interval
            interval = ToPrimitive<int>( argv[2] );
        }
        else
        {
            // toDo, interval (first = interval)
            interval = first;
        }
    }
    catch( PSERROR_Scripting_ConversionFailed )
    {
        JS_ReportError( cx, "Invalid timer parameters" );
        return( JS_TRUE );
    }

    switch( JS_TypeOfValue( cx, argv[0] ) )
    {
    case JSTYPE_STRING:
    {
        CStrW fragment = g_ScriptingHost.ValueToUCString( argv[0] );
        int id = g_Scheduler.PushInterval( first, interval, fragment, JS_GetScopeChain( cx ) );
        *rval = INT_TO_JSVAL( id );
        return( JS_TRUE );
    }
    case JSTYPE_FUNCTION:
    {
        JSFunction* fn = JS_ValueToFunction( cx, argv[0] );
        int id = g_Scheduler.PushInterval( first, interval, fn, JS_GetScopeChain( cx ) );
        *rval = INT_TO_JSVAL( id );
        return( JS_TRUE );
    }
    default:
        JS_ReportError( cx, "Invalid timer script" );
        return( JS_TRUE );
    }
}

// Cause all periodic functions registered via SetInterval to
//   no longer be called.
// params:
// returns:
// notes:
// - Execution continues until the end of the triggered function or
//   script fragment, but is not triggered again.
JSBool CancelInterval( JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval )
{
    JSU_REQUIRE_NO_PARAMS();

    g_Scheduler.m_abortInterval = true;
    return( JS_TRUE );
}

// Cause the scheduled task (timeout or interval) with the given ID to
//   no longer be called.
// params:
// returns:
// notes:
// - Execution continues until the end of the triggered function or
//   script fragment, but is not triggered again.
JSBool CancelTimer( JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval )
{
    JSU_REQUIRE_PARAMS(1);

    try
    {
        int id = ToPrimitive<int>( argv[0] );
        g_Scheduler.CancelTask( id );
    }
    catch( PSERROR_Scripting_ConversionFailed )
    {
        JS_ReportError( cx, "Invalid ID parameter" );
        return( JS_TRUE );
    }

    return( JS_TRUE );
}

//Set the simulation rate scalar-time becomes time * SimRate.
//Params: rate [float] : sets SimRate
JSBool SetSimRate(JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval)
{
    JSU_REQUIRE_PARAMS(1);

    g_Game->SetSimRate( ToPrimitive<float>(argv[0]) );
    return JS_TRUE;
}

//Generate a random float in [0, 1) using the simulation's random generator
JSBool SimRand(JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval)
{
    JSU_REQUIRE_NO_PARAMS();

    *rval = ToJSVal( g_Game->GetSimulation()->RandFloat() );
    return JS_TRUE;
}

//Generate a random float int between 0 and the given number - 1 using the simulation's RNG
JSBool SimRandInt(JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval)
{
    JSU_REQUIRE_PARAMS(1);
    JSU_ASSERT(JSVAL_IS_INT(argv[0]), "SimRandInt(): first parameter must be an int");

    *rval = ToJSVal( g_Game->GetSimulation()->RandInt(ToPrimitive<int>(argv[0])) );
    return JS_TRUE;
}

// Script profiling functions: Begin timing a piece of code with StartJsTimer(num)
// and stop timing with StopJsTimer(num). The results will be printed to stdout
// when the game exits.

static const uint MAX_JS_TIMERS = 20;
static Timer js_timer;
static TimerUnit js_start_times[MAX_JS_TIMERS];
static TimerUnit js_timer_overhead;
static TimerClient js_timer_clients[MAX_JS_TIMERS];
static char js_timer_descriptions_buf[MAX_JS_TIMERS * 12];  // depends on MAX_JS_TIMERS and format string below

static void InitJsTimers()
{
    char* pos = js_timer_descriptions_buf;
    for(uint i = 0; i < MAX_JS_TIMERS; i++)
    {
        const char* description = pos;
        pos += sprintf(pos, "js_timer %d", i)+1;
        timer_add_client(&js_timer_clients[i], description);
    }

    // call several times to get a good approximation of 'hot' performance.
    // note: don't use a separate timer slot to warm up and then judge
    // overhead from another: that causes worse results (probably some
    // caching effects inside JS, but I don't entirely understand why).
    static const char* calibration_script =
        "startXTimer(0);\n"
        "stopXTimer (0);\n"
        "startXTimer(0);\n"
        "stopXTimer (0);\n"
        "startXTimer(0);\n"
        "stopXTimer (0);\n"
        "startXTimer(0);\n"
        "stopXTimer (0);\n"
        "\n";
    g_ScriptingHost.RunMemScript(calibration_script, strlen(calibration_script));
    js_timer_overhead = js_timer_clients[0].sum/4;
}

JSBool StartJsTimer(JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval)
{
    ONCE(InitJsTimers());

    JSU_REQUIRE_PARAMS(1);
    uint slot = ToPrimitive<uint>(argv[0]);
    if (slot >= MAX_JS_TIMERS)
        return JS_FALSE;

    debug_assert(js_start_times[slot] == 0);
    js_start_times[slot] = js_timer.get_timestamp();
    return JS_TRUE;
}


JSBool StopJsTimer(JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval)
{
    JSU_REQUIRE_PARAMS(1);
    uint slot = ToPrimitive<uint>(argv[0]);
    if (slot >= MAX_JS_TIMERS)
        return JS_FALSE;

    debug_assert(js_start_times[slot] != 0);
    TimerUnit dt = js_timer.get_timestamp() -js_start_times[slot] -js_timer_overhead;
    js_start_times[slot] = 0;
    timer_bill_client(&js_timer_clients[slot], dt);
    return JS_TRUE;
}


//-----------------------------------------------------------------------------
// Game Setup
//-----------------------------------------------------------------------------

// Create a new network server object.
// params:
// returns: net server object
JSBool CreateServer(JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval)
{
    JSU_REQUIRE_NO_PARAMS();

    if( !g_Game )
        g_Game = new CGame();
    if( !g_NetServer )
        g_NetServer = new CNetServer(g_Game, &g_GameAttributes);

    *rval = OBJECT_TO_JSVAL(g_NetServer->GetScript());
    return( JS_TRUE );
}


// Create a new network client object.
// params:
// returns: net client object
JSBool CreateClient(JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval)
{
    JSU_REQUIRE_NO_PARAMS();

    if( !g_Game )
        g_Game = new CGame();
    if( !g_NetClient )
        g_NetClient = new CNetClient(g_Game, &g_GameAttributes);

    *rval = OBJECT_TO_JSVAL(g_NetClient->GetScript());
    return( JS_TRUE );
}


// Begin the process of starting a game.
// params:
// returns: success [bool]
// notes:
// - Performs necessary initialization while calling back into the
//   main loop, so the game remains responsive to display+user input.
// - When complete, the engine calls the reallyStartGame JS function.
// TODO: Replace StartGame with Create(Game|Server|Client)/game.start() -
//   after merging CGame and CGameAttributes
JSBool StartGame(JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval)
{
    JSU_REQUIRE_NO_PARAMS();

    *rval = BOOLEAN_TO_JSVAL(JS_TRUE);

    // Hosted MP Game
    if (g_NetServer) 
    {
        *rval = BOOLEAN_TO_JSVAL(g_NetServer->StartGame() == 0);
    }
    // Joined MP Game
    else if (g_NetClient)
    {
        *rval = BOOLEAN_TO_JSVAL(g_NetClient->StartGame() == 0);
    }
    // Start an SP Game Session
    else if (!g_Game)
    {
        g_Game = new CGame();
        PSRETURN ret = g_Game->StartGame(&g_GameAttributes);
        if (ret != PSRETURN_OK)
        {
            // Failed to start the game - destroy it, and return false

            delete g_Game;
            g_Game = NULL;

            *rval = BOOLEAN_TO_JSVAL(JS_FALSE);
            return( JS_TRUE );
        }
    }
    else
    {
        *rval = BOOLEAN_TO_JSVAL(JS_FALSE);
    }

    return( JS_TRUE );
}


// Immediately ends the current game (if any).
// params:
// returns:
JSBool EndGame(JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval)
{
    JSU_REQUIRE_NO_PARAMS();

    EndGame();
    return JS_TRUE;
}

JSBool GetGameMode(JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval)
{
    JSU_REQUIRE_NO_PARAMS();

    *rval = ToJSVal( g_GameAttributes.GetGameMode() );
    return JS_TRUE;
}

//-----------------------------------------------------------------------------
// Internationalization
//-----------------------------------------------------------------------------

// these remain here instead of in the i18n tree because they are
// really related to the engine's use of them, as opposed to i18n itself.
// contrariwise, translate() cannot be moved here because that would
// make i18n dependent on this code and therefore harder to reuse.

// Replaces the current language (locale) with a new one.
// params: language id [string] as in I18n::LoadLanguage
// returns:
JSBool LoadLanguage(JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval)
{
    JSU_REQUIRE_PARAMS(1);

    CStr lang = g_ScriptingHost.ValueToString(argv[0]);
    I18n::LoadLanguage(lang);

    return JS_TRUE;
}


// Return identifier of the current language (locale) in use.
// params:
// returns: language id [string] as in I18n::LoadLanguage
JSBool GetLanguageID(JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval)
{
    JSU_REQUIRE_NO_PARAMS();
    *rval = JSVAL_NULL;

    JSString* s = JS_NewStringCopyZ(cx, I18n::CurrentLanguageName());
    if (!s)
    {
        JS_ReportError(cx, "Error creating string");
        return JS_FALSE;
    }
    *rval = STRING_TO_JSVAL(s);
    return JS_TRUE;
}


//-----------------------------------------------------------------------------
// Debug
//-----------------------------------------------------------------------------


// Deliberately cause the game to crash.
// params:
// returns:
// notes:
// - currently implemented via access violation (read of address 0)
// - useful for testing the crashlog/stack trace code.
JSBool ProvokeCrash(JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval)
{
    JSU_REQUIRE_NO_PARAMS();

    MICROLOG(L"Crashing at user's request.");
    return *(JSBool*)0;
}


// Force a JS garbage collection cycle to take place immediately.
// params:
// returns: true [bool]
// notes:
// - writes an indication of how long this took to the console.
JSBool ForceGarbageCollection(JSContext* cx, JSObject* UNUSED(obj), uint argc, jsval* argv, jsval* rval)
{
    JSU_REQUIRE_NO_PARAMS();

    double time = get_time();
    JS_GC(cx);
    time = get_time() -time;
    g_Console->InsertMessage(L"Garbage collection completed in: %f", time);
    *rval = JSVAL_TRUE;
    return JS_TRUE ;
}



//-----------------------------------------------------------------------------
// GUI
//-----------------------------------------------------------------------------

// Returns the sort-of-global object associated with the current GUI.
// params:
// returns: global object
// notes:
// - Useful for accessing an object from another scope.
JSBool GetGuiGlobal(JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval)
{
    JSU_REQUIRE_NO_PARAMS();

    *rval = OBJECT_TO_JSVAL(g_GUI.GetScriptObject());
    return JS_TRUE;
}

// Resets the entire GUI state and reloads the XML files.
// params:
// returns:
JSBool ResetGui(JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval)
{
    JSU_REQUIRE_NO_PARAMS();

    // Slightly unpleasant code, because CGUI is a Singleton but we don't really
    // want it to be
    g_GUI.Destroy();
    delete &g_GUI;
    new CGUI;
    GUI_Init();
    g_GUI.SendEventToAll("load");

    return JS_TRUE;
}


//-----------------------------------------------------------------------------
// Misc. Engine Interface
//-----------------------------------------------------------------------------

// Return the global frames-per-second value.
// params:
// returns: FPS [int]
// notes:
// - This value is recalculated once a frame. We take special care to
//   filter it, so it is both accurate and free of jitter.
JSBool GetFps( JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval )
{
    JSU_REQUIRE_NO_PARAMS();

    *rval = INT_TO_JSVAL(fps);
    return JS_TRUE;
}


// Cause the game to exit gracefully.
// params:
// returns:
// notes:
// - Exit happens after the current main loop iteration ends
//   (since this only sets a flag telling it to end)
JSBool ExitProgram( JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval )
{
    JSU_REQUIRE_NO_PARAMS();

    kill_mainloop();
    return JS_TRUE;
}


// Write an indication of total/available video RAM to console.
// params:
// returns:
// notes:
// - Not supported on all platforms.
// - Only a rough approximation; do not base low-level decisions
//   ("should I allocate one more texture?") on this.
JSBool WriteVideoMemToConsole( JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval )
{
    JSU_REQUIRE_NO_PARAMS();

#if OS_WIN
    int left, total;
    if (GetVRAMInfo(left, total))
        g_Console->InsertMessage(L"VRAM: used %d, total %d, free %d", total-left, total, left);
    else
        g_Console->InsertMessage(L"VRAM: failed to detect");
#else
    g_Console->InsertMessage(L"VRAM: [not available on non-Windows]");
#endif
    return JS_TRUE;
}


// Change the mouse cursor.
// params: cursor name [string] (i.e. basename of definition file and texture)
// returns:
// notes:
// - Cursors are stored in "art\textures\cursors"
JSBool SetCursor( JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval )
{
    JSU_REQUIRE_PARAMS(1);
    g_CursorName = g_ScriptingHost.ValueToString(argv[0]);
    return JS_TRUE;
}

JSBool GetCursorName( JSContext* UNUSED(cx), JSObject*, uint UNUSED(argc), jsval* UNUSED(argv), jsval* rval )
{
    *rval = ToJSVal(g_CursorName);
    return JS_TRUE;
}

// Trigger a rewrite of all maps.
// params:
// returns:
// notes:
// - Usefulness is unclear. If you need it, consider renaming this and updating the docs.
JSBool _RewriteMaps( JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval )
{
    JSU_REQUIRE_NO_PARAMS();

    g_Game->GetWorld()->RewriteMap();
    return JS_TRUE;
}


// Change the LOD bias.
// params: LOD bias [float]
// returns:
// notes:
// - value is as required by GL_TEXTURE_LOD_BIAS.
// - useful for adjusting image "sharpness" (since it affects which mipmap level is chosen)
JSBool _LodBias( JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval )
{
    JSU_REQUIRE_PARAMS(1);

    g_Renderer.SetOptionFloat(CRenderer::OPT_LODBIAS, ToPrimitive<float>(argv[0]));
    return JS_TRUE;
}


// Focus the game camera on a given position.
// params: target position vector [CVector3D]
// returns: success [bool]
JSBool SetCameraTarget( JSContext* cx, JSObject* UNUSED(obj), uint argc, jsval* argv, jsval* rval )
{
    JSU_REQUIRE_PARAMS(1);
    *rval = JSVAL_NULL;

    CVector3D* target = ToNative<CVector3D>( argv[0] );
    if(!target)
    {
        JS_ReportError( cx, "Invalid camera target" );
        return( JS_TRUE );
    }
    g_Game->GetView()->SetCameraTarget( *target );

    *rval = JSVAL_TRUE;
    return( JS_TRUE );
}


//-----------------------------------------------------------------------------
// Miscellany
//-----------------------------------------------------------------------------

// Return the date/time at which the current executable was compiled.
// params: none (-> "date time") OR
//   what to display [int]: 0 (-> "date"); 1 (-> "time")
// returns: date and/or time [string]
// notes:
// - Displayed on main menu screen; tells non-programmers which auto-build
//   they are running. Could also be determined via .EXE file properties,
//   but that's a bit more trouble.
// - To be exact, the date/time returned is when scriptglue.cpp was
//   last compiled; since the auto-build does full rebuilds, that is moot.
JSBool GetBuildTimestamp( JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval )
{
    JSU_REQUIRE_PARAM_RANGE(0, 1);

    // no param => "date time"
    // param = 0 => "date"
    // param = 1 => "time"
    JSString* s = JS_NewStringCopyZ(cx,
        argc && argv[0]==JSVAL_ONE ? __TIME__
        :   argc ? __DATE__
        : __DATE__" "__TIME__
        );
    *rval = STRING_TO_JSVAL(s);
    return JS_TRUE;
}


// Return distance between 2 points.
// params: 2 position vectors [CVector3D]
// returns: Euclidean distance [float]
JSBool ComputeDistanceBetweenTwoPoints( JSContext* cx, JSObject* UNUSED(obj), uint argc, jsval* argv, jsval* rval )
{
    JSU_REQUIRE_PARAMS(2);

    CVector3D* a = ToNative<CVector3D>( argv[0] );
    CVector3D* b = ToNative<CVector3D>( argv[1] );
    float dist = ( *a -*b ).Length();
    *rval = ToJSVal( dist );
    return( JS_TRUE );
}


// Returns the global object.
// params:
// returns: global object
// notes:
// - Useful for accessing an object from another scope.
JSBool GetGlobal( JSContext* cx, JSObject* globalObject, uint argc, jsval* argv, jsval* rval )
{
    JSU_REQUIRE_NO_PARAMS();

    *rval = OBJECT_TO_JSVAL( globalObject );
    return( JS_TRUE );
}

// Saves the current profiling data to the logs/profile.txt file
JSBool SaveProfileData( JSContext* cx, JSObject* UNUSED(globalObject), uint argc, jsval* argv, jsval* rval )
{
    JSU_REQUIRE_NO_PARAMS();
    g_ProfileViewer.SaveToFile();
    return( JS_TRUE );
}

// Activates the building placement cursor for placing a building. The currently selected units
// are then ordered to construct the building if it is placed.
// params: templateName - the name of the entity to place.
// returns: true if cursor was activated, false if cursor was already active.
JSBool StartPlacing( JSContext* cx, JSObject* UNUSED(globalObject), uint argc, jsval* argv, jsval* rval )
{
    CStrW name;
    if(argc == 0) {
        name = L"hele_ho";          // save some typing during testing
    }
    else {
        if(!ToPrimitive<CStrW>( g_ScriptingHost.GetContext(), argv[0], name ))
        {
            JS_ReportError( cx, "Invalid template name argument" );
            *rval = JSVAL_NULL;
            return( JS_FALSE );
        }
    }

    *rval = g_BuildingPlacer.Activate(name) ? JS_TRUE : JS_FALSE;

    return( JS_TRUE );
}

// Toggles drawing the sky
JSBool ToggleSky( JSContext* cx, JSObject* UNUSED(globalObject), uint argc, jsval* argv, jsval* rval )
{
    JSU_REQUIRE_NO_PARAMS();
    g_Renderer.GetSkyManager()->m_RenderSky = !g_Renderer.GetSkyManager()->m_RenderSky;
    *rval = JSVAL_VOID;
    return( JS_TRUE );
}

// Toggles drawing territory outlines
JSBool ToggleTerritoryRendering( JSContext* cx, JSObject* UNUSED(globalObject), uint argc, jsval* argv, jsval* rval )
{
    JSU_REQUIRE_NO_PARAMS();
    g_Renderer.m_RenderTerritories = !g_Renderer.m_RenderTerritories;
    *rval = JSVAL_VOID;
    return( JS_TRUE );
}


//-----------------------------------------------------------------------------
// water

// Toggles drawing the water plane
JSBool ToggleWater( JSContext* cx, JSObject* UNUSED(globalObject), uint argc, jsval* argv, jsval* rval )
{
    JSU_REQUIRE_NO_PARAMS();
    g_Renderer.GetWaterManager()->m_RenderWater = !g_Renderer.GetWaterManager()->m_RenderWater;
    *rval = JSVAL_VOID;
    return( JS_TRUE );
}

// Sets the water plane height
JSBool SetWaterHeight( JSContext* cx, JSObject* UNUSED(globalObject), uint argc, jsval* argv, jsval* rval )
{
    JSU_REQUIRE_PARAMS( 1 );
    float newHeight;
    if(!ToPrimitive( g_ScriptingHost.GetContext(), argv[0], newHeight ))
    {
        JS_ReportError( cx, "Invalid water height argument" );
        *rval = JSVAL_VOID;
        return( JS_FALSE );
    }
    g_Renderer.GetWaterManager()->m_WaterHeight = newHeight;
    g_TerrainModified = true;
    *rval = JSVAL_VOID;
    return( JS_TRUE );
}

// Gets the water plane height
JSBool GetWaterHeight( JSContext* cx, JSObject* UNUSED(globalObject), uint argc, jsval* argv, jsval* rval )
{
    JSU_REQUIRE_NO_PARAMS();
    *rval = ToJSVal(g_Renderer.GetWaterManager()->m_WaterHeight);
    return( JS_TRUE );
}

// Sets the water color
JSBool SetWaterColor( JSContext* cx, JSObject* UNUSED(globalObject), uint argc, jsval* argv, jsval* rval )
{
    JSU_REQUIRE_PARAMS( 3 );
    float r,g,b;
    if(!ToPrimitive( g_ScriptingHost.GetContext(), argv[0], r )
        || !ToPrimitive( g_ScriptingHost.GetContext(), argv[1], g )
        || !ToPrimitive( g_ScriptingHost.GetContext(), argv[2], b ))
    {
        JS_ReportError( cx, "Invalid arguments" );
        *rval = JSVAL_VOID;
        return( JS_FALSE );
    }
    g_Renderer.GetWaterManager()->m_WaterColor = CColor(r, g, b, 1.0f);
    *rval = JSVAL_VOID;
    return( JS_TRUE );
}

// Sets the water tint (used to tint reflections in fancy water)
JSBool SetWaterTint( JSContext* cx, JSObject* UNUSED(globalObject), uint argc, jsval* argv, jsval* rval )
{
    JSU_REQUIRE_PARAMS( 3 );
    float r,g,b;
    if(!ToPrimitive( g_ScriptingHost.GetContext(), argv[0], r )
        || !ToPrimitive( g_ScriptingHost.GetContext(), argv[1], g )
        || !ToPrimitive( g_ScriptingHost.GetContext(), argv[2], b ))
    {
        JS_ReportError( cx, "Invalid arguments" );
        *rval = JSVAL_VOID;
        return( JS_FALSE );
    }
    g_Renderer.GetWaterManager()->m_WaterTint = CColor(r, g, b, 1.0f);
    *rval = JSVAL_VOID;
    return( JS_TRUE );
}

// Sets the water tint (used to tint reflections in fancy water)
JSBool SetReflectionTint( JSContext* cx, JSObject* UNUSED(globalObject), uint argc, jsval* argv, jsval* rval )
{
    JSU_REQUIRE_PARAMS( 3 );
    float r,g,b;
    if(!ToPrimitive( g_ScriptingHost.GetContext(), argv[0], r )
        || !ToPrimitive( g_ScriptingHost.GetContext(), argv[1], g )
        || !ToPrimitive( g_ScriptingHost.GetContext(), argv[2], b ))
    {
        JS_ReportError( cx, "Invalid arguments" );
        *rval = JSVAL_VOID;
        return( JS_FALSE );
    }
    g_Renderer.GetWaterManager()->m_ReflectionTint = CColor(r, g, b, 1.0f);
    *rval = JSVAL_VOID;
    return( JS_TRUE );
}

// Sets the max water alpha (achieved when it is at WaterFullDepth or deeper)
JSBool SetWaterMaxAlpha( JSContext* cx, JSObject* UNUSED(globalObject), uint argc, jsval* argv, jsval* rval )
{
    JSU_REQUIRE_PARAMS( 1 );
    float val;
    if(!ToPrimitive( g_ScriptingHost.GetContext(), argv[0], val ))
    {
        JS_ReportError( cx, "Invalid argument" );
        *rval = JSVAL_VOID;
        return( JS_FALSE );
    }
    g_Renderer.GetWaterManager()->m_WaterMaxAlpha = val;
    *rval = JSVAL_VOID;
    return( JS_TRUE );
}

// Sets the water full depth (when it is colored WaterMaxAlpha)
JSBool SetWaterFullDepth( JSContext* cx, JSObject* UNUSED(globalObject), uint argc, jsval* argv, jsval* rval )
{
    JSU_REQUIRE_PARAMS( 1 );
    float val;
    if(!ToPrimitive( g_ScriptingHost.GetContext(), argv[0], val ))
    {
        JS_ReportError( cx, "Invalid argument" );
        *rval = JSVAL_VOID;
        return( JS_FALSE );
    }
    g_Renderer.GetWaterManager()->m_WaterFullDepth = val;
    *rval = JSVAL_VOID;
    return( JS_TRUE );
}

// Sets the water alpha offset (added to tweak water alpha near the shore)
JSBool SetWaterAlphaOffset( JSContext* cx, JSObject* UNUSED(globalObject), uint argc, jsval* argv, jsval* rval )
{
    JSU_REQUIRE_PARAMS( 1 );
    float val;
    if(!ToPrimitive( g_ScriptingHost.GetContext(), argv[0], val ))
    {
        JS_ReportError( cx, "Invalid argument" );
        *rval = JSVAL_VOID;
        return( JS_FALSE );
    }
    g_Renderer.GetWaterManager()->m_WaterAlphaOffset = val;
    *rval = JSVAL_VOID;
    return( JS_TRUE );
}

//-----------------------------------------------------------------------------

// Is the game paused?
JSBool IsPaused( JSContext* cx, JSObject* UNUSED(globalObject), uint argc, jsval* argv, jsval* rval )
{
    JSU_REQUIRE_NO_PARAMS();

    if( !g_Game )
    {
        JS_ReportError( cx, "Game is not started" );
        return JS_FALSE;
    }

    *rval = g_Game->m_Paused ? JSVAL_TRUE : JSVAL_FALSE;
    return JS_TRUE ;
}

// Pause/unpause the game
JSBool SetPaused( JSContext* cx, JSObject* UNUSED(globalObject), uint argc, jsval* argv, jsval* rval )
{
    JSU_REQUIRE_PARAMS( 1 );

    if( !g_Game )
    {
        JS_ReportError( cx, "Game is not started" );
        return JS_FALSE;
    }

    try
    {
        g_Game->m_Paused = ToPrimitive<bool>( argv[0] );
    }
    catch( PSERROR_Scripting_ConversionFailed )
    {
        JS_ReportError( cx, "Invalid parameter to SetPaused" );
    }

    return  JS_TRUE;
}

// Get game time
JSBool GetGameTime( JSContext* cx, JSObject* UNUSED(globalObject), uint argc, jsval* argv, jsval* rval )
{
    JSU_REQUIRE_NO_PARAMS();

    if( !g_Game )
    {
        JS_ReportError( cx, "Game is not started" );
        return JS_FALSE;
    }

    *rval = ToJSVal(g_Game->GetTime());
    return JS_TRUE;
}

JSBool RegisterTrigger( JSContext* cx, JSObject* UNUSED(globalObject), uint argc, jsval* argv, jsval* rval )
{
    JSU_REQUIRE_PARAMS_CPP(1);
    CTrigger* trigger = ToNative<CTrigger>( argv[0] );
    debug_assert( trigger );

    g_TriggerManager.AddTrigger( trigger );
    *rval = JSVAL_NULL;
    return JS_TRUE;
}

JSBool GetTrigger( JSContext* cx, JSObject* UNUSED(globalObject), uint argc, jsval* argv, jsval* rval )
{
    JSU_REQUIRE_PARAMS_CPP(1);
    CStrW name = ToPrimitive<CStrW>( argv[0] );
    
    if ( g_TriggerManager.m_TriggerMap.find(name) != g_TriggerManager.m_TriggerMap.end() )
        *rval = ToJSVal( g_TriggerManager.m_TriggerMap[name] );
    else
    {
        debug_printf("Invalid trigger name %ws", name.c_str());
        *rval = JSVAL_NULL;
    }
    return JS_TRUE;
}

// Reveal map
JSBool RevealMap( JSContext* cx, JSObject* UNUSED(globalObject), uint argc, jsval* argv, jsval* rval )
{
    JSU_REQUIRE_PARAM_RANGE(0, 1);

    uint newValue;
    if(argc == 0)
    {
        newValue = 2;
    }
    else if(!ToPrimitive( g_ScriptingHost.GetContext(), argv[0], newValue ) || newValue > 2)
    {
        JS_ReportError( cx, "Invalid argument (should be 0, 1 or 2)" );
        *rval = JSVAL_VOID;
        return( JS_FALSE );
    }

    g_Game->GetWorld()->GetLOSManager()->m_LOSSetting = newValue;
    *rval = JSVAL_VOID;
    return( JS_TRUE );
}

//-----------------------------------------------------------------------------
// function table
//-----------------------------------------------------------------------------

// the JS interpreter expects the table to contain 5-tuples as follows:
// - name the function will be called as from script;
// - function which will be called;
// - number of arguments this function expects
// - Flags (deprecated, always zero)
// - Extra (reserved for future use, always zero)
//
// we simplify this a bit with a macro:
#define JS_FUNC(script_name, cpp_function, min_params) { script_name, cpp_function, min_params, 0, 0 },

JSFunctionSpec ScriptFunctionTable[] =
{
    // Console
    JS_FUNC("writeConsole", JSI_Console::writeConsole, 1)   // external

    // Entity
    JS_FUNC("getEntityByUnitID", GetEntityByUnitID, 1)
    JS_FUNC("GetPlayerUnitCount", GetPlayerUnitCount, 1)
    JS_FUNC("getEntityTemplate", GetEntityTemplate, 1)
    JS_FUNC("issueCommand", IssueCommand, 2)
    JS_FUNC("startPlacing", StartPlacing, 1)

    // Formation
    JS_FUNC("createEntityFormation", CreateEntityFormation, 2)
    JS_FUNC("removeFromFormation", RemoveFromFormation, 1)
    JS_FUNC("lockEntityFormation", LockEntityFormation, 1)
    JS_FUNC("isFormationLocked", IsFormationLocked, 1)

    // Trigger
    JS_FUNC("registerTrigger", RegisterTrigger, 1)
    
    // Tech
    JS_FUNC("getTechnology", GetTechnology, 2)

    // Camera
    JS_FUNC("setCameraTarget", SetCameraTarget, 1)

    // Sky
    JS_FUNC("toggleSky", ToggleSky, 0)

    // Water
    JS_FUNC("toggleWater", ToggleWater, 0)
    JS_FUNC("setWaterHeight", SetWaterHeight, 1)
    JS_FUNC("getWaterHeight", GetWaterHeight, 0)
    JS_FUNC("setWaterColor", SetWaterColor, 3)
    JS_FUNC("setWaterTint", SetWaterTint, 3)
    JS_FUNC("setReflectionTint", SetReflectionTint, 3)
    JS_FUNC("setWaterMaxAlpha", SetWaterMaxAlpha, 0)
    JS_FUNC("setWaterFullDepth", SetWaterFullDepth, 0)
    JS_FUNC("setWaterAlphaOffset", SetWaterAlphaOffset, 0)

    // Territory rendering
    JS_FUNC("toggleTerritoryRendering", ToggleTerritoryRendering, 0)

    // GUI
#ifndef NO_GUI
    JS_FUNC("getGUIObjectByName", JSI_IGUIObject::getByName, 1) // external
    JS_FUNC("getGUIGlobal", GetGuiGlobal, 0)
    JS_FUNC("resetGUI", ResetGui, 0)
#endif

    // Events
    JS_FUNC("addGlobalHandler", AddGlobalHandler, 2)
    JS_FUNC("removeGlobalHandler", RemoveGlobalHandler, 2)

    // Timer
    JS_FUNC("setTimeout", SetTimeout, 2)
    JS_FUNC("setInterval", SetInterval, 2)
    JS_FUNC("cancelInterval", CancelInterval, 0)
    JS_FUNC("cancelTimer", CancelTimer, 0)
    JS_FUNC("setSimRate", SetSimRate, 1)

    // Random number generator
    JS_FUNC("simRand", SimRand, 0)
    JS_FUNC("simRandInt", SimRandInt, 1)

    // Profiling
    JS_FUNC("startXTimer", StartJsTimer, 1)
    JS_FUNC("stopXTimer", StopJsTimer, 1)

    // Game Setup
    JS_FUNC("startGame", StartGame, 0)
    JS_FUNC("endGame", EndGame, 0)
    JS_FUNC("getGameMode", GetGameMode, 0)
    JS_FUNC("createClient", CreateClient, 0)
    JS_FUNC("createServer", CreateServer, 0)

    // VFS (external)
    JS_FUNC("buildDirEntList", JSI_VFS::BuildDirEntList, 1)
    JS_FUNC("getFileMTime", JSI_VFS::GetFileMTime, 1)
    JS_FUNC("getFileSize", JSI_VFS::GetFileSize, 1)
    JS_FUNC("readFile", JSI_VFS::ReadFile, 1)
    JS_FUNC("readFileLines", JSI_VFS::ReadFileLines, 1)
    JS_FUNC("archiveBuilderCancel", JSI_VFS::ArchiveBuilderCancel, 1)

    // Internationalization
    JS_FUNC("loadLanguage", LoadLanguage, 1)
    JS_FUNC("getLanguageID", GetLanguageID, 0)
    // note: i18n/ScriptInterface.cpp registers translate() itself.
    // rationale: see implementation section above.

    // Debug
    JS_FUNC("crash", ProvokeCrash, 0)
    JS_FUNC("forceGC", ForceGarbageCollection, 0)
    JS_FUNC("revealMap", RevealMap, 1)

    // Misc. Engine Interface
    JS_FUNC("writeLog", WriteLog, 1)
    JS_FUNC("exit", ExitProgram, 0)
    JS_FUNC("isPaused", IsPaused, 0)
    JS_FUNC("setPaused", SetPaused, 1)
    JS_FUNC("getGameTime", GetGameTime, 0)
    JS_FUNC("vmem", WriteVideoMemToConsole, 0)
    JS_FUNC("_rewriteMaps", _RewriteMaps, 0)
    JS_FUNC("_lodBias", _LodBias, 0)
    JS_FUNC("setCursor", SetCursor, 1)
    JS_FUNC("getCursorName", GetCursorName, 0)
    JS_FUNC("getFPS", GetFps, 0)
    JS_FUNC("isOrderQueued", isOrderQueued, 1)

    // Miscellany
    JS_FUNC("v3dist", ComputeDistanceBetweenTwoPoints, 2)
    JS_FUNC("buildTime", GetBuildTimestamp, 0)
    JS_FUNC("getGlobal", GetGlobal, 0)
    JS_FUNC("saveProfileData", SaveProfileData, 0)

    // end of table marker
    {0, 0, 0, 0, 0}
};
#undef JS_FUNC


//-----------------------------------------------------------------------------
// property accessors
//-----------------------------------------------------------------------------

JSBool GetEntitySet( JSContext* UNUSED(cx), JSObject* UNUSED(obj), jsval UNUSED(argv), jsval* vp )
{
    std::vector<HEntity> extant;
    g_EntityManager.GetExtantAsHandles(extant);
    *vp = OBJECT_TO_JSVAL(EntityCollection::Create(extant));

    return JS_TRUE;
}


JSBool GetPlayerSet( JSContext* UNUSED(cx), JSObject* UNUSED(obj), jsval UNUSED(id), jsval* vp )
{
    std::vector<CPlayer*>* players = g_Game->GetPlayers();

    *vp = OBJECT_TO_JSVAL( PlayerCollection::Create( *players ) );

    return( JS_TRUE );
}


JSBool GetLocalPlayer( JSContext* UNUSED(cx), JSObject* UNUSED(obj), jsval UNUSED(id), jsval* vp )
{
    *vp = OBJECT_TO_JSVAL( g_Game->GetLocalPlayer()->GetScript() );
    return( JS_TRUE );
}


JSBool GetGaiaPlayer( JSContext* UNUSED(cx), JSObject* UNUSED(obj), jsval UNUSED(id), jsval* vp )
{
    *vp = OBJECT_TO_JSVAL( g_Game->GetPlayer( 0 )->GetScript() );
    return( JS_TRUE );
}


JSBool SetLocalPlayer( JSContext* cx, JSObject* UNUSED(obj), jsval UNUSED(id), jsval* vp )
{
    CPlayer* newLocalPlayer = ToNative<CPlayer>( *vp );

    if( !newLocalPlayer )
    {
        JS_ReportError( cx, "Not a valid Player." );
        return( JS_TRUE );
    }

    g_Game->SetLocalPlayer( newLocalPlayer );
    return( JS_TRUE );
}


JSBool GetGameView( JSContext* UNUSED(cx), JSObject* UNUSED(obj), jsval UNUSED(id), jsval* vp )
{
    if (g_Game)
        *vp = OBJECT_TO_JSVAL( g_Game->GetView()->GetScript() );
    else
        *vp = JSVAL_NULL;
    return( JS_TRUE );
}


JSBool GetRenderer( JSContext* UNUSED(cx), JSObject* UNUSED(obj), jsval UNUSED(id), jsval* vp )
{
    if (CRenderer::IsInitialised())
        *vp = OBJECT_TO_JSVAL( g_Renderer.GetScript() );
    else
        *vp = JSVAL_NULL;
    return( JS_TRUE );
}


enum ScriptGlobalTinyIDs
{
    GLOBAL_SELECTION,
    GLOBAL_GROUPSARRAY,
    GLOBAL_CAMERA,
    GLOBAL_CONSOLE,
    GLOBAL_LIGHTENV
};

// shorthand
#define PERM  JSPROP_PERMANENT
#define CONST JSPROP_READONLY

JSPropertySpec ScriptGlobalTable[] =
{
    { "selection"  , GLOBAL_SELECTION,   PERM,         JSI_Selection::getSelection, JSI_Selection::SetSelection },
    { "groups"     , GLOBAL_GROUPSARRAY, PERM,         JSI_Selection::getGroups,    JSI_Selection::setGroups },
    { "camera"     , GLOBAL_CAMERA,      PERM,         JSI_Camera::getCamera,       JSI_Camera::setCamera },
    { "console"    , GLOBAL_CONSOLE,     PERM | CONST, JSI_Console::getConsole,     0 },
    { "lightenv"   , GLOBAL_LIGHTENV,    PERM,         JSI_LightEnv::getLightEnv,   JSI_LightEnv::setLightEnv },
    { "entities"   , 0,                  PERM | CONST, GetEntitySet,                0 },
    { "players"    , 0,                  PERM | CONST, GetPlayerSet,                0 },
    { "localPlayer", 0,                  PERM        , GetLocalPlayer,              SetLocalPlayer },
    { "gaiaPlayer" , 0,                  PERM | CONST, GetGaiaPlayer,               0 },
    { "gameView"   , 0,                  PERM | CONST, GetGameView,                 0 },
    { "renderer"   , 0,                  PERM | CONST, GetRenderer,                 0 },

    // end of table marker
    { 0, 0, 0, 0, 0 },
};