//
// SqratObject: Referenced Squirrel Object Wrapper
//

//
// Copyright (c) 2009 Brandon Jones
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
//  1. The origin of this software must not be misrepresented; you must not
//  claim that you wrote the original software. If you use this software
//  in a product, an acknowledgment in the product documentation would be
//  appreciated but is not required.
//
//  2. Altered source versions must be plainly marked as such, and must not be
//  misrepresented as being the original software.
//
//  3. This notice may not be removed or altered from any source
//  distribution.
//

#if !defined(_SCRAT_OBJECT_H_)
#define _SCRAT_OBJECT_H_

#include <squirrel.h>
#include <string.h>

#include "sqratAllocator.h"
#include "sqratTypes.h"
#include "sqratOverloadMethods.h"
#include "sqratUtil.h"

namespace Sqrat {

class Object {
protected:
    HSQUIRRELVM vm;
    HSQOBJECT obj;
    bool release;

    Object(HSQUIRRELVM v, bool releaseOnDestroy = true) : vm(v), release(releaseOnDestroy) {
        sq_resetobject(&obj);
    }

public:
    Object() : vm(0), release(true) {
        sq_resetobject(&obj);
    }

    Object(const Object& so) : vm(so.vm), obj(so.obj), release(so.release) {
        sq_addref(vm, &obj);
    }

    Object(HSQOBJECT o, HSQUIRRELVM v = DefaultVM::Get()) : vm(v), obj(o), release(true) {
        sq_addref(vm, &obj);
    }

    template<class T>
    Object(T* instance, HSQUIRRELVM v = DefaultVM::Get()) : vm(v), release(true) {
        ClassType<T>::PushInstance(vm, instance);
        sq_getstackobj(vm, -1, &obj);
        sq_addref(vm, &obj);
    }

    virtual ~Object() {
        if(release) {
            Release();
        }
    }

    Object& operator=(const Object& so) {
        if(release) {
            Release();
        }
        vm = so.vm;
        obj = so.obj;
        release = so.release;
        sq_addref(vm, &GetObject());
        return *this;
    }

    HSQUIRRELVM& GetVM() {
        return vm;
    }

    HSQUIRRELVM GetVM() const {
        return vm;
    }

    SQObjectType GetType() const {
        return GetObject()._type;
    }

    bool IsNull() const {
        return sq_isnull(GetObject());
    }

    virtual HSQOBJECT GetObject() const {
        return obj;
    }

    virtual HSQOBJECT& GetObject() {
        return obj;
    }

    operator HSQOBJECT&() {
        return GetObject();
    }

    void Release() {
        sq_release(vm, &obj);
    }

    SQUserPointer GetInstanceUP(SQUserPointer tag = NULL) const {
        SQUserPointer up;
        sq_pushobject(vm, GetObject());
        sq_getinstanceup(vm, -1, &up, tag);
        sq_pop(vm, 1);
        return up;
    }

    Object GetSlot(const SQChar* slot) const {
        HSQOBJECT slotObj;
        sq_pushobject(vm, GetObject());
        sq_pushstring(vm, slot, -1);
        if(SQ_FAILED(sq_get(vm, -2))) {
            sq_pop(vm, 1);
            return Object(vm); // Return a NULL object
        } else {
            sq_getstackobj(vm, -1, &slotObj);
            Object ret(slotObj, vm); // must addref before the pop!
            sq_pop(vm, 2);
            return ret;
        }
    }

    template <class T>
    T Cast() const {
        sq_pushobject(vm, GetObject());
        T ret = Var<T>(vm, -1).value;
        sq_pop(vm, 1);
        return ret;
    }

    Object GetSlot(SQInteger index) const {
        HSQOBJECT slotObj;
        sq_pushobject(vm, GetObject());
        sq_pushinteger(vm, index);
        if(SQ_FAILED(sq_get(vm, -2))) {
            sq_pop(vm, 1);
            return Object(vm); // Return a NULL object
        } else {
            sq_getstackobj(vm, -1, &slotObj);
            Object ret(slotObj, vm); // must addref before the pop!
            sq_pop(vm, 2);
            return ret;
        }
    }

    template <class T>
    inline Object operator[](T slot)
    {
        return GetSlot(slot);
    }


    SQInteger GetSize() const {
        sq_pushobject(vm, GetObject());
        SQInteger ret = sq_getsize(vm, -1);
        sq_pop(vm, 1);
        return ret;
    }
    
    template <class C>
    Object& SetReleaseHook(){
        sq_pushobject(vm, GetObject());
        sq_setreleasehook(vm, -1, &DefaultAllocator<C>::Delete);
        sq_pop(vm, 1);
        return *this;
    }
       
    struct iterator
    {
        friend class Object;
        iterator()
        {
            Index = 0;
            sq_resetobject(&Key);
            sq_resetobject(&Value);
            Key._type = OT_NULL;
            Value._type = OT_NULL;
        }
        HSQOBJECT getKey() { return Key; }
        HSQOBJECT getValue() { return Value; }
    private:
        
        HSQOBJECT Key;
        HSQOBJECT Value;
        SQInteger Index;
    };

    bool Next(iterator& iter) const
    {
        sq_pushobject(vm,obj);
        sq_pushinteger(vm,iter.Index);
        if(SQ_SUCCEEDED(sq_next(vm,-2)))
        {
            sq_getstackobj(vm,-1,&iter.Value);
            sq_getstackobj(vm,-2,&iter.Key);
            sq_getinteger(vm,-3,&iter.Index);
            sq_pop(vm,4);
            return true;
        }
        else
        {
            sq_pop(vm,2);
            return false;
        }
    }    
    
protected:
    // Bind a function and it's associated Squirrel closure to the object
    inline void BindFunc(const SQChar* name, void* method, size_t methodSize, SQFUNCTION func, bool staticVar = false) {
        sq_pushobject(vm, GetObject());
        sq_pushstring(vm, name, -1);

        SQUserPointer methodPtr = sq_newuserdata(vm, static_cast<SQUnsignedInteger>(methodSize));
        memcpy(methodPtr, method, methodSize);

        sq_newclosure(vm, func, 1);
        sq_newslot(vm, -3, staticVar);
        sq_pop(vm,1); // pop table
    }

    inline void BindFunc(const SQInteger index, void* method, size_t methodSize, SQFUNCTION func, bool staticVar = false) {
        sq_pushobject(vm, GetObject());
        sq_pushinteger(vm, index);

        SQUserPointer methodPtr = sq_newuserdata(vm, static_cast<SQUnsignedInteger>(methodSize));
        memcpy(methodPtr, method, methodSize);

        sq_newclosure(vm, func, 1);
        sq_newslot(vm, -3, staticVar);
        sq_pop(vm,1); // pop table
    }


    // Bind a function and it's associated Squirrel closure to the object
    inline void BindOverload(const SQChar* name, void* method, size_t methodSize, SQFUNCTION func, SQFUNCTION overload, int argCount, bool staticVar = false) {
        string overloadName = SqOverloadName::Get(name, argCount);

        sq_pushobject(vm, GetObject());

        // Bind overload handler
        sq_pushstring(vm, name, -1);
        sq_pushstring(vm, name, -1); // function name is passed as a free variable
        sq_newclosure(vm, overload, 1);
        sq_newslot(vm, -3, staticVar);

        // Bind overloaded function
        sq_pushstring(vm, overloadName.c_str(), -1);
        SQUserPointer methodPtr = sq_newuserdata(vm, static_cast<SQUnsignedInteger>(methodSize));
        memcpy(methodPtr, method, methodSize);
        sq_newclosure(vm, func, 1);
        sq_newslot(vm, -3, staticVar);

        sq_pop(vm,1); // pop table
    }

    // Set the value of a variable on the object. Changes to values set this way are not reciprocated
    template<class V>
    inline void BindValue(const SQChar* name, const V& val, bool staticVar = false) {
        sq_pushobject(vm, GetObject());
        sq_pushstring(vm, name, -1);
        PushVar(vm, val);
        sq_newslot(vm, -3, staticVar);
        sq_pop(vm,1); // pop table
    }
    template<class V>
    inline void BindValue(const SQInteger index, const V& val, bool staticVar = false) {
        sq_pushobject(vm, GetObject());
        sq_pushinteger(vm, index);
        PushVar(vm, val);
        sq_newslot(vm, -3, staticVar);
        sq_pop(vm,1); // pop table
    }

    // Set the value of an instance on the object. Changes to values set this way are reciprocated back to the source instance
    template<class V>
    inline void BindInstance(const SQChar* name, V* val, bool staticVar = false) {
        sq_pushobject(vm, GetObject());
        sq_pushstring(vm, name, -1);
        PushVar(vm, val);
        sq_newslot(vm, -3, staticVar);
        sq_pop(vm,1); // pop table
    }
    template<class V>
    inline void BindInstance(const SQInteger index, V* val, bool staticVar = false) {
        sq_pushobject(vm, GetObject());
        sq_pushinteger(vm, index);
        PushVar(vm, val);
        sq_newslot(vm, -3, staticVar);
        sq_pop(vm,1); // pop table
    }
};


template<>
inline void Object::BindValue<int>(const SQChar* name, const int & val, bool staticVar /* = false */) {
    sq_pushobject(vm, GetObject());
    sq_pushstring(vm, name, -1);
    PushVar<int>(vm, val);
    sq_newslot(vm, -3, staticVar);
    sq_pop(vm,1); // pop table
}

//
// Overridden Getter/Setter
//

template<>
struct Var<Object> {
    Object value;
    Var(HSQUIRRELVM vm, SQInteger idx) {
        HSQOBJECT sqValue;
        sq_getstackobj(vm, idx, &sqValue);
        value = Object(sqValue, vm);
    }
    static void push(HSQUIRRELVM vm, Object& value) {
        sq_pushobject(vm, value.GetObject());
    }
};

template<>
struct Var<Object&> {
    Object value;
    Var(HSQUIRRELVM vm, SQInteger idx) {
        HSQOBJECT sqValue;
        sq_getstackobj(vm, idx, &sqValue);
        value = Object(sqValue, vm);
    }
    static void push(HSQUIRRELVM vm, Object& value) {
        sq_pushobject(vm, value.GetObject());
    }
};

template<>
struct Var<const Object&> {
    Object value;
    Var(HSQUIRRELVM vm, SQInteger idx) {
        HSQOBJECT sqValue;
        sq_getstackobj(vm, idx, &sqValue);
        value = Object(sqValue, vm);
    }
    static void push(HSQUIRRELVM vm, Object& value) {
        sq_pushobject(vm, value.GetObject());
    }
};


}

#endif
