/*
 * Copyright (C) 2017 FAUmachine Team <info@faumachine.org>.
 * This program is free software. You can redistribute it and/or modify it
 * under the terms of the GNU General Public License, either version 2 of
 * the License, or (at your option) any later version. See COPYING.
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

int output_format_i;
long long start_time_l;
long long end_time_l;
char *input_file;
char *output_file;
char *output_format;
char *start_time;
char *end_time;

enum output_formats{
	GNUPLOT,
	JSON,
	XML
};

struct header_data 
{
	int number_analog_channels;
	int number_digital_channels;
	unsigned long long start_time;
	unsigned long long end_time;
	int sweep_time; /* in seconds */
	double time_hz_d;
	unsigned int sample_rate;
	/*unsigned int number_data_points[];*/
} header_data;

struct data_point
{
	int mV;
	unsigned long long time_stamp;	
	double time_stamp_d;
} data_point;

struct faum_plotter{
	FILE *input;
	FILE *output;
	long long start_time;
	long long end_time;
	double start_time_d;
	double end_time_d;
};

static void 
faum_plotter_destroy(struct faum_plotter *f_p)
{
	
	if (f_p != NULL){
		fclose(f_p->input);
		fclose(f_p->output);
		free(f_p);
	}
	free(input_file);
	free(output_file);
	free(output_format);
	free(start_time);
	free(end_time);
}

static void usage(void)
{
	const char *usage_msg = "Usage: plotter -i [path_to_input_file] -o [path_to_output_file] -f [gnuplot|json|xml] -s [start_time] -e [end_time]"; 
	fprintf(stderr, "%s\n", usage_msg);
}

static void 
export_xml(struct header_data *h_d
	, struct faum_plotter *f_p
	, unsigned int number_data_points[])
{
	int channel;
	struct data_point d_p;
	fprintf(f_p->output, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>");	
	fprintf(f_p->output, "<root>\n");	
	fprintf(f_p->output, "\t<data>\n");
	for (channel = 0; channel < h_d->number_analog_channels + h_d->number_digital_channels; channel++){
		if (channel < h_d->number_analog_channels){
			fprintf(f_p->output, "\t\t<CH%d>\n", channel + 1);
		}else{
			fprintf(f_p->output, "\t\t<D%d>\n", channel - h_d->number_analog_channels + 1);
		}
		int nbr_d_p;
		int d_p_count;
		nbr_d_p = number_data_points[channel];
		d_p_count = 0;
		while (d_p_count < nbr_d_p
			&&(fread((void*) &d_p, sizeof(d_p), 1, f_p->input)) != 0){
			double time;
			time = ((double)d_p.time_stamp - (double)h_d->start_time)
				/(double)h_d->time_hz_d;
			if (time < f_p->start_time_d 
			|| f_p->end_time_d < time){
				/* do nothing */
			}else{
				fprintf(f_p->output, "\t\t\t<element>\n");
				fprintf(f_p->output, "\t\t\t\t<X>%.9lf</X>\n", time);
				fprintf(f_p->output, "\t\t\t\t<Y>%d</Y>\n", d_p.mV);
				fprintf(f_p->output, "\t\t\t</element>\n");
			}
			d_p_count++;
		}
		if (channel < h_d->number_analog_channels){
			fprintf(f_p->output, "\t\t</CH%d>\n", channel + 1);
		}else{
			fprintf(f_p->output, "\t\t</D%d>\n", channel - h_d->number_analog_channels + 1);
		}
	}	
	fprintf(f_p->output, "\t</data>\n");/* end data */
	fprintf(f_p->output, "</root>\n");/* end object */


}


static void 
export_json(struct header_data *h_d
	, struct faum_plotter *f_p
	, unsigned int number_data_points[])
{
	int channel;
	struct data_point d_p;
	fprintf(f_p->output, "{\n");	
	fprintf(f_p->output, "\"data\":{\n");
	for (channel = 0; channel < h_d->number_analog_channels + h_d->number_digital_channels; channel++){
		if (channel < h_d->number_analog_channels){
			fprintf(f_p->output, "\"CH%d\": [\n", channel + 1);
		}else{
			fprintf(f_p->output, "\"D%d\": [\n", channel - h_d->number_analog_channels + 1);
		}
		int nbr_d_p;
		int d_p_count;
		int written_d_p;
		nbr_d_p = number_data_points[channel];
		d_p_count = 0;
		written_d_p = 0;
		while (d_p_count < nbr_d_p
			&&(fread((void*) &d_p, sizeof(d_p), 1, f_p->input)) != 0){
			double time;
			time = ((double)d_p.time_stamp - (double)h_d->start_time)
				/(double)h_d->time_hz_d;
			if (time < f_p->start_time_d 
			|| f_p->end_time_d < time){
				/* do nothing */
			}else{
				if (written_d_p != 0){
					fprintf(f_p->output, ",\n");
				}
				fprintf(f_p->output, "{\"X\": %.9lf,", time);
				fprintf(f_p->output, "\"Y\": %d}\n", d_p.mV);
				written_d_p++;
			}
			d_p_count++;
		}
		if (channel != h_d->number_analog_channels + h_d->number_digital_channels - 1 ){
			fprintf(f_p->output, "],\n");
		}else{
			fprintf(f_p->output, "]\n");
		}
	}	
	fprintf(f_p->output, "}\n");/* end data */
	fprintf(f_p->output, "}\n");/* end object */
}

static void 
export_gnu_plot(struct header_data *h_d, 
		struct faum_plotter *f_p, 
		unsigned int number_data_points[])
{
	int channel;
	
	for (channel = 0; channel < h_d->number_analog_channels + h_d->number_digital_channels; channel++){
		fprintf(f_p->output, "#X\tY\n");
		struct data_point d_p;
		int nbr_d_p;
		int d_p_count;
		nbr_d_p = number_data_points[channel];
		d_p_count = 0;
		while (d_p_count < nbr_d_p
			&&(fread((void*) &d_p, sizeof(d_p), 1, f_p->input)) != 0){
			double time;
			time = ((double)d_p.time_stamp - (double)h_d->start_time)
				/(double)h_d->time_hz_d;
			if (time < f_p->start_time_d 
			|| f_p->end_time_d < time){
				/* do nothing */
			}else{
				fprintf(f_p->output, "%.9lf\t%d\n", time, d_p.mV);
			}
			d_p_count++; 
			
		}
		fprintf(f_p->output, "\n\n"); 
	} 
}

int main(int argc, char ** argv)
{
	struct faum_plotter *f_p;
	int opt;
	double start_time_d, end_time_d;
	start_time_d = -1.0;
	end_time_d = -1.0;
	output_format_i = -1;
	start_time_l = -1;
	end_time_l = -1;
	input_file = NULL;
	output_file = NULL;
	output_format = NULL;
	start_time = NULL;
	end_time = NULL;
	
	#if 0
	if (argc < 4){
		usage();
		return EXIT_FAILURE;
	}	
	#endif
	f_p = malloc(sizeof(struct faum_plotter*));
	if (f_p == NULL){
		fprintf(stderr, "Something went wrong\n");
		return EXIT_FAILURE;
	}

	/* parse input parameters */
	while ((opt = getopt(argc, argv, "i:o:f:s:e:")) != -1){
		switch(opt){
			case 'i':
				input_file = calloc(strlen(optarg) + 1, sizeof(char));
				sprintf(input_file, "%s", optarg);
				break;
			case 'o':
				output_file = calloc(strlen(optarg) + 1, sizeof(char));
				sprintf(output_file, "%s", optarg);
				break;
			case 'f':
				output_format = calloc(strlen(optarg) + 1, sizeof(char));
				sprintf(output_format, "%s", optarg);
				break;
			case 's':
				start_time = calloc(strlen(optarg) + 1, sizeof(char));
				sprintf(start_time, "%s", optarg);	
				break;
			case 'e':
				end_time = calloc(strlen(optarg) + 1, sizeof(char));
				sprintf(end_time, "%s", optarg);	
				break;
			default:
				fprintf(stderr, "usage...\n");
		}

	}
	if (input_file != NULL){
		f_p->input = fopen(input_file, "r+b");
		if (f_p->input == NULL){
			fprintf(stderr, "Could not open file '%s'\n", input_file);
			faum_plotter_destroy(f_p);
			return EXIT_FAILURE;
		}
	}else{
		usage();
		return EXIT_FAILURE;
	}
	if (output_file != NULL){
		f_p->output = fopen(output_file, "w");
		if (f_p->output == NULL){
			fprintf(stderr, "Could not open output file '%s'\n", output_file);
			faum_plotter_destroy(f_p);
		}
	}else{
		usage();
		return EXIT_FAILURE;
	}

	if (output_format != NULL){
		if (strcmp(output_format, "gnuplot") == 0){
			output_format_i = GNUPLOT;
		}else if (strcmp(output_format, "json") == 0){
			output_format_i = JSON;
		}else if (strcmp(output_format, "xml") == 0){
			output_format_i = XML;
		}/* extendable */	
	}else{
		usage();
		faum_plotter_destroy(f_p);
		return EXIT_FAILURE;
	}
	
	if (start_time != NULL){
		char *end;
		/*start_time_l= strtol(start_time, &end, 10);*/
		start_time_d = strtod(start_time, &end);
		if (start_time == end){
			fprintf(stderr, "Could not resolve start time '%s'\n", start_time);
			faum_plotter_destroy(f_p);
			return EXIT_FAILURE;
		}
	}else{
	}
	
	if (end_time != NULL){
		char *end;
		/*end_time_l= strtol(end_time, &end, 10);*/
		end_time_d = strtod(end_time, &end);
		if (end_time == end){
			fprintf(stderr, "Could not resolve end time '%s'\n", end_time);
			faum_plotter_destroy(f_p);
			return EXIT_FAILURE;

		}
	}
	
	struct header_data h_d;
	fseek(f_p->input, 0, SEEK_SET);
	if ((fread((void*) &h_d, sizeof(h_d), 1, f_p->input)) == 0){
		fprintf(stderr, "Could not read file '%s'\n", input_file);
		return EXIT_FAILURE;
	}
	unsigned int nbr_d_p[h_d.number_analog_channels + h_d.number_digital_channels];
	fseek(f_p->input, 0, SEEK_CUR);	
	if ((fread((void*) &nbr_d_p, sizeof(nbr_d_p), 1, f_p->input)) == 0){
		fprintf(stderr, "Could not read file '%s'\n", input_file);
		return EXIT_FAILURE;
	}
	if (start_time_d == -1){
		start_time_d = 0.0;
	}	
	else if (start_time_d < 0 || ((double)h_d.end_time-(double)h_d.start_time)/h_d.time_hz_d < start_time_d){			
		fprintf(stderr, "Start time value not valid. Please select value between %.9lf and %.9lf\n", 
				0.0,
				((double)h_d.end_time-(double)h_d.start_time)/h_d.time_hz_d); 		
		return EXIT_FAILURE;
	}
	f_p->start_time_d = start_time_d;

	if (end_time_d == -1){
		end_time_d = ((double)h_d.end_time - (double)h_d.start_time)/h_d.time_hz_d;
		fprintf(stderr, "end_time_d %.9lf\n", end_time_d);
	}else if (end_time_d < 0 || ((double)h_d.end_time-(double)h_d.start_time)/h_d.time_hz_d  < end_time_d){
		fprintf(stderr, "End time value not valid. Please select value between %.9lf and %.9lf\n", 
				0.0,
				((double)h_d.end_time-(double)h_d.start_time)/h_d.time_hz_d); 		
		return EXIT_FAILURE;
	}
	f_p->end_time_d = end_time_d;
	
	if (f_p->end_time_d < 0 
	|| f_p->start_time_d < 0
	|| f_p->end_time < f_p->start_time){
		fprintf(stderr, "Error. Corrupt data file\n");
		return EXIT_FAILURE;
	}
	switch (output_format_i){
		case GNUPLOT:
			export_gnu_plot(&h_d, f_p, nbr_d_p);
			break;
		case JSON:
			export_json(&h_d, f_p, nbr_d_p);
			break;
		case XML:
			export_xml(&h_d, f_p, nbr_d_p);
			break;
		default:
			fprintf(stderr, "Please specify valid output format\n");
			usage();
	}
	faum_plotter_destroy(f_p);
	fprintf(stderr, "Exporting plot data done\n");
	return EXIT_SUCCESS;
}



#undef BEHAVIOR
