#pragma once
/*
 * Copyright (c) 2016 MariaDB Corporation Ab
 *
 * Use of this software is governed by the Business Source License included
 * in the LICENSE.TXT file and at www.mariadb.com/bsl11.
 *
 * Change Date: 2020-01-01
 *
 * On the date above, in accordance with the Business Source License, use
 * of this software will be governed by version 2 or later of the General
 * Public License.
 */

#include <maxscale/cppdefs.hh>
#include <stdio.h>
#include <tr1/unordered_map>

namespace maxscale
{

/**
 * @class CloserTraits utils.hh <maxscale/utils.hh>
 *
 * A traits class used by Closer. To be specialized for all types that are
 * used with Closer.
 */
template<class T>
struct CloserTraits
{
    /**
     * Closes/frees/destroys a resource.
     *
     * @param t  Close the resource *if* it has not been closed already.
     */
    static void close_if(T t);

    /**
     * Resets a reference to a resource. After the call, the value of t should
     * be such that @c close_if can recognize that the reference already has
     * been closed.
     *
     * @param t  Reference to a resource.
     */
    static void reset(T& t);
};

/**
 * @class Closer utils.hh <maxscale/utils.hh>
 *
 * The template class Closer is a class that is intended to be used
 * for ensuring that a C style resource is released at the end of a
 * scoped block, irrespective of how that block is exited (by reaching
 * the end of it, or by a return or exception in the middle of it).
 *
 * Closer performs the actual resource releasing using CloserTraits
 * that need to be specialized for every type of resource to be managed.
 *
 * Example:
 * @code
 * void f()
 * {
 *     FILE* pFile = fopen(...);
 *
 *     if (pFile)
 *     {
 *         Closer<FILE*> file(pFile);
 *
 *         // Use pFile, call functions that potentually may throw
 *     }
 * }
 * @endcode
 *
 * Without @c Closer all code would have to be placed within try/catch
 * blocks, which quickly becomes unwieldy as the number of managed
 * resources grows.
 */
template<class T>
class Closer
{
public:
    /**
     * Creates the closer and stores the provided resourece. Note that
     * the constructor assumes that the resource exists already.
     *
     * @param resource  The resource whose closing is to be ensured.
     */
    Closer(T resource)
        : m_resource(resource)
    {
    }

    /**
     * Destroys the closer and releases the resource.
     */
    ~Closer()
    {
        CloserTraits<T>::close_if(m_resource);
    }

    /**
     * Returns the original resource. Note that the ownership of the
     * resource remains with the closer.
     *
     * @return The resource that was provided in the constructor.
     */
    T get() const
    {
        return m_resource;
    }

    /**
     * Resets the closer, that is, releases the resource.
     */
    void reset()
    {
        CloserTraits<T>::close_if(m_resource);
        CloserTraits<T>::reset(m_resource);
    }

    /**
     * Resets the closer, that is, releases the resource and assigns a
     * new resource to it.
     */
    void reset(T resource)
    {
        CloserTraits<T>::close_if(m_resource);
        m_resource = resource;
    }

    /**
     * Returns the original resource together with its ownership. That is,
     * after this call the responsibility for releasing the resource belongs
     * to the caller.
     *
     * @return The resource that was provided in the constructor.
     */
    T release()
    {
        T resource = m_resource;
        CloserTraits<T>::reset(m_resource);
        return resource;
    }

private:
    Closer(const Closer&);
    Closer& operator = (const Closer&);

private:
    T m_resource;
};

}


namespace maxscale
{

/**
 * @class CloserTraits<FILE*> utils.hh <maxscale/utils.hh>
 *
 * Specialization of @c CloserTraits for @c FILE*.
 */
template<>
struct CloserTraits<FILE*>
{
    static void close_if(FILE* pFile)
    {
        if (pFile)
        {
            fclose(pFile);
        }
    }

    static void reset(FILE*& pFile)
    {
        pFile = NULL;
    }
};

/* Helper type for Registry. Must be specialized for each EntryType. The types
 * listed below are just examples and will not compile. */
template<typename EntryType>
struct RegistryTraits
{
    typedef int id_type;
    typedef EntryType* entry_type;

    static id_type get_id(entry_type entry)
    {
        static_assert(sizeof(EntryType) != sizeof(EntryType), "get_id() and the"
                " surrounding struct must be specialized for every EntryType!");
        return 0;
    }
    static entry_type null_entry()
    {
        return NULL;
    }
};

/**
 * Class Registy wraps a map, allowing only a few operations on it. The intended
 * use is simple registries, such as the session registry in Worker. The owner
 * can expose a reference to this class without exposing all the methods the
 * underlying container implements. When instantiating with a new EntryType, the
 * traits-class RegistryTraits should be specialized for the new type as well.
 */
template <typename EntryType>
class Registry
{
    Registry(const Registry&);
    Registry& operator = (const Registry&);
public:
    typedef typename RegistryTraits<EntryType>::id_type id_type;
    typedef typename RegistryTraits<EntryType>::entry_type entry_type;

    Registry()
    {
    }
    /**
     * Find an entry in the registry.
     *
     * @param id Entry key
     * @return The found entry, or NULL if not found
     */
    entry_type lookup(id_type id) const
    {
        entry_type rval = RegistryTraits<EntryType>::null_entry();
        typename ContainerType::const_iterator iter = m_registry.find(id);
        if (iter != m_registry.end())
        {
            rval = iter->second;
        }
        return rval;
    }

    /**
     * Add an entry to the registry.
     *
     * @param entry The entry to add
     * @return True if successful, false if id was already in
     */
    bool add(entry_type entry)
    {
        id_type id = RegistryTraits<EntryType>::get_id(entry);
        typename ContainerType::value_type new_value(id, entry);
        return m_registry.insert(new_value).second;
    }

    /**
     * Remove an entry from the registry.
     *
     * @param id Entry id
     * @return True if an entry was removed, false if not found
     */
    bool remove(id_type id)
    {
        entry_type rval = lookup(id);
        if (rval)
        {
            m_registry.erase(id);
        }
        return rval;
    }

private:
    typedef typename std::tr1::unordered_map<id_type, entry_type> ContainerType;
    ContainerType m_registry;
};

}
