/* vim: set ts=4 sw=4 tw=99 et:
 *
 * Copyright (C) 2012 David Anderson
 *
 * This file is part of JITCraft.
 *
 * JITCraft is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 * 
 * Foobar is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * JITCraft. If not, see http://www.gnu.org/licenses/.
 */
#include <string.h>
#include "../include/keima.h"
#include "Zone.h"
#include "RootScopes.h"
#include "compiler/CompileContext.h"
#include "Modules.h"
#include "GlobalRoots.h"
#include "Reference.h"
#include "Array.h"
#include "Interpreter.h"
#include "Stack.h"
#include "Strings.h"
#include "Packages.h"
#include "NativeTable.h"
#include "Spaces-inl.h"
#include "Heap-inl.h"

using namespace ke;

bool
Ke::Keima::Initialize()
{
    SingleThreadedZone = new Zone();
    if (!SingleThreadedZone || !SingleThreadedZone->initialize())
        return false;
    return true;
}

void
Ke::Keima::Shutdown()
{
    delete SingleThreadedZone;
    SingleThreadedZone = NULL;
}

Ke::HandleScope::HandleScope()
  : zone_(ke::ZONE()),
    position_(zone_->rootScopes().position()),
    prev_(*zone_->rootScopes().scope())
{
    *zone_->rootScopes().scope() = this;
}

Ke::HandleScope::~HandleScope()
{
    zone_->rootScopes().unwind(position_);
    *zone_->rootScopes().scope() = prev_;
}

ke::Object **
Ke::HandleScope::CreateHandle(ke::Object *obj)
{
    if (!obj)
        return NULL;

    return ZONE()->rootScopes().make(obj);
}

template <typename From, typename To> static inline To *
Open(Ke::Handle<From> from)
{
    if (!from)
        return NULL;
    return *reinterpret_cast<To **>(*from);
}

template <typename From, typename To> static inline To *
Open(From *from)
{
    if (!from)
        return NULL;
    return *reinterpret_cast<To **>(from);
}

template <typename From, typename To> static inline Ke::Local<To>
ToLocal(From *from)
{
    if (!from)
        return Ke::Local<To>();
    return Ke::Local<To>(reinterpret_cast<To *>(Ke::HandleScope::CreateHandle(from)));
}

bool
InternalCompile(ke::Handle<ke::Package> package, const char *name)
{
    PoolScope poolScope(ZONE()->pool());
    RootScope roots(ZONE());
    CompileContext cc(ZONE(), name);

    if (!cc.initialize(package, name))
        return false;

    if (!cc.compile())
        return false;

    return true;
}

// This is split out from InternalCompile so the RootScope doesn't conflict
// with making a return handle.
bool
Ke::Keima::Compile(Handle<Package> argPackage, const char *name)
{
    ke::Local<ke::Package> package(ke::ZONE(), Open<Ke::Package, ke::Package>(argPackage));
    return InternalCompile(package, name);
}

void
Ke::Keima::InstallMessageReporter(Ke::IMessageReporter *reporter)
{
    ZONE()->installErrorReporter(reporter);
}

class StackFrameIterator : public Ke::IStackFrameIterator
{
    StackIter iter_;

  public:
    StackFrameIterator()
      : iter_(ZONE())
    {
    }

    virtual bool done() {
        return iter_.done();
    }

    const char *function() {
        return iter_.function();
    }

    const char *source() {
        return iter_.source();
    }

    unsigned lineNumber() {
        return iter_.lineNumber();
    }

    void prev() {
        iter_.prev();
    }
};

Ke::IStackFrameIterator *
Ke::Keima::NewStackFrameIterator()
{
    return new StackFrameIterator();
}

static inline ke::HeapRoot *
FromAPI(ke::HeapRoot *from)
{
    ke::Address address = reinterpret_cast<ke::Address>(from);
    address -= offsetof(ke::HeapRoot, obj);
    return reinterpret_cast<ke::HeapRoot *>(address);
}

ke::HeapRoot *
Ke::Internals::NewHeapRoot()
{
    ke::HeapRoot *root = ke::ZONE()->globalRoots()->create();
    if (!root)
        return NULL;

    // Compute an interior pointer, so it looks like a Handle to an object.
    return reinterpret_cast<ke::HeapRoot *>(&root->obj);
}

void
Ke::Internals::Destroy(ke::HeapRoot *root)
{
    ZONE()->globalRoots()->destroy(FromAPI(root));
}

#if 0
bool
Ke::Keima::IsInCallStack(Handle<Package> package)
{
    ke::Local<ke::Module> module(ke::ZONE(), Open<Ke::Module, ke::Module>(argModule));
    return ZONE()->stack().isInCallStack(module);
}
#endif

void
Ke::Keima::CollectGarbage()
{
    ke::ZONE()->heap().reclaim();
}

void
Ke::Keima::ClearPendingException()
{
    ke::ZONE()->clearPendingException();
}

void
Ke::Package::setNativeIdentity(void *identity)
{
    ke::Local<ke::Package> package(ke::ZONE(), Open<Ke::Package, ke::Package>(this));
    package->setNativeIdentity(identity);
}

unsigned
Ke::String::length()
{
    ke::Local<ke::String> obj(ke::ZONE(), Open<Ke::String, ke::String>(this));
    return obj->length();
}

const char *
Ke::String::chars()
{
    ke::Local<ke::String> obj(ke::ZONE(), Open<Ke::String, ke::String>(this));
    return obj->chars();
}

Ke::Local<Ke::String>
Ke::Function::name()
{
    ke::Local<ke::Function> fun(ke::ZONE(), Open<Ke::Function, ke::Function>(this));
    return ToLocal<ke::String, Ke::String>(fun->name());
}

Ke::Local<Ke::Package>
Ke::Package::New(Handle<Package> argParent,
                 Handle<String> argName,
                 const char *path,
                 unsigned flags)
{
    ke::Local<ke::Package> parent(ke::ZONE(), Open<Ke::Package, ke::Package>(argParent));
    ke::Local<ke::String> name(ke::ZONE(), Open<Ke::String, ke::String>(argName));
    ke::Local<ke::Package> package(ke::ZONE(), ke::Package::New(ke::ZONE(), parent, path, name, flags));
    return ToLocal<ke::Package, Ke::Package>(package);
}

static inline const char *
GetInvokeTypeName(Ke::Parameter::Type type)
{
    switch (type) {
      case Ke::Parameter::Array:
        return "array";
      case Ke::Parameter::CharArray:
        return "String";
      case Ke::Parameter::Float:
        return "Float";
      case Ke::Parameter::FloatRef:
        return "&Float";
      case Ke::Parameter::Int:
        return "int";
      case Ke::Parameter::IntRef:
        return "&int";
      default:
        return "<unknown>";
    }
}

static inline bool
CheckInvokeType(const Ke::Parameter &param, Type *type)
{
    switch (param.type()) {
      case Ke::Parameter::Array:
      {
        if (!type->isArray())
            return false;

        ArrayType *atype = ArrayType::cast(type);
        return atype->levels() == 1 &&
               (atype->contained()->isPrimitive() || atype->contained()->isEnum()) &&
               (atype->contained()->primitive() != PrimitiveType_Char);
      }
      case Ke::Parameter::CharArray:
        return type->isArray() && ArrayType::cast(type)->isCharArray();
      case Ke::Parameter::Float:
        return type->isFloat();
      case Ke::Parameter::FloatRef:
        return type->isReference() && ReferenceType::cast(type)->contained()->isFloat();
      case Ke::Parameter::Int:
        return type->isInt32() || type->isEnum();
      case Ke::Parameter::IntRef:
       return type->isReference() &&
              (ReferenceType::cast(type)->contained()->isInt32() ||
               ReferenceType::cast(type)->contained()->isEnum());
      default:
        return false;
    }
}

static bool
CheckInvokeTypes(Ke::Handle<Ke::Function> argFun, const Ke::Parameter *params, unsigned nparams)
{
    ke::Local<ke::Function> fun(ke::ZONE(), Open<Ke::Function, ke::Function>(argFun));

    if (fun->nargs() != nparams)
        return false;

    for (unsigned i = 0; i < nparams; i++) {
        if (!CheckInvokeType(params[i], fun->argType(i))) {
            char name[255];
            ke::ZONE()->reportError(Message_TypeMismatchDuringInvoke,
                                    GetInvokeTypeName(params[i].type()),
                                    GetTypeName(fun->argType(i), name, sizeof(name)));
            return false;
        }
    }

    // Functions can only return tagged cells, for ISPE1-compatible Invoke().
    ke::Local<ke::Type> returnType(ke::ZONE(), fun->type()->returnType());
    if (!returnType->isPrimitive() &&
        !returnType->isEnum() &&
        !returnType->isVoid())
    {
        char name[255];
        ke::ZONE()->reportError(Message_CannotInvokeReturnType,
                                GetTypeName(returnType, name, sizeof(name)));
        return false;
    }

    return true;
}

bool
Ke::Keima::Invoke(Handle<Function> argFun, const Parameter *params, unsigned nparams,
                  bool checkTypes, int *retval)
{
    assert(CheckInvokeTypes(argFun, params, nparams));

    ke::Local<ke::Function> fun(ke::ZONE(), Open<Ke::Function, ke::Function>(argFun));

    if (fun->nargs() != nparams) {
        // :TODO: report error
        assert(false);
        return false;
    }

    ke::InvokeArgs args;
    if (!ke::ZONE()->stack().pushInvokeFrame(fun, args))
        return false;

    if (checkTypes && !CheckInvokeTypes(argFun, params, nparams))
        return false;

    for (unsigned i = 0; i < nparams; i++) {
        ke::LocalSlot &slot = args.arg(i);
        switch (params[i].type()) {
          case Ke::Parameter::Int:
            slot.i32 = params[i].i32();
            break;

          case Ke::Parameter::Float:
            slot.f = params[i].f();
            break;

          case Ke::Parameter::IntRef:
            if (params[i].writeback()) {
                slot.ref = Reference::NewStack(ke::ZONE(), params[i].i32p(), Heap::Tenure_Stack);
            } else {
                LocalSlot other;
                other.i32 = *params[i].i32p();
                slot.ref = Reference::NewStack(ke::ZONE(), other, Heap::Tenure_Stack);
            }
            if (!slot.ref)
                return false;
            break;

          case Ke::Parameter::FloatRef:
            if (params[i].writeback()) {
                slot.ref = Reference::NewStack(ke::ZONE(), params[i].fp(), Heap::Tenure_Stack);
            } else {
                LocalSlot other;
                other.f = *params[i].fp();
                slot.ref = Reference::NewStack(ke::ZONE(), other, Heap::Tenure_Stack);
            }
            if (!slot.ref)
                return false;
            break;

          case Ke::Parameter::Array:
          {
            ke::Local<ke::ArrayMap> map(ke::ZONE(), ke::ZONE()->externalGenericArrayMap());
            if (params[i].writeback()) {
                slot.obj = Array::NewExternal(ke::ZONE(), map, params[i].buffer(),
                                              params[i].length(), Heap::Tenure_Stack);
                if (!slot.obj)
                    return false;
            } else {
                Array *array = Array::New1D(ke::ZONE(), map, params[i].length(), Heap::Tenure_Stack);
                if (!slot.obj)
                    return false;
                if (params[i].length())
                    memcpy(array->bytes(), params[i].buffer(), params[i].length() * sizeof(int));
            }
            break;
          }

          case Ke::Parameter::CharArray:
          {
            ke::Local<ke::ArrayMap> map(ke::ZONE(), ke::ZONE()->externalCharArrayMap());
            if (params[i].writeback()) {
                slot.obj = Array::NewExternal(ke::ZONE(), map, params[i].buffer(),
                                              params[i].length(), Heap::Tenure_Stack);
                if (!slot.obj)
                    return false;
            } else {
                Array *array = Array::New1D(ke::ZONE(), map, params[i].length(), Heap::Tenure_Stack);
                if (!slot.obj)
                    return false;
                if (params[i].length())
                    memcpy(array->bytes(), params[i].buffer(), params[i].length() * sizeof(char));
            }
            break;
          }
        }
    }

    LocalSlot result;
    if (!Interpret(ke::ZONE(), NULL, &result))
        return false;

    if (fun->type()->returnType()->isVoid())
        result.i32 = 0;

    *retval = result.i32;
    return true;
}

bool
Ke::Keima::BindNative(const char *name, void *address)
{
    return ke::ZONE()->nativeTable()->bind(name, address);
}

void
Ke::Keima::SetPublicFunctionListener(IPublicFunctionListener *listener)
{
    ke::ZONE()->setPublicFunctionListener(listener);
}

void
Zone::registerPublic(ke::Handle<Function> fun)
{
    if (!publicFunctionListener_)
        return;

    Ke::Local<Ke::Function> local = ToLocal<ke::Function, Ke::Function>(fun);
    publicFunctionListener_->registerPublic(local);
}
