/* 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 <stdlib.h>
#include "Zone.h"
#include "StringPool.h"
#include "TypeManager.h"
#include "GlobalRoots.h"
#include "AllocatorPolicies.h"
#include "Array.h"
#include "NativeTable.h"
#include "Spaces-inl.h"
#include "Heap-inl.h"

using namespace ke;

Zone *ke::SingleThreadedZone;

void * const NullAddress::Sentinel = NULL;

Zone::Zone()
  : stack_(thisFromCtor()),
    heap_(thisFromCtor()),
    types_(NULL),
    symbols_(NULL),
    globalRoots_(NULL),
    lastLocalRoot_(NULL),
    specialRoots_(NULL),
    hasPendingException_(false),
    reporter_(NULL)
{
}

Zone::~Zone()
{
    delete types_;
    delete nativeTable_;
    delete globalRoots_;
    delete symbols_;
}

bool
Zone::initialize()
{
    if (!stack_.initialize())
        return false;
    if (!heap_.initialize())
        return false;

    symbols_ = new StringPool(this);
    if (!symbols_ || !symbols_->initialize())
        return false;

    globalRoots_ = new GlobalRoots(this);
    if (!globalRoots_ || !globalRoots_->initialize())
        return false;

    nativeTable_ = new NativeTable(this);
    if (!nativeTable_ || !nativeTable_->initialize())
        return false;

    MapKind maps[] =
    {
        MapKind_Type,
        MapKind_ArrayType,
        MapKind_FunctionType,
        MapKind_StructType,
        MapKind_ReferenceType,
        MapKind_EnumType,
        MapKind_FixedArray,
        MapKind_ByteArray,
        MapKind_Code,
        MapKind_Reference,
        MapKind_MetaArray,
        MapKind_MetaStruct,
        MapKind_MetaEnvironment,
        MapKind_Module,
        MapKind_Function,
        MapKind_String,
        MapKind_Native,
        MapKind_Descriptor,
        MapKind_Box,
        MapKind_FreeRegion,
        MapKind_EnvironmentSlotDescriptorArray
    };

    maps_[MapKind_Meta] = heap_.metaMap();
    for (size_t i = 0; i < sizeof(maps) / sizeof(maps[0]); i++) {
        if ((maps_[maps[i]] = allocateMap(maps[i], sizeof(Map))) == NULL)
            return false;
    }

    types_ = new TypeManager(this);
    if (!types_ || !types_->initialize())
        return false;

    // Create the cached array maps for generic arrays. This is for the API
    // version of Invoke(), which needs to make arrays very quickly from C++.
    {
        // Generic "cell" array (contents do not matter, just non-gc things).
        Local<Type> type(this, types_->getPrimitive(PrimitiveType_Int32));
        if (!type)
            return false;

        type = types_->newExternalArray(type);
        if (!type)
            return false;

        // :TODO:
#if 0
        externalGenericArrayMap_ = ArrayMap::Attach(this, type);
        if (!externalGenericArrayMap_)
            return false;
#endif

        type = types_->getPrimitive(PrimitiveType_Char);
        if (!type)
            return false;

        type = types_->newExternalArray(type);
        if (!type)
            return false;

        // :TODO:
#if 0
        externalCharArrayMap_ = ArrayMap::Attach(this, type);
        if (!externalCharArrayMap_)
            return false;
#endif
    }

    return true;
}

Map *
Zone::allocateMap(MapKind kind, size_t bytes, MapKind parent)
{
    assert(maps_[parent]);
    Map *map = heap_.allocateMap(kind, bytes);
    if (!map)
        return NULL;
    map->init(maps_[parent]);
    return map;
}

void
Zone::accept(VirtualObjectVisitor *visitor)
{
    for (unsigned i = 0; i < MapKinds_Total; i++)
        visitor->visit(maps_[i].address());
    visitor->visit(externalCharArrayMap_.address());
    visitor->visit(externalGenericArrayMap_.address());
    types()->accept(visitor);

    Local<Object> *local = lastLocalRoot_;
    while (local) {
        visitor->visit(local->address());
        local = local->prev();
    }

    globalRoots_->accept(visitor);
    rootScopes_.accept(visitor);
    nativeTable_->accept(visitor);

    stack_.accept(visitor);
}

void
Zone::sweepWeakRoots()
{
    symbols_->sweep();
    types()->sweep();
}

String *
Zone::makeSymbol(const char *str, size_t length)
{
    return symbols_->add(str, length);
}

String *
Zone::makeSymbol(const char *str)
{
    return makeSymbol(str, strlen(str));
}

bool
Zone::hasPendingException()
{
    return hasPendingException_;
}

void
Zone::reportOutOfMemory()
{
    
}

void
Zone::reportError(Message message, ...)
{
    va_list ap;
    va_start(ap, message);
    reportErrorVA(message, ap);
    va_end(ap);
}

#if defined(_MSC_VER)
# define VA_COPY(to, from)  to = from
#else
# define VA_COPY(to, from)  va_copy(to, from)
#endif

static char *
BuildErrorMessage(const char *format, va_list ap)
{
    va_list use;
    VA_COPY(use, ap);

    // We over-allocate and zero terminate ahead of time, since LIBCRT's
    // version of vsnprintf is screwy.
    size_t size = 255;
    char *buffer = (char *)calloc(size + 1, 1);
    if (!buffer)
        return NULL;

    for (;;) {
        int r = vsnprintf(buffer, size, format, use);

        if (r > -1 && size_t(r) < size)
            return buffer;
       
#if defined(_MSC_VER)
        if (r < 0)
            size *= 2;
#else
        if (r > -1)
            size = size_t(r) + 1;
        else
            size *= 2;
#endif

        free(buffer);
        if ((buffer = (char *)calloc(size + 1, 1)) == NULL)
            return NULL;

        VA_COPY(use, ap);
    }
}

void
Zone::reportErrorVA(Message message, va_list ap)
{
    char *report = BuildErrorMessage(Messages[message].format, ap);
    if (!report) {
        reportOutOfMemory();
        return;
    }

    if (reporter_) {
        // Reporters can re-enter. When we start having actual exception
        // objects that can be caught and inspected, this code will have to
        // save and restore the exception state.
        reporter_->onUncaughtException(report);
    }

    hasPendingException_ = true;

    free(report);
}

void
Zone::beginCompilationErrors(const char *file)
{
    if (reporter_)
        reporter_->beginCompilationErrors(file);
}

void
Zone::reportCompilationError(Message message, unsigned line, unsigned col, va_list ap)
{
    char *report = BuildErrorMessage(Messages[message].format, ap);
    if (!report) {
        reportOutOfMemory();
        return;
    }

    if (reporter_) {
        // Reporters can re-enter. When we start having actual exception
        // objects that can be caught and inspected, this code will have to
        // save and restore the exception state.
        reporter_->reportCompilationError(report, line, col);
    }

    free(report);
}

void
Zone::endCompilationErrors()
{
    if (reporter_)
        reporter_->endCompilationErrors();
}

void
Zone::installErrorReporter(Ke::IMessageReporter *reporter)
{
    reporter_ = reporter;
}

void
ZoneAllocatorPolicy::reportOutOfMemory()
{
    ZONE()->reportOutOfMemory();
}

void
ZoneAllocatorPolicy::reportAllocationOverflow()
{
    ZONE()->reportAllocationOverflow();
}

void
Zone::reportAllocationOverflow()
{
    reportError(Message_AllocationOverflow);
}

void
Zone::clearPendingException()
{
    hasPendingException_ = false;
}

void
Zone::setPublicFunctionListener(Ke::IPublicFunctionListener *listener)
{
    publicFunctionListener_ = listener;
}

AutoRooted::AutoRooted(Zone *zone)
  : zone_(zone),
    prev_(*zone->lastSpecialRoot())
{
    *zone->lastSpecialRoot() = this;
}

AutoRooted::~AutoRooted()
{
    *zone_->lastSpecialRoot() = prev_;
}
