/* +------------------------------------------------------------------------+
   |                     Mobile Robot Programming Toolkit (MRPT)            |
   |                          https://www.mrpt.org/                         |
   |                                                                        |
   | Copyright (c) 2005-2022, Individual contributors, see AUTHORS file     |
   | See: https://www.mrpt.org/Authors - All rights reserved.               |
   | Released under BSD License. See: https://www.mrpt.org/License          |
   +------------------------------------------------------------------------+ */
#pragma once

#include <cstdlib>
#include <stdexcept>

namespace mrpt::containers
{
/** \addtogroup mrpt_containers_grp
 * @{ */

/** Wrapper to a std::shared_ptr<>, adding deep-copy semantics to copy ctor and
 * copy operator, suitable for polymorphic classes with a `clone()` method.
 * Example use: `deepcopy_poly_ptr<mrpt::poses::CPosePDF::Ptr>`
 * \sa for non-virtual classes, see copy_ptr<T>
 */
template <typename T>
class deepcopy_poly_ptr
{
   public:
	/** Ctor from a smart pointer; makes deep copy. */
	deepcopy_poly_ptr(const T& ptr)
	{
		m_smartptr.reset(dynamic_cast<typename T::element_type*>(ptr->clone()));
	}
	/** Default ctor; init to nullptr. */
	deepcopy_poly_ptr() = default;
	/** copy ctor: makes a copy of the object via `clone()` */
	deepcopy_poly_ptr(const deepcopy_poly_ptr<T>& o)
	{
		m_smartptr.reset(
			dynamic_cast<typename T::element_type*>(o.m_smartptr->clone()));
	}
	deepcopy_poly_ptr<T>& operator=(const deepcopy_poly_ptr<T>& o)
	{
		if (this == &o) return *this;
		m_smartptr.reset(
			dynamic_cast<typename T::element_type*>(o.m_smartptr->clone()));
		return *this;
	}
	deepcopy_poly_ptr<T>& operator=(const T& o_ptr)
	{
		m_smartptr.reset(
			dynamic_cast<typename T::element_type*>(o_ptr->clone()));
		return *this;
	}
	/** move ctor */
	deepcopy_poly_ptr(deepcopy_poly_ptr&& o)
	{
		m_smartptr = o.m_smartptr;
		o.m_smartptr.reset();
	}
	/** move operator */
	deepcopy_poly_ptr<T>& operator=(deepcopy_poly_ptr<T>&& o)
	{
		if (this == &o) return *this;
		m_smartptr = o.m_smartptr;
		o.m_smartptr.reset();
		return *this;
	}
	~deepcopy_poly_ptr() = default;
	typename T::element_type* get()
	{
		if (m_smartptr) return m_smartptr.get();
		else
			throw std::runtime_error("dereferencing nullptr poly_ptr");
	}
	const typename T::element_type* get() const
	{
		if (m_smartptr) return m_smartptr.get();
		else
			throw std::runtime_error("dereferencing nullptr poly_ptr");
	}

	typename T::element_type* operator->() { return get(); }
	const typename T::element_type* operator->() const { return get(); }
	typename T::element_type& operator*(void) { return *get(); }
	const typename T::element_type& operator*() const { return *get(); }
	operator bool() const { return m_smartptr ? true : false; }
	bool operator!(void) const { return m_smartptr ? false : true; }
	const T& get_ptr() const { return m_smartptr; }
	T& get_ptr() { return m_smartptr; }
	void reset() { m_smartptr.reset(); }

   private:
	T m_smartptr;
};

/** @} */  // end of grouping
}  // namespace mrpt::containers
