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

#pragma once

#include "glmAbstractAttribute.h"
#include "glmAssert.h"
#include "glmLog.h"

namespace glm
{
    // Template class for inspectable attributes by pointer to member
    template <class TValue, class TOwner>
    class Attribute : public AbstractAttributeT<TValue, TOwner>
    {
        GLM_VIRTUAL_RTTI_T0_T1("2d7c0267-4f4d-428f-ac62-d29fd4af04e5", glm::Attribute, TValue, TOwner, addBaseClasses)

    public:
        typedef TValue value_type;

        Attribute(const char* name, TValue TOwner::*attribute);
        Attribute(const Attribute& attribute);

        virtual ~Attribute();

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

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

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

    private:
        static void addBaseClasses();
        Attribute()
        {
        }

        // Inherited
        TValue& doGetValue(const TOwner& owner) const;
        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;

        TValue TOwner::*_attribute; // member pointer to the owner's attribute
    };

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

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

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

    //-------------------------------------------------------------------------
    template <class TValue, class TOwner>
    Attribute<TValue, TOwner>::Attribute(const char* name, TValue TOwner::*attribute)
        : AbstractAttributeT<TValue, TOwner>(name, &TOwner::getClassRTTI())
        , _attribute(attribute)
    {
        this->_attributeType = AbstractAttribute::AttributeType::VALUE;
    }

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

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

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

    //-------------------------------------------------------------------------
    template <class TValue, class TOwner>
    TValue Attribute<TValue, TOwner>::getValue(const TOwner& owner) const
    {
        GLM_DEBUG_ASSERT(owner.getRTTI().isDerivedFrom(this->getOwnerRTTI()));
        GLM_DEBUG_ASSERT_MSG(this->getAttributeRTTI().isDerivedFrom(RTTIAccessor<TValue>::getClassRTTI()),
                             "unable to get a value of type %s from the attribute named %s of type %s from an object of type %s",
                             RTTIAccessor<TValue>::getClassRTTI().getTypeName(), this->getName(), getAttributeRTTI().getTypeName(), this->getOwnerRTTI().getTypeName());

        return owner.*_attribute;
    }

    //-------------------------------------------------------------------------
    template <class TValue, class TOwner>
    void Attribute<TValue, TOwner>::setValue(TOwner& owner, TValue value) const
    {
        GLM_DEBUG_ASSERT_MSG(getAttributeRTTI().isDerivedFrom(RTTIAccessor<TValue>::getClassRTTI()),
                             "unable to set a value of type %s to the attribute named %s of type %s from an object of type %s",
                             RTTIAccessor<TValue>::getClassRTTI().getTypeName(), this->getName(), getAttributeRTTI().getTypeName(), this->getOwnerRTTI().getTypeName());

        owner.*(_attribute) = value;
    }

    //-------------------------------------------------------------------------
    template <class TValue, class TOwner>
    TValue& Attribute<TValue, TOwner>::doGetValue(const TOwner& owner) const
    {
        return const_cast<TValue&>(owner.*_attribute);
    }

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

    //-------------------------------------------------------------------------
    template <class TValue, class TOwner>
    void Attribute<TValue, TOwner>::unpack(BinaryStream& stream, void* owner) const
    {
        TValue& value(doGetValue(*static_cast<TOwner*>(owner)));
        stream.read(value);
    }

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

    //-------------------------------------------------------------------------
    template <class TValue, class TOwner>
    void Attribute<TValue, TOwner>::unpack(XMLStream& stream, const DataItem& configurationItem, void* owner) const
    {
        TValue& value(const_cast<TValue&>(doGetValue(*static_cast<TOwner*>(owner))));
        stream.read(configurationItem, value);
    }

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

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

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

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

    //-------------------------------------------------------------------------
    // partial specialization for TValue*
    //-------------------------------------------------------------------------
    template <class TValue, class TOwner>
    class Attribute<TValue*, TOwner> : public AbstractAttributeT<TValue*, TOwner>
    {
        GLM_VIRTUAL_RTTI_T0_T1("3b91bae6-10e3-421e-bdd9-31bd15f97067", glm::Attribute, TValue*, TOwner, addBaseClasses)

    public:
        typedef TValue value_type;

        Attribute(const char* name, TValue* TOwner::*attribute)
            : AbstractAttributeT<TValue*, TOwner>(name, &TOwner::getClassRTTI())
            , _attribute(attribute)
        {
            this->_attributeType = AbstractAttribute::AttributeType::POINTER;
        }

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

        virtual ~Attribute()
        {
        }

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

        virtual TValue* getPointer(const TOwner* owner) const
        {
            GLM_DEBUG_ASSERT_MSG(getAttributeRTTI().isDerivedFrom(RTTIAccessor<TValue>::getClassRTTI()) || getAttributeRTTI().isDerivedFrom(RTTIAccessor<TValue*>::getClassRTTI()),
                                 "unable to get a value of type %s from the attribute named %s of type %s from an object of type %s",
                                 RTTIAccessor<TValue>::getClassRTTI().getTypeName(), this->getName(), getAttributeRTTI().getTypeName(), this->getOwnerRTTI().getTypeName());

            return owner->*_attribute;
        }

        virtual void setPointer(TOwner* owner, TValue* value) const
        {
            GLM_DEBUG_ASSERT_MSG(getAttributeRTTI().isDerivedFrom(RTTIAccessor<TValue>::getClassRTTI()) || getAttributeRTTI().isDerivedFrom(RTTIAccessor<TValue*>::getClassRTTI()),
                                 "unable to set a value of type %s to the attribute named %s of type %s from an object of type %s",
                                 RTTIAccessor<TValue>::getClassRTTI().getTypeName(), this->getName(), getAttributeRTTI().getTypeName(), this->getOwnerRTTI().getTypeName());

            owner->*(_attribute) = value;
        }

        virtual 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:
        static void addBaseClasses()
        {
            Attribute<TValue*, TOwner>::getClassRTTI().template addBaseClass<AbstractAttributeT<TValue*, TOwner>>();
        }
        Attribute()
        {
        }

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

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

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

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

        TValue* TOwner::*_attribute;
    };

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

    //-------------------------------------------------------------------------
    // template< const TValue* >
    //-------------------------------------------------------------------------
    template <class TValue, class TOwner>
    class Attribute<const TValue*, TOwner> : public AbstractAttributeT<const TValue*, TOwner>
    {
        GLM_VIRTUAL_RTTI_T0_T1("22a8891c-2050-4256-bd71-e47f5cc1c087", glm::Attribute, const TValue*, TOwner, addBaseClasses)

    public:
        typedef TValue value_type;

        Attribute(const char* name, const TValue* TOwner::*attribute)
            : AbstractAttributeT<const TValue*, TOwner>(name, &TOwner::getClassRTTI())
            , _attribute(attribute)
        {
            this->_attributeType = AbstractAttribute::AttributeType::CONST_POINTER;
        }

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

        virtual ~Attribute()
        {
        }

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

        virtual const TValue* getConstPointer(const TOwner& owner) const
        {
            GLM_DEBUG_ASSERT_MSG(getAttributeRTTI().isDerivedFrom(RTTIAccessor<TValue>::getClassRTTI()) || getAttributeRTTI().isDerivedFrom(RTTIAccessor<const TValue*>::getClassRTTI()),
                                 "unable to get a value of type %s from the attribute named %s of type %s from an object of type %s",
                                 RTTIAccessor<TValue>::getClassRTTI().getTypeName(), this->getName(), getAttributeRTTI().getTypeName(), this->getOwnerRTTI().getTypeName());

            return owner.*_attribute;
        }

        virtual void setConstPointer(TOwner& owner, const TValue* value) const
        {
            GLM_DEBUG_ASSERT_MSG(getAttributeRTTI().isDerivedFrom(RTTIAccessor<TValue>::getClassRTTI()) || getAttributeRTTI().isDerivedFrom(RTTIAccessor<const TValue*>::getClassRTTI()),
                                 "unable to set a value of type %s to the attribute named %s of type %s from an object of type %s",
                                 RTTIAccessor<TValue>::getClassRTTI().getTypeName(), this->getName(), getAttributeRTTI().getTypeName(), this->getOwnerRTTI().getTypeName());

            owner.*(_attribute) = value;
        }

        virtual 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:
        static void addBaseClasses()
        {
            Attribute<const TValue*, TOwner>::getClassRTTI().template addBaseClass<AbstractAttributeT<TValue*, TOwner>>();
        }
        Attribute()
        {
        }

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

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

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

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

        const TValue* TOwner::*_attribute;
    };

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