/* -*- mia-c++  -*-
 *
 * This file is part of MIA - a toolbox for medical image analysis 
 * Copyright (c) Leipzig, Madrid 1999-2017 Gert Wollny
 *
 * MIA is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with MIA; if not, see <http://www.gnu.org/licenses/>.
 *
 */

#include <mia/core/msgstream.hh>
#include <mia/core/cmdoption.hh>
#include <mia/core/tools.hh>
#include <mia/core/xmlinterface.hh>

#include <sstream>
#include <stdexcept>
#include <cassert>

using std::ostream; 
using std::string; 
using std::ostringstream; 
using std::invalid_argument;

NS_MIA_BEGIN

CCmdOption::CCmdOption(char short_opt, const char *long_opt, 
		       const char *long_help, const char *short_help, CCmdOptionFlags flags):
	m_short_opt(short_opt), 
	m_long_opt(long_opt),
	m_long_help(long_help), 
	m_short_help(short_help),
	m_flags(flags)
{
	TRACE_FUNCTION; 
	cvdebug() << "Create option '" << long_opt << "'\n"; 
        assert(long_opt);
        assert(long_help);

}


CCmdOption::~CCmdOption()
{
}

size_t CCmdOption::get_needed_args() const
{
	return do_get_needed_args();
}


void CCmdOption::add_option(CShortoptionMap& sm, CLongoptionMap& lm)
{
	do_add_option(sm, lm);
}

void CCmdOption::print_short_help(std::ostream& os) const
{
	do_print_short_help(os);
}

void CCmdOption::get_long_help(std::ostream& os) const
{
	os << long_help();
	do_get_long_help(os);
}

void CCmdOption::do_get_long_help(std::ostream& /*os*/) const
{
}

string CCmdOption::get_long_help_xml(CXMLElement& parent, HandlerHelpMap& handler_map) const
{
	cvdebug() << "write XML for '" << m_long_opt << "'\n"; 
	ostringstream shelp; 
	shelp << long_help();
	do_get_long_help_xml(shelp, parent, handler_map); 
	return shelp.str(); 
}

void CCmdOption::do_get_long_help_xml(std::ostream& os, CXMLElement& /*parent*/, HandlerHelpMap& /*handler_map*/) const
{
	do_get_long_help(os);
}

const char *CCmdOption::long_help() const
{
	return m_long_help;
}

char CCmdOption::get_short_option() const
{
	return m_short_opt; 
}

const char *CCmdOption::get_long_option() const
{
	return m_long_opt;
}

void CCmdOption::clear_required()
{
	m_flags -= CCmdOptionFlags::required; 
}

bool CCmdOption::is_required() const
{
	return (m_flags & CCmdOptionFlags::required) == CCmdOptionFlags::required; 
}

void CCmdOption::get_opt_help(std::ostream& os) const
{
	do_get_opt_help(os);
}

void   CCmdOption::write_value(std::ostream& os) const
{
	do_write_value(os);
}

const std::string CCmdOption::get_value_as_string() const
{
	return do_get_value_as_string();
}

const std::string CCmdOption::do_get_value_as_string() const
{
	return "";
}

size_t CCmdOption::do_get_needed_args() const
{
	return 1; 
}

#if 0 
void CCmdOption::do_get_long_help(std::ostream& /*os*/) const
{
}
#endif 

void CCmdOption::do_print_short_help(std::ostream& os) const
{
	if ( m_short_opt ) {
		os << '-' << m_short_opt; 
		if (  m_short_help )
			os << " " << m_short_help;
		os  << " ";
	}
}

void CCmdOption::do_get_opt_help(std::ostream& os) const
{
	if ( get_short_option() )
		os  << "  -" << get_short_option() << " ";
	else
		os  << "     ";
	os << "--" << get_long_option();
	write_value(os);
	os << " ";
}

void CCmdOption::xmlhelp_set_attribute(CXMLElement& node, const char *name, const std::string& value) const
{
	node.set_attribute(name, value); 
}

void CCmdOption::set_value(const char *str_value)
{
	bool result;

	try {
		result = do_set_value(str_value);
	}

	catch (std::invalid_argument& x) {
		ostringstream msg;
		msg << "Error parsing --" << get_long_option() << ":" << x.what();
		throw std::invalid_argument(msg.str());
	}

	if (!result) {
		throw invalid_argument(string("Error parsing option '") +
				       string(get_long_option()) +
				       string("' from input '") +
				       string(str_value) + string("'"));
	}
	clear_required();
}

void CCmdOption::do_add_option(CShortoptionMap& sm, CLongoptionMap& lm)
{
	TRACE_FUNCTION;
	if (get_short_option() != 0) {
		auto so = sm.find(get_short_option()); 
		if ( so != sm.end()) {
			cverr() << "Trying to add the short option '" << get_short_option() << "' twice\n"; 
			if (get_long_option()) {
				if (so->second) 
					cverr() << "corresponding long options are '" << get_long_option() 
						<< "' and '"  << so->second->get_long_option() << "'\n";
			}
			assert(0 && "don't add the same short option twice" );
		}
		sm[get_short_option()] = this;
	}

	if (get_long_option() != 0) {
		if (lm.find(get_long_option()) != lm.end()) {
			cverr() << "Trying to add the long option '" << get_long_option() << "' twice\n"; 
			assert(0 && "don't add the same long option twice");
		}
		lm[get_long_option()] = this;
	}
}

void CCmdOption::add_option_xml(CXMLElement& parent, HandlerHelpMap& handler_map) const
{
	TRACE_FUNCTION;
	auto option = parent.add_child("option"); 
	option->set_attribute("short", to_string<char>(get_short_option()));
	option->set_attribute("long", get_long_option());
	option->set_attribute("default", get_value_as_string()); 
	
	auto flagstring = get_flag_string(); 
	if (!flagstring.empty()) {
		auto flags = option->add_child("flags"); 
		flags->set_child_text(flagstring);
	}
	option->set_child_text(get_long_help_xml(*option, handler_map));
}

string CCmdOption::get_flag_string()const 
{
	ostringstream ss; 
        if (mia::has_flag(m_flags, CCmdOptionFlags::input))
		ss << "input "; 
        if (mia::has_flag(m_flags, CCmdOptionFlags::output))
		ss << "output "; 
        if (mia::has_flag(m_flags, CCmdOptionFlags::required))
		ss << "required ";
        if (mia::has_flag(m_flags, CCmdOptionFlags::nonipype))
		ss << "nonipype ";

	return ss.str(); 
}

void CCmdOption::post_set()
{
	do_post_set(); 
}

void CCmdOption::do_post_set()
{
}

bool CCmdOption::has_flag(CCmdOptionFlags test_flags) const
{
        return mia::has_flag(m_flags, test_flags);
}


NS_MIA_END
