// license:BSD-3-Clause
// copyright-holders:Couriersud

#ifndef POPTIONS_H_
#define POPTIONS_H_

///
/// \file poptions.h
///

#include "pfmtlog.h"
#include "pstonum.h"
#include "pstring.h"
#include "putil.h"

namespace plib {

	/// \brief options base class
	///
	class options;

	class option_base
	{
	public:
		option_base(options &parent, const pstring &help);
		virtual ~option_base() = default;

		PCOPYASSIGNMOVE(option_base, delete)

		virtual pstring help() const { return m_help; }
	private:
		pstring m_help;
	};

	class option_group : public option_base
	{
	public:
		option_group(options &parent, const pstring &group, const pstring &help)
		: option_base(parent, help), m_group(group) { }

		pstring group() const { return m_group; }
	private:
		pstring m_group;
	};

	class option_example : public option_base
	{
	public:
		option_example(options &parent, const pstring &group, const pstring &help)
		: option_base(parent, help), m_example(group) { }

		pstring example() const { return m_example; }
	private:
		pstring m_example;
	};


	class option : public option_base
	{
	public:
		option(options &parent, const pstring &ashort, const pstring &along, const pstring &help, bool has_argument);

		// no_argument options will be called with "" argument

		pstring short_opt() const { return m_short; }
		pstring long_opt() const { return m_long; }
		bool has_argument() const { return m_has_argument ; }
		bool was_specified() const { return m_specified; }

		int do_parse(const pstring &argument)
		{
			m_specified = true;
			return parse(argument);
		}

	protected:
		virtual int parse(const pstring &argument) = 0;

	private:
		pstring m_short;
		pstring m_long;
		bool m_has_argument;
		bool m_specified;
	};

	class option_str : public option
	{
	public:
		option_str(options &parent, const pstring &ashort, const pstring &along, const pstring &defval, const pstring &help)
		: option(parent, ashort, along, help, true), m_val(defval)
		{}

		pstring operator ()() const { return m_val; }

	protected:
		int parse(const pstring &argument) override;

	private:
		pstring m_val;
	};

	class option_str_limit_base : public option
	{
	public:
		option_str_limit_base(options &parent, const pstring &ashort, const pstring &along, std::vector<pstring> &&limit, const pstring &help)
		: option(parent, ashort, along, help, true)
		, m_limit(limit)
		{
		}
		const std::vector<pstring> &limit() const { return m_limit; }

	protected:

	private:
		std::vector<pstring> m_limit;
	};


	template <typename T>
	class option_str_limit : public option_str_limit_base
	{
	public:
		option_str_limit(options &parent, const pstring &ashort, const pstring &along, const T &defval, std::vector<pstring> &&limit, const pstring &help)
		: option_str_limit_base(parent, ashort, along, std::move(limit), help), m_val(defval)
		{
		}

		T operator ()() const { return m_val; }

		pstring as_string() const { return limit()[m_val]; }

	protected:
		int parse(const pstring &argument) override
		{
			auto raw = plib::container::indexof(limit(), argument);

			if (raw != plib::container::npos)
			{
				m_val = narrow_cast<T>(raw);
				return 0;
			}

			return 1;
		}

	private:
		T m_val;
	};

	class option_bool : public option
	{
	public:
		option_bool(options &parent, const pstring &ashort, const pstring &along, const pstring &help)
		: option(parent, ashort, along, help, false), m_val(false)
		{}

		bool operator ()() const { return m_val; }

	protected:
		int parse(const pstring &argument) override;

	private:
		bool m_val;
	};

	template <typename T>
	class option_num : public option
	{
	public:
		option_num(options &parent, const pstring &ashort, const pstring &along, T defval,
				const pstring &help,
				T minval = std::numeric_limits<T>::lowest(),
				T maxval = std::numeric_limits<T>::max() )
		: option(parent, ashort, along, help, true)
		, m_val(defval)
		, m_min(minval)
		, m_max(maxval)
		, m_def(defval)
		{}

		T operator ()() const { return m_val; }

		pstring help() const override
		{
			auto hs(option::help());
			return plib::pfmt(hs)(m_def, m_min, m_max);
		}

	protected:
		int parse(const pstring &argument) override
		{
			bool err(false);
			m_val = pstonum_ne<T>(argument, err);
			return (err ? 1 : (m_val < m_min || m_val > m_max));
		}

	private:
		T m_val;
		T m_min;
		T m_max;
		T m_def;
	};

	class option_vec : public option
	{
	public:
		option_vec(options &parent, const pstring &ashort, const pstring &along, const pstring &help)
		: option(parent, ashort, along, help, true)
		{}

		const std::vector<pstring> &operator ()() const { return m_val; }

	protected:
		int parse(const pstring &argument) override;

	private:
		std::vector<pstring> m_val;
	};

	class option_args : public option_vec
	{
	public:
		option_args(options &parent, const pstring &help)
		: option_vec(parent, "", "", help)
		{}
	};

	class options
	{
	public:

		options();
		explicit options(option **o);

		~options() = default;

		PCOPYASSIGNMOVE(options, delete)

		void register_option(option_base *opt);
		std::size_t parse(const std::vector<putf8string> &argv);

		pstring help(const pstring &description, const pstring &usage,
				unsigned width = 72, unsigned indent = 20) const;

		pstring app() const { return m_app; }

	private:
		static pstring split_paragraphs(const pstring &text, unsigned width, unsigned indent,
				unsigned firstline_indent, const pstring &line_end = "\n");

		void check_consistency() noexcept(false);

		template <typename T>
		T *getopt_type() const
		{
			for (const auto & optbase : m_opts )
			{
				if (auto opt = dynamic_cast<T *>(optbase))
					return opt;
			}
		return nullptr;
	}

		option *getopt_short(const pstring &arg) const;
		option *getopt_long(const pstring &arg) const;

		std::vector<option_base *> m_opts;
		pstring m_app;
		option_args * m_other_args;
};

} // namespace plib

#endif // POPTIONS_H_
