#include "conf-update.h"

char **get_listing(char *cmd, char *delim) {
        FILE *pipe;
        char *buf = NULL;
        size_t bufsize = 0;
        ssize_t read;
        char **listing;
        int count = 1, i = 0;
		unsigned int j;
        char c;

        pipe = popen(cmd, "r");
        if (pipe) {
                read = getdelim(&buf, &bufsize, '\0', pipe);
				char *buf_backup = buf;
                if (read != -1) {
                        // determine number of tokens
                        while ((c = buf[i]) != '\0') {
                                for (j=0;j<strlen(delim);j++) {
                                        if (c == delim[j]) {
                                                count++;
                                        }
                                }
                                i++;
                        }
						
                        listing = (char **) malloc(sizeof(char *) * count);
						char *str;
						i=0;
						while ((str = strsep(&buf, delim))) {
							listing[i] = strdup(str);
							i++;
						}
						free(buf_backup);
						// make sure the last one is always LAST_ENTRY
						if(i == count) {
							free(listing[count-1]);
						}
						listing[count-1] = LAST_ENTRY;
                        pclose(pipe);
                        return listing;
                } else {
					pclose(pipe);
					listing = (char **)malloc(sizeof(char *));
					listing[0] = LAST_ENTRY;
					free(buf_backup);
					return listing;
                }
        } else {
                exit_error(0, cmd);
        }
	// just for gcc, that bitch
	return NULL;
}

int compare_updates(const void *a, const void *b) {
	char *real_a;
	char *real_b;
	int result;
	signed mod = 1;
	
	if (is_valid_entry(*(char **)a)) { 
		real_a	= get_real_filename(*(char **)a);
	} else {
		real_a = NULL;
	}
	if (is_valid_entry(*(char **)b)) {
		real_b = get_real_filename(*(char **)b);
	} else {
		real_b = NULL;
	}
	
	if (!real_a && !real_b) {
		result = -1;
	} else if (!real_a) {
		result = 1;
	} else if (!real_b) {
		result = -1;
	} else {
		// both valid updates
		if ((result = strcmp(real_a, real_b)) == 0) {
			// same target
			if ((result = strncmp(strstr(*(char **)a, "._cfg"), strstr(*(char **)b, "._cfg"), strlen("._cfg????"))) == 0) {
				// same update number. interactively merged vs. predefined, 1:0 for the user.
				mod = -1;
			}
			result = mod * strcmp(*(char **)a, *(char **)b);
		}
	}
	
	free(real_a);
	free(real_b);
	
	return result;
}

struct node *fold_updates(char **list) {
	struct node *root = malloc(sizeof(struct node));
	struct node *mynode, *newnode;
	char *endtok, *curtok;
	int i;
	int run;
	
	root->name = strdup("/");
	root->children = malloc(sizeof(struct node *));
	root->ct_children = 0;
	root->parent = NULL;
	root->dir = TRUE;
	root->link = NULL;
	
	for (i=0;!is_last_entry(list[i]);i++) {
		if (is_valid_entry(list[i])) {
			endtok = list[i]+1;
			run = 1;
			while (run) {
				if (run == 2) {
					// 2 means we're on the last run
					run = 0;
					endtok = list[i] + strlen(list[i]) + 1;
				} else {
					if ((endtok = strchr(endtok+1, '/')) == NULL) {
						run = 2;
					}
				}
				curtok = strndup(list[i], endtok - list[i]);
				
				mynode = find_node(root, curtok);
				if (mynode == (struct node *)FALSE) {
					mynode = root;
				}
				if (mynode != (struct node *)TRUE) {
					// mynode is the parent of the new to be inserted node
					newnode = malloc(sizeof(struct node));
					newnode->name = strdup(curtok);
					if (!strcmp(curtok,list[i])) {
						// it's the file
						newnode->dir = FALSE;
						newnode->link = &list[i];
					} else {
						newnode->dir = TRUE;
						newnode->link = NULL;
					}
					newnode->children = malloc(sizeof(struct node *));
					newnode->ct_children = 0;
					newnode->parent = mynode;
					
					mynode->ct_children++;
					mynode->children = realloc(mynode->children, sizeof(struct node *) * mynode->ct_children);
					mynode->children[mynode->ct_children-1] = newnode;
				}
				
				free(curtok);
			}
		}
	}
	
	return root;
	
}

struct node *find_node(struct node *root, char *path) {
	int i;
	struct node *mynode;
	
	if (!strcmp(root->name, path)) {
		// already exists
		return (struct node *)TRUE;
	} else if (!strncmp(root->name, path, strlen(root->name)) && root->dir == TRUE) {
		// at least it's in the same direction, go through the list of children
		for (i=0;i<root->ct_children;i++) {
			mynode = find_node(root->children[i], path);
			if (mynode == (struct node *)TRUE) {
				return (struct node *)TRUE;
			} else if (mynode != (struct node *)FALSE) {
				return mynode;
			}
		}
		// if we hit this point, nothing was found, meaning that it has to be a child of the current node
		return root;
	} else {
		// completely wrong
		return (struct node *)FALSE;
	}
}
void sanity_checks() {
	extern struct configuration config;
	unsigned int i;
	FILE *pipe;
	char *cmd = NULL;
	char *tools[] = {
		"diff",
		"portageq",
		strndup(config.pager, strchrnul(config.pager, ' ') - config.pager),
		strndup(config.diff_tool, strchrnul(config.diff_tool, ' ') - config.diff_tool),
		strndup(config.merge_tool, strchrnul(config.merge_tool, ' ') - config.merge_tool),
		strndup(config.edit_tool, strchrnul(config.edit_tool, ' ') - config.edit_tool)
	};
	gchar *program_in_path;
	
	if (getuid() != 0) {
		fprintf(stderr, "!!! Oops, you're not root!\n");
		exit(EXIT_FAILURE);
	}

	for (i=0;i<sizeof(tools)/sizeof(tools[0]);i++) {
		// "" is okay for pager
		if (strcmp(tools[i], "")) {
			if (!(program_in_path = g_find_program_in_path((gchar *)tools[i]))) {
				fprintf(stderr, "!!! ERROR: couldn't find necesary tool: %s\n", tools[i]);
				exit(EXIT_FAILURE);
			} else {
				g_free(program_in_path);
			}
		}
	}
	free(cmd);
	free(tools[2]);
	free(tools[3]);
	free(tools[4]);
	free(tools[5]);

	mkdir (MD5SUM_INDEX_DIR, 0755);
	if ((pipe = fopen(MD5SUM_INDEX, "a"))) {
		fclose(pipe);
	} else {
		fprintf(stderr, "!!! ERROR: Can't write to %s; check permissions", MD5SUM_INDEX);
		exit(EXIT_FAILURE);
	}
}

void draw_legend(WINDOW *inner) {
	int i;
	
	wattron(inner, COLOR_PAIR(2));
	wattron(inner, A_BOLD);
	box(inner, 0, 0);
	for (i=1;i<LINES-5;i++) {
		mvwhline(inner, i, 1, ' ', COLS-6);
	}
	
	wattroff(inner, A_BOLD);
	wattron(inner, COLOR_PAIR(3));
	mvwprintw(inner, 1, 2, "Select current: !!!!!!! | !!!ll | !!!nselect all");
	mvwprintw(inner, 2, 2, "Show diff: !!!!!!! | !!!dit update | !!!erge interactively");
	mvwprintw(inner, 3, 2, "Actions for all selected: !!!eplace config file(s) | !!!elete update(s)"); // | merge !!!nteractively");
	mvwprintw(inner, 4, 2, "Quit: !!!");
	
	wattron(inner, COLOR_PAIR(4));
	mvwprintw(inner, 1, 2 + strlen("Select current: "), "[SPACE]");
	mvwprintw(inner, 1, 2 + strlen("Select current: !!!!!!! | "), "[A]");
	mvwprintw(inner, 1, 2 + strlen("Select current: !!!!!!! | !!!ll | "), "[U]");
	
	mvwprintw(inner, 2, 2 + strlen("Show diff: "), "[ENTER]");
	mvwprintw(inner, 2, 2 + strlen("Show diff: !!!!!!! | "), "[E]");
	mvwprintw(inner, 2, 2 + strlen("Show diff: !!!!!!! | !!!dit update | "), "[M]");
	
	mvwprintw(inner, 3, 2 + strlen("Actions for all selected: "), "[R]");
	mvwprintw(inner, 3, 2 + strlen("Actions for all selected: !!!eplace config file(s) | "), "[D]");
	//mvwprintw(inner, 3, 2 + strlen("Action shortcuts: !!!erge | !!!elete update | merge "), "[I]");

	mvwprintw(inner, 4, 2 + strlen("Quit: "), "[Q]");
	
	wattron(inner, COLOR_PAIR(2) | A_BOLD);
	// TODO: replace COLS - 4/LINES - 7 with a function to determine size of inner
	mvwhline(inner, 5, 1, 0, COLS - 4 -2);
	mvwhline(inner, LINES - 7, 1, 0, COLS - 4 -2);
	
	wrefresh(inner);
}

void draw_background() {
	int i;
	
	attron(A_BOLD);
	attron(COLOR_PAIR(1));
	// why does clear() not work here?
	for (i=0;i<LINES;i++) {
		mvhline(i, 0, ' ', COLS);
	}
	attron(COLOR_PAIR(5));
	mvhline(LINES-2, 3, ' ', COLS-4);
	mvvline(3, COLS-2, ' ', LINES-4);
	attron(COLOR_PAIR(1));
	mvprintw(0,1, PROG_NAME);
	mvprintw(0,strlen(PROG_NAME) + 2, PROG_VERSION);
	mvhline(1, 1, ACS_HLINE, COLS - 2);
	
	refresh();
}

char *get_indent_name(struct node *update, int width) {
	int ct_indents = 0;
	struct node *mynode = update;
	char *start, *name;
	char *indent_name;
	char number[] = "0000";
	int num, remainder, i;
	
	while ((mynode = mynode->parent)) {
		ct_indents++;
	}
	indent_name = calloc(width + 1, sizeof(char));
	if ((start = strstr(update->name, "._cfg"))) {
		name = start+strlen("._cfg????_");
		while (ct_indents > 0) {
			strcat(indent_name, INDENT_STR);
			ct_indents--;
		}
		strcat(indent_name, name);
		strcat(indent_name, " (");
		strncpy(number, start+strlen("._cfg"), 4);
		num = atoi(number) + 1;
		snprintf(indent_name + strlen(indent_name), 4, "%d", num);
		strcat(indent_name, ")");
		if (*(name - 1) == '-') {
			strcat(indent_name, "(merged)");	
		}
	} else {
		start = strrchr(update->name, '/') + 1;
		while (ct_indents > 0) {
			strcat(indent_name, INDENT_STR);
			ct_indents--;
		}
		strcat(indent_name, start);
		strcat(indent_name, "/");
	}
	remainder = width - strlen(indent_name);
	for(i=0;i<remainder;i++) {
		strcat(indent_name, " ");
	}
	
	return indent_name;
}
int count_array_items(struct node *root) {
	int count = 0, i;
	
	for (i=0;i<root->ct_children;i++) {
		count += count_array_items(root->children[i]);
	}
	return 1 + count;
}

void build_item_array(ITEM **item_array, struct node *root, int menu_width) {
	int i = 0;
	
	// fast-forward to the next NULL entry
	while (item_array[i]) {
		i++;
	}
	item_array[i] = new_item(get_indent_name(root, menu_width), "");
	set_item_userptr(item_array[i], root->link);
	
	for (i=0;i<root->ct_children;i++) {
		build_item_array(item_array, root->children[i], menu_width);
	}
}
void free_folded(struct node *root) {
	int i;
	
	for (i=0;i<root->ct_children;i++) {
		free_folded(root->children[i]);
	}
	// if (root->dir) { // it seems name is assigned unconditionally since $sometime
		free(root->name);
	// }
	free(root->children);
	free(root);
}

bool get_confirmation(WINDOW *win, char *action) {
	bool result;
	echo();
	nocbreak();
	char ret[2] = " ";
	wattron(win, COLOR_PAIR(4));
	wattroff(win, A_BOLD);
	// TODO: replace COLS - 4/LINES - 7 with a function to determine size of inner
	mvwprintw(win, LINES - 6, 2, "Do you really want to ");
	wprintw(win, action);
	wprintw(win, " all selected updates? [y/n] ");
	refresh();
	wgetnstr(win, ret, 1);
	if (!strcmp(ret,"y")) {
		result = true;
	} else {
		result = false;
	}
	mvwhline(win, LINES - 6, 1, ' ', COLS - 4 -2);
	noecho();
	cbreak();
	return result;
}
void exit_error(bool expr, char *hint) {
	if (!expr) {
		endwin();
		char *mystring = calloc(sizeof(char), strlen("!!! ERROR: ") + strlen(hint) + 1);
		sprintf(mystring, "!!! ERROR: %s", hint);
		perror(mystring);
		exit(EXIT_FAILURE);
	}
}

char **get_files_list(char *searchpath, char **index, int *max) {
	struct stat mystat, tmpstat;
	int i = 0, j;
	DIR *dirfd;
	struct dirent *file = (struct dirent *)TRUE;
	char *absfile;
	char *myfile;
	bool ignore;
	
	if(-1 == lstat(searchpath, &mystat)) {
		return index;
	}
	if (S_ISDIR(mystat.st_mode)) {
		dirfd = opendir(searchpath);
		if (dirfd) {
			while (file) {
				file = readdir(dirfd);
				if (file && strcmp(file->d_name, ".") && strcmp(file->d_name, "..") && \
					strcmp(file->d_name, ".svn") && strcmp(file->d_name, "CVS")) {
					absfile = (char *)calloc((strlen(searchpath) + strlen("/") + strlen(file->d_name) + 1), sizeof(char *));
					strcpy(absfile, searchpath);
					strcat(absfile, "/");
					strcat(absfile, file->d_name);
					index = get_files_list(absfile, index, max);
					free(absfile);
				}
			}
		} else if (errno == ENOENT) {
			return index;
		} else {
			exit_error(0, searchpath);
		}
		closedir(dirfd);	
	} else if (S_ISREG(mystat.st_mode)) {
		if (!strncmp(strrchr(searchpath, '/')+1, "._cfg", strlen("._cfg"))) {
			if (*(searchpath+strlen(searchpath)-1) != '~' && \
			strcmp(searchpath+strlen(searchpath)-4,".bak")) {
				myfile = get_real_filename(searchpath);
				if (access(myfile, F_OK) != 0) {
					// we don't want phantom updates
					unlink(searchpath);
				} else {
					// we don't want duplicates either
					ignore = FALSE;
					for (j=0;j<(*max) && index[j] != LAST_ENTRY;j++) {
						lstat(index[j], &tmpstat);
						if (tmpstat.st_dev == mystat.st_dev && \
							tmpstat.st_ino == mystat.st_ino) {
								ignore = TRUE;
						}
					}
					if (!ignore) {
						while (i < (*max) && !is_last_entry(index[i])) {
							i++;
						}
						if (i + 1 >= (*max)) {
							(*max)++;
							index = (char **)realloc(index, (*max) * sizeof(char *));
						}
						index[i] = strdup(searchpath);
						index[i+1] = LAST_ENTRY;
					}
				}
				free(myfile);
			}
		}
	}
	return index;
}
