/*
 * Copyright (C) 2010  Miroslav Lichvar <mlichvar@redhat.com>
 * 
 * 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, see <http://www.gnu.org/licenses/>.
 */

#include "sysheaders.h"
#include "network.h"

bool load_config(const char *file, Network *network, unsigned int nodes) {
	Generator_generator generator;
	FILE *f;
	const char *ws = " \t\n\r";
	char line[1000], *var, *arg, *end;
	unsigned int node, node2;

	f = fopen(file, "r");
	if (!f)
		return false;

	while (fgets(line, sizeof (line), f)) {
		end = line + strlen(line);
		var = line + strspn(line, ws);
		arg = line + strcspn(line, "=");
		*arg++ = '\0';

		if (var >= end || *var == '#')
			continue;

		if (arg >= end)
			return false;

		while (end > line && (end[-1] == '\r' || end[-1] == '\n' || end[-1] == '\t' || end[-1] == ' '))
			*--end = '\0';

		arg += strspn(arg, ws);

		if (strncmp(var, "node", 4))
			return false;

		var += 4;
		node = atoi(var) - 1;
		if (node >= nodes)
			continue;

		var += strcspn(var, "_") + 1;
		if (var >= end)
			return false;

		if (strncmp(var, "offset", 6) == 0)
			network->get_node(node)->get_clock()->set_time(atof(arg));
		else if (strncmp(var, "start", 5) == 0)
			network->get_node(node)->set_start_time(atof(arg));
		else if (strncmp(var, "freq", 4) == 0) {
			if (arg[0] == '(')
				network->get_node(node)->get_clock()->set_freq_generator(generator.generate(arg));
			else
				network->get_node(node)->get_clock()->set_freq(atof(arg));
		} else if (strncmp(var, "step", 4) == 0)
			network->get_node(node)->get_clock()->set_step_generator(generator.generate(arg));
		else if (strncmp(var, "shift_pll", 9) == 0)
			network->get_node(node)->get_clock()->set_ntp_shift_pll(atoi(arg));
		else if (strncmp(var, "fll_mode2", 9) == 0)
			network->get_node(node)->get_clock()->set_ntp_flag(atoi(arg), CLOCK_NTP_FLL_MODE2);
		else if (strncmp(var, "pll_clamp", 9) == 0)
			network->get_node(node)->get_clock()->set_ntp_flag(atoi(arg), CLOCK_NTP_PLL_CLAMP);
		else if (strncmp(var, "delay", 5) == 0) {
			var += 5;
			node2 = atoi(var) - 1;
			if (node2 >= nodes)
				continue;
			network->set_link_delay_generator(node, node2, generator.generate(arg));
		} else if (strncmp(var, "refclock", 8) == 0)
			network->get_node(node)->get_refclock()->set_offset_generator(generator.generate(arg));
		else
			return false;
	}

	fclose(f);

	return true;
}

void run_generator(char *expr, int num) {
	Generator_generator gen_generator;
	Generator *generator;

	generator = gen_generator.generate(expr);
	while (num--)
		printf("%.9e\n", generator->generate(NULL));
	delete generator;
}

int main(int argc, char **argv) {
	int nodes, subnets = 1, help = 0, verbosity = 2, generate_only = 0, rate = 1;
	double limit = 10000.0, reset = 0.0;
	const char *offset_log = NULL, *freq_log = NULL, *rawfreq_log = NULL,
	      *packet_log = NULL, *config, *socket = "clknetsim.sock", *env;
	struct timeval tv;

	int r, opt;
	Network *network;

	while ((opt = getopt(argc, argv, "l:r:R:n:o:f:Gg:p:s:v:h")) != -1) {
		switch (opt) {
			case 'l':
				limit = atof(optarg);
				break;
			case 'r':
				reset = atof(optarg);
				break;
			case 'R':
				rate = atoi(optarg);
				break;
			case 'n':
				subnets = atoi(optarg);
				break;
			case 'o':
				offset_log = optarg;
				break;
			case 'f':
				freq_log = optarg;
				break;
			case 'g':
				rawfreq_log = optarg;
				break;
			case 'p':
				packet_log = optarg;
				break;
			case 's':
				socket = optarg;
				break;
			case 'G':
				generate_only = 1;
				break;
			case 'v':
				verbosity = atoi(optarg);
				break;
			case 'h':
			default:
				help = 1;
		}
	}

	if (optind + 2 != argc || help) {
		printf("usage: clknetsim [options] config nodes\n");
		printf("   or: clknetsim -G expr num\n");
		printf("       -l secs       set time limit to secs (default 10000)\n");
		printf("       -r secs       reset clock stats after secs (default 0)\n");
		printf("       -R rate       set freq/log/stats update rate (default 1 per second)\n");
		printf("       -n subnets    set number of subnetworks (default 1)\n");
		printf("       -o file       log time offsets to file\n");
		printf("       -f file       log frequency offsets to file\n");
		printf("       -g file       log raw (w/o slew) frequency offsets to file\n");
		printf("       -p file       log packet delays to file\n");
		printf("       -s socket     set server socket name (default clknetsim.sock)\n");
		printf("       -v level      set verbosity level (default 2)\n");
		printf("       -G            print num numbers generated by expr\n");
		printf("       -h            print usage\n");
		return 1;
	}
	
	config = argv[optind];
	nodes = atoi(argv[optind + 1]);

	env = getenv("CLKNETSIM_RANDOM_SEED");
	if (env) {
		srandom(atoi(env));
	} else {
		gettimeofday(&tv, NULL);
		srandom(tv.tv_sec ^ tv.tv_usec);
	}

	if (generate_only) {
		run_generator(argv[optind], nodes);
		return 0;
	}

	network = new Network(socket, nodes, subnets, rate);
	
	if (offset_log)
		network->open_offset_log(offset_log);
	if (freq_log)
		network->open_freq_log(freq_log);
	if (rawfreq_log)
		network->open_rawfreq_log(rawfreq_log);
	if (packet_log)
		network->open_packet_log(packet_log);

	if (!load_config(config, network, nodes)) {
		fprintf(stderr, "Couldn't parse config %s\n", config);
		return 1;
	}

	if (!network->prepare_clients())
		return 1;

	fprintf(stderr, "Running simulation...");

	if (reset && reset < limit) {
		r = network->run(reset);
		network->reset_clock_stats();
	} else
		r = true;

	if (r)
		r = network->run(limit);

	if (r) {
		fprintf(stderr, "done\n\n");
		network->print_stats(verbosity);
	} else
		fprintf(stderr, "failed\n");

	delete network;

	return !r;
}
