/* +------------------------------------------------------------------------+
   |                     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 <mrpt/serialization/CSerializable.h>

#include <cstdint>
#include <memory>
#include <stdexcept>
#include <string>
#include <string_view>
#include <vector>

namespace mrpt::serialization
{
/** Pure virtual class carrying the implementation
 * of CSchemeArchiveBase as per the PIMPL idiom.
 * \ingroup mrpt_serialization_grp
 */
class CSchemeArchiveBase_impl
{
   public:
	virtual ~CSchemeArchiveBase_impl() = default;
	virtual CSchemeArchiveBase& operator=(const int32_t) = 0;
	virtual CSchemeArchiveBase& operator=(const uint32_t) = 0;
	virtual CSchemeArchiveBase& operator=(const int64_t) = 0;
	virtual CSchemeArchiveBase& operator=(const uint64_t) = 0;
	virtual CSchemeArchiveBase& operator=(const float) = 0;
	virtual CSchemeArchiveBase& operator=(const double) = 0;
	virtual CSchemeArchiveBase& operator=(const std::string) = 0;
	virtual CSchemeArchiveBase& operator=(bool) = 0;

	// Type conversion methods
	virtual explicit operator int32_t() const = 0;
	virtual explicit operator uint32_t() const = 0;
	virtual explicit operator int64_t() const = 0;
	virtual explicit operator uint64_t() const = 0;
	virtual explicit operator float() const = 0;
	virtual explicit operator double() const = 0;
	virtual explicit operator bool() const = 0;
	virtual explicit operator std::string() const = 0;
	/** Reads object from the archive */
	virtual void readTo(CSerializable& obj) = 0;
	/** Writes object to archive, with synxtax `out["name"] = obj;`*/
	virtual CSchemeArchiveBase& operator=(
		const mrpt::serialization::CSerializable&) = 0;

	/** Writes the scheme to a plain-text output */
	virtual std::ostream& writeToStream(std::ostream& out) const = 0;
	/** Reads the scheme from a plain-text input */
	virtual std::istream& readFromStream(std::istream& in) = 0;

	// List accessor
	virtual CSchemeArchiveBase operator[](size_t) = 0;
	// Dict accessor
	virtual CSchemeArchiveBase operator[](std::string) = 0;

   public:	// should make it private by virtue of friend class
	void setParent(CSchemeArchiveBase* parent) { m_parent = parent; }

   protected:
	CSchemeArchiveBase* m_parent;
	void ReadObject(CSchemeArchiveBase& out, const CSerializable& obj);
	void WriteObject(CSchemeArchiveBase& in, CSerializable& obj);
};
/** Virtual base class for "schematic archives" (JSON, XML,...)
 * \ingroup mrpt_serialization_grp
 */
class CSchemeArchiveBase
{
   public:
	friend class CSchemeArchiveBase_impl;
	using Ptr = std::shared_ptr<CSchemeArchiveBase>;
	/** @name Serialization API for schema based "archives"
	 * @{ */
	// Constructor
	CSchemeArchiveBase(std::unique_ptr<CSchemeArchiveBase_impl> ptr)
		: pimpl(std::move(ptr))
	{
		pimpl->setParent(this);
	}
	// Note: Due to the definition of this one, the compiler should not
	// automatically define copy ctor, so we define it next.
	// It's better to have the dtor defined here since we'll have derived
	// classes.
	virtual ~CSchemeArchiveBase() = default;
	CSchemeArchiveBase(CSchemeArchiveBase&& o) : pimpl(std::move(o.pimpl)) {}

	CSchemeArchiveBase& operator=(const int32_t val)
	{
		return (*pimpl).operator=(val);
	}
	CSchemeArchiveBase& operator=(const uint32_t val)
	{
		return (*pimpl).operator=(val);
	}
	CSchemeArchiveBase& operator=(const int64_t val)
	{
		return (*pimpl).operator=(val);
	}
	CSchemeArchiveBase& operator=(const uint64_t val)
	{
		return (*pimpl).operator=(val);
	}
	CSchemeArchiveBase& operator=(const float val)
	{
		return (*pimpl).operator=(val);
	}
	CSchemeArchiveBase& operator=(const double val)
	{
		return (*pimpl).operator=(val);
	}
	CSchemeArchiveBase& operator=(const std::nullptr_t val)
	{
		return (*pimpl).operator=(val);
	}
	CSchemeArchiveBase& operator=(const std::string val)
	{
		return (*pimpl).operator=(val);
	}
	CSchemeArchiveBase& operator=(bool val) { return (*pimpl).operator=(val); }
	// Type conversion methods
	explicit operator int32_t() const { return static_cast<int32_t>(*pimpl); }
	explicit operator uint32_t() const { return static_cast<uint32_t>(*pimpl); }
	explicit operator int64_t() const { return static_cast<int64_t>(*pimpl); }
	explicit operator uint64_t() const { return static_cast<uint64_t>(*pimpl); }
	explicit operator float() const { return static_cast<float>(*pimpl); }
	explicit operator double() const { return static_cast<double>(*pimpl); }
	explicit operator bool() const { return static_cast<bool>(*pimpl); }
	explicit operator std::string() const
	{
		return static_cast<std::string>(*pimpl);
	}
	void readTo(CSerializable& obj) { pimpl->readTo(obj); }
	CSchemeArchiveBase& operator=(const mrpt::serialization::CSerializable& obj)
	{
		return (*pimpl).operator=(obj);
	}
	// List accessor
	CSchemeArchiveBase operator[](size_t val)
	{
		return (*pimpl).operator[](val);
	}
	// Dict accessor
	CSchemeArchiveBase operator[](std::string val)
	{
		return (*pimpl).operator[](val);
	}

	// class CSchemeArchiveBase_impl;
   protected:
	// Read Object
	static void ReadObject(CSchemeArchiveBase& out, const CSerializable& obj)
	{
		obj.serializeTo(out);
	}
	// Write Object
	static void WriteObject(CSchemeArchiveBase& in, CSerializable& obj)
	{
		obj.serializeFrom(in);
	}

   private:
	std::unique_ptr<CSchemeArchiveBase_impl> pimpl;
	friend std::ostream& operator<<(
		std::ostream& out, const CSchemeArchiveBase& a);
	friend std::istream& operator>>(std::istream& in, CSchemeArchiveBase& a);
};

inline std::ostream& operator<<(std::ostream& out, const CSchemeArchiveBase& a)
{
	a.pimpl->writeToStream(out);
	return out;
}
inline std::istream& operator>>(std::istream& in, CSchemeArchiveBase& a)
{
	a.pimpl->readFromStream(in);
	return in;
}

}  // namespace mrpt::serialization
