/**
 * =========================================================================
 * File        : JSInterface_LightEnv.cpp
 * Project     : Pyrogenesis
 * Description : Provide the LightEnv object type for JavaScript
 * =========================================================================
 */

#include "precompiled.h"

#include "maths/scripting/JSInterface_Vector3D.h"

#include "graphics/scripting/JSInterface_LightEnv.h"
#include "graphics/LightEnv.h"

#include "ps/World.h"

#include "scripting/JSConversions.h"


namespace JSI_LightEnv {

namespace {

extern JSClass JSI_class;

/**
 * This enumeration is used to index properties with the JavaScript implementation.
 */
enum
{
    lightenv_elevation,
    lightenv_rotation,
    lightenv_terrainShadowTransparency,
    lightenv_sun,
    lightenv_terrainAmbient,
    lightenv_unitsAmbient
};

///////////////////////////////////////////////////////////////////////////////////////////////
// LightEnv_Info, the private structure that holds data for individual LightEnv objects

struct LightEnv_Info : public IPropertyOwner
{
    CLightEnv* m_Data;
    bool m_EngineOwned;

    // Create a new LightEnv that will only be used by JavaScript
    LightEnv_Info()
    {
        m_Data = new CLightEnv;
        m_EngineOwned = false;
    }

    // Use the given CLightEnv from the engine. The caller must guarantee that
    // the copy object will not be deleted.
    LightEnv_Info(CLightEnv* copy)
    {
        m_Data = copy;
        m_EngineOwned = true;
    }

    ~LightEnv_Info()
    {
        if (!m_EngineOwned)
            delete m_Data;
    }
};


///////////////////////////////////////////////////////////////////////////////////////////////
// Construction and finalization of LightEnvs

/**
 * construct: the LightEnv constructor has been called from JavaScript, so create a new
 * LightEnv object
 */
JSBool construct(JSContext* cx, JSObject* UNUSED(obj), uintN argc, jsval* argv, jsval* rval)
{
    JSObject* lightenv = JS_NewObject(cx, &JSI_class, NULL, NULL);

    JSU_REQUIRE_NO_PARAMS();

    JS_SetPrivate(cx, lightenv, new LightEnv_Info);
    *rval = OBJECT_TO_JSVAL(lightenv);
    return JS_TRUE;
}

/**
 * finalize: callback from the JS engine to indicate we should free our private data
 */
void finalize(JSContext* cx, JSObject* obj)
{
    delete (LightEnv_Info*)JS_GetPrivate(cx, obj);
}


///////////////////////////////////////////////////////////////////////////////////////////////
// Accessing properties of a LightEnv object

// Can't use ToJSVal here because we need live updates from the vectors
JSBool getVectorProperty(JSContext* cx, LightEnv_Info* lightenvInfo, CVector3D* vec, jsval* vp)
{
    JSObject* vector3d = JS_NewObject(cx, &JSI_Vector3D::JSI_class, NULL, NULL);
    JS_SetPrivate(cx, vector3d, new JSI_Vector3D::Vector3D_Info(vec, lightenvInfo));
    *vp = OBJECT_TO_JSVAL(vector3d);
    return JS_TRUE;
}


JSBool getProperty(JSContext* cx, JSObject* obj, jsval id, jsval* vp)
{
    if (!JSVAL_IS_INT(id))
        return JS_TRUE ;

    LightEnv_Info* lightenvInfo = (LightEnv_Info*)JS_GetPrivate(cx, obj);
    if (!lightenvInfo)
    {
        JS_ReportError(cx, "[LightEnv] Invalid Reference");
        return JS_TRUE;
    }

    CLightEnv* lightenv = lightenvInfo->m_Data;

    switch(ToPrimitive<int>(id)) {
    case lightenv_elevation: *vp = ToJSVal(lightenv->GetElevation()); break;
    case lightenv_rotation: *vp = ToJSVal(lightenv->GetRotation()); break;
    case lightenv_terrainShadowTransparency: *vp = ToJSVal(lightenv->GetTerrainShadowTransparency()); break;
    case lightenv_sun: return getVectorProperty(cx, lightenvInfo, &lightenv->m_SunColor, vp);
    case lightenv_terrainAmbient: return getVectorProperty(cx, lightenvInfo, &lightenv->m_TerrainAmbientColor, vp);
    case lightenv_unitsAmbient: return getVectorProperty(cx, lightenvInfo, &lightenv->m_UnitsAmbientColor, vp);
    default: break;
    }

    return JS_TRUE;
}


JSBool setProperty(JSContext* cx, JSObject* obj, jsval id, jsval* vp)
{
    if (!JSVAL_IS_INT(id))
        return( JS_TRUE );

    LightEnv_Info* lightenvInfo = (LightEnv_Info*)JS_GetPrivate(cx, obj);
    if (!lightenvInfo)
    {
        JS_ReportError(cx, "[LightEnv] Invalid reference");
        return JS_TRUE;
    }

    CLightEnv* lightenv = lightenvInfo->m_Data;

    switch(ToPrimitive<int>(id)) {
    case lightenv_elevation: lightenv->SetElevation(ToPrimitive<float>(*vp)); break;
    case lightenv_rotation: lightenv->SetRotation(ToPrimitive<float>(*vp)); break;
    case lightenv_terrainShadowTransparency: lightenv->SetTerrainShadowTransparency(ToPrimitive<float>(*vp)); break;
    case lightenv_sun: lightenv->m_SunColor = ToPrimitive<CVector3D>(*vp); break;
    case lightenv_terrainAmbient: lightenv->m_TerrainAmbientColor = ToPrimitive<CVector3D>(*vp); break;
    case lightenv_unitsAmbient: lightenv->m_UnitsAmbientColor = ToPrimitive<CVector3D>(*vp); break;
    default: break;
    }

    return JS_TRUE;
}



///////////////////////////////////////////////////////////////////////////////////////////////
// Registration of LightEnv class with JavaScript

JSClass JSI_class = {
    "LightEnv", JSCLASS_HAS_PRIVATE,
    JS_PropertyStub, JS_PropertyStub,
    getProperty, setProperty,
    JS_EnumerateStub, JS_ResolveStub,
    JS_ConvertStub, finalize,
    NULL, NULL, NULL, NULL
};

JSPropertySpec JSI_props[] =
{
    { "elevation", lightenv_elevation, JSPROP_ENUMERATE },
    { "rotation", lightenv_rotation, JSPROP_ENUMERATE },
    { "terrainShadowTransparency", lightenv_terrainShadowTransparency, JSPROP_ENUMERATE },
    { "sun", lightenv_sun, JSPROP_ENUMERATE },
    { "terrainAmbient", lightenv_terrainAmbient, JSPROP_ENUMERATE },
    { "unitsAmbient", lightenv_unitsAmbient, JSPROP_ENUMERATE },
    { 0 },
};

JSFunctionSpec JSI_methods[] =
{
    { 0 }
};

} // anonymous namespace

/**
 * init: called by GameSetup to register the LightEnv class with the JS engine.
 */
void init()
{
    g_ScriptingHost.DefineCustomObjectType( &JSI_class, construct, 0, JSI_props, JSI_methods, NULL, NULL );
}


///////////////////////////////////////////////////////////////////////////////////////////////
// Accessing the global lightenv

JSBool getLightEnv(JSContext* cx, JSObject* UNUSED(obj), jsval UNUSED(id), jsval* vp)
{
    JSObject* lightenv = JS_NewObject(cx, &JSI_class, NULL, NULL);
    JS_SetPrivate(cx, lightenv, new LightEnv_Info(&g_LightEnv));
    *vp = OBJECT_TO_JSVAL(lightenv);
    return JS_TRUE;
}

JSBool setLightEnv(JSContext* cx, JSObject* UNUSED(obj), jsval UNUSED(id), jsval* vp)
{
    JSObject* lightenv = JSVAL_TO_OBJECT(*vp);
    LightEnv_Info* lightenvInfo;

    if (!JSVAL_IS_OBJECT(*vp) || NULL == (lightenvInfo = (LightEnv_Info*)JS_GetInstancePrivate(cx, lightenv, &JSI_class, NULL)))
    {
        JS_ReportError( cx, "[LightEnv] Invalid object" );
    }
    else
    {
        g_LightEnv = *lightenvInfo->m_Data;
    }

    return JS_TRUE;
}

} // namespace JSI_LightEnv