/********************************************************
 *  ██████╗  ██████╗████████╗██╗
 * ██╔════╝ ██╔════╝╚══██╔══╝██║
 * ██║  ███╗██║        ██║   ██║
 * ██║   ██║██║        ██║   ██║
 * ╚██████╔╝╚██████╗   ██║   ███████╗
 *  ╚═════╝  ╚═════╝   ╚═╝   ╚══════╝
 * 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.
 ******************************************************/

#include "get_option.h"

void gctl::parse_key_value(std::string in_str, std::string separator, std::string &key, 
	std::string &value)
{
	if (in_str.empty())
	{
		throw runtime_error("Empty string. From parse_key_value(...)");
	}

	// 程序寻找参数对中的分隔符并以此为标准初步分割参数名称和值
	int pos = in_str.find(separator);
	if (pos == in_str.npos)
	{
		throw runtime_error("Invalid string: " + in_str + ". From parse_key_value(...)");
	}

	key = in_str.substr(0, pos);
	value = in_str.substr(pos+1, in_str.length());

	// 去除字符串开头与尾部的空白字符 包括空格与制表符
	key.erase(0, key.find_first_not_of(" \t"));
	key.erase(key.find_last_not_of(" \t") + 1);
	value.erase(0, value.find_first_not_of(" \t"));
	value.erase(value.find_last_not_of(" \t") + 1);

	if (value.empty())
	{
		throw runtime_error("No value found for the key: " + key + ". From parse_key_value(...)");
	}

	return;
}

gctl::text_option::text_option()
{
	group = -1;
	mandatory = false;
	name = value = "NullValue";
}

void gctl::text_option::set(std::string n, bool m)
{
	name = n;
	mandatory = m;
	return;
}

void gctl::text_option::set_group(int g)
{
	group = g;
	return;
}

gctl::getoption::~getoption()
{
	clear();
}

void gctl::getoption::clear()
{
	options_.clear();
}

void gctl::getoption::add_option(std::string opt_name, bool manda, int gp)
{
	text_option tmp_p;
	tmp_p.set(opt_name, manda);
	tmp_p.set_group(gp);

	options_.push_back(tmp_p);
	return;
}

void gctl::getoption::add_options(std::initializer_list<std::string> opt_name, std::initializer_list<bool> opt_manda)
{
	if (opt_name.size() != opt_manda.size())
	{
		throw gctl::runtime_error("Unequal initialization lists. From gctl::getoption::init_options(...)");
	}

	text_option tmp_p;

	std::initializer_list<std::string>::iterator s_iter = opt_name.begin();
	std::initializer_list<bool>::iterator b_iter = opt_manda.begin();
	for (size_t i = 0; i < opt_name.size(); i++)
	{
		tmp_p.set(*s_iter, *b_iter);
		s_iter++;
		b_iter++;

		options_.push_back(tmp_p);
	}
	
	return;
}

void gctl::getoption::set_group(int gp, std::initializer_list<std::string> opt_name)
{
	if (gp == -1)
	{
		throw std::runtime_error("group tag can't be -1. From gctl::getoption::set_groups(...)");
	}

	std::initializer_list<std::string>::iterator s_iter;
	for (size_t i = 0; i < options_.size(); i++)
	{
		for (s_iter = opt_name.begin(); s_iter != opt_name.end(); ++s_iter)
		{
			if (*s_iter == options_[i].name)
			{
				options_[i].group = gp;
				break;
			}
		}
	}

	group_idx_.push_back(gp);
	return;
}

void gctl::getoption::read_options(std::string filename, std::string separator, 
	std::string conntector, char annotate)
{
	std::ifstream infile;
	open_matched_infile(infile, filename, ".txt,.log,.opt,.job,.default");

	bool all_blank;
	std::string str;
	while(std::getline(infile, str))
	{
		if (str.empty() || str[0] == annotate) continue;

		all_blank = true;
		for (size_t i = 0; i < str.size(); i++)
		{
			if (str[i] != ' ')
			{
				all_blank = false;
				break;
			}
		}
		if (all_blank) continue;

		std::string key, value;
		parse_key_value(str, separator, key, value);

		for (size_t i = 0; i < options_.size(); i++)
		{
			if (options_[i].name == key)
			{
				if (options_[i].value == "NullValue") options_[i].value = value;
				else options_[i].value += conntector + value;

				break;
			}
		}
	}

	infile.close();
	return;
}

void gctl::getoption::read_options(const std::vector<std::string> &str_vec, 
	std::string separator, std::string conntector, char annotate)
{
	bool all_blank;
	std::string str;
	for (int i = 0; i < str_vec.size(); i++)
	{
		str = str_vec[i];
		if (str.empty() || str[0] == annotate) continue;

		all_blank = true;
		for (size_t i = 0; i < str.size(); i++)
		{
			if (str[i] != ' ')
			{
				all_blank = false;
				break;
			}
		}
		if (all_blank) continue;

		std::string key, value;
		parse_key_value(str, separator, key, value);

		for (size_t i = 0; i < options_.size(); i++)
		{
			if (options_[i].name == key)
			{
				if (options_[i].value == "NullValue") options_[i].value = value;
				else options_[i].value += conntector + value;

				break;
			}
		}
	}

	return;
}

void gctl::getoption::read_options(const gctl::array<std::string> &str_vec, 
	std::string separator, std::string conntector, char annotate)
{
	bool all_blank;
	std::string str;
	for (int i = 0; i < str_vec.size(); i++)
	{
		str = str_vec[i];
		if (str.empty() || str[0] == annotate) continue;

		all_blank = true;
		for (size_t i = 0; i < str.size(); i++)
		{
			if (str[i] != ' ')
			{
				all_blank = false;
				break;
			}
		}
		if (all_blank) continue;

		std::string key, value;
		parse_key_value(str, separator, key, value);

		for (size_t i = 0; i < options_.size(); i++)
		{
			if (options_[i].name == key)
			{
				if (options_[i].value == "NullValue") options_[i].value = value;
				else options_[i].value += conntector + value;

				break;
			}
		}
	}

	return;
}

void gctl::getoption::check_mandatory()
{
	bool err = false;
	std::string err_str;
	for (size_t i = 0; i < options_.size(); i++)
	{
		// Not in a group, is mandatory and is not set
		if (options_[i].group == -1 && options_[i].mandatory && options_[i].value == "NullValue")
		{
			err_str = "Mandatory option not set: " + options_[i].name;
			GCTL_ShowWhatError(err_str, GCTL_ERROR_ERROR, 0, 0, 0);
			err = true;
		}
	}

	if (err)
	{
		throw gctl::runtime_error("Fail to pass the mandatory test. From gctl::getoption::check_mandatory()");
	}
	return;
}

void gctl::getoption::check_group(int gp)
{
	bool err = true;
	for (size_t i = 0; i < options_.size(); i++)
	{
		// In the given group and is set
		if (options_[i].group == gp && options_[i].value != "NullValue")
		{
			err = false;
			break;
		}
	}

	if (err)
	{
		GCTL_ShowWhatError("At least one of the following options must be set:", GCTL_ERROR_ERROR, 0, 0, 0);

		for (size_t i = 0; i < options_.size(); i++)
		{
			if (options_[i].group == gp) std::cerr << options_[i].name << "\n";
		}

		throw gctl::runtime_error("Fail to pass the group test. From gctl::getoption::check_group(...)");
	}
	return;
}

void gctl::getoption::check_groups()
{
	for (size_t i = 0; i < group_idx_.size(); i++)
	{
		check_group(group_idx_[i]);
	}
	return;
}

void gctl::getoption::show_options(std::ostream& outstream, bool show_all)
{
	for (size_t i = 0; i < options_.size(); i++)
	{
		if (options_[i].value != "NullValue")
		{
			outstream << options_[i].name << ":\t" << options_[i].value << std::endl;
		}
		else if (show_all)
		{
			outstream << options_[i].name << ":\t[Not set]" << std::endl;
		}
	}

	outstream << "======================" << std::endl;
	return;
}

bool gctl::getoption::has_value(std::string key, char delimiter)
{
	std::vector<std::string> tmp_keys;

	// 分割待匹配的字符串
	parse_string_to_vector(key, delimiter, tmp_keys);

	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)
			{
				if (options_[o].value != "NullValue")
				{
					return true;
				}
			}
		}
	}

	return false;
}

std::string gctl::getoption::get_value(std::string key, char delimiter)
{
	std::vector<std::string> tmp_keys;

	// 分割待匹配的字符串
	parse_string_to_vector(key, delimiter, tmp_keys);

	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)
			{
				return options_[o].value;	
			}
		}
	}

	// 全部没找到
	throw runtime_error("Option not recored: " + key + ". From getoption::get_value(...)");
	return "null";
}
