
#include <assert.h>
#include <string>
using namespace std;

#include <angelscript.h>
#include <scriptstdstring/scriptstdstring.h>
#include <Box2D/Box2D.h>

#include "Log.h"
#include "utils.h"

#include "Entity.h"
#include "scripting.h"
#include "settings.h"
#include "ScreenLog.h"
#include "Game.h"

asIScriptEngine *g_scriptEngine = NULL;

//callback for angelscript problems
void MessageCallback(const asSMessageInfo *msg, void *param)
{
    NOT_USED(param);

    int type = LL_ERROR;
    if( msg->type == asMSGTYPE_WARNING )
        type = LL_WARNING;
    else if( msg->type == asMSGTYPE_INFORMATION )
        type = LL_INFO;

    g_log.log(type, "%s (%d, %d) : %s", msg->section, msg->row, msg->col, msg->message);
}

bool registerScriptStuff()
{
    assert(!g_scriptEngine);
    g_scriptEngine = asCreateScriptEngine(ANGELSCRIPT_VERSION);

    int r = g_scriptEngine->SetMessageCallback(asFUNCTION(MessageCallback), 0, asCALL_CDECL);
    assert( r >= 0 );

    RegisterStdString(g_scriptEngine);

    //b2Vec2
    r = g_scriptEngine->RegisterObjectType("b2Vec2", sizeof(b2Vec2), asOBJ_VALUE | asOBJ_POD | asOBJ_APP_CLASS);
    assert( r >= 0 );
    r = g_scriptEngine->RegisterObjectProperty("b2Vec2", "float x", offsetof(b2Vec2,x));
    assert( r >= 0 );
    r = g_scriptEngine->RegisterObjectProperty("b2Vec2", "float y", offsetof(b2Vec2,y));
    assert( r >= 0 );

    //b2Color
    r = g_scriptEngine->RegisterObjectType("b2Color", sizeof(b2Color), asOBJ_VALUE | asOBJ_POD | asOBJ_APP_CLASS);
    assert( r >= 0 );
    r = g_scriptEngine->RegisterObjectProperty("b2Color", "float r", offsetof(b2Color,r));
    assert( r >= 0 );
    r = g_scriptEngine->RegisterObjectProperty("b2Color", "float g", offsetof(b2Color,g));
    assert( r >= 0 );
    r = g_scriptEngine->RegisterObjectProperty("b2Color", "float b", offsetof(b2Color,b));
    assert( r >= 0 );

    //Entity
    r = g_scriptEngine->RegisterObjectType("Entity", 0, asOBJ_REF);
    assert( r >= 0 );
    r = g_scriptEngine->RegisterObjectBehaviour("Entity", asBEHAVE_ADDREF, "void f()", asMETHOD(Entity,AddRef), asCALL_THISCALL);
    assert( r >= 0 );
    r = g_scriptEngine->RegisterObjectBehaviour("Entity", asBEHAVE_RELEASE, "void f()", asMETHOD(Entity,Release), asCALL_THISCALL);
    assert( r >= 0 );
    r = g_scriptEngine->RegisterObjectMethod("Entity", "string getName()", asMETHOD(Entity,getName), asCALL_THISCALL);
    assert( r >= 0 );
    r = g_scriptEngine->RegisterObjectMethod("Entity", "b2Vec2 getPos()", asMETHOD(Entity,getPos), asCALL_THISCALL);
    assert( r >= 0 );
    r = g_scriptEngine->RegisterObjectMethod("Entity", "b2Vec2 getVel()", asMETHOD(Entity,getVel), asCALL_THISCALL);
    assert( r >= 0 );
    r = g_scriptEngine->RegisterObjectMethod("Entity", "void setColor(b2Color)", asMETHOD(Entity,setColor), asCALL_THISCALL);
    assert( r >= 0 );

    //globals
    r = g_scriptEngine->RegisterGlobalFunction("void print(const string &in)", asFUNCTION(print), asCALL_CDECL);
    assert( r >= 0 );
    r = g_scriptEngine->RegisterGlobalFunction("float rnd()", asFUNCTION(rnd), asCALL_CDECL);
    assert( r >= 0 );

    //more globals (settings)
    r = g_scriptEngine->RegisterGlobalFunction("void setFullscreen(bool tf)", asFUNCTION(setFullscreen), asCALL_CDECL);
    assert( r >= 0 );
    r = g_scriptEngine->RegisterGlobalFunction("void setDisplayDimensions(int, int)", asFUNCTION(setDisplayDimensions), asCALL_CDECL);
    assert( r >= 0 );
    r = g_scriptEngine->RegisterGlobalFunction("void setPlayerStats(float, float, float, float)", asFUNCTION(setPlayerStats), asCALL_CDECL);
    assert( r >= 0 );
    r = g_scriptEngine->RegisterGlobalFunction("void setCameraMode(int)", asFUNCTION(setCameraMode), asCALL_CDECL);
    assert( r >= 0 );
    r = g_scriptEngine->RegisterGlobalFunction("void setDoDebugDraw(bool tf)", asFUNCTION(setDoDebugDraw), asCALL_CDECL);
    assert( r >= 0 );

    //more globals (events)
    r = g_scriptEngine->RegisterGlobalFunction("void scheduleEvent(int, float)", asFUNCTION(scheduleEvent), asCALL_CDECL);
    assert( r >= 0 );
    r = g_scriptEngine->RegisterGlobalFunction("void narrate(int, int, const string &in, float)", asFUNCTION(narrate), asCALL_CDECL);
    assert( r >= 0 );
    r = g_scriptEngine->RegisterGlobalFunction("void destroyJoint(const string &in)", asFUNCTION(destroyJoint), asCALL_CDECL);
    assert( r >= 0 );
    r = g_scriptEngine->RegisterGlobalFunction("void destroyFixture(const string &in)", asFUNCTION(destroyFixture), asCALL_CDECL);
    assert( r >= 0 );

    return true;
}

void cleanupScriptStuff()
{
    assert(g_scriptEngine);
    g_scriptEngine->Release();
}


//called from script into application
void scheduleEvent(int eventId, float timeAheadSeconds)
{
    if ( g_game )
        g_game->scheduleEvent(eventId, timeAheadSeconds);
    g_log.log(LL_INFO, "Scheduled event %d at %.3f", eventId, timeAheadSeconds);
}

void narrate(int actorId, int msgType, const string &s, float timeoutSeconds)
{
    sf::Color color = slNarrativeColor;
    switch (msgType) {
    case 0 : color = slNarrativeColor; break;
    case 1 : color = slSpeechColor; break;
    default: break;
    }

    string msg = s;
    if ( actorId == EA_PLAYER )
        msg = string("You: ") + msg;
    else if ( actorId == EA_BUDDY )
        msg = string("Buddy: ") + msg;

    if ( g_game && actorId != -1 )
        g_game->setSpeech(actorId, s, timeoutSeconds);

    if ( g_screenLog )
        g_screenLog->addMessage(msg, color, timeoutSeconds);
}

void destroyJoint(const string &s)
{
    if ( g_game )
        g_game->destroyJoint(s);
    g_log.log(LL_INFO, "Destroyed joint '%s'", s.c_str());
}

void destroyFixture(const string &s)
{
    if ( g_game )
        g_game->destroyFixture(s);
    g_log.log(LL_INFO, "Destroyed fixture '%s'", s.c_str());
}

//called from application into script
bool runEvent(int actorId, int eventSensorId)
{
    asIScriptModule *mod = g_scriptEngine->GetModule("settings");
    if ( !mod ) {
        g_log.log(LL_ERROR, "Could not find script module 'settings'");
        return false;
    }

    const char* funcSig = "bool runEvent(int, int)";
    asIScriptFunction *func = mod->GetFunctionByDecl(funcSig);
    if ( func == 0 )
    {
        g_log.log(LL_ERROR, "Could not find function id for '%s'", funcSig);
        return false;
    }

    asIScriptContext *ctx = g_scriptEngine->CreateContext();
    ctx->Prepare(func);
    ctx->SetArgDWord(0, actorId);
    ctx->SetArgDWord(1, eventSensorId);
    if ( ctx->Execute() == asEXECUTION_EXCEPTION )
    {
        g_log.log(LL_ERROR, "getNarrativeText(actorId:%d, eventSensorId:%d) failed (%s)", actorId, eventSensorId, ctx->GetExceptionString());
        return false;
    }

    bool deleteWhenTriggered = ctx->GetReturnByte();//presumably, byte is good for bool return value?
    ctx->Release();

    return deleteWhenTriggered;
}



