/* This file is part of KDevelop
    Copyright 2008 David Nolden <david.nolden.kdevelop@art-master.de>

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License version 2 as published by the Free Software Foundation.

   This library 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
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; see the file COPYING.LIB.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02110-1301, USA.
 */

#ifndef KDEVPLATFORM_TYPEREGISTER_H
#define KDEVPLATFORM_TYPEREGISTER_H

#include <QHash>

#include "abstracttype.h"
#include "typesystemdata.h"

namespace KDevelop {
/**
 * \short A factory class for type data.
 *
 * This class provides an interface for creating private data
 * structures for classes.
 */
class KDEVPLATFORMLANGUAGE_EXPORT AbstractTypeFactory
{
public:
    /**
     * Create a new type for the given \a data.
     *
     * \param data Data to assign to the new type. The data type must match the class type.
     */
    virtual AbstractType* create(AbstractTypeData* data) const = 0;

    /**
     * Call the destructor of the data-type.
     */
    virtual void callDestructor(AbstractTypeData* data) const = 0;

    /**
     * Copy contents of type-data \a from one location \a to another.
     *
     * \param from data to copy from
     * \param to data to copy to. This data must not be initialized yet
     *           (the constructor must not have been called yet)
     * \param constant set to true if \a to is to be a static unchangeable
     *                 data type (eg. in the type-repository), or false if
     *                 \a to is to be a dynamic changeable type data.
     */
    virtual void copy(const AbstractTypeData& from, AbstractTypeData& to, bool constant) const = 0;

    /**
     * Return the memory size of the given private \a data, including dynamic data.
     *
     * \param data data structure
     * \returns the size in memory of the data.
     */
    virtual uint dynamicSize(const AbstractTypeData& data) const = 0;

    /// Destructor.
    virtual ~AbstractTypeFactory()
    {
    }
};

/**
 * Template class to implement factories for each AbstractType subclass you want
 * to instantiate.
 */
template <class T, class Data = typename T::Data>
class TypeFactory
    : public AbstractTypeFactory
{
public:
    AbstractType* create(AbstractTypeData* data) const override
    {
/*    if(!m_reUseTypes.isEmpty()) {
      return new (m_reUseTypes.pop()) T(*static_cast<typename T::Data*>(data));
    }else{*/
        return new T(*static_cast<typename T::Data*>(data));
//     }
    }

    void copy(const AbstractTypeData& from, AbstractTypeData& to, bool constant) const override
    {
        Q_ASSERT(from.typeClassId == T::Identity);

        if (( bool )from.m_dynamic == ( bool )!constant) {
            //We have a problem, "from" and "to" should both be either dynamic or constant. We must copy once more.
            Data* temp = &AbstractType::copyDataDirectly<Data>(static_cast<const Data&>(from));

            new (&to) Data(*temp); //Call the copy constructor to initialize the target

            Q_ASSERT(( bool )to.m_dynamic == ( bool )!constant);
            destroyData(temp);
        } else {
            new (&to) Data(static_cast<const Data&>(from)); //Call the copy constructor to initialize the target
        }
    }

    void destroyData(AbstractTypeData* data) const
    {
        callDestructor(data);
        delete[] ( char* )data;
    }

    void callDestructor(AbstractTypeData* data) const override
    {
        Q_ASSERT(data->typeClassId == T::Identity);
        static_cast<Data*>(data)->~Data();
    }

    uint dynamicSize(const AbstractTypeData& data) const override
    {
        Q_ASSERT(data.typeClassId == T::Identity);
        return static_cast<const Data&>(data).dynamicSize();
    }
};

/**
 * \short A class which registers data types and creates factories for them.
 *
 * TypeSystem is a global static class which allows you to register new
 * AbstractType subclasses for creation.
 */
class KDEVPLATFORMLANGUAGE_EXPORT TypeSystem
{
public:
    /**
     * Register a new AbstractType subclass.
     */
    template <class T, class Data = typename T::Data>
    void registerTypeClass()
    {
        registerTypeClassInternal(new TypeFactory<T, Data>(), sizeof(Data), T::Identity);
    }

    /**
     * Unregister an AbstractType subclass.
     */
    template <class T, class Data = typename T::Data>
    void unregisterTypeClass()
    {
        unregisterTypeClassInternal(T::Identity);
    }

    /**
     * Create an AbstractType for the given data. The returned type must be put into a AbstractType::Ptr immediately.
     * Can return null if no type-factory is available for the given data (for example when a language-part is not loaded)
     */
    AbstractType* create(AbstractTypeData* data) const;

    /**
     * This just calls the correct constructor on the target. The target must be big enough to hold all the data.
     */
    void copy(const AbstractTypeData& from, AbstractTypeData& to, bool constant) const;

    ///Calls the dynamicSize(..) member on the given data, in the most special class. Since we cannot use virtual functions, this is the only way.
    uint dynamicSize(const AbstractTypeData& data) const;

    ///Returns the size of the derived class, not including dynamic data.
    ///Returns zero if the class is not known.
    uint dataClassSize(const AbstractTypeData& data) const;

    ///Calls the destructor, but does not delete anything. This is needed because the data classes must not contain virtual members.
    void callDestructor(AbstractTypeData* data) const;

    ///Returns true if the factory for this data type is loaded.
    ///If false is returned, then any of the other calls will fail.
    bool isFactoryLoaded(const AbstractTypeData& data) const;

    /// Access the static TypeSystem instance.
    static TypeSystem& self();

private:
    void registerTypeClassInternal(AbstractTypeFactory* repo, uint dataClassSize, uint identity);
    void unregisterTypeClassInternal(uint identity);

    bool ensureFactoryLoaded(const AbstractTypeData& data) const;

    QHash<uint, AbstractTypeFactory*> m_factories;
    QHash<uint, uint> m_dataClassSizes;
};

/// Helper class to register an AbstractType subclass.
///
/// Just use the REGISTER_TYPE(YourTypeClass) macro in your code, and you're done.
template <class T, class Data = typename T::Data>
struct TypeSystemRegistrator
{
    TypeSystemRegistrator()
    {
        TypeSystem::self().registerTypeClass<T, Data>();
    }
    ~TypeSystemRegistrator()
    {
        TypeSystem::self().unregisterTypeClass<T, Data>();
    }
private:
    Q_DISABLE_COPY(TypeSystemRegistrator)
};

///You must add this into your source-files for every AbstractType based class
///For this to work, the class must have an "Identity" enumerator, that is unique among all types.
///It should be a unique value, but as small as possible, because a buffer at least as big as that number is created internally.
#define REGISTER_TYPE(Class) TypeSystemRegistrator<Class, Class ## Data> register ## Class
}

#endif
