/***************************************************************************
 *   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 "transaction.h"
#include "xmlparser.h"
#include "httpclient.h"
#include <boost/lexical_cast.hpp>
#include <boost/regex.hpp>
#include <boost/format.hpp>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <errno.h>
#include <stdlib.h>

using boost::lexical_cast;
using boost::bad_lexical_cast;
using namespace xmlParser;

Transaction::Transaction(vector<string> options) :
		abstractcommandlineparser::CmdLineParser<commandline::Parameters,commandline::parsedParameters>(options),
		m_nextAttributeValue(""),
		m_globalFault(false),
		m_currentCall(0) {
	if(!this->m_parseSuccessful) return;
	
	vector<shared_ptr<commandline::callParameter> >& calls = m_parsed->m_call;

	for( vector<shared_ptr<commandline::callParameter> >::iterator c = calls.begin(); c != calls.end(); c++ ) {
		shared_ptr<commandline::callParameter> call = *c;

		vector<shared_ptr<commandline::orderParameter> >::iterator orderhere = call->m_order.end();
		if( call->m_globalOrderIndex >= 0 ) orderhere = call->m_order.begin()+call->m_globalOrderIndex;

		call->m_order.insert( orderhere, m_parsed->m_globalOrder.begin(), m_parsed->m_globalOrder.end() );
		call->m_where.insert( call->m_where.end(), m_parsed->m_only.begin(), m_parsed->m_only.end() );
		call->m_set.insert( call->m_set.end(), m_parsed->m_setall.begin(), m_parsed->m_setall.end() );
		call->m_unset.insert( call->m_unset.end(), m_parsed->m_unsetall.begin(), m_parsed->m_unsetall.end() );

		if( m_parsed->m_ignoreerrors ) call->m_ignoreerror = true;
	}
};

void Transaction::operator()(shared_ptr<ConfigFileParser> cfgfile) {

	class {
		public:
		void post(string &url) {
			GenericHttpClient * client = createHttpClient(url);
			m_response = client->post(m_request);
		};
	
		void execCall(Transaction *t, string &username, string &url, shared_ptr<ConfigFileParser> cfgfile) {
			m_request = t->getParsed()->toXML(username,cfgfile);
			Logger::log(Logger::XML,"--XML request--\n"+m_request+"\n---");
	
			this->post(url);

			ResponseParser parse;
			if( !parse(m_response, t) ) Logger::log(Logger::XML,"parse error");
			Logger::log(Logger::XML,"response parsed:");

			if(Logger::XML && Logger::levels[Logger::level]) logNode(t->m_docelem,0);
		}

		void logNode(Node* node, int ind) {
			Text *text;
			Element *elm;
			string indent = "";
			for( int j = 0; j < ind; j++ ) indent += "    ";
			string attributes;

			switch(node->m_type) {
				case ELEMENT:
					elm = (Element*)node;
					for(map<string,string>::iterator i = elm->m_attributes.begin(); i != elm->m_attributes.end(); i++)
					attributes += " "+(i->first)+"=\""+(i->second)+"\"";
					Logger::log(Logger::XML,indent+"<"+elm->m_name+attributes+">");
					for( vector<Node*>::iterator i = elm->m_nodes.begin(); i != elm->m_nodes.end(); i++ ) 
						logNode(*i,ind+1);
					Logger::log(Logger::XML,indent+"</"+elm->m_name+">");
					break;
				case TEXT:
					text = (Text*)node;
					Logger::log(Logger::XML,indent+text->m_content);
					break;
			}
		}

		private:
		int connectToHost(const string hostname, const int port) const {
			int sock;

			struct sockaddr_in server_addr;
			server_addr.sin_family = AF_INET;
			if( (server_addr.sin_addr.s_addr = inet_addr(hostname.c_str())) == INADDR_NONE ) {
				struct hostent* hp = gethostbyname(hostname.c_str());
				if( hp == 0 ) {
					string msg = Logger::getMessageFormatString(Logger::CantResolveHostname);
					boost::format fmt(msg);
					fmt % hostname;
					Logger::log(Logger::FATAL,fmt.str());
					exit(1);
				}
				bcopy(hp->h_addr, (char*) &server_addr.sin_addr, hp->h_length);
			}
			server_addr.sin_port = htons(port);
			
			if( (sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0 ) {
				Logger::log(Logger::FATAL,Logger::getMessageFormatString(Logger::CantOpenSocket));
				exit(1);
			}
			if( connect(sock, (struct sockaddr*) &server_addr, sizeof(server_addr)) < 0 ) {
				string msg = Logger::getMessageFormatString(Logger::CantConnetcToHost);
				boost::format fmt(msg);
				fmt % hostname % port;
				Logger::log(Logger::FATAL,fmt.str());
				close(sock);
				exit(1);
			}
			return sock;
		}

		string m_request;
		string m_response;
	} XMLRPCrequest;


	string username = "";
	ConfigFileParser::config cfg = cfgfile->getConfig(username);
	string url = cfg.server;
	username = this->getUser();
	cfg = cfgfile->getConfig(username);
	if( cfg.server != "" ) url = cfg.server;

	Logger::log(Logger::DEBUG,"now I know which Config to use");

	XMLRPCrequest.execCall(this,username,url,cfgfile);
}



// XML Parser stuff
Element* Transaction::stepin(int count,Element* value) {
	if( count<= 0 ) return value;
	for( ; count > 0; count-- )
		if(value->m_nodes[0]->m_type == xmlParser::ELEMENT ) value = (Element*)value->m_nodes[0];
	return value;
}

string Transaction::getContent(Element* value) {
	while( value->m_nodes.size() > 0 && value->m_nodes[0]->m_type == xmlParser::ELEMENT ) value = (Element*)value->m_nodes[0];

	if( value->m_nodes.size() > 0 ) {
		Text* txt = (Text*)value->m_nodes[0];

		if( value->m_name == "boolean") {
			int b = boost::lexical_cast<int>(replaceEntities(txt->m_content));
			return (b == 0)?"false":"true";
		} else if( value->m_name == "dateTime.iso8601") {
			string datestring = replaceEntities(txt->m_content);
			string year  = datestring.substr(0,4);
			string month = datestring.substr(4,2);
			string day   = datestring.substr(6,2);
			string hour  = datestring.substr(9,2);
			string minute= datestring.substr(12,2);
			string second= datestring.substr(15,2);
			return day+"."+month+"."+year+" "+hour+":"+minute+":"+second;
		} else if( value->m_name == "base64") {
			string encoded = replaceEntities(txt->m_content);
			string decoded;
			char next = '\0';
			int shift = 0;
			while( encoded.size() ) {
				int d = encoded[0];
				encoded = encoded.substr(1,encoded.size()-1);
				if      ( d >= 'A' && d <= 'Z') d -= 'A';
				else if ( d >= 'a' && d <= 'z') d = (d-'a')+26;
				else if ( d >= '0' && d <= '9') d = (d-'0')+52;
				else if ( d == '+') d = 62;
				else if ( d == '/') d = 63;
				else continue;

				next += d << shift;
				if( !shift ) { decoded += (char)(next&255); next = next >> 8; }

				shift = (shift-2)%8;
			}
			if( shift ) decoded += next;
			return decoded;
		} else {
			return replaceEntities(txt->m_content);
		}
	}
	return string("");
}

void Transaction::handleElem(Element * elem) {
	if( m_tagstack.size() == 0 ) {
		m_docelem = elem;
		return;
	}

	Element *array, *data;
	int tab = 8;

	switch( m_tagstack.size() ) {
		case 6: // fill next call
			if(elem->m_name != "value" ) break;
			array = (Element*)(elem->m_nodes[0]);

			if( array->m_name == "struct" ) {
				map<string,string> errorstruct;
				if(array->m_nodes.size() != 0) for(vector<Node*>::iterator n = array->m_nodes.begin(); n != array->m_nodes.end(); n++) {
					Element* member = (Element*)(*n);
					if(member->m_nodes.size() >= 2) {
						Element *name, *value;
						name = (Element*)member->m_nodes[0];
						if( name->m_name == "value" ) {
							value = name;
							name = (Element*)member->m_nodes[1];
						} else
							value = (Element*)member->m_nodes[1];

						while( name->m_nodes.size() > 0 && name->m_nodes[0]->m_type == xmlParser::ELEMENT ) name = (Element*)name->m_nodes[0];
						while( value->m_nodes.size() > 0 && value->m_nodes[0]->m_type == xmlParser::ELEMENT ) value = (Element*)value->m_nodes[0];
						if( value->m_nodes.size() > 0 && name->m_nodes.size() > 0 ) {
							string namtxt = getContent(name);
							string valtxt = getContent(value);
							errorstruct[namtxt] = valtxt;
						}
					}
				}
				string msg = Logger::getMessageFormatString(Logger::ServerErrorCode);
				
				int errnum = boost::lexical_cast<int>(errorstruct["errorcode"]);
				string errorstring = Logger::getErrnoMessage(errnum);
				if( errorstring != "" ) errorstruct["errorstring"] = errorstring;

				boost::format fmt(msg);
				fmt % errorstruct["errorcode"] % errorstruct["errorstring"];
				Logger::log(Logger::ERROR,fmt.str());
				
				break;
			}

			data = (Element*)(array->m_nodes[0]);
			
			if(data->m_nodes.size() != 0) {
				vector<string> headlines;
				int tabpos = 0;
				vector<int> tabstops;

				Element* headlinesdata = stepin(3,data);

				if( m_parsed->m_call[m_currentCall]->m_display == "" ) {
					if(headlinesdata->m_nodes.size() != 0) for(vector<Node*>::iterator in = headlinesdata->m_nodes.begin(); in != headlinesdata->m_nodes.end(); in++) {
						string output = "";
						if((*in)->m_type == xmlParser::ELEMENT ) {
							output = getContent((Element*)(*in));
						} else {
							Text* txt = (Text*)(*in);
							output = replaceEntities(txt->m_content);
						}
						std::cout << output;
						tabstops.push_back(tabpos);
						tabpos += output.length();
						if( in+1 != headlinesdata->m_nodes.end() )do {
							std::cout << " ";
							tabpos++;
						} while( tabpos % tab );
					}
					std::cout << "\n";
					for(;tabpos;tabpos--) std::cout << "-";
					std::cout << "\n";
				} else {
					if(headlinesdata->m_nodes.size() != 0) for(vector<Node*>::iterator in = headlinesdata->m_nodes.begin(); in != headlinesdata->m_nodes.end(); in++) {
						string headline = getContent((Element*)(*in));
						if( headline != "" ) headlines.push_back( headline );
					}
				}

				
				Element* contentvalue;
				if(data->m_nodes[1]->m_type == xmlParser::ELEMENT ) contentvalue = (Element*)data->m_nodes[1];
				else                                                contentvalue = data;
				Element* contentdata = stepin(2,contentvalue);

				if(contentdata->m_nodes.size() != 0) for(vector<Node*>::iterator n = contentdata->m_nodes.begin(); n != contentdata->m_nodes.end(); n++) {
					if( m_parsed->m_call[m_currentCall]->m_display == "" ) {
						tabpos = 0;
						int i = 0;

						Element* innerdata = stepin(2,(Element*)(*n));
						if(innerdata->m_nodes.size() != 0) for(vector<Node*>::iterator in = innerdata->m_nodes.begin(); in != innerdata->m_nodes.end(); in++) {
							string oputput = getContent((Element*)(*in));

							while( tabpos < tabstops[i] ) { 
								std::cout << " "; tabpos++; 
							}
							std::cout << oputput;
							tabpos += oputput.length();
							i++;
						}
						std::cout << "\n";
					} else {
						map<string,string> values;
						int i = 0;

						Element* innerdata = stepin(2,(Element*)(*n));
						if(innerdata->m_nodes.size() != 0) for(vector<Node*>::iterator in = innerdata->m_nodes.begin(); in != innerdata->m_nodes.end(); in++) {
							string oputput = getContent((Element*)(*in));

							if( oputput != "" ) {
								values[headlines[i]] = oputput;
								i++;
							}
						}
						std::cout << m_parsed->m_call[m_currentCall]->evalDisplay(values);
					}
				}
			}
			m_currentCall++;
			break;
		default:
			break;
	}

	m_tagstack.top().m_nodes.push_back(elem);
}

void Transaction::handleGlobalFaultElem(xmlParser::Element * elem) {
	if( m_tagstack.size() == 0 ) {
		m_docelem = elem;
		return;
	}

	Text *txt;
	switch( m_tagstack.size() ) {
		case 5:
			if(elem->m_name == "value" )
				Logger::log(Logger::FATAL,getContent(elem));
			break;
		default:
			break;
	}

	m_tagstack.top().m_nodes.push_back(elem);
}

string Transaction::replaceEntities(const string &input) {
	string rval;
	for( string::const_iterator i = input.begin(); i != input.end(); i++ ) {
		if( *i != '&' ) rval += *i;
		else {
			string tmp;
			int j;

			for( j = 1; *(i+j) != ';' && i+j != input.end(); j++ ) tmp += *(i+j);

			if( *(i+j) != ';' ) { rval += "&"; continue; }

			if( tmp == "amp" ) {
				rval += "&";
				i+=j;
			} else if( tmp == "gt" ) {
				rval += ">";
				i+=j;
			} else if( tmp == "lt" ) {
				rval += "<";
				i+=j;
			} else if( tmp == "quot" ) {
				rval += "\"";
				i+=j;
			} else if( tmp == "apos" ) {
				rval += "'";
				i+=j;
			} else if( tmp[0] == '#' ) {
				if( tmp[1] != 'x' ) {
					wchar_t value[2];
					value[0] = lexical_cast<int>(tmp.substr(1));
					value[1] = 0;
					char u[255];
					bzero(u,255);

					wcstombs(u,value,254);
					rval += u;
					i+=j;
				} else {
					wchar_t value[2];
					value[0] = 0;
					value[1] = 0;
					if     ( tmp[2] >= '0' && tmp[2] <= '9' ) value[0] = tmp[2]-'0';
					else if( tmp[2] >= 'a' && tmp[2] <= 'f' ) value[0] = tmp[2]-'a';
					else if( tmp[2] >= 'A' && tmp[2] <= 'F' ) value[0] = tmp[2]-'A';
					else { rval += "&"; continue;}
					value[0] = value[0] << 4;
					if     ( tmp[3] >= '0' && tmp[3] <= '9' ) value[0] += tmp[3]-'0';
					else if( tmp[3] >= 'a' && tmp[3] <= 'f' ) value[0] += tmp[3]-'a';
					else if( tmp[3] >= 'A' && tmp[3] <= 'F' ) value[0] += tmp[3]-'A';
					else { rval += "&"; continue; }
					char u[255];
					bzero(u,255);
					
					wcstombs(u,value,254);
					rval += u;
					i+=j;
				}
			} else {
				rval += "&";
			}
		}
	}
	return rval;
}

bool Transaction::operator()(int type, string content) {
	string tmp;
	Element elem, currentelem;
	Text text;

	switch( type ) {
		case responseParserHook::ELEMENT:
			break;
		case responseParserHook::ATTRIBUTE:
			m_nextAttributeList[content] = m_nextAttributeValue;
			m_nextAttributeValue = "";
			break;
		case responseParserHook::TEXT:
			if( m_tagstack.size() ) {
				m_tagstack.top().m_nodes.push_back(new Text(content));
			} else return false;
			break;
		case responseParserHook::CDATA:
			if( m_tagstack.size() ) {
				m_tagstack.top().m_nodes.push_back(new Text(content));
			} else return false;
			break;
		case responseParserHook::PROCESSING_INSTRUCTION:
			if( m_nextAttributeList["version"] != "1.0" || content != "xml" ) return false;
			tmp = "";
			for(map<string,string>::iterator i = m_nextAttributeList.begin(); i != m_nextAttributeList.end(); i++)
				tmp += " "+(i->first)+"="+(i->second);
			m_nextAttributeList.clear();
			break;
		case responseParserHook::COMMENT:
			break;
		case responseParserHook::DOCUMENT:
			break;
		case responseParserHook::START_ELEMENT:
			if(m_tagstack.size() == 1 && content == "fault") m_globalFault = true;
			elem = Element(content,m_nextAttributeList,vector<Node*>());
			m_nextAttributeList.clear();
			m_tagstack.push(elem);
			break;
		case responseParserHook::END_ELEMENT:
			currentelem = m_tagstack.top();
			m_tagstack.pop();
			if( currentelem.m_name != content ) {
				return false;
			}
			if( !m_globalFault ) handleElem(new Element(currentelem));
			else handleGlobalFaultElem(new Element(currentelem));
			break;
		case responseParserHook::EMPTY_ELEMENT:
			if(m_tagstack.size() == 1 && content == "fault") m_globalFault = true;
			if( !m_globalFault ) handleElem(new Element(content,m_nextAttributeList,vector<Node*>()));
			else handleGlobalFaultElem(new Element(content,m_nextAttributeList,vector<Node*>()));
			m_nextAttributeList.clear();
			break;
		case responseParserHook::ATTRIBUTE_VALUE:
			m_nextAttributeValue = content;
			break;
		default:
			string msg = Logger::getMessageFormatString(Logger::UnknownCallbackInXMLParser);
			boost::format fmt(msg);
			fmt % type % content;
			Logger::log(Logger::FATAL,fmt.str());
			break;
	}
	return true;
}
