#include <stdio.h>
#include <stdlib.h>
#include "objectives.h"

#define LINE "__________________________________________________________________"

#define PERCENTF(val, val_max) (((float)val / (float)val_max) * 100.f)
#define CLAMP_VAL(value, min, max) (((value) < (min)) ? (min) : (((value) > (max)) ? (max) : ((value) < (min)) ? (min) : (value)))

enum PROGRAM_ARGUMENTS
{
	PROGRAM_NAME = 0,
	SRC_DIR,              /* int, size in 32-bit words of completed functions, in src/ directory */
    SRC_DIR_MAX,          /* int, size in 32-bit words of all tracked functions, in src/ directory */
    GAME_DIR,             /* int, size in 32-bit words of completed functions, in src/game/ directory */
    GAME_DIR_MAX,         /* int, size in 32-bit words of all tracked functions, in src/game/ directory */
    INFLATE_DIR,          /* int, size in 32-bit words of completed functions, in src/inflate/ directory */
    INFLATE_DIR_MAX,      /* int, size in 32-bit words of all tracked functions, in src/inflate/ directory */
    LIBULTRA_DIR,         /* int, size in 32-bit words of completed functions, in src/libultrare/ directory */
    LIBULTRA_DIR_MAX,     /* int, size in 32-bit words of all tracked functions, in src/libultrare/ directory */
    DECOMPILED_WORDS,     /* int, size in 32-bit words of all completed functions */
    DECOMPILED_WORDS_MAX, /* int, size in 32-bit words of all tracked functions */
    DECOMPILED_FILES,     /* int, count of all fully completed files */
    DECOMPILED_FILES_MAX, /* int, count of all tracked files */
    HTML_TEMPLATE,        /* string: path to html template input file */
    HTML_OUTPUT,          /* string: path to generated html file */
    LAST_MODIFIED_FILE,   /* string: name of last modified file */
    LOG_LEVEL,            /* int (enum): LOG_MIN = 0, LOG_DEF, LOG_MAX */
	ARGS_MAX
};

enum VERBOSITY_LOGGING
{
	LOG_MIN = 0,
	LOG_DEF,
	LOG_MAX
};

int lazy_rand(unsigned long int seed)
{
	seed = (214013 * seed + 2531011);
	return (seed >> 16) & 0x7FFF;
}

int total_objectives(void)
{
	/************************/
	int tmp_mis = 0;
	int tmp_obj = OBJ_A;
	int max_objs = 0;
	/************************/

	for(;;)
	{
		if(missions[tmp_mis].obj[tmp_obj][LINE1] == NULL) /* reached end of mission's objectives, check next mission */
		{
			if(missions[tmp_mis + 1].obj[OBJ_A][LINE1] == NULL) /* reached end of struct, stop counting */
			{
				break;
			}
			tmp_mis++;
			tmp_obj = OBJ_A;
		}
		tmp_obj++;
		max_objs++;
	}
	return max_objs;
}

void calc_mission_and_objective(int *cur_mis, int *cur_obj, int decomp_progress)
{
	/************************/
	int max_objs = 0;
	/************************/

	for(;;)
	{
		if(missions[*cur_mis].obj[*cur_obj][LINE1] == NULL) /* reached end of mission's objectives, check next mission */
		{
			if(missions[*cur_mis + 1].obj[OBJ_A][LINE1] == NULL) /* reached end of struct, stop counting */
			{
				break;
			}
			*cur_mis += 1;
			*cur_obj = OBJ_A;
		}
		*cur_obj += 1;
		max_objs += 1;
		if(max_objs >= decomp_progress)
		{
			break;
		}
	}
}

int max_objectives(const int cur_mis)
{
	/************************/
	int cur_obj = OBJ_A;
	/************************/

	for(;;)
	{
		if(missions[cur_mis].obj[cur_obj][LINE1] == NULL) /* reached end of mission's objectives, break */
		{
			break;
		}
		cur_obj++;
	}
	return cur_obj;
}

int main(int argc, char **argv)
{
	/************************/
	long int src_dir, src_dir_max;
	long int game_dir, game_dir_max;
	long int inflate_dir, inflate_dir_max;
	long int libultra_dir, libultra_dir_max;
	long int decompiled_words, decompiled_words_max;
	long int decompiled_files, decompiled_files_max;
	int cur_mis = 0, cur_obj = OBJ_A, cur_time;
	int cur_mis_objs_max = 0, max_objs = 0;
	int tmp_obj, cur_line = 0;
	int last_modified_file_arg_active = 0;
	int verbosity_level = LOG_DEF;
	float total_complete, obj_remaining;
	FILE *html_in, *html_out;
	char html_char;
	/************************/

	/* read logging level */
	if(argc == ARGS_MAX)
	{
		verbosity_level = CLAMP_VAL(atoi(argv[LOG_LEVEL]), 0, 2);
	}
	if(verbosity_level != LOG_MIN)
	{
		printf("\n  GoldenEye 007 Decompiled Statistics Generator\n%s\n", LINE);
	}
	if(argc != ARGS_MAX) /* incorrect number of arguments */
	{
		printf("\n  About: Generate decompiled statistics website\n\n  Syntax: %s src max game max inflate max libultra max decompiled_words max_words decompiled_files all_files html_template html_output last_modified_file verbosity_level (0-2)\n\n  Note: Each dir's statistic must be followed with the total words for the dir.\n  Example: 1481 15854 12641 232276 564 1312 556 20330 15242 269772 49 336 ./template.html ./index.html \"src/game/bondview.c\" 1", argv[PROGRAM_NAME]);
		goto exit;
	}

	/* read arguments */
	src_dir = atol(argv[SRC_DIR]), src_dir_max = atol(argv[SRC_DIR_MAX]);
	game_dir = atol(argv[GAME_DIR]), game_dir_max = atol(argv[GAME_DIR_MAX]);
	inflate_dir = atol(argv[INFLATE_DIR]), inflate_dir_max = atol(argv[INFLATE_DIR_MAX]);
	libultra_dir = atol(argv[LIBULTRA_DIR]), libultra_dir_max = atol(argv[LIBULTRA_DIR_MAX]);
	decompiled_words = atol(argv[DECOMPILED_WORDS]), decompiled_words_max = atol(argv[DECOMPILED_WORDS_MAX]);
	decompiled_files = atol(argv[DECOMPILED_FILES]), decompiled_files_max = atol(argv[DECOMPILED_FILES_MAX]);
	if(src_dir > src_dir_max)
	{
		printf("\n  Error: Aborted, src larger than src max argument");
		goto exit;
	}
	if(game_dir > game_dir_max)
	{
		printf("\n  Error: Aborted, game larger than game max argument");
		goto exit;
	}
	if(inflate_dir > inflate_dir_max)
	{
		printf("\n  Error: Aborted, inflate larger than inflate max argument");
		goto exit;
	}
	if(libultra_dir > libultra_dir_max)
	{
		printf("\n  Error: Aborted, libultra larger than libultra max argument");
		goto exit;
	}
	if(decompiled_words > decompiled_words_max)
	{
		printf("\n  Error: Aborted, decompiled words larger than total words argument");
		goto exit;
	}
	if(decompiled_files > decompiled_files_max)
	{
		printf("\n  Error: Aborted, decompiled files larger than total files argument");
		goto exit;
	}
	if(src_dir < 0 || src_dir_max < 0 || game_dir < 0 || game_dir_max < 0 || inflate_dir < 0 || inflate_dir_max < 0 || libultra_dir < 0 || libultra_dir_max < 0 || decompiled_words < 0 || decompiled_words_max < 0|| decompiled_files < 0 || decompiled_files_max < 0)
	{
		printf("\n  Error: Aborted, detected negative arguments (invalid or overflow)");
		goto exit;
	}
	last_modified_file_arg_active = (argv[LAST_MODIFIED_FILE] != NULL);

	/* print all arguments */
	if(verbosity_level == LOG_MAX)
	{
		printf("\n  src_dir:\t\t%ld / %ld", src_dir, src_dir_max);
		printf("\n  game_dir:\t\t%ld / %ld", game_dir, game_dir_max);
		printf("\n  inflate_dir:\t\t%ld / %ld", inflate_dir, inflate_dir_max);
		printf("\n  libultra_dir:\t\t%ld / %ld", libultra_dir, libultra_dir_max);
		printf("\n  decompiled_words:\t%ld / %ld", decompiled_words, decompiled_words_max);
		printf("\n  decompiled_files:\t%ld / %ld", decompiled_files, decompiled_files_max);
		printf("\n  last_modified_file:\t%s\n%s\n", last_modified_file_arg_active ? argv[LAST_MODIFIED_FILE] : "NULL", LINE);
	}

	/* copy html template to output */
	html_in = fopen(argv[HTML_TEMPLATE], "rb");
	if(html_in == NULL)
	{
		printf("\n  Error: Aborted, %s cannot be opened", argv[HTML_TEMPLATE]);
		goto exit;
	}
	html_out = fopen(argv[HTML_OUTPUT], "wb");
	if(html_out == NULL)
	{
		printf("\n  Error: Aborted, %s cannot be opened", argv[HTML_OUTPUT]);
		fclose(html_in);
		goto exit;
	}
	while((html_char = fgetc(html_in)) != EOF)
	{
		fputc(html_char, html_out);
	}
	fclose(html_in);

	max_objs = total_objectives();
	total_complete = ((float)decompiled_words / (float)decompiled_words_max) * (float)max_objs;
	obj_remaining = total_complete - (float)((int)total_complete);
	calc_mission_and_objective(&cur_mis, &cur_obj, (int)total_complete);
	cur_mis_objs_max = max_objectives(cur_mis);
	cur_time = missions[cur_mis].time + 1;
	if(cur_obj != cur_mis_objs_max) /* if mission is incomplete */
	{
		cur_time += 10 + (lazy_rand((unsigned long int)decompiled_words) % 25);
	};

	fprintf(html_out, "<text x=\"363\" y=\"648\">%s</text>\n", missions[cur_mis].diff);
	fprintf(html_out, "<text x=\"363\" y=\"754\">%s</text>\n", missions[cur_mis].title);
	fprintf(html_out, "<text x=\"363\" y=\"858\">%s</text>\n", missions[cur_mis].part);
	fprintf(html_out, "<text x=\"363\" y=\"1015\">REPORT:</text>\n");
	fprintf(html_out, "<text x=\"363\" y=\"1173\">Mission status:</text>\n");
	fprintf(html_out, "<text x=\"1004\" y=\"1173\"%s</text>\n", cur_obj == cur_mis_objs_max ? ">Completed" : " class=\"failed\">FAILED");

	for(tmp_obj = OBJ_A; tmp_obj < cur_mis_objs_max; tmp_obj++, cur_line++)
	{
		fprintf(html_out, "<text x=\"363\" y=\"%d\">%s</text>\n", line_rows[cur_line], diff_char[tmp_obj]);
		fprintf(html_out, "<text x=\"493\" y=\"%d\">%s</text>\n", line_rows[cur_line], missions[cur_mis].obj[tmp_obj][LINE1]);
		fprintf(html_out, "<text x=\"2032\" y=\"%d\"%s</text>\n", line_rows[cur_line], tmp_obj < cur_obj ? ">Completed" : " class=\"failed\">FAILED");
		if(missions[cur_mis].obj[tmp_obj][LINE2] != NULL) /* if objective took up two lines, skip an extra line for next objective */
		{
			fprintf(html_out, "<text x=\"562\" y=\"%d\">%s</text>\n", line_rows[cur_line+1], missions[cur_mis].obj[tmp_obj][LINE2]);
			cur_line++;
		}
	}

	fprintf(html_out, "<a onclick=\"swap_pages()\"><rect x=\"2573\" y=\"945\" height=\"434\" width=\"79\" class=\"button\"></rect></a>\n");
	fprintf(html_out, "</svg>\n");
	fprintf(html_out, "<svg viewBox=\"0 0 2880 2160\" class=\"stats\" id=\"page2\" style=\"display: none;\">\n");
	fprintf(html_out, "<text x=\"363\" y=\"648\">%s</text>\n", missions[cur_mis].diff);
	fprintf(html_out, "<text x=\"363\" y=\"754\">%s</text>\n", missions[cur_mis].title);
	fprintf(html_out, "<text x=\"363\" y=\"858\">%s</text>\n", missions[cur_mis].part);
	fprintf(html_out, "<text x=\"363\" y=\"1015\">STATISTICS:</text>\n");
	fprintf(html_out, "<text x=\"363\" y=\"1172\">Time:</text>\n");
	fprintf(html_out, "<text x=\"856\" y=\"1172\">%02d:%02d</text>\n", cur_time / 60, cur_time % 60);
	fprintf(html_out, "<text x=\"363\" y=\"1284\">Target:</text>\n");
	fprintf(html_out, "<text x=\"856\" y=\"1284\">%02d:%02d</text>\n", missions[cur_mis].time / 60,  missions[cur_mis].time % 60);
	if(cur_obj == cur_mis_objs_max)
	{
		fprintf(html_out, "<text x=\"1250\" y=\"1284\">(Best Time: %02d:%02d)</text>\n", cur_time / 60, cur_time % 60);
	}
	fprintf(html_out, "<text x=\"363\" y=\"1416\">Decomp:</text>\n");
	fprintf(html_out, "<text x=\"856\" y=\"1416\">%0.1f%%</text>\n", PERCENTF(decompiled_words, decompiled_words_max));
	if(last_modified_file_arg_active)
	{
		fprintf(html_out, "<text x=\"363\" y=\"1520\">Last modified file:</text>\n");
		fprintf(html_out, "<text x=\"1180\" y=\"1520\">%s</text>\n", argv[LAST_MODIFIED_FILE]);
	}
	else
	{
		fprintf(html_out, "<text x=\"363\" y=\"1520\">Weapon of choice:</text>\n");
		fprintf(html_out, "<text x=\"1246\" y=\"1520\">PP7 (silenced)</text>\n");
	}
	fprintf(html_out, "<text x=\"363\" y=\"1678\">Files done:</text>\n");
	fprintf(html_out, "<text x=\"856\" y=\"1678\">%ld</text>\n", decompiled_files);
	fprintf(html_out, "<text x=\"363\" y=\"1776\">Files total:</text>\n");
	fprintf(html_out, "<text x=\"856\" y=\"1776\">%ld</text>\n", decompiled_files_max);
	fprintf(html_out, "<text x=\"1180\" y=\"1678\">src:</text>\n");
	fprintf(html_out, "<text x=\"1800\" y=\"1678\">%ld (%0.1f%%)</text>\n", src_dir, PERCENTF(src_dir, src_dir_max));
	fprintf(html_out, "<text x=\"1180\" y=\"1776\">src/game:</text>\n");
	fprintf(html_out, "<text x=\"1800\" y=\"1776\">%ld (%0.1f%%)</text>\n", game_dir, PERCENTF(game_dir, game_dir_max));
	fprintf(html_out, "<text x=\"1180\" y=\"1875\">src/inflate:</text>\n");
	fprintf(html_out, "<text x=\"1800\" y=\"1875\">%ld (%0.1f%%)</text>\n", inflate_dir, PERCENTF(inflate_dir, inflate_dir_max));
	fprintf(html_out, "<text x=\"1180\" y=\"1973\">src/libultra:</text>\n");
	fprintf(html_out, "<text x=\"1800\" y=\"1973\">%ld (%0.1f%%)</text>\n", libultra_dir, PERCENTF(libultra_dir, libultra_dir_max));
	fprintf(html_out, "<a onclick=\"swap_pages()\"><rect x=\"2573\" y=\"1548\" height=\"434\" width=\"79\" class=\"button\"></rect></a>\n");
	fprintf(html_out, "</svg>\n");
	fprintf(html_out, "</div>\n");
	fprintf(html_out, "</body>\n");
	fprintf(html_out, "</html>");
	fclose(html_out);

	if(verbosity_level != LOG_MIN)
	{
		printf("\n  Successfully written stats to %s\n\n", argv[HTML_OUTPUT]);
	}
	printf("    %s\n", missions[cur_mis].out_name);
	for(tmp_obj = OBJ_A; tmp_obj < cur_mis_objs_max; tmp_obj++)
	{
		printf("\n      [%s] %s", tmp_obj < cur_obj ? "X" : " ", missions[cur_mis].obj[tmp_obj][LINE1]);
		if(missions[cur_mis].obj[tmp_obj][LINE2] != NULL)
		{
			printf(" %s", missions[cur_mis].obj[tmp_obj][LINE2]);
		}
		if(tmp_obj == cur_obj)
		{
			printf(" - %0.1f%%", obj_remaining * 100.f);
		}
	}

	printf("\n\n    Mission Status: %s", cur_obj == cur_mis_objs_max ? "Completed" : "FAILED");
	if(total_complete == max_objs)
	{
		printf("\n\n    Baron has been defeated - decomp is complete!!");
	}
	else if(cur_obj == cur_mis_objs_max)
	{
		printf("\n\n    Progress To Next Mission: %0.1f%%", obj_remaining * 100.f);
	}

exit:
	if(verbosity_level != LOG_MIN)
	{
		printf("\n%s\n\n", LINE);
	}
	return 0;
}
