/***************************************************************************
*                                                                          *
*  Copyright (C) Golaem S.A.  All Rights Reserved.                         *
*                                                                          *
****************************************************************************/

#pragma once

#include "glmAbstractAttribute.h"
#include "glmBinaryStream.h"
#include "glmXMLStream.h"

namespace glm
{
    // Template class for inspectable attributes through pointer to value, const reference and const pointer accessors.
    template <class TValue, class TOwner>
    class AccessorsAttribute : public AbstractAttributeT<TValue, TOwner>
    {
        GLM_VIRTUAL_RTTI_T0_T1("e4a562e2-368f-4cb8-87af-d6f6cf8c7ebc", glm::AccessorsAttribute, TValue, TOwner, addBaseClasses)

    public:
        typedef TValue (TOwner::*Getter)() const;     // Getter function for primitive data types
        typedef void (TOwner::*Setter)(TValue value); // Setter function for primitive data types

        typedef TValue value_type;

        AccessorsAttribute(const char* name, TValue (TOwner::*getter)() const, void (TOwner::*setter)(TValue value));
        AccessorsAttribute(const AccessorsAttribute& attribute);

        virtual ~AccessorsAttribute();

        const AccessorsAttribute& operator=(const AccessorsAttribute& attribute);

        // Inherited
        virtual TValue getValue(const TOwner& owner) const;
        virtual void setValue(TOwner& owner, TValue value) const;
        virtual const RTTI& getAttributeRTTI() const;

        static const RTTI& getAttributeClassRTTI();
        static void createAttributeRTTI();
        static void destroyAttributeRTTI();

    private:
        // Inherited
        virtual void pack(BinaryStream& stream, const void* owner) const;
        virtual void unpack(BinaryStream& stream, void* owner) const;
        virtual void pack(XMLStream& stream, DataItem& configurationItem, const void* owner) const;
        virtual void unpack(XMLStream& stream, const DataItem& configurationItem, void* owner) const;

        static void addBaseClasses();
        AccessorsAttribute()
        {
        }

        Getter _getter; // pointer to the owner's get accessor to the attribute value
        Setter _setter; // pointer to the owner's set accessor to the attribute value
    };

    template <class TValue, class TOwner>
    RTTI* glm::AccessorsAttribute<TValue, TOwner>::_rtti(NULL);

    //-------------------------------------------------------------------------
    template <class TValue, class TOwner>
    void AccessorsAttribute<TValue, TOwner>::addBaseClasses()
    {
        AccessorsAttribute<TValue, TOwner>::getClassRTTI().template addBaseClass<AbstractAttributeT<TValue, TOwner>>();
    }

    //-------------------------------------------------------------------------
    template <class TValue, class TOwner>
    AccessorsAttribute<TValue, TOwner>::AccessorsAttribute(const char* name, TValue (TOwner::*getter)() const, void (TOwner::*setter)(TValue value))
        : AbstractAttributeT<TValue, TOwner>(name, &TOwner::getClassRTTI())
        , _getter(static_cast<Getter>(getter))
        , _setter(static_cast<Setter>(setter))
    {
        GLM_DEBUG_ASSERT((_getter != NULL) && (_setter != NULL));
    }

    //-------------------------------------------------------------------------
    template <class TValue, class TOwner>
    AccessorsAttribute<TValue, TOwner>::AccessorsAttribute(const AccessorsAttribute& attribute)
        : AbstractAttributeT<TValue, TOwner>(attribute)
    {
        *this = attribute;
    }

    //-------------------------------------------------------------------------
    template <class TValue, class TOwner>
    const AccessorsAttribute<TValue, TOwner>& AccessorsAttribute<TValue, TOwner>::operator=(const AccessorsAttribute& attribute)
    {
        if (this != &attribute)
        {
            AbstractAttributeT<TValue, TOwner>::operator=(attribute);
            _getter = attribute._getter;
            _setter = attribute._setter;
        }
        return (*this);
    }

    //-------------------------------------------------------------------------
    template <class TValue, class TOwner>
    AccessorsAttribute<TValue, TOwner>::~AccessorsAttribute()
    {
    }

    //-------------------------------------------------------------------------
    template <class TValue, class TOwner>
    TValue AccessorsAttribute<TValue, TOwner>::getValue(const TOwner& owner) const
    {
        GLM_DEBUG_ASSERT(owner.getRTTI().isDerivedFrom(this->getOwnerRTTI()));
        return (owner.*_getter)();
    }

    //-------------------------------------------------------------------------
    template <class TValue, class TOwner>
    void AccessorsAttribute<TValue, TOwner>::setValue(TOwner& owner, TValue value) const
    {
        GLM_DEBUG_ASSERT(owner.getRTTI().isDerivedFrom(this->getOwnerRTTI()));
        (owner.*_setter)(value);
    }

    //-------------------------------------------------------------------------
    template <class TValue, class TOwner>
    void AccessorsAttribute<TValue, TOwner>::pack(BinaryStream& stream, const void* owner) const
    {
        stream.write(getValue(*static_cast<const TOwner*>(owner)));
    }

    //-------------------------------------------------------------------------
    template <class TValue, class TOwner>
    void AccessorsAttribute<TValue, TOwner>::unpack(BinaryStream& stream, void* owner) const
    {
        TValue value(getValue(*static_cast<TOwner*>(owner)));
        stream.read(value);
        if (!stream.isFirstPass())
        {
            setValue(*static_cast<TOwner*>(owner), value);
        }
    }

    //-------------------------------------------------------------------------
    template <class TValue, class TOwner>
    void AccessorsAttribute<TValue, TOwner>::pack(XMLStream& stream, DataItem& configurationItem, const void* owner) const
    {
        stream.write(configurationItem, getValue(*static_cast<const TOwner*>(owner)));
    }

    //-------------------------------------------------------------------------
    template <class TValue, class TOwner>
    void AccessorsAttribute<TValue, TOwner>::unpack(XMLStream& stream, const DataItem& configurationItem, void* owner) const
    {
        TValue value(getValue(*static_cast<TOwner*>(owner)));
        stream.read(configurationItem, value);
        if (!stream.isFirstPass())
        {
            setValue(*static_cast<TOwner*>(owner), value);
        }
    }

    //-------------------------------------------------------------------------
    template <class TValue, class TOwner>
    const RTTI& AccessorsAttribute<TValue, TOwner>::getAttributeRTTI() const
    {
        return RTTIAccessor<TValue>::getClassRTTI();
    }

    //-------------------------------------------------------------------------
    template <class TValue, class TOwner>
    const RTTI& AccessorsAttribute<TValue, TOwner>::getAttributeClassRTTI()
    {
        return RTTIAccessor<TValue>::getClassRTTI();
    }

    //-------------------------------------------------------------------------
    template <class TValue, class TOwner>
    void AccessorsAttribute<TValue, TOwner>::createAttributeRTTI()
    {
        RTTIAccessor<TValue>::createClassRTTI();
    }

    //-------------------------------------------------------------------------
    template <class TValue, class TOwner>
    void AccessorsAttribute<TValue, TOwner>::destroyAttributeRTTI()
    {
        RTTIAccessor<TValue>::destroyClassRTTI();
    }

    //-------------------------------------------------------------------------
    // partial specialization AccessorsAttribute<TValue&>
    //-------------------------------------------------------------------------
    template <class TValue, class TOwner>
    class AccessorsAttribute<TValue&, TOwner> : public AbstractAttributeT<TValue&, TOwner>
    {
        GLM_VIRTUAL_RTTI_T0_T1("9b15c392-1dea-4d5b-bae9-a048301ada8b", glm::AccessorsAttribute, TValue&, TOwner, addBaseClasses)

    public:
        typedef TValue& (TOwner::*Getter)() const;
        typedef void (TOwner::*Setter)(TValue& value);

        typedef TValue value_type;

        AccessorsAttribute(const char* name, TValue& (TOwner::*getter)() const, void (TOwner::*setter)(TValue& value))
            : AbstractAttributeT<TValue&, TOwner>(name, &TOwner::getClassRTTI())
            , _getter(static_cast<Getter>(getter))
            , _setter(static_cast<Setter>(setter))
        {
            GLM_DEBUG_ASSERT((_getter != NULL) && (_setter != NULL));
            this->_attributeType = AbstractAttribute::AttributeType::REFERENCE;
        }

        AccessorsAttribute(const char* name, TValue& (TOwner::*getter)() const)
            : AbstractAttributeT<TValue&, TOwner>(name, &TOwner::getClassRTTI(), true)
            , _getter(static_cast<Getter>(getter))
            , _setter(NULL)
        {
            GLM_DEBUG_ASSERT(_getter != NULL);
        }

        virtual ~AccessorsAttribute()
        {
        }

        AccessorsAttribute(const AccessorsAttribute& attribute)
            : AbstractAttributeT<TValue&, TOwner>(attribute)
        {
            *this = attribute;
        }

        const AccessorsAttribute& operator=(const AccessorsAttribute& attribute)
        {
            if (this != &attribute)
            {
                AbstractAttributeT<TValue&, TOwner>::operator=(attribute);
                _getter = attribute._getter;
                _setter = attribute._setter;
            }
            return (*this);
        }

        virtual TValue& getReference(const TOwner& owner) const
        {
            GLM_DEBUG_ASSERT(owner.getRTTI().isDerivedFrom(this->getOwnerRTTI()));
            return (owner.*_getter)();
        }

        virtual void setReference(TOwner& owner, TValue& value) const
        {
            GLM_DEBUG_ASSERT(owner.getRTTI().isDerivedFrom(this->getOwnerRTTI()));
            (owner.*_setter)(value);
        }

        const RTTI& getAttributeRTTI() const
        {
            return RTTIAccessor<TValue&>::getClassRTTI();
        }

        static const RTTI& getAttributeClassRTTI()
        {
            return RTTIAccessor<TValue&>::getClassRTTI();
        }

        static void createAttributeRTTI()
        {
            RTTIAccessor<TValue&>::createClassRTTI();
        }

        static void destroyAttributeRTTI()
        {
            RTTIAccessor<TValue&>::destroyClassRTTI();
        }

    private:
        void pack(BinaryStream& stream, const void* owner) const
        {
            stream.write(getReference(*static_cast<const TOwner*>(owner)));
        }

        void unpack(BinaryStream& stream, void* owner) const
        {
            TValue& value(getReference(*static_cast<TOwner*>(owner)));
            stream.read(value);
            if (!stream.isFirstPass())
            {
                setReference(*static_cast<TOwner*>(owner), value);
            }
        }

        void pack(XMLStream& stream, DataItem& configurationItem, const void* owner) const
        {
            stream.write(configurationItem, getReference(*static_cast<const TOwner*>(owner)));
        }

        void unpack(XMLStream& stream, const DataItem& configurationItem, void* owner) const
        {
            TValue& value(getReference(*static_cast<TOwner*>(owner)));
            stream.read(configurationItem, value);
            if (!stream.isFirstPass())
            {
                setReference(*static_cast<TOwner*>(owner), value);
            }
        }

        static void addBaseClasses()
        {
            AccessorsAttribute<const TValue&, TOwner>::getClassRTTI().template addBaseClass<AbstractAttributeT<TValue, TOwner>>();
        }
        AccessorsAttribute()
        {
        }

        Getter _getter;
        Setter _setter;
    };

    template <class TValue, class TOwner>
    RTTI* glm::AccessorsAttribute<TValue&, TOwner>::_rtti(NULL);

    //-------------------------------------------------------------------------
    // partial specialization AccessorsAttribute<const TValue&>
    //-------------------------------------------------------------------------
    template <class TValue, class TOwner>
    class AccessorsAttribute<const TValue&, TOwner> : public AbstractAttributeT<const TValue&, TOwner>
    {
        GLM_VIRTUAL_RTTI_T0_T1("b22c090f-7457-4212-8553-0b88e7a70285", glm::AccessorsAttribute, const TValue&, TOwner, addBaseClasses)

    public:
        typedef const TValue& (TOwner::*Getter)() const;
        typedef void (TOwner::*Setter)(const TValue& value);

        typedef TValue value_type;

        AccessorsAttribute(const char* name, const TValue& (TOwner::*getter)() const, void (TOwner::*setter)(const TValue& value))
            : AbstractAttributeT<const TValue&, TOwner>(name, &TOwner::getClassRTTI())
            , _getter(static_cast<Getter>(getter))
            , _setter(static_cast<Setter>(setter))
        {
            GLM_DEBUG_ASSERT((_getter != NULL) && (_setter != NULL));
            this->_attributeType = AbstractAttribute::AttributeType::CONST_REFERENCE;
        }

        AccessorsAttribute(const char* name, const TValue& (TOwner::*getter)() const)
            : AbstractAttributeT<const TValue&, TOwner>(name, &TOwner::getClassRTTI(), true)
            , _getter(static_cast<Getter>(getter))
            , _setter(NULL)
        {
            GLM_DEBUG_ASSERT(_getter != NULL);
        }

        virtual ~AccessorsAttribute()
        {
        }

        AccessorsAttribute(const AccessorsAttribute& attribute)
            : AbstractAttributeT<const TValue&, TOwner>(attribute)
        {
            *this = attribute;
        }

        const AccessorsAttribute& operator=(const AccessorsAttribute& attribute)
        {
            if (this != &attribute)
            {
                AbstractAttributeT<const TValue&, TOwner>::operator=(attribute);
                _getter = attribute._getter;
                _setter = attribute._setter;
            }
            return (*this);
        }

        virtual const TValue& getConstReference(const TOwner& owner) const
        {
            GLM_DEBUG_ASSERT(owner.getRTTI().isDerivedFrom(this->getOwnerRTTI()));
            return (owner.*_getter)();
        }

        virtual void setConstReference(TOwner& owner, const TValue& value) const
        {
            GLM_DEBUG_ASSERT(owner.getRTTI().isDerivedFrom(this->getOwnerRTTI()));
            (owner.*_setter)(value);
        }

        const RTTI& getAttributeRTTI() const
        {
            return RTTIAccessor<const TValue&>::getClassRTTI();
        }

        static const RTTI& getAttributeClassRTTI()
        {
            return RTTIAccessor<const TValue&>::getClassRTTI();
        }

        static void createAttributeRTTI()
        {
            RTTIAccessor<const TValue&>::createClassRTTI();
        }

        static void destroyAttributeRTTI()
        {
            RTTIAccessor<const TValue&>::destroyClassRTTI();
        }

    private:
        void pack(BinaryStream& stream, const void* owner) const
        {
            stream.write(getConstReference(*static_cast<const TOwner*>(owner)));
        }

        void unpack(BinaryStream& stream, void* owner) const
        {
            if (stream.isFirstPass())
            {
                TValue* value = new TValue();
                stream.pushValueInFifo(static_cast<void*>(value));
                stream.read(*value);
            }
            else
            {
                TValue* value = (TValue*)(stream.popValuefromFifo());
                stream.read(*value);
                setConstReference(*static_cast<TOwner*>(owner), *value);

                GLM_SAFE_DELETE(value);
            }
        }

        void pack(XMLStream& stream, DataItem& configurationItem, const void* owner) const
        {
            stream.write(configurationItem, getConstReference(*static_cast<const TOwner*>(owner)));
        }

        void unpack(XMLStream& stream, const DataItem& configurationItem, void* owner) const
        {
            if (stream.isFirstPass())
            {
                TValue* value = new TValue();
                stream.pushValueInFifo(static_cast<void*>(value));
                stream.read(configurationItem, *value);
            }
            else
            {
                TValue* value = (TValue*)(stream.popValuefromFifo());
                stream.read(configurationItem, *value);

                setConstReference(*static_cast<TOwner*>(owner), *value);

                GLM_SAFE_DELETE(value);
            }
        }

        static void addBaseClasses()
        {
            AccessorsAttribute<const TValue&, TOwner>::getClassRTTI().template addBaseClass<AbstractAttributeT<TValue, TOwner>>();
        }
        AccessorsAttribute()
        {
        }

        Getter _getter;
        Setter _setter;
    };

    template <class TValue, class TOwner>
    RTTI* glm::AccessorsAttribute<const TValue&, TOwner>::_rtti(NULL);

    //-------------------------------------------------------------------------
    // partial specialization AccessorsAttribute<TValue*>
    //-------------------------------------------------------------------------
    template <class TValue, class TOwner>
    class AccessorsAttribute<TValue*, TOwner> : public AbstractAttributeT<TValue*, TOwner>
    {
        GLM_VIRTUAL_RTTI_T0_T1("f01bb8ae-464c-4938-9e8a-09da2467452b", glm::AccessorsAttribute, TValue*, TOwner, addBaseClasses)

    public:
        typedef TValue* (TOwner::*Getter)() const;
        typedef void (TOwner::*Setter)(TValue* value);

        typedef TValue value_type;

        AccessorsAttribute(const char* name, TValue* (TOwner::*getter)() const, void (TOwner::*setter)(TValue* value))
            : AbstractAttributeT<TValue*, TOwner>(name, &TOwner::getClassRTTI())
            , _getter(static_cast<Getter>(getter))
            , _setter(static_cast<Setter>(setter))
        {
            GLM_DEBUG_ASSERT((_getter != NULL) && (_setter != NULL));
            this->_attributeType = AbstractAttribute::AttributeType::POINTER;
        }

        AccessorsAttribute(const char* name, TValue* (TOwner::*getter)() const)
            : AbstractAttributeT<TValue*, TOwner>(name, &TOwner::getClassRTTI(), true)
            , _getter(static_cast<Getter>(getter))
            , _setter(NULL)
        {
            GLM_DEBUG_ASSERT(_getter != NULL);
        }

        virtual ~AccessorsAttribute()
        {
        }

        AccessorsAttribute(const AccessorsAttribute& attribute)
            : AbstractAttributeT<TValue*, TOwner>(attribute)
        {
            *this = attribute;
        }

        const AccessorsAttribute& operator=(const AccessorsAttribute& attribute)
        {
            if (this != &attribute)
            {
                AbstractAttributeT<TValue*, TOwner>::operator=(attribute);
                _getter = attribute._getter;
                _setter = attribute._setter;
            }
            return (*this);
        }

        TValue* getPointer(const TOwner* owner) const
        {
            GLM_DEBUG_ASSERT(owner->getRTTI().isDerivedFrom(this->getOwnerRTTI()));
            return (owner->*_getter)();
        }

        void setPointer(TOwner* owner, TValue* value) const
        {
            GLM_DEBUG_ASSERT(owner->getRTTI().isDerivedFrom(this->getOwnerRTTI()));
            (owner->*_setter)(value);
        }

        const RTTI& getAttributeRTTI() const
        {
            return RTTIAccessor<TValue*>::getClassRTTI();
        }

        static const RTTI& getAttributeClassRTTI()
        {
            return RTTIAccessor<TValue*>::getClassRTTI();
        }

        static void createAttributeRTTI()
        {
            RTTIAccessor<TValue*>::createClassRTTI();
        }

        static void destroyAttributeRTTI()
        {
            RTTIAccessor<TValue*>::destroyClassRTTI();
        }

    private:
        void pack(BinaryStream& stream, const void* owner) const
        {
            stream.write(getPointer(static_cast<const TOwner*>(owner)));
        }

        void unpack(BinaryStream& stream, void* owner) const
        {
            TValue* value(NULL);
            stream.read(value);
            if (!stream.isFirstPass())
            {
                setPointer(static_cast<TOwner*>(owner), value);
            }
        }

        void pack(XMLStream& stream, DataItem& configurationItem, const void* owner) const
        {
            stream.write(configurationItem, getPointer(static_cast<const TOwner*>(owner)));
        }

        void unpack(XMLStream& stream, const DataItem& configurationItem, void* owner) const
        {
            TValue* value(NULL);
            stream.read(configurationItem, value);
            if (!stream.isFirstPass())
            {
                setPointer(static_cast<TOwner*>(owner), value);
            }
        }

        static void addBaseClasses()
        {

            AccessorsAttribute<TValue*, TOwner>::getClassRTTI().template addBaseClass<AbstractAttributeT<TValue*, TOwner>>();
        }
        AccessorsAttribute()
        {
        }

        Getter _getter;
        Setter _setter;
    };

    template <class TValue, class TOwner>
    RTTI* glm::AccessorsAttribute<TValue*, TOwner>::_rtti(NULL);

    //-------------------------------------------------------------------------
    // partial specialization AccessorsAttribute<const TValue*>
    //-------------------------------------------------------------------------
    template <class TValue, class TOwner>
    class AccessorsAttribute<const TValue*, TOwner> : public AbstractAttributeT<const TValue*, TOwner>
    {
        GLM_VIRTUAL_RTTI_T0_T1("e98077c4-25f0-420e-9b49-37bc005e8cd3", glm::AccessorsAttribute, const TValue*, TOwner, addBaseClasses)

    public:
        typedef const TValue* (TOwner::*Getter)() const;
        typedef void (TOwner::*Setter)(const TValue* value);

        typedef TValue value_type;

        AccessorsAttribute(const char* name, const TValue* (TOwner::*getter)() const, void (TOwner::*setter)(const TValue* value))
            : AbstractAttributeT<const TValue*, TOwner>(name, &TOwner::getClassRTTI())
            , _getter(static_cast<Getter>(getter))
            , _setter(static_cast<Setter>(setter))
        {
            GLM_DEBUG_ASSERT((_getter != NULL) && (_setter != NULL));
            this->_attributeType = AbstractAttribute::AttributeType::CONST_POINTER;
        }

        AccessorsAttribute(const char* name, const TValue* (TOwner::*getter)() const)
            : AbstractAttributeT<const TValue*, TOwner>(name, &TOwner::getClassRTTI(), true)
            , _getter(static_cast<Getter>(getter))
            , _setter(NULL)
        {
            GLM_DEBUG_ASSERT(_getter != NULL);
        }

        virtual ~AccessorsAttribute()
        {
        }

        AccessorsAttribute(const AccessorsAttribute& attribute)
            : AbstractAttributeT<const TValue*, TOwner>(attribute)
        {
            *this = attribute;
        }

        const AccessorsAttribute& operator=(const AccessorsAttribute& attribute)
        {
            if (this != &attribute)
            {
                AbstractAttributeT<const TValue*, TOwner>::operator=(attribute);
                _getter = attribute._getter;
                _setter = attribute._setter;
            }
            return (*this);
        }

        const TValue* getConstPointer(const TOwner& owner) const
        {
            GLM_DEBUG_ASSERT(owner.getRTTI().isDerivedFrom(this->getOwnerRTTI()));
            return (owner.*_getter)();
        }

        void setConstPointer(TOwner& owner, const TValue* value) const
        {
            GLM_DEBUG_ASSERT(owner.getRTTI().isDerivedFrom(this->getOwnerRTTI()));
            (owner.*_setter)(value);
        }

        const RTTI& getAttributeRTTI() const
        {
            return RTTIAccessor<const TValue*>::getClassRTTI();
        }

        static const RTTI& getAttributeClassRTTI()
        {
            return RTTIAccessor<const TValue*>::getClassRTTI();
        }

        static void createAttributeRTTI()
        {
            RTTIAccessor<const TValue*>::createClassRTTI();
        }

        static void destroyAttributeRTTI()
        {
            RTTIAccessor<const TValue*>::destroyClassRTTI();
        }

    private:
        void pack(BinaryStream& stream, const void* owner) const
        {
            stream.write(getConstPointer(*static_cast<const TOwner*>(owner)));
        }

        void unpack(BinaryStream& stream, void* owner) const
        {
            const TValue* value(NULL);
            stream.read(value);
            if (!stream.isFirstPass())
            {
                setConstPointer(*static_cast<TOwner*>(owner), value);
            }
        }

        void pack(XMLStream& stream, DataItem& configurationItem, const void* owner) const
        {
            stream.write(configurationItem, getConstPointer(*static_cast<const TOwner*>(owner)));
        }

        void unpack(XMLStream& stream, const DataItem& configurationItem, void* owner) const
        {
            const TValue* value(NULL);
            stream.read(configurationItem, value);
            if (!stream.isFirstPass())
            {
                setConstPointer(*static_cast<TOwner*>(owner), value);
            }
        }

        static void addBaseClasses()
        {

            AccessorsAttribute<const TValue*, TOwner>::getClassRTTI().template addBaseClass<AbstractAttributeT<const TValue*, TOwner>>();
        }
        AccessorsAttribute()
        {
        }

        Getter _getter;
        Setter _setter;
    };

    template <class TValue, class TOwner>
    RTTI* glm::AccessorsAttribute<const TValue*, TOwner>::_rtti(NULL);
} // namespace glm
