//
// Created by pgess on 3/15/15.
//

#ifndef _XREATE_ATTACHMENTS_H_
#define _XREATE_ATTACHMENTS_H_
#include <map>
#include <assert.h>
#include <type_traits>

namespace xreate
{
        //Attachemnt Tags:
    struct IsDeclVisited{};
    struct IsImplementationOnTheFly{};

        //Atachments dictionary
    template<class Tag>
    struct AttachmentsDict
    {
        // typedef void Data;
    };

    template<>
    struct AttachmentsDict<IsDeclVisited>
    {
        typedef bool Data;
        static const unsigned int key = 0;
    };

    template<>
    struct AttachmentsDict<IsImplementationOnTheFly>
    {
        typedef bool Data;
        static const unsigned int key = 3;
    };



    class Symbol;
    typedef unsigned int VID;

    class AttachmentsImpl
    {
        friend class SymbolAttachments;

    };

        //TODO copy function to copy whole data from symbol to symbol: copy(sTo, sFrom);
    class SymbolAttachments
    {
    public:
                //TODO add specialization for pointers
        template<class Tag>
        using  Data = typename AttachmentsDict<Tag>::Data;

        template<class Tag>
        using  Input = typename std::conditional<std::is_pointer<Data<Tag>>::value, Data<Tag>, Data<Tag>&&>::type;

        template<class Tag>
        static typename std::enable_if<std::is_pointer<Data<Tag>>::value>::type
        put(const Symbol& symbol,  Input<Tag> data)
        {
            const unsigned int key = AttachmentsDict<Tag>::key;
            put(symbol, key, (void*) data);
        }

        template<class Tag>
        static typename std::enable_if<! std::is_pointer<Data<Tag>>::value>::type
        put(const Symbol& symbol,  Input<Tag> data)
        {
            const unsigned int key = AttachmentsDict<Tag>::key;
            Data<Tag>* holder = new Data<Tag>(data);
            put(symbol, key, (void*) holder);
        }

        /*
        template<class Tag>
        using Tag2 = std::enable_if<std::is_pointer<Tag>, Tag>::
        static void put(const Symbol& symbol, typename AttachmentsDict<Tag>::Data && data)
        {
            typedef typename AttachmentsDict<Tag>::Data Typ;
            Typ* ptr = new Typ(data);

            AttachmentsImpl::put(symbol, ptr);
        }
        */

        template<class Tag>
        static typename AttachmentsDict<Tag>::Data& get(const Symbol& symbol, typename AttachmentsDict<Tag>::Data&& valueDefault)
        {
            typedef typename AttachmentsDict<Tag>::Data Typ;
            const unsigned int key = AttachmentsDict<Tag>::key;

            Typ* def = new Typ(valueDefault);
            Typ* result = static_cast<Typ*> (get(symbol, key, def));

            if (result != def) delete def; //TODO check is there some mem leaks?
            return *result;
        }

        template<class Tag>
        static typename std::enable_if<std::is_pointer<Data<Tag>>::value, Data<Tag>>::type
        get(const Symbol& symbol)
        {
            const unsigned int key = AttachmentsDict<Tag>::key;
            Data<Tag> result = reinterpret_cast<Data<Tag>> (get(symbol, key, nullptr));

            assert(result != nullptr); // data not found for the symbol
            return result;
        }

        template<class Tag>
        static typename std::enable_if<! std::is_pointer<Data<Tag>>::value, Data<Tag>&>::type
                get(const Symbol& symbol)
        {
            const unsigned int key = AttachmentsDict<Tag>::key;

            Data<Tag>* result = reinterpret_cast<Data<Tag>*> (get(symbol, key, nullptr));

            assert(result != nullptr); // data not found for the symbol
            return *result;
        }

        template<class Tag>
        static bool exists(const Symbol& symbol)
        {
            const unsigned int key = AttachmentsDict<Tag>::key;
            return exists(symbol, key);
        }

    private:
        typedef std::map<VID, void*> Attachment;
        std::map<unsigned int, Attachment> __data;

        static void put(const Symbol& symbol, unsigned int key, void* data);
        static void* get(const Symbol& symbol, unsigned int key,  void *def);
        static bool exists(const Symbol& symbol, unsigned int key);

    };
}

#endif //_XREATE_ATTACHMENTS_H_
