/* -*- mia-c++  -*-
 *
 * This file is part of MIA - a toolbox for medical image analysis
 * Copyright (c) Leipzig, Madrid 1999-2017 Gert Wollny
 *
 * MIA is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with MIA; if not, see <http://www.gnu.org/licenses/>.
 *
 */

#ifndef mia_singular_refobj_hh
#define mia_singular_refobj_hh


#include <mia/core/defines.hh>
#include <cassert>

NS_MIA_BEGIN



/**
   \brief a singulater reference counted object that gets destroyed when the refount goes to zero

   This template handles singular objects that must no be copied (e.g. a resource)
   but that do also not fit the sigleton pattern. An example use are the HDF5 file, group, and
   dataset handles.

   \tparam T type of the data to be hold
   \tparam D type of a functor that frees the resource. It must provide a static method
         static void apply(T& data) that frees the resource.

*/


template <typename T>
class TSingleReferencedObject
{
public:
       struct Destructor {
              Destructor() {}
              virtual void operator ()(T& MIA_PARAM_UNUSED(data)) const = 0;
       };

       struct EmptyDestructor : public Destructor {
              EmptyDestructor() {}
              virtual void operator ()(T& MIA_PARAM_UNUSED(data))const {}
       };

       static const EmptyDestructor empty_destructor;

       TSingleReferencedObject();

       TSingleReferencedObject(T data, const Destructor& d = empty_destructor);

       TSingleReferencedObject(const TSingleReferencedObject<T>& other);

       TSingleReferencedObject& operator = (const TSingleReferencedObject<T>& other);

       TSingleReferencedObject(TSingleReferencedObject<T>&& other);

       TSingleReferencedObject& operator = (TSingleReferencedObject<T>&& other);


       ~TSingleReferencedObject();

       operator T()const;

       unsigned get_refcount()const;

private:
       class TheObject
       {
       public:
              TheObject(T data, const TSingleReferencedObject::Destructor& d);
              ~TheObject();
              void add_ref();
              bool del_ref();
              T get() const;

              unsigned get_refcount()const;
       private:
              TheObject(const TheObject& data) = delete;
              TheObject& operator = (const TheObject& data) = delete;
              T m_data;
              unsigned m_refcount;
              const Destructor& m_destructor;
       };
       TheObject *m_object;
};

template <typename T>
const typename TSingleReferencedObject<T>::EmptyDestructor TSingleReferencedObject<T>::empty_destructor;


template <typename T>
TSingleReferencedObject<T>::TSingleReferencedObject():
       m_object(nullptr)
{
}

template <typename T>
TSingleReferencedObject<T>::TSingleReferencedObject(T data, const Destructor& d)
{
       m_object = new TheObject(data, d);
}

template <typename T>
TSingleReferencedObject<T>::TSingleReferencedObject(const TSingleReferencedObject<T>& other)
{
       m_object = other.m_object;

       if (m_object)
              m_object->add_ref();
}

template <typename T>
TSingleReferencedObject<T>& TSingleReferencedObject<T>::operator = (const TSingleReferencedObject<T>& other)
{
       if (m_object)
              m_object->del_ref();

       m_object = other.m_object;

       if (m_object)
              m_object->add_ref();

       return *this;
}

template <typename T>
TSingleReferencedObject<T>::TSingleReferencedObject(TSingleReferencedObject<T>&& other):
       m_object(other.m_object)
{
       other.m_object = nullptr;
}

template <typename T>
TSingleReferencedObject<T>& TSingleReferencedObject<T>::operator = (TSingleReferencedObject<T>&& other)
{
       if (&other != this) {
              if (m_object)
                     m_object->del_ref();

              m_object = other.m_object;
              other.m_object = nullptr;
       }

       return *this;
}


template <typename T>
TSingleReferencedObject<T>::~TSingleReferencedObject()
{
       if (m_object)
              if (m_object->del_ref())
                     delete m_object;
}

template <typename T>
TSingleReferencedObject<T>::operator T() const
{
       return m_object->get();
}

template <typename T>
unsigned TSingleReferencedObject<T>::get_refcount()const
{
       if (m_object)
              return m_object->get_refcount();
       else
              return 0;
}

template <typename T>
TSingleReferencedObject<T>::TheObject::TheObject(T data, const Destructor& d):
       m_data(data),
       m_refcount(1),
       m_destructor(d)
{
}

template <typename T>
TSingleReferencedObject<T>::TheObject::~TheObject()
{
       assert(m_refcount == 0);
       m_destructor(m_data);
}

template <typename T>
void TSingleReferencedObject<T>::TheObject::add_ref()
{
       ++m_refcount;
}

template <typename T>
bool TSingleReferencedObject<T>::TheObject::del_ref()
{
       --m_refcount;
       return (m_refcount <= 0);
}

template <typename T>
unsigned TSingleReferencedObject<T>::TheObject::get_refcount()const
{
       return m_refcount;
}

template <typename T>
T TSingleReferencedObject<T>::TheObject::get() const
{
       return m_data;
}

NS_MIA_END
#endif
