/*
 * 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_jitcraft_array_h_
#define _include_jitcraft_array_h_

#include "Heap.h"
#include "Handles.h"
#include "Types.h"

namespace ke {

class ArrayMap;

class Array : public Object
{
    friend class ArrayMap;

    uint32 nelements_;
    void *elements_;

  public:
    static Array *New(Zone *zone, Handle<ArrayMap> map, uint32 elements,
                      Heap::Tenure = Heap::Tenure_Default);
    static Array *NewExternal(Zone *zone, Handle<ArrayMap> map, uint32 nelements,
                              void *elements, Heap::Tenure = Heap::Tenure_Default);

    uint32 nelements() {
        return nelements_;
    }
    void *vec() {
        return elements_;
    }

    inline int *int32Vec();
    inline float *floatVec();
    inline Object **objVec();
    inline char *chars();

    inline Type *type();
    inline size_t elementSize();

    int readInt32(size_t index) {
        assert(index < nelements());
        return int32Vec()[index];
    }
    void writeInt32(size_t index, int i) {
        assert(index < nelements());
        int32Vec()[index] = i;
    }
    char readChar(size_t index) {
        assert(index < nelements());
        return chars()[index];
    }
    void writeChar(size_t index, char c) {
        assert(index < nelements());
        chars()[index] = c;
    }
    float readFloat(size_t index) {
        assert(index < nelements());
        return floatVec()[index];
    }
    void writeFloat(size_t index, float f) {
        assert(index < nelements());
        floatVec()[index] = f;
    }
    inline void *slotPtr(size_t index);
    Object *readObject(size_t index) {
        assert(index < nelements());
        return objVec()[index];
    }
    void writeObject(Zone *zone, size_t index, Object *ptr) {
        assert(index < nelements());
        write(zone, &objVec()[index], ptr);
    }

    bool isExternal() {
        // Inline arrays always have their elements pointing at the next slot.
        Address buffer = reinterpret_cast<Address>(elements_);
        return buffer != reinterpret_cast<Address>(this + 1);
    }

    static inline size_t SizeOf(Array *array) {
        if (array->isExternal())
            return sizeof(Array);
        return sizeof(Array) + array->nelements() * array->elementSize();
    }

    static Array *cast(Object *obj) {
        assert(!obj || obj->is(MapKind_Array));
        return reinterpret_cast<Array *>(obj);
    }
};

class ArrayMap : public Map
{
    Barriered<Type> type_;
    Barriered<ArrayMap> contained_;

  public:
    static ArrayMap *New(Zone *zone, Handle<Type> type, Handle<ArrayMap> contained);

    Type *type() {
        return type_;
    }
    ArrayMap *contained() {
        return contained_;
    }

    // GC Descriptor.
    static inline size_t offsetOfType() {
        return OFFSETOF(ArrayMap, type_);
    }
    static inline size_t offsetOfContained() {
        return OFFSETOF(ArrayMap, contained_);
    }
    // End GC Descriptor.
    
    static ArrayMap *cast(Object *obj) {
        assert(!obj || obj->is(MapKind_MetaArray));
        return reinterpret_cast<ArrayMap *>(obj);
    }
};

inline int *
Array::int32Vec()
{
    assert(type()->levels() == 1);
    assert(type()->contained()->isInt32());
    return reinterpret_cast<int *>(vec());
}

inline float *
Array::floatVec()
{
    assert(type()->levels() == 1);
    assert(type()->contained()->isFloat());
    return reinterpret_cast<float *>(vec());
}

inline Object **
Array::objVec()
{
    assert(type()->levels() > 1);
    return reinterpret_cast<Object **>(vec());
}

inline void *
Array::slotPtr(size_t index)
{
    assert(type()->contained()->primitive() == PrimitiveType_Int32 ||
           type()->contained()->primitive() == PrimitiveType_Float);
    assert(index < nelements());
    return reinterpret_cast<int *>(vec()) + index;
}

inline char *
Array::chars()
{
    assert(type()->levels() == 1);
    assert(type()->contained()->primitive() == PrimitiveType_Char);
    return reinterpret_cast<char *>(vec());
}

inline Type *
Array::type()
{
    return ArrayMap::cast(map())->type();
}

inline size_t
Array::elementSize()
{
    assert(type()->isArray());
    if (type()->contained()->isArray())
        return sizeof(Object *);
    switch (type()->contained()->primitive()) {
      case PrimitiveType_Float:
        return sizeof(float);
      case PrimitiveType_Int32:
        return sizeof(int);
      default:
        assert(type()->contained()->primitive() == PrimitiveType_Char);
        return sizeof(char);
    }
}

}

#endif // _include_jitcraft_array_h_
