// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
// Please see LICENSE.md in repository root for license information
// https://github.com/AtomicGameEngine/AtomicGameEngine

#include "JSAPI.h"
#include "JSVM.h"

/*

  // script can new an instance
  // script can get an engine ptr back (which may be to a previously script new'd instance)
  // script should always get the same JS Object back which means a lookup table (strong ref)
  // we can look directly at the tval (exposed from duktape internals) to see if the reference count is 1

  // script owned
  // new'd script side, script holds a RefCounted reference

  // native owned

  // native script classes not derived from refcounted
  // if not inherited from RefCounted can script access?
  // if so, script side will always need to delete?

*/

namespace Atomic
{

void js_class_get_prototype(duk_context* ctx, const char* classname)
{
    duk_get_global_string(ctx, "Atomic");
    duk_get_prop_string(ctx, -1, classname);
    duk_get_prop_string(ctx, -1, "prototype");
    duk_remove(ctx, -2); // remove class object
    duk_remove(ctx, -2); // remove Atomic object
}

void js_constructor_basecall(duk_context* ctx, const char* baseclass)
{
    int top = duk_get_top(ctx);
    duk_get_global_string(ctx, "Atomic");
    duk_get_prop_string(ctx, -1, baseclass);
    assert(duk_is_function(ctx, -1));
    duk_push_this(ctx);
    duk_call_method(ctx, 0);
    duk_pop_n(ctx, 2);
    assert (top == duk_get_top(ctx));
}

void js_class_declare(JSVM* vm, const char* classname, duk_c_function constructor)
{
    duk_context* ctx = vm->GetJSContext();
    duk_get_global_string(ctx, "Atomic");
    duk_push_c_function(ctx, constructor, DUK_VARARGS);
    duk_put_prop_string(ctx, -2, classname);
    duk_pop(ctx);
}

void js_class_push_propertyobject(JSVM* vm, const char* classname)
{
    duk_context* ctx = vm->GetJSContext();
    String pname;
    pname.AppendWithFormat("__%s__Properties", classname);

    duk_get_global_string(ctx, "Atomic");
    duk_push_object(ctx);
    duk_dup(ctx, -1);
    duk_put_prop_string(ctx, -3, pname.CString());
    duk_remove(ctx, -2); // remove Atomic object
}



void js_setup_prototype(JSVM* vm, const char* classname, const char* basename, bool hasProperties)
{
    duk_context* ctx = vm->GetJSContext();

    String pname;
    pname.AppendWithFormat("__%s__Properties", classname);

    int top = duk_get_top(ctx);

    duk_get_global_string(ctx, "Atomic");
    duk_get_prop_string(ctx, -1, classname);
    assert(duk_is_c_function(ctx, -1));

    if (!strlen(basename))
    {
        // prototype
        duk_push_object(ctx);
        duk_dup(ctx, -2); // AObject constructor function
        duk_put_prop_string(ctx, -2, "constructor");
        duk_put_prop_string(ctx, -2, "prototype");

        duk_pop_n(ctx, 2);

        assert (top == duk_get_top(ctx));

        return;

    }

    // prototype
    duk_get_global_string(ctx, "Object");
    duk_get_prop_string(ctx, -1, "create");

    assert(duk_is_function(ctx, -1));

    duk_remove(ctx, -2); // remove Object

    duk_get_global_string(ctx, "Atomic");
    duk_get_prop_string(ctx, -1, basename);
    assert(duk_is_function(ctx, -1));
    duk_get_prop_string(ctx, -1, "prototype");

    assert(duk_is_object(ctx, -1));

    duk_remove(ctx, -2); // remove basename

    int numargs = 1;
    if (hasProperties)
    {
        duk_get_prop_string(ctx, -2, pname.CString());
        assert(duk_is_object(ctx, -1));
        duk_remove(ctx, -3); // remove Atomic
        numargs++;
    }
    else
        duk_remove(ctx, -2); // remove Atomic

    duk_call(ctx, numargs);

    assert(duk_is_object(ctx, -1));

    duk_dup(ctx, -2);
    duk_put_prop_string(ctx, -2, "constructor");

    //duk_dup(ctx, -1);
    duk_put_prop_string(ctx, -2, "prototype");

    // pop the classname object
    duk_pop(ctx);

    // pop the Atomic Object
    duk_pop(ctx);

    assert (top == duk_get_top(ctx));
}

}
