#ifndef CVD_INTERNAL_IO_PARAMETER_H
#define CVD_INTERNAL_IO_PARAMETER_H

#include <map>
#include <memory>
#include <string>
#include <typeinfo>

namespace CVD
{

#ifndef DOXYGEN_IGNORE_INTERNAL
namespace Internal
{
	class UntypedParameter;
}
template <class C = void>
class Parameter;

template <>
class Parameter<Internal::UntypedParameter>
{
	virtual const void* get_data_pointer() const = 0;

	public:
	virtual ~Parameter() = default;
	virtual Parameter* duplicate() const = 0;
};
#endif

///Class for holding parameters for image savers, with type erasure. Use like:
///@code
///  map<string, Parameter<> > p;
///  p["jpeg.quality"] = Parameter<int>(75);
///  img_save(image, "foo.jpg", p);
///@endcode
///@ingroup gImageIO
template <class C>
class Parameter : public Parameter<Internal::UntypedParameter>
{
	private:
	C parameter;

	public:
	Parameter(const C& c)
	    : parameter(c)
	{
	}

	Parameter* duplicate() const
	{
		return new Parameter(*this);
	}

	virtual const void* get_data_pointer() const
	{
		return &parameter;
	}
};

template <>
class Parameter<void>
{
	private:
	std::unique_ptr<Parameter<Internal::UntypedParameter>> param;

	public:
	Parameter()
	{
	}

	Parameter(const Parameter& p)
	{
		if(p.param.get())
			std::unique_ptr<Parameter<Internal::UntypedParameter>>(p.param->duplicate());
	}

	template <class C>
	Parameter(const Parameter<C>& p)
	    : param(std::unique_ptr<Parameter<Internal::UntypedParameter>>(static_cast<Parameter<Internal::UntypedParameter>*>(new Parameter<C>(p))))
	{
	}

	template <class C>
	Parameter& operator=(const Parameter<C>& p)
	{
		param = std::unique_ptr<Parameter<Internal::UntypedParameter>>(static_cast<Parameter<Internal::UntypedParameter>*>(new Parameter<C>(p)));
		return *this;
	}

	template <class C>
	bool is() const
	{
		if(param.get() == 0)
			return 0;

		const Parameter<C>* p = dynamic_cast<const Parameter<C>*>(param.get());
		return p != 0;
	}

	template <class C>
	const C& get() const
	{
		if(param.get() == 0)
			throw std::bad_cast();

		const Parameter<C>& p = dynamic_cast<const Parameter<C>&>(*param);
		return *(const C*)p.get_data_pointer();
	}
};

}
#endif
