/*
 * Author:
 *         Kendy Kutzner <kutzner@ira.uka.de>
 *
 * (c) 2005 Research Group Dr. Fuhrmann, University of Karlsruhe, Germany
 * All rights reserved.
 *
 * $Id: main.cc,v 1.23 2006/01/10 17:07:41 kutzner Exp $
 *
 */

#include <map>
#include <string>
#include <fstream>
using namespace std;

#include <getopt.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

#include "node.h"
#include "callbacklist.h"
#define _IN_MAIN_
#include "debug.h"
#undef _IN_MAIN_
#include "utils.h"

bool global_continue = true;

typedef map<string,string> configvals_t;
typedef std::pair<string,string> config_t;

/**
 * read a line from a stream
 * implementation slow and stupid
 */

string
getlinefromfile(ifstream &i){
	char c;
	string s;
	c = i.get();
	while (c!='\n' && i.good()){
		s += c;
		c = i.get();
	}
	return s;
}

/**
 * function to be called when interrupt signal is caught
 */

void
siginthandler(int i){
	dbg.print(dbg.NODE, 3, "SIG %i caught, shutting down\n", i);
	signal(i, SIG_DFL);
	global_continue = false;
}

/**
 * read the config file, line by line
 * returns an array of every well formed lines
 */

int
read_configfile(const string & filename, configvals_t & vals){
	ifstream cf;
	string l;
	string arg;
	string val;
	int linecount = 0;
	unsigned int colpos = 0;
	cf.open(filename.c_str()); // Argl, why do STL functions don't accept strings?
	while (cf.good()){
		// cf >> l; // read a line
		l = getlinefromfile(cf);
		linecount++;
		if (l.size()<4) { // val, colon, space, arg
			dbg.print(dbg.CFG, 6, "ignoring short line %i\n", linecount);
			continue;
		}
		if (l.find('#')==0) { //comment
			dbg.print(dbg.CFG, 6, "ignoring comment on line %i\n", linecount);
			continue;
		}
		colpos = l.find(':');
		if (l.size()<colpos+2){ // space, arg
			dbg.print(dbg.CFG, 6, "ignoring short line %i\n", linecount);
			continue;
		}
		arg = l.substr(0, colpos);
		val = l.substr(colpos+2, l.size());
		vals.erase(val);
		vals.insert(config_t(arg,val));
		dbg.print(dbg.CFG, 6, "good cfg line %i arg '%s' val '%s'\n", linecount, arg.c_str(), val.c_str());
	}
	return 0;
}

void
usage(char * name){
	printf("%s usage:\n", name);
	printf(
" -h --help       show usage\n"
" -b --bootstrap  transport address of bootstrap node\n"
" -i --id         id of the local node\n"
" -p --netport    port where to listen for other nodes\n"
" -u --clientport port where to listen for clients\n"
" -c --config     config file\n"
" -d --debug      debug values\n"
	      );
	return;
}

/**
 * The main function.
 *  - interpret command line arguments
 *  - initialize stuff
 *  - run main loop
 */

int main(int argc, char ** argv){
	int c;
	configvals_t configvals;
	string configfile;

	signal(SIGINT, siginthandler);
	signal(SIGTERM, siginthandler);

	static struct option options[] = {
		{ "bootstrap", required_argument, NULL, 'b' },
		{ "id", required_argument, NULL, 'i' },
		{ "netport", required_argument, NULL, 'p' },
		{ "clientport", required_argument, NULL, 'u' },
		{ "config", required_argument, NULL, 'c' },
		{ "debug", required_argument, NULL, 'd' },
		{ "help", no_argument, NULL, 'h' },
		{ NULL, 0, NULL, 0}
	};
	
	// read the default config file	
	configfile.clear();
	configfile += argv[0];
       	configfile += ".conf";
	read_configfile("/etc/igor.conf",                       configvals);
	read_configfile(string("/etc/") + configfile,           configvals);
	read_configfile("/usr/local/etc/igor.conf",             configvals);
	read_configfile(string("/usr/local/etc/") + configfile, configvals);
	read_configfile("igor.conf",                            configvals);
	read_configfile(string("./") + configfile,              configvals);
	read_configfile(configfile,                             configvals);
	configfile.clear();
	
	// read command line parameters
	while ((c = getopt_long(argc, argv, "b:i:p:u:c:d:h", options, NULL)) > 0){
		switch(c){
			case 'b':
				configvals.insert(config_t("bootstrap", optarg));
				break;
			case 'i':
				configvals.insert(config_t("id", optarg));
				break;
			case 'p':
				configvals.insert(config_t("netport", optarg));
				break;
			case 'u':
				configvals.insert(config_t("clientport", optarg));
				break;
			case 'd':
				// eval imediately
				dbg.conf(optarg); 
				break;
			case 'c':
				configfile = optarg;
				break;
			case 'h':
				usage(argv[0]);
				return 0;
			default:
				// dbg.print("option error\n");
				// getopt_long will print an error message
				return -1;
			}
	}

	// if there was another config file specified on the command line, slurp that	
	if (configfile.size()>0){
		read_configfile(configfile, configvals);
	}

	// open the log file
	FILE* debugfd = fopen ( "igor.log", "a");
	if ( NULL == debugfd ){
		dbg.print("Couldn't open log file. Using stderr.\n");
	} else {
		dbg.setfd(debugfd);
		printf("logfile opened. Forking now.\n");
		pid_t forkres = fork();
		if (-1 == forkres){
			printf("fork failed (%s). Continuing anyway.\n", strerror(errno));
		}
		if (0 == forkres) { // we are the child
			close(0); close(1); close(2); // close standard file descriptors
			open("/dev/null", O_RDWR|O_APPEND); dup(0); dup(0) ; // and connect them to /dev/null
			setsid(); // detach from terminal			
		} else { // we are parent
			printf("fork() successful. Exiting parent.\n");
			return(0);
		}
	}

	callbacklist cbl;
	node node;

	// init_stuff
	dbg.conf(extractfrommap(&configvals, "debug"));
	node.init(&cbl, configvals);
	dbg.print("back to main, starting main loop\n");

	int cblret = 1; // cbl.wait_and_execute return 0 if nothing is left to do

	while (global_continue){
		cblret = cbl.wait_and_execute();
		if (!cblret)
			return 0; // the programm ended
	}

	node.shutdown();
	
	return (0);
}

