
#include <assert.h>
#include <stdio.h>
#include <string>
#include <angelscript.h>
#include <scriptbuilder/scriptbuilder.h>
using namespace std;

#include "Log.h"
#include "utils.h"
#include "scripting.h"
#include "sound.h"

#include "Entity.h"

#include "../run/EntityMessages.as"

string Entity::s_objectName;//module name, eg. Script_Entity
string Entity::s_scriptObjectName;//eg. Entity
class asIObjectType* Entity::s_scriptObjectType = NULL;

Entity::Entity()
{
    if ( ! Entity::loadScript("Entity"/*, "Entity.as"*/) ) {
        g_log.log(LL_FATAL, "Could not load script file Entity.as");
    }
    assert(s_scriptObjectType);

    instanciate();

    b2Vec2 v;
    v.x = 1 + rnd() * 8;
    v.y = 1 + rnd() * 8;
    setPos( v );
    v.x = -1.5 + rnd() * 3;
    v.y = -1.5 + rnd() * 3;
    setVel( v );

    m_color.Set(1,1,1);

    m_nearWall = false;
    m_tickTimeout = 0;
}

Entity::~Entity()
{
    if ( m_scriptContext )
        m_scriptContext->Release();
    if ( m_scriptObject )
        m_scriptObject->Release();
}

Entity* Entity_Factory()
{
    return new Entity();
}

bool Entity::loadScript(const char* objectName)
{
    assert(g_scriptEngine);

    if ( s_scriptObjectType )
        return true;

    assert(s_objectName == "");
    s_objectName = objectName;
    s_scriptObjectName = string("Script_") + objectName;

    CScriptBuilder builder;
    int r = builder.StartNewModule(g_scriptEngine, s_scriptObjectName.c_str());
    if( r < 0 )
    {
        g_log.log(LL_ERROR, "StartNewModule failed (%s)", s_scriptObjectName.c_str());
        return false;
    }

    //it's ok if this fails
    string messagesHeaderFile = string(objectName)+string("Messages.as");
    builder.AddSectionFromFile(messagesHeaderFile.c_str());

    //it's bad if this fails
    string scriptFile = string(objectName)+string(".as");
    r = builder.AddSectionFromFile(scriptFile.c_str());
    if( r < 0 )
    {
        g_log.log(LL_ERROR, "AddSectionFromFile failed (%s)", scriptFile.c_str());
        return false;
    }

    r = builder.BuildModule();
    if( r < 0 )
    {
        g_log.log(LL_ERROR, "BuildModule failed (%s)", scriptFile.c_str());
        return false;
    }

    asIScriptModule *module = g_scriptEngine->GetModule(s_scriptObjectName.c_str());
    s_scriptObjectType = g_scriptEngine->GetObjectTypeById(module->GetTypeIdByDecl(s_scriptObjectName.c_str()));
    assert(s_scriptObjectType);

    return true;
}

bool Entity::instanciate()
{
    assert(s_scriptObjectType);

    // Get the factory function id from the object type
    char buf[128];
    sprintf(buf, "%s @%s()", s_scriptObjectName.c_str(), s_scriptObjectName.c_str());
    int factoryId = s_scriptObjectType->GetFactoryIdByDecl(buf);//eg "abc @abc()"
    if( factoryId < 0 )
    {
        g_log.log(LL_ERROR, "Could not find class constructor for %s", s_scriptObjectName.c_str());
        return false;
    }

    asIScriptContext *ctx = g_scriptEngine->CreateContext();
    ctx->Prepare(factoryId);
    int r = ctx->Execute();
    if( r == asEXECUTION_EXCEPTION )
    {
        g_log.log(LL_ERROR, "Failed to instanciate class %s : %s", s_scriptObjectName.c_str(), ctx->GetExceptionString());
        return false;
    }

    // Get the object that was created
    m_scriptObject = *(asIScriptObject**)ctx->GetAddressOfReturnValue();
    m_scriptObject->AddRef();

    m_scriptContext = g_scriptEngine->CreateContext();

    asIScriptModule *module = g_scriptEngine->GetModule(s_scriptObjectName.c_str());
    if ( !module )
        return false;

    const char* funcSig = "void construct(Entity@)";
    int funcId = s_scriptObjectType->GetMethodIdByDecl(funcSig);
    if ( funcId < 0 )
    {
        g_log.log(LL_ERROR, "Could not find function id for '%s'", funcSig);
        return false;
    }

    m_scriptContext->Prepare(funcId);
    m_scriptContext->SetArgObject(0, this);
    m_scriptContext->SetObject(m_scriptObject);
    m_scriptContext->Execute();
    r = 0;
    if( r == asEXECUTION_EXCEPTION )
    {
        g_log.log(LL_ERROR, "An exception '%s' occurred. Please correct the code and try again.", m_scriptContext->GetExceptionString());
        return false;
    }
    return true;
}

bool Entity::script_init()
{
    asIScriptModule *module = g_scriptEngine->GetModule(s_scriptObjectName.c_str());
    if ( !module )
        return false;

    const char* funcSig = "void init()";
    int funcId = s_scriptObjectType->GetMethodIdByDecl(funcSig);
    if ( funcId < 0 )
    {
        g_log.log(LL_ERROR, "Could not find function id for '%s'", funcSig);
        return false;
    }

    m_scriptContext->Prepare(funcId);
    m_scriptContext->SetObject(m_scriptObject);
    m_scriptContext->Execute();
    int r = 0;
    if( r == asEXECUTION_EXCEPTION )
    {
        g_log.log(LL_ERROR, "An exception '%s' occurred. Please correct the code and try again.", m_scriptContext->GetExceptionString());
        return false;
    }
    return true;
}

float Entity::script_message(int msg, Entity* e)
{
    assert(m_scriptObject);
    if (!m_scriptObject)
        return -1;

    const char* funcSig = "float message(int, Entity@)";
    int funcId = s_scriptObjectType->GetMethodIdByDecl(funcSig);
    if ( funcId < 0 )
    {
        g_log.log(LL_ERROR, "Could not find function id for '%s'", funcSig);
        return -1;
    }

    m_scriptContext->Prepare(funcId);
    m_scriptContext->SetObject(m_scriptObject);
    m_scriptContext->SetArgDWord( 0, msg );
    m_scriptContext->SetArgObject( 1, e );
    m_scriptContext->Execute();
    int r = 0;
    if( r == asEXECUTION_EXCEPTION )
    {
        g_log.log(LL_ERROR, "An exception '%s' occurred. Please correct the code and try again.", m_scriptContext->GetExceptionString());
        return -1;
    }

    return m_scriptContext->GetReturnFloat();
}

string Entity::script_tostring()
{
    asIScriptModule *module = g_scriptEngine->GetModule(s_scriptObjectName.c_str());
    if ( !module )
        return "script_tostring failed: no module";

    const char* funcSig = "void tostring(string &out s)";
    int funcId = s_scriptObjectType->GetMethodIdByDecl(funcSig);
    if ( funcId < 0 )
    {
        g_log.log(LL_ERROR, "Could not find function id for '%s'", funcSig);
        return "script_tostring failed: no function";
    }

    m_scriptContext->Prepare(funcId);
    m_scriptContext->SetObject(m_scriptObject);
    string s;
    m_scriptContext->SetArgAddress(0, &s);
    if ( m_scriptContext->Execute() == asEXECUTION_EXCEPTION )
    {
        g_log.log(LL_ERROR, "An exception '%s' occurred. Please correct the code and try again.", m_scriptContext->GetExceptionString());
        return "script_tostring failed: execution exception";
    }

    return s;
}

void Entity::setTostring(string s) { m_tostring = s; }
string Entity::getTostring() { return m_tostring; }

void Entity::setName(string s) { m_name = s; }
string Entity::getName() { return m_name; }

void Entity::setPos(b2Vec2 v) { m_pos = v; }
b2Vec2 Entity::getPos() { return m_pos; }

void Entity::setVel(b2Vec2 v) { m_vel = v; }
b2Vec2 Entity::getVel() { return m_vel; }

void Entity::setColor(b2Color c) { m_color = c; }


/////////////////////////

void Entity::step()
{
    float t = 1 / 60.0;
    b2Vec2 p = getPos();
    b2Vec2 v = getVel();
    p.x += v.x * t;
    p.y += v.y * t;

    if (p.x < 0 || p.x > 10)
        v.x *= -1;
    if (p.y < 0 || p.y > 10)
        v.y *= -1;

    setPos(p);
    setVel(v);

    float m_wallRange = 0.2f;
    b2Color c = m_color;
    if (p.x < m_wallRange || p.x > (10-m_wallRange) || p.y < m_wallRange || p.y > (10-m_wallRange)) {
        if ( !m_nearWall ) {
            playSound(SID_TEST);
            float requestedNextTick = script_message(EM_BEGIN_SENSE_ENTITY, NULL);
            setTostring(script_tostring());
            if ( requestedNextTick > 0 )
                m_tickTimeout = (int)(requestedNextTick * 60);
        }
        m_nearWall = true;
        c.g = c.b = 0;
        v.x = v.y = 0;
        setVel(v);
    }
    setColor(c);

    if ( m_tickTimeout == 0 )
    {
        float requestedNextTick = script_message(EM_TICK, NULL);
        setTostring(script_tostring());
        if ( !m_nearWall && requestedNextTick > 0 )
            m_tickTimeout = (int)(requestedNextTick * 60);
    }
    m_tickTimeout--;
}





