/*

    Minimum Profit 5.x
    A Programmer's Text Editor

    Text User Interface.

    Copyright (C) 1991-2014 Angel Ortega <angel@triptico.com>

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation; either version 2
    of the License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

    http://triptico.com

*/

/* main TUI namespace */

mp.tui = {};

/** colors **/

mp.colors.menu = {
    text:   [ 'white', 'blue' ],
    flags:  [ 'bright' ]
};

/** code **/

sub mp.tui.prompt(prompt, y)
/* draw a prompt on screen */
{
	/* no y? default to last line */
	if (y == NULL)
		y = mp.window.ty - 1;

	/* delete all possible newlines */
	prompt = sregex(prompt, "/\n/g", ' ');

	mp.tui.attr(mp.colors.menu.attr);
	mp.tui.move(0, y, 1);
	mp.tui.addstr(prompt);
	mp.tui.attr(mp.colors.normal.attr);
	mp.tui.refresh();
}


sub mp.tui.readline(prompt, history, default, flags)
/* the readline function, with special functionality in 'flags' */
{
	local c, r, h, i, x;

	mp.tui.prompt(prompt ~ ' ', flags.y);
	c = mp.tui.getxy();
	r = default || '';

	/* get the history stack */
	h = mp.get_history(history);

	i = 0;
	x = size(r);

	/* store in c[2] the usable size */
	push(c, mp.window.tx - c[0] - 1);

	while (1) {
		local s = r;
		local v = 0;

		/* is the string bigger than the usable size? */
		if (size(r) >= c[2]) {
			/* if x is beyond the usable size,
			   cut from the start */
			if (x >= c[2])
				v = x - c[2];

			s = splice(s, NULL, v, c[2]);
			s = s[1];
		}

		/* if it's a password, change everything to asterisks */
		if (flags.password)
			s = sregex(s, '/./g', flags.password);

		/* draws the string */
		mp.tui.move(c[0], c[1], 1);
		mp.tui.addstr(s);
		mp.tui.move(c[0] + x - v, c[1]);

        mp.tui.refresh();

		local k = mp.tui.getkey();

		if (k eq 'space')
			k = ' ';

		if (k eq 'enter')
			break;
		else
		if (k eq 'escape') {
			r = NULL;
			break;
		}
		else
		if (k eq 'backspace' && x > 0) {
			x--;
			r = splice(r, NULL, x, 1);
			r = r[0];
		}
		else
		if(k eq 'delete') {
			r = splice(r, NULL, x, 1);
			r = r[0];
		}
		else
		if (k eq 'ctrl-u') {
			x = 0;
			r = '';
		}
		else
		if (k eq 'ctrl-k') {
			r = splice(r, NULL, x, -1);
			r = r[0];
		}
		else
		if (k eq 'cursor-up' && size(h)) {
			i--;
			r = h[i % size(h)];
			x = size(r);
		}
		else
		if (k eq 'cursor-down' && size(h)) {
			i++;
			r = h[i % size(h)];
			x = size(r);
		}
		else
		if (k eq 'cursor-left' && x > 0) {
			x--;
		}
		else
		if (k eq 'cursor-right' && x < size(r)) {
			x++;
		}
		else
		if (k eq 'home') {
			x = 0;
		}
		else
		if (k eq 'end') {
			x = size(r);
		}
		else
		if (k eq 'tab' && flags.file) {
            if (regex(r, '/^~/') && ENV['HOME']) {
                r = sregex(r, '/^~/', ENV['HOME']);
            }

			local done = 0;

			while (!done) {
				local l = glob(r ~ '*');

				if (size(l)) {
					ins(l, './', 0);
					ins(l, '../', 1);

					local p = mp.tui.list(prompt, l, 0);

					if (p == NULL) {
						r = NULL;
						done = 1;
					}
					else {
						r = l[p];

						/* if it's not a directory, retry */
						if (regex(r, '@/$@') == NULL)
							done = 1;
					}
				}
                else
                    break;
			}

            if (done)
                break;
		}
		else
		if (size(k) == 1) {
			r = splice(r, k, x, 0);
			r = r[0];
			x++;
		}
	}

	/* if a string was accepted, store in the history */
	if (h != NULL && size(r) && h[-1] ne r)
		push(h, r);

	return r;
}


sub mp.tui.list(prompt, data, pos)
/* select from a list */
{
	local vy, ty, r;

	mp.tui.attr(mp.colors.menu.attr);
	mp.tui.move(0, 0, 1);
	mp.tui.addstr(prompt);
	mp.tui.attr(mp.colors.normal.attr);

	vy = 0;
	ty = mp.window.ty - 1;

	/* clipping regex */
	r = '/^.{1,' ~ (mp.window.tx) ~ '}/';

	if (pos == NULL)
		pos = 0;

	while (1) {
		local k, n;

		/* limits for pos */
		if (pos < 0)
			pos = 0;
		if (pos >= size(data))
			pos = size(data) - 1;

		/* limits for vy */
		if (pos < vy)
			vy = pos;
		if (vy + ty <= pos)
			vy = pos - ty + 1;

		/* draw all the lines */
		n = 0;
		while (n < ty) {
			local l = data[n + vy];

			/* no more data? */
			if (l == NULL)
				break;

			mp.tui.move(0, n + 1, 1);

			if (n + vy == pos)
				mp.tui.attr(mp.colors.cursor.attr);
			else
				mp.tui.attr(mp.colors.normal.attr);

			mp.tui.addstr(regex(sprintf("%-" ~ mp.window.tx ~ "s", l), r));

			n++;
		}

		/* clean the rest of lines */
		mp.tui.attr(mp.colors.normal.attr);
		while (n < ty) {
			mp.tui.move(0, n + 1, 1);
			n++;
		}

        mp.tui.refresh();

		k = mp.tui.getkey();

		if (k eq 'cursor-up')
			pos--;
		else
		if (k eq 'cursor-down')
			pos++;
		else
		if (k eq 'page-up')
			pos -= ty;
		else
		if (k eq 'page-down')
			pos += ty;
		else
		if (k eq 'home')
			pos = 0;
		else
		if (k eq 'end')
			pos = size(data) - 1;
		else
		if (k eq 'enter')
			break;
		else
		if (k eq 'escape') {
			pos = NULL;
			break;
		}
        else
        if (k eq 'mouse-left-button') {
            pos = vy + mp.mouse_y - 1;
            break;
        }
        else
        if ((ord(k) >= ord('a') && ord(k) <= ord('z')) ||
            (ord(k) >= ord('A') && ord(k) <= ord('Z'))) {
            /* search the first item >= k */
            pos = 0;

            while (pos < size(data) - 1) {
                local c = regex(data[pos], '/^./');

                if (ord(c) >= ord(k))
                    break;

                pos++;
            }
        }
	}

	return pos;
}


sub mp.tui.confirm(msg, def, ypos)
{
	local y, n;
	local ret = NULL;

	/* get the initials for localized 'Yes' and 'No' */
	y = regex(L("Yes"), '/^./');
	n = regex(L("No"), '/^./');

	/* add options */
	msg = msg ~ ' (' ~ y ~ '/' ~ n ~ ')';

	if (def != NULL) {
		/* a default option? add to prompt */
		msg = msg ~ ' [' ~ (def && y || n) ~ ']';
	}

	mp.tui.prompt(msg, ypos);

	while (ret == NULL) {
		local k = mp.tui.getkey();

		if (regex(k, '/^' ~ y ~ '$/i'))
			ret = 1;
		if (regex(k, '/^' ~ n ~ '$/i'))
			ret = 2;
		if (k eq 'escape')
			ret = 0;
		if (k eq 'enter')
			ret = (def && 1 || 2);
	}

	return ret;
}


sub mp.tui.draw_scrollbar(doc)
/* draws a vertical read-only scrollbar */
{
    local ty = mp.window.ty - 1;

    /* draw scrollbar only if doc is longer than the window height */
    if (size(doc.txt.lines) > ty) {
        local y1, y2, n;

        y1 = sprintf("%d", (doc.txt.vy * ty) / size(doc.txt.lines));
        y2 = sprintf("%d", ((doc.txt.vy + ty) * ty) / size(doc.txt.lines));

        /* draw at least 1 line of "thumb" */
        if (y1 == y2)
            y2++;

    	mp.tui.attr(mp.colors.normal.attr);

        for (n = 0; n < ty; n++) {
            if (n >= y1 && n < y2) {
            	mp.tui.attr(mp.colors.cursor.attr);
                mp.tui.move(mp.window.tx - 1, n);
                mp.tui.addstr(' ');
            }
        }

    	mp.tui.attr(mp.colors.normal.attr);
    }
}


sub mp.tui.draw(doc)
{
	/* draw the document part */
	mp.tui.doc_draw(doc);

    /* draws the scrollbar */
    mp.tui.draw_scrollbar(doc);

	/* draw the status line */
	mp.tui.attr(mp.colors.normal.attr);
	mp.tui.move(0, mp.window.ty - 1, 1);
	mp.tui.addstr(mp.build_status_line());

	/* draw the 'menu' hint */
	local t = "ctrl-a: " ~ L("Menu");
	mp.tui.move(mp.window.tx - size(t), mp.window.ty - 1);
	mp.tui.addstr(t);

	/* if a hardware cursor is desired, set it */
	if (mp.config.hw_cursor) {
		mp.tui.move(
			mp_c.x2vx(doc.txt.lines[doc.txt.y],
				doc.txt.x - doc.txt.vx),
			doc.txt.y - doc.txt.vy
		);
	}

	mp.tui.refresh();
}


/** interface **/

sub mp_drv.alert(msg)
{
    local k;

	mp.tui.prompt(msg ~ L(" [ENTER]"));

	while ((k = mp.tui.getkey()) ne 'enter' && k ne 'mouse-left-button');
}


sub mp_drv.openfile(prompt)
{
	mp.tui.readline(prompt, 'openfile', NULL, { 'file' => 1 } );
}


sub mp_drv.savefile(prompt)
{
	mp.tui.readline(prompt, 'savefile', NULL, { 'file' => 1 } );
}


sub mp_drv.openfolder(prompt)
{
    mp.tui.readline(prompt, 'openfolder', NULL, {
        file:   1,
        folder: 1
        }
    );
}


sub mp_drv.confirm(msg, def)
{
	mp.tui.confirm(msg, def);
}


sub mp_drv.form(widgets)
{
	local r = [];
	local pos = mp.window.ty - size(widgets);
	local y = pos;

	/* print first all prompts */
	foreach (w, widgets) {
		if (w.type ne 'list')
			mp.tui.prompt(w.label, y++);
	}

	y = pos;

	/* now iterate widgets */
	foreach (w, widgets) {
		local r1 = NULL;

		if (w.type eq 'text')
			r1 = mp.tui.readline(w.label, w.history, w.value,
				{ 'y' => y } );
		else
		if (w.type eq 'password')
			r1 = mp.tui.readline(w.label, NULL, NULL,
				{ 'password' => '*', 'y' => y });
		else
		if (w.type eq 'checkbox') {
			/* return value conversion */
			local c = [ NULL, 1, 0 ];

			r1 = c[mp.tui.confirm(w.label, w.value, y)];
		}
		else
		if (w.type eq 'list')
			r1 = mp.tui.list(w.label, w.list, w.value);

		/* cancellation? */
		if (r1 == NULL) {
			r = NULL;
			break;
		}

		/* store value */
		push(r, r1);
		y++;
	}

	return r;
}


sub mp_drv.menu()
{
	local mx = 0;
	local action = NULL;
	local key = NULL;
    local mouse_x = [];

	while (action == NULL && key ne 'escape') {
		local pos, mo, my, vy;
		local n = 0;

		/* wrap */
		if (mx < 0)
			mx = size(mp.menu) - 1;
		if (mx >= size(mp.menu))
			mx = 0;

		/* draw the menu bar */
		mp.tui.attr(mp.colors.menu.attr);
		mp.tui.move(0, 0, 1);

		while (n < size(mp.menu)) {
			/* get the label */
			local l = L(mp.menu[n][0]);

			/* strip (by now) the & */
			l = sregex(l, '/&/g', NULL);

			mp.tui.attr(mp.colors.menu.attr);
			mp.tui.addstr('   ');

			if (n == mx) {
				pos = mp.tui.getxy();
				mp.tui.attr(mp.colors.cursor.attr);
			}

			mp.tui.addstr(l);

            local p = mp.tui.getxy();
            push(mouse_x, p[0]);

			n++;
		}

		/* get the menu options */
		mo = mp.menu[mx][1];

		/* calculate panel optimal dimensions */
		pos[2] = 0;

		foreach (i, mo) {
			local l = mp.menu_label(i);

			if (size(l) > pos[2])
				pos[2] = size(l);
		}

		/* if the panel will surpass the right margin,
		   move to the left */
		if (pos[0] + pos[2] > mp.window.tx)
			pos[0] = mp.window.tx - pos[2];

		local pty = size(mo);

		if (pty > mp.window.ty - 2)
			pty = mp.window.ty - 2;

		mp.tui.refresh();
		mp.tui.attr(mp.colors.menu.attr);

		my = 0;
		vy = 0;

		while (key ne 'escape') {
			/* draw the options */
			if (my < vy) vy = my;
			if (my > vy + (pty - 1)) vy = my - (pty - 1);

			n = 0;

            local empty = [1 .. pos[2]]->map(sub { ' '; })->join();
            local sep = [1 .. pos[2]]->map(sub { "\x{2500}"; })->join();

			while (n < pty) {
				local l = mp.menu_label(mo[n + vy]);

				/* set color */
				if (n == my - vy)
					mp.tui.attr(mp.colors.cursor.attr);
				else
					mp.tui.attr(mp.colors.menu.attr);

				mp.tui.move(pos[0], 1 + n);

                if (l) {
                    mp.tui.addstr(empty);
				    mp.tui.move(pos[0], 1 + n);
				    mp.tui.addstr(l);
                }
                else
                    mp.tui.addstr(sep);

				n++;
			}

			/* add visual cues that the menu is bigger */
			if (size(mo) > pty) {
				mp.tui.attr(mp.colors.menu.attr);

				if (vy) {
					mp.tui.move(pos[0] + pos[2] - 1, 1);
					mp.tui.addstr('+');
				}

				if (pty + vy < size(mo)) {
					mp.tui.move(pos[0] + pos[2] - 1, pty);
					mp.tui.addstr('+');
				}
			}

			/* move the hw cursor to the selected option */
			mp.tui.move(pos[0] , 1 + my - vy);

			mp.tui.refresh();

			key = mp.tui.getkey();

			if (key eq 'cursor-up') {
				/* move up avoiding separators */
				while (1) {
					if (--my < 0)
						my = size(mo) - 1;

					if (mo[my] ne '-')
						break;
				}
			}
			else
			if (key eq 'cursor-down') {
				/* move down avoiding separators */
				while (1) {
					if (++my >= size(mo))
						my = 0;

					if (mo[my] ne '-')
						break;
				}
			}
			else
			if (key eq 'cursor-right') {
				mx++;
				break;
			}
			else
			if (key eq 'cursor-left') {
				mx--;
				break;
			}
			else
			if (key eq 'enter') {
				action = mo[my];
				break;
			}
            else
            if (key eq 'mouse-left-button') {
                if (mp.mouse_y == 0) {
                    mx = 0;
                    foreach (x, mouse_x) {
                        if (x > mp.mouse_x)
                            break;
                        mx++;
                    }
                }
                else
                if (mp.mouse_x < pos[0] ||
                    mp.mouse_x > pos[0] + pos[2] - 1 ||
                    mp.mouse_y > size(mo)) {
                    key = 'escape';
                }
                else {
                    local a = mo[mp.mouse_y - 1];
                    if (a ne '-')
                        action = a;
                }

                break;
            }
		}

        mp.tui.attr(mp.color.normal.attr);
        mp.tui.draw(mp.active());
	}

	mp.tui.attr(mp.color.normal.attr);

	if (action != NULL)
		mp.process_action(action);

	return NULL;
}


sub mp_drv.busy(onoff)
{
	mp.tui.prompt(onoff && L("Please, wait...") || '');
}


sub mp_drv.main_loop()
{
	while (!mp_c.exit_requested()) {
		mp.tui.draw(mp.active());

		mp.process_event(mp.tui.getkey());
	}
}


/* returns the main namespace */
mp.tui;
