/* 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 "Scopes.h"
#include "Zone.h"
#include "Types.h"
#include "TypeManager.h"
#include "Strings.h"
#include "CompileContext.h"
#include "Environments.h"
#include "Spaces-inl.h"
#include "Heap-inl.h"

using namespace ke;

Scope::Scope(PoolAllocator &pool, Kind kind, Scope *enclosing)
  : pool_(pool),
    locals_(0),
    maxContainedLocals_(0),
    numFunctions_(0),
    numNatives_(0),
    lifoSlot_(NULL),
    kind_(kind),
    enclosing_(enclosing)
{
}

bool
Scope::initialize()
{
    if (enclosing_ && !enclosing_->children_.append(this))
        return false;
    return true;
}

void
Scope::unlink()
{
    assert(empty());

    assert(enclosing_->children_[enclosing_->children_.length() - 1] == this);
    enclosing_->children_.pop();

    // OOM here is swallowed and checked after compilation.
    // :TODO: make the above true.
    for (size_t i = 0; i < children_.length(); i++)
        enclosing_->children_.append(children_[i]);
}

Scope *
Scope::unlinkIfEmpty()
{
    if (empty()) {
        unlink();
        return NULL;
    }
    return this;
}

BoundName *
Scope::localLookup(Handle<String> name)
{
    for (size_t i = 0; i < names_.length(); i++) {
        if (names_[i]->name() == name)
            return names_[i];
    }
    return NULL;
}

BoundName *
Scope::lookup(Handle<String> name)
{
    for (Scope *scope = this; scope; scope = scope->enclosing()) {
        if (BoundName *bound = scope->localLookup(name))
            return bound;
    }
    return NULL;
}

Variable *
Scope::declareVariable(Handle<String> name, const SourcePosition &pos)
{
    assert(!localLookup(name));
    
    Variable *var = new (pool_) Variable(this, name, pos);
    if (!names_.append(var))
        return NULL;
    return var;
}

Variable *
Scope::declareArgument(Handle<String> name, const SourcePosition &pos)
{
    assert(kind() == FUNCTION);
    Variable *var = declareVariable(name, pos);
    if (!names_.append(var))
        return NULL;
    if (!parameters_.append(var))
        return NULL;
    return var;
}

NamedFunction *
Scope::declareFunction(Handle<String> name, const SourcePosition &pos, TokenKind token)
{
    assert(!localLookup(name));
    assert(kind() == GLOBAL);

    NamedFunction *fun = new (pool_) NamedFunction(this, name, pos, token);
    if (!names_.append(fun))
        return NULL;
    return fun;
}

NamedEnum *
Scope::declareEnum(Handle<String> name, const SourcePosition &pos)
{
    NamedEnum *e = new (pool_) NamedEnum(this, name, pos);
    if (!names_.append(e))
        return NULL;
    return e;
}

NamedConstant *
Scope::declareConstant(Handle<String> name, const SourcePosition &pos)
{
    NamedConstant *constant = new (pool_) NamedConstant(this, name, pos);
    if (!names_.append(constant))
        return NULL;
    return constant;
}

void
Scope::allocateGlobals()
{
    assert(kind() == GLOBAL);

    for (size_t i = 0; i < children_.length(); i++) {
        assert(children_[i]->kind() == FUNCTION);
        children_[i]->allocate();
    }

    Local<Type> type(ZONE());
    for (size_t i = 0; i < names_.length(); i++) {
        BoundName *name = names_[i];
        
        if (name->kind() == BoundName::FUNCTION) {
            NamedFunction *fun = name->toNamedFunction();
            if (fun->isNative())
                fun->allocate(numNatives_++);
            else
                fun->allocate(numFunctions_++);
        } else if (name->kind() == BoundName::VARIABLE) {
            allocate(name->toVariable());
        }
    }
}

void
Scope::allocate(Variable *var)
{
    if (!var->unallocated())
        return;

    if (kind() == GLOBAL) {
        var->allocate(Variable::ENVIRONMENT, envVars_.length());
        envVars_.append(var);
    } else {
        var->allocate(Variable::STACK, maxContainedLocals_ + locals_);
        locals_++;
    }
}

void
Scope::allocate()
{
    maxContainedLocals_ = 0;
    for (size_t i = 0; i < children_.length(); i++) {
        Scope *scope = children_[i];
        scope->allocate();
        if (scope->maxContainedLocals() > maxContainedLocals_)
            maxContainedLocals_ = scope->maxContainedLocals();
    }

    for (size_t i = 0; i < parameters_.length(); i++)
        parameters_[i]->allocate(Variable::PARAMETER, i);

    if (lifoSlot_)
        allocate(lifoSlot_);
    for (size_t i = 0; i < names_.length(); i++) {
        if (names_[i]->kind() != BoundName::VARIABLE)
            continue;

        allocate(names_[i]->toVariable());
    }

    maxContainedLocals_ += locals_;
}

bool
Scope::Bind(CompileContext &cc, ParseTree *tree)
{
    Local<String> name(ZONE());
    for (size_t i = 0; i < tree->unbound()->length(); i++) {
        NameProxy *proxy = tree->unbound()->at(i);
        name = proxy->name();
        BoundName *binding = tree->scope()->lookup(name);

        if (!binding) {
            cc.reportError(proxy->pos(), Message_IdentifierNotFound, name->chars());
            return false;
        }

        // We only allow global functions to be late-bound.
        if (binding->kind() != BoundName::FUNCTION ||
            binding->scope()->kind() != Scope::GLOBAL)
        {
            cc.reportError(proxy->pos(), Message_IdentifierNotFound, name->chars());
            return false;
        }

        proxy->bind(binding);
    }
    return true;
}

void
Scope::Allocate(ParseTree *tree)
{
    tree->scope()->allocateGlobals();
}

void
Scope::setUsesLifoHeap()
{
    // Only one lifo slot is needed per scope.
    if (lifoSlot_)
        return;

    // If the parent of this scope is the function scope, then this is the
    // function's block scope, and the lifo slot is part of the Frame instead.
    // Note we can get a function scope here if we're making a temporary in a
    // function with no local variables.
    if (kind() == FUNCTION || enclosing()->kind() == FUNCTION)
        return;

    Local<Type> type(ZONE(), ZONE()->types()->getPrimitive(PrimitiveType_Native));
    if (!type)
        return;

    Local<String> name(ZONE());
    lifoSlot_ = new (pool_) Variable(this, name, SourcePosition());
    lifoSlot_->setType(type);
}

EnvironmentMap *
Scope::createEnvironmentMap(Zone *zone)
{
    return EnvironmentMap::New(zone, envVars_.length(), envVars_.buffer());
}
