/********************************************************
 *  ██████╗  ██████╗████████╗██╗
 * ██╔════╝ ██╔════╝╚══██╔══╝██║
 * ██║  ███╗██║        ██║   ██║
 * ██║   ██║██║        ██║   ██║
 * ╚██████╔╝╚██████╗   ██║   ███████╗
 *  ╚═════╝  ╚═════╝   ╚═╝   ╚══════╝
 * Geophysical Computational Tools & Library (GCTL)
 *
 * Copyright (c) 2023  Yi Zhang (yizhang-geo@zju.edu.cn)
 *
 * GCTL is distributed under a dual licensing scheme. You can redistribute 
 * it and/or modify it under the terms of the GNU Lesser General Public 
 * License as published by the Free Software Foundation, either version 2 
 * of the License, or (at your option) any later version. You should have 
 * received a copy of the GNU Lesser General Public License along with this 
 * program. If not, see <http://www.gnu.org/licenses/>.
 * 
 * If the terms and conditions of the LGPL v.2. would prevent you from using 
 * the GCTL, please consider the option to obtain a commercial license for a 
 * fee. These licenses are offered by the GCTL's original author. As a rule, 
 * licenses are provided "as-is", unlimited in time for a one time fee. Please 
 * send corresponding requests to: yizhang-geo@zju.edu.cn. Please do not forget 
 * to include some description of your company and the realm of its activities. 
 * Also add information on how to contact you by electronic and paper mail.
 ******************************************************/

#ifndef _GCTL_GETOPTION_H
#define _GCTL_GETOPTION_H

#include "../core/array.h"
#include "../io/file_io.h"

namespace gctl
{
	/**
	 * @brief      Gets the key and value string from an input string.
	 * 
	 * Format of the input string: <key><separator><value>
	 *
	 * @param[in]  in_str     The input string
	 * @param[in]  separator  The separator string
	 * @param      key        The key
	 * @param      value      The value
	 */
	void parse_key_value(std::string in_str, std::string separator, std::string &key, std::string &value);

	struct text_option
	{
		int group;
		bool mandatory;
		std::string name, value;

		text_option();
		void set(std::string n, bool m);
		void set_group(int g);
	};

	/**
	 * @brief      Read options from a file and parse arguments.
	 * 
	 */
	class getoption
	{
	public:
		/**
		 * @brief Construct a new getoption object.
		 * 
		 */
		getoption(){}

		/**
		 * @brief      Destroys the object.
		 */
		virtual ~getoption();

		/**
		 * @brief      clear class objects
		 */
		void clear();

		/**
		 * @brief Initialize an option.
		 * 
		 * @param opt_name Option name.
		 * @param manda Wether the option is mandatory or not.
		 * @param gp Group of the option.
		 */
		void add_option(std::string opt_name, bool manda = false, int gp = -1);

		/**
		 * @brief Initialize the option list
		 * 
		 * @param opt_name Option names
		 * @param opt_manda Wether the options are mandatory or not.
		 */
		void add_options(std::initializer_list<std::string> opt_name, std::initializer_list<bool> opt_manda);

		/**
		 * @brief Set the options' group
		 * 
		 * @param gp group
		 * @param opt_name Option names
		 */
		void set_group(int gp, std::initializer_list<std::string> opt_name);

		/**
		 * @brief      reading options and arguments from a file.
		 * 
		 * @warning    Any blank characters before and after an option or an argument
		 * will be removed before it is saved. If there is more than one argument that 
		 * is recorded for an option, the arguments will be saved as a single string 
		 * separated by the connecting symbol. The default is comma.
		 * 
		 * File format:
		 * 1. Any line starts with the annotate symbol (default is #) will be skipped.
		 * 2. Empty lines will be skipped.
		 * 3. option-name <separator> option-value
		 * 
		 * @param[in]  filename    File name of the options.
		 * @param[in]  separator   The symbol that separates option and argument
		 * @param[in]  connector   The symbol that connect multiple found arguments
		 * @param[in]  annotate    The symbol which indicates a comment line.
		 *
		 * @return     0 for success and -1 for failure.
		 */
		void read_options(std::string filename, std::string separator = "=", 
			std::string conntector = ",", char annotate = '#');

		/**
		 * @brief      reading options and arguments from a string vector.
		 * 
		 * @warning    Any blank characters before and after an option or an argument
		 * will be removed before it is saved. If there is more than one argument that 
		 * is recorded for an option, the arguments will be saved as a single string 
		 * separated by the connecting symbol. The default is comma.
		 * 
		 * File format:
		 * 1. Any line starts with the annotate symbol (default is #) will be skipped.
		 * 2. Empty lines will be skipped.
		 * 3. option-name <separator> option-value
		 * 
		 * @param[in]  str_vec     Input string vector
		 * @param[in]  separator   The symbol that separates option and argument
		 * @param[in]  connector   The symbol that connect multiple found arguments
		 * @param[in]  annotate    The symbol which indicates a comment line.
		 *
		 * @return     0 for success and -1 for failure.
		 */
		void read_options(const std::vector<std::string> &str_vec, std::string separator = "=", 
			std::string conntector = ",", char annotate = '#');

		/**
		 * @brief      reading options and arguments from a string array.
		 * 
		 * @warning    Any blank characters before and after an option or an argument
		 * will be removed before it is saved. If there is more than one argument that 
		 * is recorded for an option, the arguments will be saved as a single string 
		 * separated by the connecting symbol. The default is comma.
		 * 
		 * File format:
		 * 1. Any line starts with the annotate symbol (default is #) will be skipped.
		 * 2. Empty lines will be skipped.
		 * 3. option-name <separator> option-value
		 * 
		 * @param[in]  str_vec     Input string vector
		 * @param[in]  separator   The symbol that separates option and argument
		 * @param[in]  connector   The symbol that connect multiple found arguments
		 * @param[in]  annotate    The symbol which indicates a comment line.
		 *
		 * @return     0 for success and -1 for failure.
		 */
		void read_options(const gctl::array<std::string> &str_vec, std::string separator = "=", 
			std::string conntector = ",", char annotate = '#');

		/**
		 * @brief Check if all mandatory options are recorded. If an option is part of a group. It will not be checked here.
		 * 
		 */
		void check_mandatory();
		
		/**
		 * @brief Check if at least one option of the group is recorded
		 * 
		 * @param gp 
		 */
		void check_group(int gp);

		/**
		 * @brief Check all groups
		 * 
		 */
		void check_groups();

		/**
		 * @brief      display all recorded options and arguments.
		 */
		void show_options(std::ostream& outstream = std::clog, bool show_all = false);

		/**
		 * @brief      Check if an input key has value
		 *
		 * @param[in]  key         Key value of an option. Multilpe key values must be separated by a delimiter (default is '|'). e.g. 'Model|model|Model-file|model-file' declares four duplicated keys for a single option value. 
		 * @param[in]  delimiter   Delimiter of multiple key values.
		 */
		bool has_value(std::string key, char delimiter = '|');

		/**
		 * @brief      Gets the argument according to an input key
		 * 
		 * @warning    An error string will be thrown out if the argument is not recorded.
		 *
		 * @param[in]  key         Key value of an option. Multilpe key values must be separated by a delimiter (default is '|'). e.g. 'Model|model|Model-file|model-file' declares four duplicated keys for a single option value. 
		 * @param[in]  delimiter   Delimiter of multiple key values.
		 *
		 * @return     The found argument.
		 */
		std::string get_value(std::string key, char delimiter = '|');

		/**
		 * @brief      Gets the argument according to an input key
		 * 
		 * @warning    An error string will be thrown out if the argument is not recorded.
		 *
		 * @param[in]  key         Key value of an option. Multilpe key values must be separated by a delimiter (default is '|'). e.g. 'Model|model|Model-file|model-file' declares four duplicated keys for a single option value. 
		 * @param[in]  delimiter   Delimiter of multiple key values.
		 *
		 * @return     The found argument.
		 */
		template <typename T> 
		T get_value_t(std::string key, char delimiter = '|', char sep = ' ')
		{
			// 分割待匹配的字符串
			std::vector<std::string> tmp_keys;
			parse_string_to_vector(key, delimiter, tmp_keys);

			T out_val;
			for (size_t o = 0; o < options_.size(); o++)
			{
				for (size_t i = 0; i < tmp_keys.size(); i++)
				{
					if (tmp_keys[i] == options_[o].name)
					{
						str2type(options_[o].value, out_val, sep);
						return out_val;
					}
				}
			}

			throw runtime_error("[gctl::getoption::get_value] Option not found for: " + key);
			return out_val;
		}

	protected:
		std::vector<text_option> options_;
		std::vector<int> group_idx_;

	};
};

#endif //_GCTL_GETOPTION_H