/***************************************************************************
 *   Copyright (C) 2005 by Christof Donat   *
 *   cdonat@gmx.de   *
 *                                                                         *
 *   This program 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 2 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 this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#include <boost/regex.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/format.hpp>

#include <iostream>
#include <sstream>
#include <fstream>

#include "cmdlineparser.h"
#include "logger.h"

#include <termios.h>
#include <unistd.h>

namespace commandline {

using std::cout;
using std::cin;
using std::flush;
using std::ifstream;
using std::ostringstream;

using boost::lexical_cast;
using boost::bad_lexical_cast;
using boost::shared_ptr;
using boost::scoped_ptr;
using boost::starts_with;
using boost::erase_first;

// dynamic cast needs this

vector<string> callParameter::parseDisplayspec() {
	vector<string> rval;
	boost::regex pattern("\\$\\{([^\\}]*)\\}");
	boost::sregex_iterator end;

	for(boost::sregex_iterator i(this->m_display.begin(),this->m_display.end(),pattern); i != end; i++ )
		rval.insert(rval.end(),(*i)[1]);

	Logger::log(Logger::DEBUG,"have parsed displayspec Parameter '"+this->m_display+"'");

	return rval;
}
string callParameter::evalDisplay(const map<string, string> &values) {
	string rval = this->m_display;
	string::size_type pos;

	for( map<string,string>::const_iterator i = values.begin(); i != values.end(); i++ ) {
		string pattstring = "${"+i->first+"}";
		while( (pos = rval.find(pattstring,0)) != string::npos ) rval.replace(pos,pattstring.length(),i->second);
	}

	while( (pos = rval.find("\\n",0)) != string::npos ) rval.replace(pos,2,"\n");
	while( (pos = rval.find("\\t",0)) != string::npos ) rval.replace(pos,2,"\t");
	while( (pos = rval.find("\\r",0)) != string::npos ) rval.replace(pos,2,"\r");

	Logger::log(Logger::DEBUG,"have evaluated displayspec Parameter '"+this->m_display+"': '"+rval+"'");

	return rval;
}

string setParameter::toXML(string &username, shared_ptr< ::ConfigFileParser > cfgfile) {
	string rpccall = "<struct>";
	rpccall += "<member><name>property</name><value>"+m_property+"</value></member>";
	rpccall += "<member><name>value</name><value>"+m_value+"</value></member>";
	rpccall += "</struct>";
	return rpccall;
}
string whereParameter::toXML(string &username, shared_ptr< ::ConfigFileParser > cfgfile) {
	string rpccall = "<struct>";
	rpccall += "<member><name>property</name><value>"+m_property+"</value></member>";
	rpccall += "<member><name>value</name><value>"+m_pattern+"</value></member>";
	rpccall += "</struct>";
	return rpccall;
}
string orderParameter::toXML(string &username, shared_ptr< ::ConfigFileParser > cfgfile) {
	string rpccall = "<struct>";
	rpccall += "<member><name>property</name><value>"+m_property+"</value></member>";
	rpccall += "<member><name>ascending</name><value><boolean>";
	rpccall += string((m_ascending?"1":"0"))+"</boolean></value></member>";
	rpccall += "</struct>";
	return rpccall;
}
string callParameter::toXML(string &username, shared_ptr< ::ConfigFileParser > cfgfile) {
	string rpccall = "<struct>";
	rpccall += "<member>";
	rpccall += "<name>module</name>";
	rpccall += "<value>"+m_module+"</value>";
	rpccall += "</member>";
	rpccall += "<member>";
	rpccall += "<name>function</name>";
	rpccall += "<value>"+m_function+"</value>";
	rpccall += "</member>";

	if( m_force ) {
		rpccall += "<member>";
		rpccall += "<name>force</name>";
		rpccall += "<value><boolean>1</boolean></value>";
		rpccall += "</member>";
	}
	if( m_ignoreerror ) {
		rpccall += "<member>";
		rpccall += "<name>ignoreerror</name>";
		rpccall += "<value><boolean>1</boolean></value>";
		rpccall += "</member>";
	}

	// read
	vector<string> reads = parseDisplayspec();

	if(reads.size()) {
		rpccall += "<member>";
		rpccall += "<name>read</name>";
		rpccall += "<value><array><data>";
		for(vector<string>::iterator j = reads.begin(); j != reads.end(); j++ )
			rpccall += "<value>"+(*j)+"</value>";
		rpccall += "</data></array></value>";
		rpccall += "</member>";
	}

	if( m_where.size() ) {
		rpccall += "<member>";
		rpccall += "<name>where</name>";
		rpccall += "<value><array><data>";
		for(vector<shared_ptr<whereParameter> >::iterator j = m_where.begin(); j != m_where.end(); j++ )
			rpccall += "<value>"+((*j)->toXML(username,cfgfile))+"</value>";
		rpccall += "</data></array></value>";
		rpccall += "</member>";
	}

	if( m_set.size() ) {
		rpccall += "<member>";
		rpccall += "<name>set</name>";
		rpccall += "<value><array><data>";
		for(vector<shared_ptr<setParameter> >::iterator j = m_set.begin(); j != m_set.end(); j++ )
			rpccall += "<value>"+((*j)->toXML(username,cfgfile))+"</value>";
		rpccall += "</data></array></value>";
		rpccall += "</member>";
	}

	if( m_order.size() ) {
		rpccall += "<member>";
		rpccall += "<name>order</name>";
		rpccall += "<value><array><data>";
		for(vector<shared_ptr<orderParameter> >::iterator j = m_order.begin(); j != m_order.end(); j++ )
			rpccall += "<value>"+((*j)->toXML(username,cfgfile))+"</value>";
		rpccall += "</data></array></value>";
		rpccall += "</member>";
	}

	if( m_objects.size() ) {
		rpccall += "<member>";
		rpccall += "<name>objectid</name>";
		rpccall += "<value><array><data>";
		for(vector<string>::iterator j = m_objects.begin(); j != m_objects.end(); j++ )
			rpccall += "<value>"+(*j)+"</value>";
		rpccall += "</data></array></value>";
		rpccall += "</member>";
	}

	if( m_unset.size() ) {
		rpccall += "<member>";
		rpccall += "<name>unset</name>";
		rpccall += "<value><array><data>";
		for(vector<string>::iterator j = m_unset.begin(); j != m_unset.end(); j++ )
			rpccall += "<value><struct><member><name>property</name><value>"+(*j)+"</value></member></struct></value>";
		rpccall += "</data></array></value>";
		rpccall += "</member>";
	}
	
	rpccall += "</struct>";

	return rpccall;
}

parsedParameters::parsedParameters(): 
	m_error(NOERROR), 
	m_user(""), 
	m_ticket(""), 
	m_defaultDisplay(""), 
	m_addConfigFile(""), 
	m_test(false), 
	m_ignoreerrors(false) {}

string parsedParameters::toXML(string &username, shared_ptr< ::ConfigFileParser > cfgfile) {
	// begin method call
	string rpccall = "<?xml version=\"1.0\"?><methodCall><methodName>hsadmin.transaction</methodName><params>";
	
	// ticket
	rpccall += "<param><value><string>";
	if( this->m_ticket != "" ) {
		string ticket;
		ifstream file(this->m_ticket.c_str());
		if( ! file ) {
			string msg = Logger::getMessageFormatString(Logger::CouldNotOpenFile);
			boost::format fmt(msg);
			fmt % this->m_ticket;
			Logger::log(Logger::FATAL,fmt.str());
			exit(-1);
		}
		
		char ch;
		while(file.get(ch)) ticket += ch;

		boost::regex findticket(".*\\n---\\n");

		ticket = boost::regex_replace(ticket,findticket,"");
		if( ticket[ticket.size()-1] == '\0' ) ticket = ticket.substr(0,ticket.size()-2);

		rpccall += ticket;
	} else {
		rpccall += cfgfile->getTicket(username);
	}
	Logger::log(Logger::DEBUG,"have read ticket");
	rpccall += "</string></value></param>";

	// global Parameters
	rpccall += "<param><value><struct>";
	if( this->m_test )
		rpccall += "<member><name>test</name><value><boolean>1</boolean></value></member>";
	rpccall += "</struct></value></param>";
		
	// calls Array
	rpccall += "<param><value><array><data>";
	for( vector<shared_ptr<callParameter> >::iterator c = m_call.begin(); c != m_call.end(); c++ )
		rpccall += "<value>"+((*c)->toXML(username,cfgfile))+"</value>";

	rpccall += "</data></array></value></param>";

	rpccall += "</params></methodCall>\n";

	return rpccall;
}


// specific Parser for --runas-Option

bool VerbosityOption::parseThis(vector<string>& options, shared_ptr<parsedParameters> result) {
	bool rval = false;
	if( starts_with(options[0], "-v") ) {
		if( options[1] == "none" || 
		    options[1] == "normal" || 
		    options[1] == "high" || 
		    options[1] == "debug" || 
		    options[1] == "debugXML" || 
		    options[1] == "debugAll" ) {
			if( rval = this->handle(options[1], result) ) {
				if( options[0] == "-v" )
					options.erase(options.begin(),options.begin()+2);
				else {
					erase_first(options[0], "v");
					options.erase(options.begin()+1);
				}
			}
		} else {
			string p;
			if( rval = this->handle(p, result) ) {
				if( options[0] == "-v" )
					options.erase(options.begin());
				else 
					erase_first(options[0], "v");
			}
		}
	}
	if( starts_with(options[0], "--verbosity=") ) {
		string p = options[0].substr(12);
		if( rval = this->handle(p, result) )
			options.erase(options.begin());
	}
	return rval;
}

// constuctors

TestOption::TestOption() {
	m_longName = "test";
	m_shortName = "t";
}

IgnoreErrorOption::IgnoreErrorOption() {
	m_longName = "ignoreerror";
	m_shortName = "e";
}

IgnoreErrorsOption::IgnoreErrorsOption() {
	m_longName = "ignoreerrors";
	m_shortName = "E";
}

VerbosityOption::VerbosityOption() {
	m_longName = "verbosity";
	m_shortName = "v";
	m_parameterSeparator = "=";
}

RunAsOption::RunAsOption() {
	m_longName = "runas";
	m_shortName = "r";
	m_parameterSeparator = "=";
}

TicketOption::TicketOption() {
	m_longName = "ticket";
	m_shortName = "T";
	m_parameterSeparator = "=";
}

ConfigOption::ConfigOption() {
	m_longName = "config";
	m_shortName = "C";
	m_parameterSeparator = "=";
}

ForceOption::ForceOption() {
	m_longName = "force";
}

GlobalsOption::GlobalsOption() {
	m_longName = "globals";
	m_shortName = "l";
}

WhereOption::WhereOption() {
	m_longName = "where";
	m_shortName = "w";
	m_parameterSeparator = ":";
}

OnlyOption::OnlyOption() {
	m_longName = "only";
	m_shortName = "W";
	m_parameterSeparator = ":";
}

SetOption::SetOption() {
	m_longName = "set";
	m_shortName = "s";
	m_parameterSeparator = ":";
}

SetAllOption::SetAllOption() {
	m_longName = "setall";
	m_shortName = "S";
	m_parameterSeparator = ":";
}

InfileOption::InfileOption() {
	m_longName = "infile";
	m_shortName = "f";
	m_parameterSeparator = ":";
}

OrderOption::OrderOption() {
	m_longName = "order";
	m_shortName = "o";
	m_parameterSeparator = ":";
}

GlobalOrderOption::GlobalOrderOption() {
	m_longName = "global-order";
	m_shortName = "O";
	m_parameterSeparator = ":";
}

InputOption::InputOption() {
	m_longName = "input";
	m_shortName = "i";
	m_parameterSeparator = ":";
}

PassInputOption::PassInputOption() {
	m_longName = "passinput";
	m_shortName = "p";
	m_parameterSeparator = ":";
}

DisplayOption::DisplayOption() {
	m_longName = "display";
	m_shortName = "d";
	m_parameterSeparator = ":";
}

DefaultDisplayOption::DefaultDisplayOption() {
	m_longName = "default-display";
	m_shortName = "D";
	m_parameterSeparator = ":";
}

CallOption::CallOption() {
	m_longName = "call";
	m_shortName = "c";
	m_parameterSeparator = ":";
}

UnsetOption::UnsetOption() {
	m_longName = "unset";
	m_shortName = "u";
	m_parameterSeparator = ":";
}

UnsetAllOption::UnsetAllOption() {
	m_longName = "unsetall";
	m_shortName = "U";
	m_parameterSeparator = ":";
}

ObjectID::ObjectID() { };


// handle functions

bool TestOption::handle(shared_ptr<parsedParameters> result) {
	result->m_test = true;
	Logger::log(Logger::DEBUG," found test Option");
	return true;
}

bool IgnoreErrorOption::handle(shared_ptr<parsedParameters> result) {
	if( result->m_call.size() <= 0 ) {
		result->m_error = NeedCall;
		return false;
	}
	result->m_call.back()->m_ignoreerror = true;
	Logger::log(Logger::DEBUG," found ignoreerror Option");
	return true;
}

bool IgnoreErrorsOption::handle(shared_ptr<parsedParameters> result) {
	result->m_ignoreerrors = true;
	Logger::log(Logger::DEBUG," found ignoreerrors Option");
	return true;
}

bool VerbosityOption::handle(string &parameter, shared_ptr<parsedParameters> result) {
	if( parameter == "none" )         Logger::setLevel(0);
	else if( parameter == "normal")   Logger::setLevel(1);
	else if( parameter == "high")     Logger::setLevel(2);
	else if( parameter == "debug")    Logger::setLevel(3);
	else if( parameter == "debugXML") Logger::setLevel(4);
	else if( parameter == "debugAll") Logger::setLevel(5);
	else                              Logger::incrementLevel();

	Logger::log(Logger::DEBUG," set Verbosity Level to "+lexical_cast<string>(Logger::level));

	return true;
}

bool QuietOption::handle(string &parameter, shared_ptr<parsedParameters> result) {
	Logger::decrementLevel();
	Logger::log(Logger::DEBUG," set Verbosity Level to "+lexical_cast<string>(Logger::level));
	return true;
}

bool RunAsOption::handle(string &parameter, shared_ptr<parsedParameters> result) {
	result->m_user = parameter;
	Logger::log(Logger::DEBUG," found runas Option: "+parameter);
	return true;
}

bool TicketOption::handle(string &parameter, shared_ptr<parsedParameters> result) {
	result->m_ticket = parameter;
	Logger::log(Logger::DEBUG," found ticket Option: "+parameter);
	return true;
}

bool ConfigOption::handle(string &parameter, shared_ptr<parsedParameters> result) {
	result->m_addConfigFile = parameter;
	Logger::log(Logger::DEBUG," found config Option: "+parameter);
	return true;
}

bool ForceOption::handle(shared_ptr<parsedParameters> result) {
	if( result->m_call.size() <= 0 ) {
		result->m_error = NeedCall;
		return false;
	}
	result->m_call.back()->m_force = true;
	Logger::log(Logger::DEBUG," found force Option");
	return true;
}

bool GlobalsOption::handle(shared_ptr<parsedParameters> result) {
	if( result->m_call.size() <= 0 ) {
		result->m_error = NeedCall;
		return false;
	}
	result->m_call.back()->m_globalOrderIndex = result->m_call.back()->m_order.size();
	Logger::log(Logger::DEBUG," found globals Option");
	return true;
}

bool WhereOption::handle(string &parameter, shared_ptr<parsedParameters> result) {
	if( result->m_call.size() <= 0 ) {
		result->m_error = NeedCall;
		return false;
	}

	string::size_type pos = parameter.find('=');
	string property(parameter,0,pos);
	string pattern(parameter,(pos != string::npos)?(pos+1):(string::npos));

	whereParameter *where = new whereParameter();
	where->m_property = property;
	where->m_pattern  = pattern;

	result->m_call.back()->m_where.push_back(shared_ptr<whereParameter>(where));

	Logger::log(Logger::DEBUG," found where Option: "+parameter);
	return true;
}

bool OnlyOption::handle(string &parameter, shared_ptr<parsedParameters> result) {
	string::size_type pos = parameter.find('=');
	string property(parameter,0,pos);
	string pattern(parameter,(pos != string::npos)?(pos+1):(string::npos));

	whereParameter *where = new whereParameter();
	where->m_property = property;
	where->m_pattern  = pattern;

	result->m_only.push_back(shared_ptr<whereParameter>(where));

	Logger::log(Logger::DEBUG," found only Option: "+parameter);
	return true;
}

bool UnsetOption::handle(string &parameter, shared_ptr<parsedParameters> result) {
	if( result->m_call.size() <= 0 ) {
		result->m_error = NeedCall;
		return false;
	}

	result->m_call.back()->m_unset.push_back(parameter);
	Logger::log(Logger::DEBUG," found unset Option: "+parameter);
	return true;
}

bool UnsetAllOption::handle(string &parameter, shared_ptr<parsedParameters> result) {
	result->m_unsetall.push_back(parameter);
	Logger::log(Logger::DEBUG," found unsetall Option: "+parameter);
	return true;
}

bool OrderOption::handle(string &parameter, shared_ptr<parsedParameters> result) {
	if( result->m_call.size() <= 0 ) {
		result->m_error = NeedCall;
		return false;
	}

	string::size_type pos = parameter.find('=');
	string *property;
	string * direction;
	if( pos != string::npos ) {
		property = new string(parameter,0,pos);
		direction = new string(parameter,(pos != string::npos)?(pos+1):(string::npos));
	} else {
		property = new string(parameter);
		direction = new string("a");
	}

	orderParameter *order = new orderParameter();
	order->m_property = *property;
	order->m_ascending  = ((*direction)[0] == 'a');

	result->m_call.back()->m_order.push_back(shared_ptr<orderParameter>(order));

	Logger::log(Logger::DEBUG," found order Option: "+parameter);
	return true;
}

bool GlobalOrderOption::handle(string &parameter, shared_ptr<parsedParameters> result) {
	string::size_type pos = parameter.find('=');
	string *property;
	string * direction;
	if( pos != string::npos ) {
		property = new string(parameter,0,pos);
		direction = new string(parameter,(pos != string::npos)?(pos+1):(string::npos));
	} else {
		property = new string(parameter);
		direction = new string("a");
	}

	orderParameter *order = new orderParameter();
	order->m_property = *property;
	order->m_ascending  = ((*direction)[0] == 'a');

	result->m_globalOrder.push_back(shared_ptr<orderParameter>(order));

	Logger::log(Logger::DEBUG," found global order Option: "+parameter);
	return true;
}

bool SetOption::handle(string &parameter, shared_ptr<parsedParameters> result) {
	if( result->m_call.size() <= 0 ) {
		result->m_error = NeedCall;
		return false;
	}

	string::size_type pos = parameter.find('=');
	string property(parameter,0,pos);
	string value(parameter,(pos != string::npos)?(pos+1):(string::npos));

	setParameter *set = new setParameter();
	set->m_property = property;
	set->m_value  = value;

	result->m_call.back()->m_set.push_back(shared_ptr<setParameter>(set));

	Logger::log(Logger::DEBUG," found set Option: "+parameter);
	return true;
}

bool SetAllOption::handle(string &parameter, shared_ptr<parsedParameters> result) {
	string::size_type pos = parameter.find('=');
	string property(parameter,0,pos);
	string value(parameter,(pos != string::npos)?(pos+1):(string::npos));

	setParameter *set = new setParameter();
	set->m_property = property;
	set->m_value  = value;

	result->m_setall.push_back(shared_ptr<setParameter>(set));

	Logger::log(Logger::DEBUG," found setall Option: "+parameter);
	return true;
}

bool InfileOption::handle(string &parameter, shared_ptr<parsedParameters> result) {
	if( result->m_call.size() <= 0 ) {
		result->m_error = NeedCall;
		return false;
	}

	string::size_type pos = parameter.find('=');
	string property(parameter,0,pos);
	string filename(parameter,(pos != string::npos)?(pos+1):(string::npos));

	setParameter *set = new setParameter();
	set->m_property = property;

	ifstream file(filename.c_str());
	if( ! file ) {
		string msg = Logger::getMessageFormatString(Logger::CouldNotOpenFile);
		boost::format fmt(msg);
		fmt % filename;
		Logger::log(Logger::FATAL,fmt.str());
		exit(-1);
	}
	char ch;
	while(file.get(ch)) set->m_value += ch;

	result->m_call.back()->m_set.push_back(shared_ptr<setParameter>(set));

	Logger::log(Logger::DEBUG," found infile Option: "+parameter);
	return true;
}

bool InputOption::handle(string &parameter, shared_ptr<parsedParameters> result) {
	if( result->m_call.size() <= 0 ) {
		result->m_error = NeedCall;
		return false;
	}

	setParameter *set = new setParameter();
	set->m_property = parameter;
	cout << "Eingabe (" << parameter << "): " << flush;
	std::getline(cin, set->m_value);

	result->m_call.back()->m_set.push_back(shared_ptr<setParameter>(set));

	Logger::log(Logger::DEBUG," found input Option: "+parameter);
	return true;
}

string readPasswd() {
	string rval = "";
	struct termios t, t2;

	tcgetattr(0,&t);
	t2 = t;
	t2.c_lflag &= ~ECHO;
	tcsetattr(0,TCSANOW,&t2);
	std::getline(cin, rval);
	tcsetattr(0,TCSANOW,&t);

	return rval;
}

bool PassInputOption::handle(string &parameter, shared_ptr<parsedParameters> result) {
	if( result->m_call.size() <= 0 ) {
		result->m_error = NeedCall;
		return false;
	}

	setParameter *set = new setParameter();
	set->m_property = parameter;
	cout << "Passworteingabe (" << parameter << "): " << flush;
	set->m_value = readPasswd();

	result->m_call.back()->m_set.push_back(shared_ptr<setParameter>(set));

	Logger::log(Logger::DEBUG," found password-input Option: "+parameter);
	return true;
}

bool DisplayOption::handle(string &parameter, shared_ptr<parsedParameters> result) {
	if( result->m_call.size() <= 0 ) {
		result->m_error = NeedCall;
		return false;
	}

	result->m_call.back()->m_display = parameter;

	Logger::log(Logger::DEBUG," found display Option: "+parameter);
	return true;
}

bool DefaultDisplayOption::handle(string &parameter, shared_ptr<parsedParameters> result) {
	result->m_defaultDisplay = parameter;

	Logger::log(Logger::DEBUG," found default display Option: "+parameter);
	return true;
}

bool ObjectID::handle(string &parameter, shared_ptr<parsedParameters> result) {
	if( result->m_call.size() <= 0 ) {
		result->m_error = NeedCall;
		return false;
	}

	result->m_call.back()->m_objects.push_back(parameter);
	Logger::log(Logger::DEBUG," found an ObjectID: "+parameter);
	return true;
}

bool CallOption::handle(string &parameter, shared_ptr<parsedParameters> result) {
	string::size_type pos = parameter.find('.');
	string module(parameter,0,pos);
	string function(parameter,(pos != string::npos)?(pos+1):(string::npos));

	callParameter *call = new callParameter();
	call->m_module   = module;
	call->m_function = function;

	result->m_call.push_back(shared_ptr<callParameter>(call));

	Logger::log(Logger::DEBUG," found call Option: "+parameter);
	return true;
}

};
