#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <console.h>
#include <com32.h>
#include <syslinux/adv.h>
#include <syslinux/config.h>
#include <setjmp.h>
#include <netinet/in.h>
#include <limits.h>
#include <minmax.h>
#include <linux/list.h>
#include <sys/exec.h>
#include <sys/module.h>
#include <dprintf.h>
#include <core.h>

#include "getkey.h"
#include "menu.h"
#include "cli.h"
#include "config.h"

static struct list_head cli_history_head;

void clear_screen(void)
{
    //dprintf("enter");
    fputs("\033e\033%@\033)0\033(B\1#0\033[?25l\033[2J", stdout);
}

static int mygetkey_timeout(clock_t *kbd_to, clock_t *tto)
{
    clock_t t0, t1;
    int key;

    t0 = times(NULL);
    key = get_key(stdin, *kbd_to ? *kbd_to : *tto);

    /* kbdtimeout only applies to the first character */
    if (*kbd_to)
	*kbd_to = 0;

    t1 = times(NULL) - t0;
    if (*tto) {
	/* Timed out. */
	if (*tto <= (long long)t1)
	    key = KEY_NONE;
	else {
	    /* Did it wrap? */
	    if (*tto > totaltimeout)
		key = KEY_NONE;

	    *tto -= t1;
	}
    }

    return key;
}

static const char * cmd_reverse_search(int *cursor, clock_t *kbd_to,
				       clock_t *tto)
{
    int key;
    int i = 0;
    char buf[MAX_CMDLINE_LEN];
    const char *p = NULL;
    struct cli_command *last_found;
    struct cli_command *last_good = NULL;

    last_found = list_entry(cli_history_head.next, typeof(*last_found), list);

    memset(buf, 0, MAX_CMDLINE_LEN);

    eprintf("\033[1G\033[1;36m(reverse-i-search)`': \033[0m");
    while (1) {
	key = mygetkey_timeout(kbd_to, tto);

	if (key == KEY_CTRL('C')) {
	    return NULL;
	} else if (key == KEY_CTRL('R')) {
	    if (i == 0)
		continue; /* User typed nothing yet */
	    /* User typed 'CTRL-R' again, so try the next */
	    last_found = list_entry(last_found->list.next, typeof(*last_found), list);
	} else if (key >= ' ' && key <= 'z') {
	        buf[i++] = key;
	} else {
	    /* Treat other input chars as terminal */
	    break;
	}

	while (!list_is_last(&last_found->list, &cli_history_head)) {
	    p = strstr(last_found->command, buf);
	    if (p)
	        break;
	    last_found = list_entry(last_found->list.next, typeof(*last_found), list);
	}

	if (!p && !last_good) {
	    return NULL;
	} else if (!p) {
	    continue;
	} else {
	    last_good = last_found;
            *cursor = p - last_good->command;
	}

	eprintf("\033[?7l\033[?25l");
	/* Didn't handle the line wrap case here */
	eprintf("\033[1G\033[1;36m(reverse-i-search)\033[0m`%s': %s",
		buf, last_good->command ? : "");
	eprintf("\033[K\r");
    }

    return last_good ? last_good->command : NULL;
}



const char *edit_cmdline(const char *input, int top /*, int width */ ,
			 int (*pDraw_Menu) (int, int, int),
			 void (*show_fkey) (int))
{
    static char cmdline[MAX_CMDLINE_LEN];
    char temp_cmdline[MAX_CMDLINE_LEN] = { };
    int key, len, prev_len, cursor;
    int redraw = 1;		/* We enter with the menu already drawn */
    int x, y;
    bool done = false;
    const char *ret;
    int width = 0;
    struct cli_command *comm_counter = NULL;
    clock_t kbd_to = kbdtimeout;
    clock_t tto = totaltimeout;

    if (!width) {
	int height;
	if (getscreensize(1, &height, &width))
	    width = 80;
    }

    cmdline[MAX_CMDLINE_LEN - 1] = '\0';

    len = cursor = 0;
    prev_len = 0;
    x = y = 0;

    while (!done) {
	if (redraw > 1) {
	    /* Clear and redraw whole screen */
	    /* Enable ASCII on G0 and DEC VT on G1; do it in this order
	       to avoid confusing the Linux console */
	    clear_screen();
	    if (pDraw_Menu)
		    (*pDraw_Menu) (-1, top, 1);
	    prev_len = 0;
	    // printf("\033[0m\033[2J\033[H");
	}

	if (redraw > 0) {
	    int dy, at;

	    prev_len = max(len, prev_len);

	    /* Redraw the command line */
	    eprintf("\033[?7l\033[?25l");
	    if (y)
		eprintf("\033[%dA", y);
	    eprintf("\033[1G%s ", input);

	    x = strlen(input);
	    y = 0;
	    at = 0;
	    while (at < prev_len) {
		putchar(at >= len ? ' ' : cmdline[at]);
		at++;
		x++;
		if (x >= width) {
		    eprintf("\r\n");
		    x = 0;
		    y++;
		}
	    }
	    eprintf("\033[K\r");

	    dy = y - (cursor + strlen(input) + 1) / width;
	    x = (cursor + strlen(input) + 1) % width;

	    if (dy) {
		eprintf("\033[%dA", dy);
		y -= dy;
	    }
	    if (x)
		eprintf("\033[%dC", x);
	    eprintf("\033[?25h");
	    prev_len = len;
	    redraw = 0;
	}

	key = mygetkey_timeout(&kbd_to, &tto);

	switch (key) {
	case KEY_NONE:
	    /* We timed out. */
	    return NULL;

	case KEY_CTRL('L'):
	    redraw = 2;
	    break;

	case KEY_ENTER:
	case KEY_CTRL('J'):
	    ret = cmdline;
	    done = true;
	    break;

	case KEY_ESC:
	case KEY_CTRL('C'):
	    ret = NULL;
	    done = true;
	    break;

	case KEY_BACKSPACE:
	case KEY_DEL:
	    if (cursor) {
		memmove(cmdline + cursor - 1, cmdline + cursor,
			len - cursor + 1);
		len--;
		cursor--;
		redraw = 1;
	    }
	    break;

	case KEY_CTRL('D'):
	case KEY_DELETE:
	    if (cursor < len) {
		memmove(cmdline + cursor, cmdline + cursor + 1, len - cursor);
		len--;
		redraw = 1;
	    }
	    break;

	case KEY_CTRL('U'):
	    if (len) {
		len = cursor = 0;
		cmdline[len] = '\0';
		redraw = 1;
	    }
	    break;

	case KEY_CTRL('W'):
	    if (cursor) {
		int prevcursor = cursor;

		while (cursor && my_isspace(cmdline[cursor - 1]))
		    cursor--;

		while (cursor && !my_isspace(cmdline[cursor - 1]))
		    cursor--;

#if 0
		memmove(cmdline + cursor, cmdline + prevcursor,
			len - prevcursor + 1);
#else
		{
		    int i;
		    char *q = cmdline + cursor;
		    char *p = cmdline + prevcursor;
		    for (i = 0; i < len - prevcursor + 1; i++)
			*q++ = *p++;
		}
#endif
		len -= (prevcursor - cursor);
		redraw = 1;
	    }
	    break;

	case KEY_LEFT:
	case KEY_CTRL('B'):
	    if (cursor) {
		cursor--;
		redraw = 1;
	    }
	    break;

	case KEY_RIGHT:
	case KEY_CTRL('F'):
	    if (cursor < len) {
		putchar(cmdline[cursor]);
		cursor++;
		x++;
		if (x >= width) {
		    eprintf("\r\n");
		    y++;
		    x = 0;
		}
	    }
	    break;

	case KEY_CTRL('K'):
	    if (cursor < len) {
		cmdline[len = cursor] = '\0';
		redraw = 1;
	    }
	    break;

	case KEY_HOME:
	case KEY_CTRL('A'):
	    if (cursor) {
		cursor = 0;
		redraw = 1;
	    }
	    break;

	case KEY_END:
	case KEY_CTRL('E'):
	    if (cursor != len) {
		cursor = len;
		redraw = 1;
	    }
	    break;

	case KEY_F1:
	case KEY_F2:
	case KEY_F3:
	case KEY_F4:
	case KEY_F5:
	case KEY_F6:
	case KEY_F7:
	case KEY_F8:
	case KEY_F9:
	case KEY_F10:
	case KEY_F11:
	case KEY_F12:
	    if (show_fkey != NULL) {
		(*show_fkey) (key);
		redraw = 1;
	    }
	    break;
	case KEY_CTRL('P'):
	case KEY_UP:
	    {
		if (!list_empty(&cli_history_head)) {
		    struct list_head *next;

		    if (!comm_counter)
			next = cli_history_head.next;
		    else
			next = comm_counter->list.next;

		    comm_counter =
			list_entry(next, typeof(*comm_counter), list);

		    if (&comm_counter->list == &cli_history_head) {
			strcpy(cmdline, temp_cmdline);
		    } else {
			strcpy(cmdline, comm_counter->command);
		    }
		    cursor = len = strlen(cmdline);
		    redraw = 1;
		}
	    }
	    break;
	case KEY_CTRL('N'):
	case KEY_DOWN:
	    {
		if (!list_empty(&cli_history_head)) {
		    struct list_head *prev;

		    if (!comm_counter)
			prev = cli_history_head.prev;
		    else
			prev = comm_counter->list.prev;

		    comm_counter =
			list_entry(prev, typeof(*comm_counter), list);

		    if (&comm_counter->list == &cli_history_head) {
			strcpy(cmdline, temp_cmdline);
		    } else {
			strcpy(cmdline, comm_counter->command);
		    }
		    cursor = len = strlen(cmdline);
		    redraw = 1;
		}
	    }
	    break;
	case KEY_CTRL('R'):
	    {
	         /* 
	          * Handle this case in another function, since it's 
	          * a kind of special.
	          */
	        const char *p = cmd_reverse_search(&cursor, &kbd_to, &tto);
	        if (p) {
	            strcpy(cmdline, p);
		    len = strlen(cmdline);
	        } else {
	            cmdline[0] = '\0';
		    len = 0;
	        }
	        redraw = 1;
	    }
	    break;
	case KEY_TAB:
	    {
		const char *p;
		size_t len;

		/* Label completion enabled? */
		if (nocomplete)
	            break;

		p = cmdline;
		len = 0;
		while(*p && !my_isspace(*p)) {
		    p++;
		    len++;
		}

		print_labels(cmdline, len);
		redraw = 1;
		break;
	    }
	case KEY_CTRL('V'):
	    if (BIOSName)
		eprintf("%s%s%s", syslinux_banner,
			MK_PTR(0, BIOSName), copyright_str);
	    else
		eprintf("%s%s", syslinux_banner, copyright_str);

	    redraw = 1;
	    break;

	default:
	    if (key >= ' ' && key <= 0xFF && len < MAX_CMDLINE_LEN - 1) {
		if (cursor == len) {
		    temp_cmdline[len] = key;
		    cmdline[len++] = key;
		    temp_cmdline[len] = cmdline[len] = '\0';
		    putchar(key);
		    cursor++;
		    x++;
		    if (x >= width) {
			eprintf("\r\n\033[K");
			y++;
			x = 0;
		    }
		    prev_len++;
		} else {
		    memmove(cmdline + cursor + 1, cmdline + cursor,
			    len - cursor + 1);
		    memmove(temp_cmdline + cursor + 1, temp_cmdline + cursor,
			    len - cursor + 1);
		    temp_cmdline[cursor] = key;
		    cmdline[cursor++] = key;
		    len++;
		    redraw = 1;
		}
	    }
	    break;
	}
    }

    eprintf("\033[?7h");

    /* Add the command to the history */
    comm_counter = malloc(sizeof(struct cli_command));
    comm_counter->command = malloc(sizeof(char) * (strlen(ret) + 1));
    strcpy(comm_counter->command, ret);
    list_add(&(comm_counter->list), &cli_history_head);

    return len ? ret : NULL;
}

static int __constructor cli_init(void)
{
	INIT_LIST_HEAD(&cli_history_head);

	return 0;
}

static void __destructor cli_exit(void)
{
	/* Nothing to do */
}
