#include "conf-update.h"

int main(int argc, char **argv) {
	bool cont, menu_changed, firstrun, doit;
	bool *tmp_index;
	char *config_protect = "", *config_protect_mask = "";
	char *cmd = "", *myfile = "", *highest = "";
	char *esc_highest = "", *esc_myfile = "";
	char **result, **envvars, **protected, **masked, **md5_cache;
	char **md5sum_cache, **myupdate, **merged_updates_report = NULL;
	char **removed_updates_report = NULL;
	const char *name, *myname;
	int indent, myindent, i, j, file_count, c, item_ct, cur;
	int merged_updates_ct = 0, removed_updates_ct = 0, arglen;
	ITEM **items_list;
	MENU *mymenu;
	WINDOW *inner, *menu_win;

	read_config();
	sanity_checks();
	
	if (argc == 1) {
		fprintf(stderr, ">>> Getting CONFIG_PROTECT* variables from portage...\n");
		#ifdef DEBUG
		// sandboxing is useful for debugging, believe me
		envvars = get_listing("portageq envvar CONFIG_PROTECT CONFIG_PROTECT_MASK | sed -e \"s:^/:${SANDBOX}/:\" -e \"s: /: ${SANDBOX}/:g\"", "\n");
		#else
		envvars = get_listing("portageq envvar CONFIG_PROTECT CONFIG_PROTECT_MASK", "\n");
		#endif
		
		if (is_valid_entry(envvars[0]) && is_valid_entry(envvars[1])) {
			config_protect = strdup(envvars[0]);
			config_protect_mask = strdup(envvars[1]);
			free(envvars[0]);
			free(envvars[1]);
			free(envvars);
		} else {
			fprintf(stderr, "!!! failed. Aborting.\n");
			exit(EXIT_FAILURE);
		}
		
		fprintf(stderr, ">>> Automerging updates in CONFIG_PROTECT_MASK...\n");
		masked = find_updates(config_protect_mask);
		free(config_protect_mask);
		for (i=0;!is_last_entry(masked[i]);i++) {
			if (is_valid_entry(masked[i])) {
				merged_updates_ct++;
				merged_updates_report = (char **)realloc(merged_updates_report, merged_updates_ct * sizeof(char *));
				merged_updates_report[merged_updates_ct-1] = get_real_filename(masked[i]);
				merge(get_highest_update(masked, masked[i]), masked);
			}
		}
		free(masked);
		fprintf(stderr, ">>> Searching for updates in CONFIG_PROTECT...\n");
	} else {
		if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
			display_help();
		} else {
			arglen = 0;
			for (i=1;i<argc;i++) {
				arglen += strlen(argv[i]) + 1;
			}
			config_protect = (char *)calloc(sizeof(char), arglen);
			for (i=1;i<argc;i++) {
				if (argv[i][0] != '/') {
					// relative paths screw our indentation. maybe this should be a TODO?
					fprintf(stderr, "!!! ERROR: Non-absolute path given as argument\n");
					exit(EXIT_FAILURE);
				}
				strcat(config_protect, argv[i]);
				if (i < argc-1) {
					strcat(config_protect, " ");
				}
			}
			fprintf(stderr, ">>> Searching for updates in specified directories...\n");
		}
	}
	
	protected = find_updates(config_protect);

	// it's important that we do this first
	if (config.automerge_unmodified) {
		fprintf(stderr, ">>> Automerging unmodified files...\n");
		file_count = 1;
		md5_cache = (char **) malloc(sizeof(char *) * file_count);
		md5sum_cache = (char **) malloc(sizeof(char *) * file_count);
		md5_cache[0] = LAST_ENTRY;
		md5sum_cache[0] = LAST_ENTRY;
		for (i=0;!is_last_entry(protected[i]);i++) {
			if (is_valid_entry(protected[i])) {
				highest = get_highest_update(protected, protected[i]);
				if (!strcmp(protected[i], highest)) {
					md5_cache = (char **) realloc(md5_cache, sizeof(char *) * (file_count + 1));
					md5sum_cache = (char **) realloc(md5sum_cache, sizeof(char *) * (file_count + 1));
					md5_cache[file_count-1] = strdup(highest);
					md5sum_cache[file_count-1] = (char *)malloc(sizeof(char) * 32);
					calc_md5(md5_cache[file_count-1], md5sum_cache[file_count-1]);
					md5_cache[file_count] = LAST_ENTRY;
					md5sum_cache[file_count] = LAST_ENTRY;
					file_count++;
				}
			}
		}
		for (i=0;!is_last_entry(protected[i]);i++) {
			if (is_valid_entry(protected[i])) {
				myfile = get_real_filename(protected[i]);
				
				if (!user_modified(myfile)) {
					merged_updates_ct++;
					merged_updates_report = (char **)realloc(merged_updates_report, merged_updates_ct * sizeof(char *));
					merged_updates_report[merged_updates_ct-1] = get_real_filename(protected[i]);
					merge(get_highest_update(protected, protected[i]), protected);
				}
				
				free(myfile);
			}
		}
		for (i=0;!is_last_entry(md5_cache[i]);i++) {
			myfile = get_real_filename(md5_cache[i]);
			md5sum_update(myfile, md5sum_cache[i]);
			free(myfile);
			free(md5_cache[i]);
			free(md5sum_cache[i]);
		}
		free(md5_cache);
		free(md5sum_cache);
	}
	
	if (config.automerge_trivial) {
		fprintf(stderr, ">>> Automerging trivial changes...\n");
		for (i=0;!is_last_entry(protected[i]);i++) {
			if (is_valid_entry(protected[i])) {
				myfile = get_real_filename(protected[i]);
				esc_myfile = g_shell_quote(myfile);
				highest = get_highest_update(protected, protected[i]);
				esc_highest = g_shell_quote(highest);
				cmd = (char *)calloc(strlen("diff -Nu % % | grep \"^[+-][^+-]\" | grep -v \"^[-+]#\" | grep -v \"^[-+][[:space:]]*$\" " ) + strlen(esc_highest) + strlen(esc_myfile), sizeof(char));
				strcpy(cmd, "diff -Nu ");
				strcat(cmd, esc_myfile);
				strcat(cmd, " ");
				strcat(cmd, esc_highest);
				strcat(cmd, " | grep \"^[+-][^+-]\" | grep -v \"^[-+]#\" | grep -v \"^[-+][[:space:]]*$\"");
				free(myfile);
				free(esc_myfile);
				free(esc_highest);
				
				result = get_listing(cmd, "\n");
				free(cmd);
				if (is_last_entry(result[0])) {
					merged_updates_ct++;
					merged_updates_report = (char **)realloc(merged_updates_report, merged_updates_ct * sizeof(char *));
					merged_updates_report[merged_updates_ct-1] = get_real_filename(highest);
					merge(highest, protected);
				}
				for (j=0;!is_last_entry(result[j]);j++) {
					free(result[j]);
				}
				free(result);
			}
		}
	}
	/***/
	// ncurses n'stuff
	initscr();
	cbreak();
	noecho();
	keypad(stdscr, TRUE);
	start_color();
	init_pair(1, COLOR_CYAN, COLOR_BLUE);
	init_pair(2, COLOR_WHITE, COLOR_WHITE);
	init_pair(3, COLOR_BLACK, COLOR_WHITE);
	init_pair(4, COLOR_RED, COLOR_WHITE);
	init_pair(5, COLOR_WHITE, COLOR_BLACK);
	
	draw_background();
	
	inner = newwin(LINES - 4, COLS - 4, 2, 2);
	keypad(inner, TRUE);
	
	draw_legend(inner);
	
	menu_win = subwin(inner, LINES - 7 - 6, COLS - 4 - 3, 8, 3);

	mymenu = create_menu(protected);
	items_list = menu_items(mymenu);
	set_menu_win(mymenu, inner);
	set_menu_sub(mymenu, menu_win);
	
	post_menu(mymenu);
	touchwin(inner);
	wrefresh(inner);
	menu_changed = FALSE;
	while ((item_count(mymenu) > 1) && (c = wgetch(inner)) != 'q' && c != 'Q') {
		switch(c) {
			// select single
			case ' ':
				if ((strrchr(item_name(current_item(mymenu)), '/'))) {
					// it's a dir, select all subdirs + files
					name = item_name(current_item(mymenu));
					indent = 0;
					while (name[indent] == INDENT_CHAR) {
						indent++;
					}
					cont = TRUE;
					while (cont) {
						menu_driver(mymenu, REQ_DOWN_ITEM);
						myname = item_name(current_item(mymenu));
						myindent = 0;
						while (myname[myindent] == INDENT_CHAR) {
							myindent++;
						}
						if (myindent > indent) {
							if ((!strrchr(myname, '/'))) {
								set_item_value(current_item(mymenu), TRUE);
							}
						} else {
							menu_driver(mymenu, REQ_UP_ITEM);
							cont = FALSE;
						}
					}
				} else {
					menu_driver(mymenu, REQ_TOGGLE_ITEM);
				}
				break;
			// select all
			case 'a':
			case 'A':
				menu_driver(mymenu, REQ_LAST_ITEM);
				for (i=0;i<item_count(mymenu);i++) {
					if ((!strrchr(item_name(current_item(mymenu)), '/'))) {
						set_item_value(current_item(mymenu), TRUE);
					}
					menu_driver(mymenu, REQ_UP_ITEM);
				}
				menu_driver(mymenu, REQ_FIRST_ITEM);
				break;
			// delete update
			case 'd':
			case 'D':
				firstrun = config.check_actions;
				doit = TRUE;
				for (i=0;i<item_count(mymenu);i++) {
					if (item_value(items_list[i]) == TRUE || (current_item(mymenu) == items_list[i] && item_userptr(items_list[i]))) {
						if (firstrun) {
							doit = get_confirmation(inner, "delete");
							firstrun = false;
						}
						if (doit) {
							myupdate = (char **)item_userptr(items_list[i]);
							exit_error(!unlink(*(myupdate)), *(myupdate));
							removed_updates_ct++;
							removed_updates_report = (char**)realloc(removed_updates_report, removed_updates_ct * sizeof(char *));
							removed_updates_report[removed_updates_ct-1] = get_real_filename(*myupdate);
							free(*myupdate);
							*myupdate = SKIP_ENTRY;
							menu_changed = TRUE;
						}
					}
				}
				break;
			// edit update
			case 'e':
			case 'E':
				if (item_userptr(current_item(mymenu))) {
					endwin();
					int ret = edit_update(*((char **)item_userptr(current_item(mymenu))));
					if (!ret) {
						fprintf(stderr, "edit_update failed with error code: %d\n", ret);
					}
					reset_prog_mode();
				}
				break;
			// down 
			case KEY_DOWN:
			case 'j':
				menu_driver(mymenu, REQ_DOWN_ITEM);
				break;
			// up
			case KEY_UP:
			case 'k':
				menu_driver(mymenu, REQ_UP_ITEM);
				break;
			// merge interactively
			case 'm':
			case 'M':
				if (item_userptr(current_item(mymenu))) {
					endwin();
					protected = merge_interactively(*((char **)item_userptr(current_item(mymenu))), protected);
					reset_prog_mode();
					menu_changed = TRUE;
				}
				break;
			// merge/replace update
			case 'r':
			case 'R':
				/* it is important that we go from last to first:
				 * if e.g. both 0000 and 0001 are selected for merging, this
				 * assures (given a sorted list), that 0001 gets merged before
				 * 0000 and therefore 0000 gets removed
				 */
				firstrun = config.check_actions;
				doit = TRUE;
				for (i=item_count(mymenu)-1;i>=0;i--) {
					if (item_value(items_list[i]) == TRUE || (current_item(mymenu) == items_list[i] && item_userptr(items_list[i]))) {
						if (firstrun) {
							doit = get_confirmation(inner, "replace");
							firstrun = false;
						} 
						if (doit) {
							myupdate = (char **)item_userptr(items_list[i]);
							if (is_valid_entry(*myupdate)) {
								merged_updates_ct++;
								merged_updates_report = (char **)realloc(merged_updates_report, merged_updates_ct * sizeof(char *));
								merged_updates_report[merged_updates_ct-1] = get_real_filename(*myupdate);
								menu_changed = TRUE;
								merge(*myupdate, protected);
							}
						}
					}
				}
				break;
			// deselect all
			case 'u':
			case 'U':
				menu_driver(mymenu, REQ_LAST_ITEM);
				for (i=0;i<item_count(mymenu);i++) {
					menu_driver(mymenu, REQ_UP_ITEM);
					set_item_value(current_item(mymenu), FALSE);
				}
				menu_driver(mymenu, REQ_FIRST_ITEM);
				break;
			// page up
			case KEY_PPAGE:
				menu_driver(mymenu, REQ_SCR_UPAGE);
				break;
			// page down 
			case KEY_NPAGE:
				menu_driver(mymenu, REQ_SCR_DPAGE);
				break;
			// top
			case KEY_HOME:
				menu_driver(mymenu, REQ_FIRST_ITEM);
				break;
			// bottom
			case KEY_END:
				menu_driver(mymenu, REQ_LAST_ITEM);
				break;
			// show differences
			case '\n':
			case KEY_ENTER:
				if (item_userptr(current_item(mymenu))) {
					endwin();
					int ret = show_diff(*((char **)item_userptr(current_item(mymenu))));
					if (!ret) {
						fprintf(stderr, "show_diff failed with error code: %d\n", ret);
					}
					reset_prog_mode();
				}
				break;
			case KEY_RESIZE:
				if (LINES > 13 && COLS > 55) {
					// we don't want to loose the selection just because of a window resize
					item_ct = item_count(mymenu);
					cur = item_index(current_item(mymenu));
					tmp_index = malloc(sizeof(bool) * item_ct);
					for (i=0;i<item_ct;i++) {
						if (item_value(items_list[i]) == TRUE || (cur == i && item_userptr(items_list[i]))) {
							tmp_index[i] = TRUE;
						} else {
							tmp_index[i] = FALSE;
						}
					}
					remove_menu(mymenu);
					delwin(menu_win);
					delwin(inner);
					draw_background();
					inner = newwin(LINES - 4, COLS - 4, 2, 2);
					keypad(inner, TRUE);
					draw_legend(inner);
					menu_win = subwin(inner, LINES - 7 - 6, COLS - 4 - 5, 8, 5);
					mymenu = create_menu(protected);
					items_list = menu_items(mymenu);
					set_menu_win(mymenu, inner);
					set_menu_sub(mymenu, menu_win);	
					post_menu(mymenu);
					
					for (i=0;i<item_ct;i++) {
						set_item_value(items_list[i], tmp_index[i]);
					}
					set_current_item(mymenu, items_list[cur]);
					free(tmp_index);
				}
				break;
		}
		if (menu_changed) {
			remove_menu(mymenu);
			draw_legend(inner);
			mymenu = create_menu(protected);
			items_list = menu_items(mymenu);
			set_menu_win(mymenu, inner);
			set_menu_sub(mymenu, menu_win);		
			post_menu(mymenu);		
			menu_changed = FALSE;
		}
		touchwin(inner);
		wrefresh(inner);
	}
	endwin();
	remove_menu(mymenu);
	
	if (merged_updates_ct > 0) {
		fprintf(stdout, ">>> Merged updates for the following files:\n");
		for (i=0;i<merged_updates_ct;i++) {
			fprintf(stdout, "\t%s\n", merged_updates_report[i]);
			free(merged_updates_report[i]);
		}
		free(merged_updates_report);
	}
	if (removed_updates_ct > 0) {
		fprintf(stdout, ">>> Deleted updates for the following files:\n");
		for (i=0;i<removed_updates_ct;i++) {
			fprintf(stdout, "\t%s\n", removed_updates_report[i]);
			free(removed_updates_report[i]);
		}
		free(removed_updates_report);
	}
	
	for (i=0;!is_last_entry(protected[i]);i++) {
		if (is_valid_entry(protected[i])) {
			free(protected[i]);
		}
	}
	free(protected);
	if (config.pager) { 
		free(config.pager);
	}
	if (config.diff_tool) {
		free(config.diff_tool);
	}
	if (config.merge_tool) {
		free(config.merge_tool);
	}
	free(config.edit_tool);
	free(config_protect);
	fprintf(stderr, ">>> Nothing left to do... Bye!\n");
	exit(EXIT_SUCCESS);
}
