/*
 * 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/.
 */
#ifndef _include_ke_environment_h_
#define _include_ke_environment_h_

#include "Objects.h"
#include "Handles.h"
#include "Heap.h"
#include "FixedArray.h"

namespace ke {

class EnvironmentMap;
class Variable;
class ByteArray;
class Type;

// An environment represents the lexical environment of a code or function
// object. It used for variables which may be accessed outside a function's
// normal activation (i.e., upvars).
//
// A module uses an Environment to store its global variables.
class Environment : public Object
{
    template <typename T>
    inline T *pointerToSlot(unsigned slot);

  public:
    static Environment *New(Zone *zone, Handle<EnvironmentMap> map, Heap::Tenure tenure = Heap::Tenure_Default);

    inline EnvironmentMap *map();
    inline size_t size();

    float getFloat(unsigned slot) {
        return *pointerToSlot<float>(slot);
    }
    int getInt32(unsigned slot) {
        return *pointerToSlot<int>(slot);
    }
    Object *getObject(unsigned slot) {
        return *pointerToSlot<Object *>(slot);
    }
    void setFloat(unsigned slot, float f) {
        *pointerToSlot<float>(slot) = f;
    }
    void setInt32(unsigned slot, int i) {
        *pointerToSlot<int>(slot) = i;
    }
    void setObject(unsigned slot, Object *obj) {
        // :TODO: write barrier
        *pointerToSlot<Object *>(slot) = obj;
    }

    static inline size_t offsetOfSlotOffset(unsigned offset) {
        return sizeof(Environment) + offset;
    }

    static inline Environment *cast(Object *obj) {
        assert(!obj || obj->is(MapKind_Environment));
        return reinterpret_cast<Environment *>(obj);
    }
};

struct EnvironmentSlotDescriptor
{
    Barriered<Type> type;
    unsigned offset;
};

class EnvironmentSlotDescriptorArray : public Object
{
    unsigned length_;

  public:
    static EnvironmentSlotDescriptorArray *New(Zone *zone, unsigned length);

    EnvironmentSlotDescriptor *slots() {
        return reinterpret_cast<EnvironmentSlotDescriptor *>(this + 1);
    }
    EnvironmentSlotDescriptor &at(unsigned slot) {
        assert(slot < length_);
        return slots()[slot];
    }
    unsigned length() {
        return length_;
    }
    unsigned size() {
        return Align(sizeof(*this) + length() * sizeof(EnvironmentSlotDescriptorArray),
                     kObjectAlignment);
    }

    static inline EnvironmentSlotDescriptorArray *cast(Object *obj) {
        assert(!obj || obj->is(MapKind_EnvironmentSlotDescriptorArray));
        return reinterpret_cast<EnvironmentSlotDescriptorArray *>(obj);
    }
};

class EnvironmentMap : public Map
{
    unsigned structureSize_;
    unsigned traceOffsetCount_;
    Barriered<EnvironmentSlotDescriptorArray> slots_;

  public:
    static EnvironmentMap *New(Zone *zone, unsigned nvars, Variable **vars);

    unsigned structureSize() {
        return structureSize_;
    }
    unsigned traceOffsetCount() {
        return traceOffsetCount_;
    }
    const unsigned *traceOffsets() {
        return reinterpret_cast<unsigned *>(this + 1);
    }
    size_t size() {
        return Align(sizeof(EnvironmentMap) + sizeof(unsigned) * traceOffsetCount(), kObjectAlignment);
    }
    EnvironmentSlotDescriptor &slotDescriptor(unsigned slot) {
        return slots_->at(slot);
    }

    static inline size_t offsetOfSlots() {
        return offsetof(EnvironmentMap, slots_);
    }

    static inline EnvironmentMap *cast(Object *obj) {
        assert(!obj || obj->is(MapKind_MetaEnvironment));
        return reinterpret_cast<EnvironmentMap *>(obj);
    }
};

size_t
Environment::size()
{
    return Align(sizeof(Environment) + map()->structureSize(), kObjectAlignment);
}

EnvironmentMap *
Environment::map()
{
    return EnvironmentMap::cast(Object::map());
}

template <typename T> T *
Environment::pointerToSlot(unsigned slot)
{
    return reinterpret_cast<T *>(reinterpret_cast<char *>(this + 1) + map()->slotDescriptor(slot).offset);
}

}

#endif // _include_ke_environment_h_
