/* vim: set ts=4 sw=4 tw=99 et:
 *
 * Copyright (C) 2012 David Anderson
 *
 * This file is part of SourcePawn.
 *
 * SourcePawn 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.
 * 
 * SourcePawn 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
 * SourcePawn. If not, see http://www.gnu.org/licenses/.
 */
#include <string.h>
#include "Types.h"
#include "TypeManager.h"
#include "Zone.h"
#include "Strings.h"
#include "Spaces-inl.h"
#include "Heap-inl.h"

using namespace ke;


TypeManager::TypeManager(Zone *zone)
  : zone_(zone),
    qualTable_(ZoneAllocatorPolicy())
{
}

bool
TypeManager::initialize()
{
    voidType_ = Type::NewVoid(zone_);
    if (!voidType_)
        return false;

    uncheckedType_ = Type::NewUnchecked(zone_);
    if (!uncheckedType_)
        return false;

    stringType_ = Type::NewString(zone_);
    if (!stringType_)
        return false;

    importableType_ = Type::NewImportable(zone_);
    if (!importableType_)
        return false;

    typeType_ = Type::NewType(zone_);
    if (!typeType_)
        return false;

    if ((primitiveTypes_[PrimitiveType_Int32] = Type::NewPrimitive(zone_, PrimitiveType_Int32)) == NULL)
        return false;
    if ((primitiveTypes_[PrimitiveType_Float] = Type::NewPrimitive(zone_, PrimitiveType_Float)) == NULL)
        return false;
    if ((primitiveTypes_[PrimitiveType_Native] = Type::NewPrimitive(zone_, PrimitiveType_Native)) == NULL)
        return false;
    if ((primitiveTypes_[PrimitiveType_Char] = Type::NewPrimitive(zone_, PrimitiveType_Char)) == NULL)
        return false;
    if ((primitiveTypes_[PrimitiveType_Bool] = Type::NewPrimitive(zone_, PrimitiveType_Bool)) == NULL)
        return false;

    Local<Type> type(zone_);
    for (unsigned i = 0; i < PrimitiveTypes_Total; i++) {
        type = primitiveTypes_[i];
        if ((referenceTypes_[i] = ReferenceType::New(zone_, type)) == NULL)
            return false;
    }

    if (!qualTable_.init(32))
        return false;

    return true;
}

ArrayType *
TypeManager::newArray(Handle<Type> contained, int elements)
{
    // :TODO: cache this.
    return ArrayType::New(zone_, contained, elements);
}

ArrayType *
TypeManager::newExternalArray(Handle<Type> contained)
{
    return ArrayType::NewExternal(zone_, contained);
}

ReferenceType *
TypeManager::getReference(Handle<Type> type, bool isConst)
{
    if (!isConst && type->isPrimitive())
        return referenceTypes_[type->primitive()];

    // :TODO: cache this.
    return ReferenceType::New(zone_, type);
}

EnumType *
TypeManager::newEnum(Handle<String> name)
{
    return EnumType::New(zone_, name);
}

Type *
TypeManager::qualify(Handle<Type> argType, TypeQualifiers quals)
{
    Local<Type> type(zone_, argType->unqualified());

    if (!quals)
        return type;

    LookupKey key(type, quals);

    // We can't use findForAdd because a GC could rehash the table.
    TypeHashTable::Result r = qualTable_.find(key);
    if (r.found())
        return *r;

    Type *quald = Type::NewQualified(zone_, quals, type);
    if (!quald)
        return NULL;

    // :TODO: assert-no-gc
    TypeHashTable::Insert i = qualTable_.findForAdd(key);
    assert(!i.found());

    if (!qualTable_.add(i, quald))
        return NULL;

    return quald;
}

void
TypeManager::accept(VirtualObjectVisitor *visitor)
{
    visitor->visit(voidType_.address());
    visitor->visit(uncheckedType_.address());
    visitor->visit(stringType_.address());
    visitor->visit(importableType_.address());
    visitor->visit(typeType_.address());
    for (unsigned i = 0; i < PrimitiveTypes_Total; i++) {
        visitor->visit(primitiveTypes_[i].address());
        visitor->visit(referenceTypes_[i].address());
    }
}

void
TypeManager::sweep()
{
    for (TypeHashTable::iterator i(&qualTable_); !i.empty(); i.next()) {
        Type *obj = *i;
        MarkBit bits = HeapChunk::GetMarkBit(obj);
        if (HeapChunk::IsWhite(bits))
            i.erase();
        else
            assert(HeapChunk::IsBlack(bits));
    }
    qualTable_.checkDensity();
}
