#define _LARGEFILE64_SOURCE	/* required for GLIBC to enable stat64 and friends */
#include <ctype.h>
#include <sys/types.h>
#include <regex.h>
#include <string.h>
#include <stdlib.h>
#include <pwd.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>

#include "mt.h"
#include "error.h"
#include "my_pty.h"
#include "utils.h"
#include "term.h"
#include "help.h"
#include "mem.h"
#include "ui.h"
#include "misc.h"
#include "globals.h"

int scrollback_search_to_new_window(buffer *pbuf, char *org_title, char *find_str, mybool_t case_insensitive);

int find_string(buffer *pbuf, char *find, int offset, char direction, mybool_t case_insensitive)
{
	int loop, index = -1, rc;
	regex_t regex;

	/* compile the searchstring (which can be a regular expression) */
	if ((rc = regcomp(&regex, find, REG_EXTENDED | (case_insensitive == MY_TRUE?REG_ICASE:0))))
	{
		regexp_error_popup(rc, &regex);

		return -1;	/* failed -> not found */
	}

	if (direction == 1)
	{
		for(loop=offset; loop<pbuf -> curpos; loop++)
		{
			if ((pbuf -> be)[loop].Bline == NULL)
				continue;

			if (regexec(&regex, (pbuf -> be)[loop].Bline, 0, NULL, 0) == 0)
			{
				index = loop;
				break;
			}
		}
	}
	else if (direction == (char)-1)
	{
		for(loop=offset; loop>=0; loop--)
		{
			if ((pbuf -> be)[loop].Bline == NULL)
				continue;

			if (regexec(&regex, (pbuf -> be)[loop].Bline, 0, NULL, 0) == 0)
			{
				index = loop;
				break;
			}
		}
	}

	regfree(&regex);

	return index;
}

void scrollback_find_popup(char **find_str, mybool_t *pcase_insensitive)
{
	char *dummy;
	NEWWIN *mywin = create_popup(5, 40);

	win_header(mywin, "Find");

	dummy = edit_string(mywin, 3, 2, 40, 80, 0, reuse_searchstring?*find_str:NULL, HELP_SCROLLBACK_EDIT_SEARCH_STRING, -1, &search_h, pcase_insensitive);
	myfree(*find_str);
	*find_str = dummy;

	delete_popup(mywin);
}

void scrollback_savefile(buffer *pbuf)
{
	char *file = NULL;
	NEWWIN *mywin = create_popup(8, 40);

	win_header(mywin, "Save buffer to file");

	mvwprintw(mywin -> win, 4, 2, "Select file");
	file = edit_string(mywin, 5, 2, 40, find_path_max(), 0, NULL, HELP_SCROLLBACK_SAVEFILE_ENTER_FILENAME, -1, &cmdfile_h, NULL);
	if (file)
	{
		FILE *fh = fopen(file, "w");
		if (fh)
		{
			int loop;

			for(loop=0; loop<pbuf -> curpos; loop++)
			{
				if ((pbuf -> be)[loop].Bline)
				{
					char display;
					char *error;
					int dummy = -1;
					regmatch_t *pmatch = NULL;

					/* check filter */
					(void)check_filter((pbuf -> be)[loop].pi, (pbuf -> be)[loop].Bline, &pmatch, &error, &dummy, 0, &display);
					if (error)
					{
						fprintf(fh, "%s\n", error);
						myfree(error);
					}
					if (display)
					{
						fprintf(fh, "%s\n", USE_IF_SET((pbuf -> be)[loop].Bline, ""));
					}

					if (pmatch) myfree(pmatch);
				}
			}

			fclose(fh);
		}
		else
		{
			error_popup("Save scrollback buffer", -1, "Cannot write to file, reason: %s", strerror(errno));
		}
	}

	delete_popup(mywin);
}

int get_lines_needed(char *string, int terminal_width)
{
	if (string)
		return (strlen(string) + terminal_width - 1) / terminal_width;
	else
		return 1;
}

void scrollback_displayline(int window_nr, NEWWIN *win, buffer *pbuf, int buffer_offset, int terminal_offset, int offset_in_line, mybool_t force_to_winwidth, char show_winnr)
{
	char *cur_line = (pbuf -> be)[buffer_offset].Bline;

	wmove(win -> win, terminal_offset, 0);

	if (cur_line)
	{
		proginfo *cur_line_meta = (pbuf -> be)[buffer_offset].pi;
		double ts = (pbuf -> be)[buffer_offset].ts;
		char old_color_settings = 0;

		if (scrollback_no_colors && cur_line_meta != NULL)
		{
			old_color_settings = cur_line_meta -> cdef.colorize;
			cur_line_meta -> cdef.colorize = 0;
		}
		if (IS_MARKERLINE(cur_line_meta))
		{
			color_print(window_nr, win, cur_line_meta, cur_line, NULL, -1, MY_FALSE, 0, 0, ts, show_winnr);
		}
		else /* just a buffered line */
		{
			char *error = NULL;
			regmatch_t *pmatch = NULL;
			int matching_regex = -1;
			char display;
			(void)check_filter(cur_line_meta, cur_line, &pmatch, &error, &matching_regex, 0, &display);
			if (error)
			{
				color_print(window_nr, win, cur_line_meta, error, NULL, -1, MY_FALSE, 0, 0, ts, show_winnr);
				myfree(error);
			}
			if (display)
			{
				if (offset_in_line)
				{
					int line_len = strlen(cur_line);
					int new_size = 0;

					if (offset_in_line < line_len)
						new_size = min(win -> width, line_len - offset_in_line);

					color_print(window_nr, win, cur_line_meta, cur_line, pmatch, matching_regex, force_to_winwidth, offset_in_line, offset_in_line + new_size, ts, show_winnr);
				}
				else
				{
					color_print(window_nr, win, cur_line_meta, cur_line, pmatch, matching_regex, force_to_winwidth, 0, 0, ts, show_winnr);
				}
			}

			myfree(pmatch);
		}
		if (scrollback_no_colors && cur_line_meta != NULL)
		{
			cur_line_meta -> cdef.colorize = old_color_settings;
		}
	}
	else /* an empty line */
	{
		/* do nothing */
	}
}

int scrollback_do(int window_nr, buffer *pbuf, int *winnrs, char *header)
{
	int rc = 0;
	char *find = NULL;
	NEWWIN *mywin1, *mywin2;
	int nlines = max_y - 6, ncols = max_x - 6;
	int offset = max(0, pbuf -> curpos - nlines); // FIXME: aantal regels laten afhangen van lengte
	char redraw = 2;
	int line_offset = 0;
	char show_winnr = default_sb_showwinnr;
	mybool_t case_insensitive = re_case_insensitive;

	if (global_highlight_str)
	{
		find = mystrdup(global_highlight_str, __FILE__, __PRETTY_FUNCTION__, __LINE__);
	}

	mywin1 = create_popup(max_y - 4, max_x - 4);

	mywin2 = create_popup(nlines, ncols);
	scrollok(mywin2 -> win, FALSE); /* supposed to always return OK, according to the manpage */

	for(;;)
	{
		int c, uc;

		if (redraw == 2)
		{
			int index = 0;
			int lines_used = 0;

			ui_inverse_on(mywin1);
			mvwprintw(mywin1 -> win, nlines + 1, 1, "%s - %d buffered lines", shorten_filename(header, max(24, ncols - 24)), pbuf -> curpos);
			ui_inverse_off(mywin1);

			if (!no_linewrap) ui_inverse_on(mywin1);
			mvwprintw(mywin1 -> win, nlines + 1, ncols - 8, "LINEWRAP");
			if (!no_linewrap) ui_inverse_off(mywin1);

			werase(mywin2 -> win);

			if (!no_linewrap && line_offset > 0)
			{
				int temp_line_offset = line_offset;
				int n_chars_to_display_left = strlen((pbuf -> be)[offset].Bline) - temp_line_offset;

				while(lines_used < nlines && n_chars_to_display_left > 0)
				{
					scrollback_displayline(winnrs?winnrs[offset]:window_nr, mywin2, pbuf, offset, lines_used, temp_line_offset, 1, show_winnr);

					temp_line_offset += ncols;
					n_chars_to_display_left -= ncols;

					lines_used++;
				}

				index++;
			}

			for(;(offset + index) < pbuf -> curpos && lines_used < nlines;)
			{
				int lines_needed = get_lines_needed((pbuf -> be)[offset + index].Bline, ncols);

				if (no_linewrap || lines_needed == 1)
				{
					scrollback_displayline(winnrs?winnrs[offset + index]:window_nr, mywin2, pbuf, offset + index, lines_used, no_linewrap?line_offset:0, no_linewrap, show_winnr);
					lines_used++;
				}
				else
				{
					int cur_line_offset = 0;

					while(lines_used < nlines && lines_needed > 0)
					{
						scrollback_displayline(winnrs?winnrs[offset + index]:window_nr, mywin2, pbuf, offset + index, lines_used, cur_line_offset, 1, show_winnr);
						cur_line_offset += ncols;
						lines_used++;
						lines_needed--;
					}
				}

				index++;
			}

			redraw = 1;
		}

		if (redraw == 1)
		{
			mydoupdate();

			redraw = 0;
		}

		c = wait_for_keypress(HELP_SCROLLBACK_HELP, 0, NULL, 1);
		uc = toupper(c);

		if (c == 'q' || uc == 'X' || c == abort_key || c == KEY_CLOSE || c == KEY_EXIT)
		{
			break;
		}
		else if (c == 'Q' || c == -1)		/* Q: close whole stack of scrollbackwindows, -1: something got closed */
		{
			rc = -1;
			break;
		}
		else if (c == 20 && winnrs != NULL)	/* ^t */
		{
			show_winnr = 1 - show_winnr;
			redraw = 2;
		}
		else if (c == 'Y')
		{
			no_linewrap = !no_linewrap;
			redraw = 2;
			line_offset = 0;
		}
		else if (c == 't')
		{
			statistics_menu();
		}
		else if ((c == KEY_LEFT ||
					c == KEY_BACKSPACE)
				&& no_linewrap)
		{
			if (line_offset > 0)
				line_offset--;

			redraw = 2;
		}
		else if (c == KEY_SLEFT && no_linewrap)
		{
			if (line_offset >= (ncols / 2))
				line_offset -= (ncols / 2);
			else
				line_offset = 0;

			redraw = 2;
		}
		else if (c == KEY_SRIGHT && no_linewrap)
		{
			line_offset += (ncols / 2);

			redraw = 2;
		}
		else if (c == KEY_BEG && no_linewrap)
		{
			if (line_offset)
			{
				line_offset = 0;
				redraw = 2;
			}
		}
		else if (c == KEY_BTAB)
		{
			if (line_offset >= 4)
				line_offset -= 4;
			else
				line_offset = 0;

			redraw = 2;
		}
		else if (c == KEY_RIGHT && no_linewrap)
		{
			line_offset++;
			redraw = 2;
		}
		else if ((c == KEY_UP ||
					c == 'y' ||
					c == 25  || /* ^y */
					c == 'k' ||
					/* c == 11  || *//* ^k */
					c == 16)    /* ^p */
				&& (offset > 0 || (!no_linewrap && line_offset > 0)))
		{
			if (no_linewrap)
			{
				offset--;
			}
			else if (line_offset > 0)
			{
				line_offset = max(0, line_offset - ncols);
			}
			else
			{
				offset--;

				line_offset = (get_lines_needed((pbuf -> be)[offset].Bline, ncols) - 1) * ncols;
			}

			wmove(mywin2 -> win, 0, 0);
			winsdelln(mywin2 -> win, 1);

			scrollback_displayline(winnrs?winnrs[offset]:window_nr, mywin2, pbuf, offset, 0, line_offset, no_linewrap, show_winnr);

			redraw = 1;
		}
		else if ((c == KEY_DOWN ||
					c == 'e' ||
					c == 5   || /* ^e */
					c == 'j' ||
					c == 14  || /* ^n */
					c == 13  ||
					c == KEY_ENTER)
				&& offset < (pbuf -> curpos - 1))
		{
			if (no_linewrap)
			{
				offset++;
			}
			else if (strlen((pbuf -> be)[offset].Bline) > (line_offset + ncols))
			{
				line_offset += ncols;
			}
			else if (offset < (pbuf -> curpos - 1))
			{
				if (strlen((pbuf -> be)[offset].Bline) > (line_offset + ncols))
					line_offset += ncols;
				else
				{
					line_offset = 0;
					offset++;
				}
			}

			redraw = 2;
		}
		else if ((c == KEY_NPAGE ||
					c == 'f' ||
					c == 6   || /* ^f */
					c == ('V' - 65 + 1) || /* ^v */
					c == ' ' ||
					c == 'z' ||
					c == 'u' ||
					c == ('U' - 65 + 1))   /* ^u */
				&& offset < (pbuf -> curpos - 1))
		{
			if (no_linewrap)
			{
				offset += nlines;
				if (offset >= pbuf -> curpos)
					offset = pbuf -> curpos - 1;
			}
			else
			{
				int n_lines_to_move = nlines;

				while(n_lines_to_move > 0 && offset < (pbuf -> curpos))
				{
					if (line_offset > 0)
					{
						if (line_offset + ncols >= strlen((pbuf -> be)[offset].Bline))
						{
							line_offset = 0;
							offset++;
							n_lines_to_move--;
						}
						else
						{
							line_offset += ncols;
							n_lines_to_move--;
						}
					}
					else
					{
						n_lines_to_move -= get_lines_needed((pbuf -> be)[offset].Bline, ncols);
						offset++;
					}
				}

				if (n_lines_to_move < 0)
					line_offset = (-n_lines_to_move) * ncols;
			}

			redraw = 2;
		}
		else if ((c == KEY_PPAGE ||
					c == 'b' ||
					c == 2   ||     /* ^b */
					c == 'w' ||
					c == 'd' ||
					c == 4)         /* ^d */
				&& offset > 0)
		{
			if (no_linewrap)
			{
				offset -= nlines;
				if (offset < 0)
					offset = 0;
			}
			else
			{
				int n_lines_to_move = nlines;

				if (line_offset)
					n_lines_to_move -= line_offset / ncols;

				while(n_lines_to_move > 0 && offset > 0)
				{
					offset--;

					n_lines_to_move -= get_lines_needed((pbuf -> be)[offset].Bline, ncols);

					if (n_lines_to_move < 0)
					{
						line_offset = (get_lines_needed((pbuf -> be)[offset].Bline, ncols) + n_lines_to_move) * ncols;
					}
				}
			}
					
			redraw = 2;
		}
		else if ((c == KEY_HOME ||
					c == 'g' ||
					c == '<' ||
					c == KEY_SBEG)
				&& offset > 0)
		{
			line_offset = offset = 0;
			redraw = 2;
		}
		else if ((c == KEY_END ||
					c == 'G' ||
					c == '>' ||
					c == KEY_SEND)
				&& offset < (pbuf -> curpos - 1))
		{
			offset = pbuf -> curpos - 1;
			redraw = 2;
		}
		else if (uc == 'R' || c == ('R' - 65 + 1) || c == ('L' - 65 + 1) || c == KEY_REFRESH)
		{
			redraw = 2;
		}
		else if (c == ('K' - 65 + 1) || c == KEY_MARK)
		{
			scrollback_find_popup(&find, &case_insensitive);

			if (find)
			{
				int rc;

				regfree(&global_highlight_re);
				myfree(global_highlight_str);
				global_highlight_str = NULL;

				if ((rc = regcomp(&global_highlight_re, find, (case_insensitive == MY_TRUE?REG_ICASE:0) | REG_EXTENDED)))
				{
					regexp_error_popup(rc, &global_highlight_re);
					myfree(find);
				}
				else
				{
					global_highlight_str = find;
				}

				redraw = 2; /* force redraw */
			}

		}
		else if (c == 'f' || c == '/' || c == '?' || c == KEY_FIND || c == KEY_SFIND)
		{
			char direction = (c == '?' || c == KEY_SFIND) ? -1 : 1;

			scrollback_find_popup(&find, &case_insensitive);

			if (find)
			{
				if (scrollback_search_new_window)
				{
					if (scrollback_search_to_new_window(pbuf, header, find, case_insensitive) == -1)
					{
						/* cascaded close */
						rc = -1;
						break;
					}
				}
				else
				{
					int new_f_index;

					redraw = 2; /* force redraw */

					regfree(&global_highlight_re);
					myfree(global_highlight_str);
					global_highlight_str = NULL;

					new_f_index = find_string(pbuf, find, 0, direction, case_insensitive);
					if (new_f_index == -1)
					{
						wrong_key();
					}
					else
					{
						offset = new_f_index;
						line_offset = 0;
					}
				}
			}
		}
		else if (uc == 'N' || c == KEY_NEXT || c == KEY_PREVIOUS || c == KEY_SNEXT)
		{
			if (find != NULL)
			{
				char direction = (c == 'n' || c == KEY_NEXT) ? 1 : -1;
				int start_offset = offset + direction;
				int new_f_index = find_string(pbuf, find, start_offset, direction, case_insensitive);
				if (new_f_index == -1)
				{
					wrong_key();
				}
				else
				{
					redraw = 2; /* force redraw */
					offset = new_f_index;
					line_offset = 0;
				}
			}
			else
			{
				wrong_key();
			}
		}
		else if (c == 's' || c == KEY_SAVE)
		{
			scrollback_savefile(pbuf);
			redraw = 2;	/* force redraw */
		}
		else if (c == 'h')
		{
			show_help(HELP_SCROLLBACK_HELP);
		}
		else if (c == 'c')
		{
			toggle_colors();
			redraw = 2;	/* force redraw */
		}
		else if (c == 'i')
		{
			info();
		}
		else if (c == 'T')
		{
			statistics_menu();
		}
		else if (c == 20)
		{
			toggle_subwindow_nr();
			redraw = 2;	/* force redraw */
		}
		else
		{
			wrong_key();
		}
	}

	delete_popup(mywin2);
	delete_popup(mywin1);

	myfree(find);

	return rc;
}

void scrollback(void)
{
	int window = 0;

	if (nfd > 1)
	{
		window = select_window(HELP_SCROLLBACK_SELECT_WINDOW, NULL);
	}

	if (window != -1)
	{
		if (lb[window].bufferwhat == 0)
			error_popup("Scrollback", HELP_SCROLLBACK_NO_MARK, "Cannot scrollback: buffering is disabled.");
	}

	if (window != -1 && lb[window].bufferwhat != 0)
	{
		int header_size = strlen(pi[window].filename) + 4;
		char *header = (char *)mymalloc(header_size + 1, __FILE__, __PRETTY_FUNCTION__, __LINE__);
		snprintf(header, header_size, "%02d] %s", window, pi[window].filename);
		scrollback_do(window, &lb[window], NULL, header);
		myfree(header);
	}
}

void merged_scrollback_with_search(char *search_for, mybool_t case_insensitive)
{
	int lc_size = nfd * sizeof(int);
	int *last_check = (int *)mymalloc(lc_size, __FILE__, __PRETTY_FUNCTION__, __LINE__);
	int *winnr = NULL;
	buffer buf;
	regex_t reg;
	int rc;

	memset(last_check, 0x00, lc_size);
	memset(&buf, 0x00, sizeof(buf));

	/* compile the search string which is supposed to be a valid regular
	 * expression
	 */
	if (search_for)
	{
		if ((rc=regcomp(&reg, search_for, REG_EXTENDED | (case_insensitive == MY_TRUE?REG_ICASE:0))))
		{
			regexp_error_popup(rc, &reg);
			return;
		}
	}

	/* merge all windows into one */
	for(;;)
	{
		int loop;
		double chosen_ts = (double)(((long long int)1) << 62);
		int chosen_win = -1;
		int curline;
		char *string;
		int checked_all = 0;

		for(loop=0; loop<nfd; loop++)
		{
			if (lb[loop].curpos == last_check[loop])
			{
				checked_all++;
				continue;
			}

			if (search_for != NULL && lb[loop].be[last_check[loop]].Bline != NULL)
			{
				rc = regexec(&reg, lb[loop].be[last_check[loop]].Bline, 0, NULL, 0);

				/* did not match? don't add and continue */
				if (rc == REG_NOMATCH)
				{
					continue;
				}

				/* is it an error? then popup and abort */
				if (rc != 0)
				{
					regexp_error_popup(rc, &reg);
					break;
				}
			}

			if (lb[loop].be[last_check[loop]].ts <= chosen_ts)
			{
				chosen_ts = lb[loop].be[last_check[loop]].ts;
				chosen_win = loop;
			}
		}

		if (chosen_win == -1)
		{
			if (checked_all == nfd)
				break;

			for(loop=0; loop<nfd; loop++)
			{
				if (lb[loop].curpos > last_check[loop])
					last_check[loop]++;
			}

			continue;
		}

		if (!IS_MARKERLINE(lb[chosen_win].be[last_check[chosen_win]].pi))
		{
			/*** ADD LINE TO BUFFER ***/
			buf.be = (buffered_entry *)myrealloc(buf.be, sizeof(buffered_entry) * (buf.curpos + 1), __FILE__, __PRETTY_FUNCTION__, __LINE__);
			winnr  = (int *)myrealloc(winnr, sizeof(int) * (buf.curpos + 1), __FILE__, __PRETTY_FUNCTION__, __LINE__);
			curline = buf.curpos++;
			/* add the logline itself */
			string = lb[chosen_win].be[last_check[chosen_win]].Bline;
			if (string)
				buf.be[curline].Bline = mystrdup(string, __FILE__, __PRETTY_FUNCTION__, __LINE__);
			else
				buf.be[curline].Bline = NULL;
			/* remember pointer to subwindow (required for setting colors etc.) */
			buf.be[curline].pi = lb[chosen_win].be[last_check[chosen_win]].pi;
			buf.be[curline].ts = lb[chosen_win].be[last_check[chosen_win]].ts;
			/* remember window nr. */
			winnr[curline] = chosen_win;
		}

		last_check[chosen_win]++;
	}

	if (buf.curpos == 0)
		error_popup("Search in all windows", -1, "Nothing found.");
	else
	{
		char *header;

		if (search_for)
		{
			char *help = "Searched for: ";
			int len = strlen(help) + strlen(search_for) + 1;
			header = mymalloc(len, __FILE__, __PRETTY_FUNCTION__, __LINE__);
			snprintf(header, len, "%s%s", help, search_for);
		}
		else
		{
			char *help = "Merge view";
			header = mymalloc(strlen(help) + 1, __FILE__, __PRETTY_FUNCTION__, __LINE__);
			sprintf(header, "%s", help);
		}

		scrollback_do(-1, &buf, winnr, header);

		myfree(header);
	}

	delete_be_in_buffer(&buf);

	myfree(winnr);

	myfree(last_check);
}

int scrollback_search_to_new_window(buffer *pbuf, char *org_title, char *find_str, mybool_t case_insensitive)
{
	int loop, rc;
	regex_t regex;
	buffer cur_lb;
	char *new_header;

	/* compile the searchstring (which can be a regular expression) */
	if ((rc = regcomp(&regex, find_str, REG_EXTENDED | (case_insensitive == MY_TRUE?REG_ICASE:0))))
	{
		regexp_error_popup(rc, &regex);
		return 0;
	}

	memset(&cur_lb, 0x00, sizeof(buffer));

	for(loop=0; loop<pbuf -> curpos; loop++)
	{
		if ((pbuf -> be)[loop].Bline == NULL)
			continue;

		if (regexec(&regex, (pbuf -> be)[loop].Bline, 0, NULL, 0) == 0)
		{
			cur_lb.be = myrealloc(cur_lb.be, (cur_lb.curpos + 1) * sizeof(buffered_entry), __FILE__, __PRETTY_FUNCTION__, __LINE__);
			cur_lb.be[cur_lb.curpos].Bline = (pbuf -> be)[loop].Bline;
			cur_lb.be[cur_lb.curpos].pi    = (pbuf -> be)[loop].pi;
			cur_lb.curpos++;
		}
	}

	new_header = (char *)mymalloc(strlen(org_title) + 1 + strlen(find_str) + 1, __FILE__, __PRETTY_FUNCTION__, __LINE__);
	sprintf(new_header, "%s %s", org_title, find_str);

	rc = scrollback_do(-1, &cur_lb, NULL, new_header);

	myfree(new_header);

	myfree(cur_lb.be);

	regfree(&regex);

	return rc;
}


syntax highlighted by Code2HTML, v. 0.9.1