#include <config.h>

#include <ctype.h>
#include <stdio.h>
#include <string.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include "global.h"
#include "tty.h"		/* COLS */
#include "color.h"		/* dialog_colors */
#include "dialog.h"
#include "widget.h"
#include "setup.h"		/* For profile_bname */
#include "profile.h"		/* Load/save directories hotlist */
#include "wtools.h"		/* QuickDialog */
#include "panel.h"		/* current_panel */
#include "main.h"		/* repaint_screen */
#include "hotlist.h"
#include "key.h"		/* KEY_M_CTRL */
#include "command.h"		/* cmdline */
#include "glibcompat.h"		/* g_strlcpy for glib < 2.0 */

#define UX		5
#define UY		2

#define BX		UX
#define BY		(LINES - 6)

#define BUTTONS		(sizeof(hotlist_but)/sizeof(struct _hotlist_but))
#define LABELS          3
#define B_ADD_CURRENT	B_USER
#define B_REMOVE        (B_USER + 1)
#define B_NEW_GROUP	(B_USER + 2)
#define B_NEW_ENTRY	(B_USER + 3)
#define B_UP_GROUP	(B_USER + 4)
#define B_INSERT	(B_USER + 5)
#define B_APPEND	(B_USER + 6)
#define B_MOVE		(B_USER + 7)
#define B_EDIT		(B_USER + 8)

/* separates hotkey and label in the list: */
static char *hotkey_separator = "   ";
/* marks groups in the list; it must not be longer than hotkey_separator 
  because it will be displayed in the separator column: */
static char *group_mark = "->";
/* "go to parent" entry in subgroups */
static char *parent_mark = "..";
/* can be made configurable */
int show_parent_mark = 1;
/* storage buffer for one read-forward token */
static int unhot_token = 0;
/* duplicate hotkeys found during file read */
int hotkey_dupes = 0;

#ifdef USE_VFS
#include "../vfs/gc.h"
#define B_FREE_ALL_VFS	(B_USER + 9)
#define B_REFRESH_VFS	(B_USER + 10)
#endif

int hotlist_has_dot_dot = 1;

static WListbox *l_hotlist;
static WListbox *l_movelist;

static Dlg_head *hotlist_dlg;
static Dlg_head *movelist_dlg;

static WLabel *pname, *pname_group, *movelist_group;

enum HotListType {
    HL_TYPE_GROUP,
    HL_TYPE_ENTRY,
    HL_TYPE_COMMENT,
    HL_TYPE_DOTDOT
};

static struct {
    /*
     * these parameters are intended to be user configurable
     */
    int		expanded;	/* expanded view of all groups at startup */

    /*
     * these reflect run time state
     */

    int		loaded;		/* hotlist is loaded */
    int		readonly;	/* hotlist readonly */
    int		file_error;	/* parse error while reading file */
    int		running;	/* we are running dlg (and have to
    				   update listbox */
    int		moving;		/* we are in moving hotlist currently */
    int		modified;	/* hotlist was modified */
    int		type;		/* LIST_HOTLIST || LIST_VFSLIST */
}   hotlist_state;

static struct _hotlist_but {
    int ret_cmd, flags, y, x;
    const char *text;
    int   type;
} hotlist_but[] = {
    /* two rows of hotlist buttons */
    /* bottom row */
    { B_NEW_GROUP, NORMAL_BUTTON,	1,	45,	N_("New group <F7>"),	LIST_HOTLIST},
    { B_MOVE, NORMAL_BUTTON,		1,	30,	N_("Move <F6>"),	LIST_HOTLIST},
    { B_REMOVE, NORMAL_BUTTON,		1,	 0,	N_("Delete <Del>"),	LIST_HOTLIST},
    /* top row */
    { B_CANCEL, NORMAL_BUTTON,		0,	53,	N_("Cancel"),		LIST_HOTLIST|LIST_VFSLIST|LIST_MOVELIST},
    { B_NEW_ENTRY, NORMAL_BUTTON,	0,	30,	N_("New entry <Ins>"),	LIST_HOTLIST},
    { B_EDIT, NORMAL_BUTTON,		0,	16,	N_("Edit <F4>"),	LIST_HOTLIST},
    { B_ENTER, DEFPUSH_BUTTON,		0,	 0,	N_("Go to"),		LIST_HOTLIST|LIST_VFSLIST},
    /* movelist buttons */
    { B_INSERT, NORMAL_BUTTON,		0,	15,	N_("Insert before"),	LIST_MOVELIST},
    { B_APPEND, DEFPUSH_BUTTON,		0,	0,	N_("Insert after"),	LIST_MOVELIST},
#ifdef	USE_VFS
    { B_REFRESH_VFS, NORMAL_BUTTON,	0,	43,	N_("&Refresh"),		LIST_VFSLIST},
    { B_FREE_ALL_VFS, NORMAL_BUTTON,	0,	20,	N_("Fr&ee VFSs now"),	LIST_VFSLIST},
#endif

};

/* Directory hotlist */
static struct hotlist{
    enum  HotListType type;
    char *directory;
    char *label;
    int hotkey;
    struct hotlist *head;
    struct hotlist *up;
    struct hotlist *next;
} *hotlist = NULL;

static struct hotlist *current_group;

static void init_movelist (int, struct hotlist *);
static void add_new_group_cmd (void);
static void add_new_entry_cmd (void);
static void remove_from_hotlist (struct hotlist *entry);
static void load_hotlist (void);
static void add_dotdot_to_list (void);

#define new_hotlist() g_new0(struct hotlist, 1)

static void
hotlist_refresh (Dlg_head * dlg)
{
    common_dialog_repaint (dlg);
    attrset (COLOR_NORMAL);
    draw_box (dlg, 2, 5, dlg->lines - (hotlist_state.moving ? 6 : 10),
	      dlg->cols - (UX * 2));
    if (!hotlist_state.moving)
	draw_box (dlg, dlg->lines - 8, 5, 3, dlg->cols - (UX * 2));
}

/* If current->data is 0, then we are dealing with a VFS pathname */
static inline void
update_path_name (void)
{
    const char *text = "";
    char *p;
    WListbox *list = hotlist_state.moving ? l_movelist : l_hotlist;
    Dlg_head *dlg = list->widget.parent;

    /* calculate and set the text for the "directory path" box at bottom */
    if (list->current) {
	if (list->current->data != 0) {
	    struct hotlist *hlp = (struct hotlist *) list->current->data;

	    if (hlp->type == HL_TYPE_ENTRY ||
		hlp->type == HL_TYPE_DOTDOT)
		text = hlp->directory;
	    else if (hlp->type == HL_TYPE_GROUP)
		text = _("Subgroup - press ENTER to see list");
	} else {
	    text = list->current->text;
	}
    }
    if (!hotlist_state.moving)
	label_set_text (pname,
			name_trunc (text, dlg->cols - (UX * 2 + 4)));

    /* calculate and set the title for the listbox */
    p = g_strconcat (" ", current_group->label, " ", (char *) NULL);
    if (!hotlist_state.moving)
	label_set_text (pname_group,
			name_trunc (p, dlg->cols - (UX * 2 + 4)));
    else
	label_set_text (movelist_group,
			name_trunc (p, dlg->cols - (UX * 2 + 4)));
    g_free (p);

    dlg_redraw (dlg);
}

#define CHECK_BUFFER  \
do { \
    int               i; \
\
    if ((i = strlen (current->label) + 3) > buflen) { \
      g_free (buf); \
      buf = g_malloc (buflen = 1024 * (i/1024 + 1)); \
    } \
    buf[0] = '\0'; \
} while (0)

int alert_duplicate_hotkey (char *title, int hotkey) 
{
    static struct hotlist *iter;

    if (!hotkey) return 0;

    for (iter = current_group->head; iter; iter = iter->next)
      if (iter->hotkey == hotkey) {
          message (D_ERROR, title, "This hotkey is already in use in this group");
          return 1;
      }

    return 0;
}

int check_duplicate_hotkey (int hotkey) 
{
    static struct hotlist *iter;

    if (hotkey) {
        for (iter = current_group->head; iter; iter = iter->next)
  	    if (iter->hotkey == hotkey) {
		hotkey_dupes = 1;
                return 0;
            }
    }
    return hotkey;
}

static void fill_listbox (void)
{
    struct hotlist *current = current_group->head;
    static char *buf = NULL;
    static int   buflen = 1024;
    char *parent_mark_formatted = g_strconcat (" ", hotkey_separator, parent_mark, NULL);

    if (!buf)
	buf = g_malloc (buflen);
    buf[0] = '\0';
    if (show_parent_mark)
        if (current_group->up != current_group) {
            if (hotlist_state.moving)
                listbox_add_item (l_movelist, 0, 0, parent_mark_formatted, NULL);
            else
                listbox_add_item (l_hotlist, 0, 0, parent_mark_formatted, NULL);
        }

    while (current){
	switch (current->type) {
	case HL_TYPE_GROUP:
	    {
		CHECK_BUFFER;
/*
strcat (strcat (buf, "->"), current->label);
if (hotlist_state.moving)
    listbox_add_item (l_movelist, 0, 0, buf, current);
else
    listbox_add_item (l_hotlist, 0, 0, buf, current);
*/
		/* in buf, construct the visible line for the hotlist */
		buf = g_strconcat (" ", hotkey_separator, current->label, NULL);
		if (current->hotkey) 
		    buf [0] = (char) current->hotkey;
		strncpy (buf + 1 + strlen (hotkey_separator) - strlen (group_mark), group_mark, strlen (group_mark));
		if (hotlist_state.moving)
		    listbox_add_item (l_movelist, 0, current->hotkey, buf, current);
		else
		    listbox_add_item (l_hotlist, 0, current->hotkey, buf, current);
	    }
	    break;
	case HL_TYPE_DOTDOT:
	case HL_TYPE_ENTRY:
	        buf = g_strconcat (" ", hotkey_separator, current->label, NULL);
		if (current->hotkey) 
		    buf [0] = (char) current->hotkey;
		if (hotlist_state.moving)
		    listbox_add_item (l_movelist, 0, current->hotkey, buf, current);
		else
		    listbox_add_item (l_hotlist, 0, current->hotkey, buf, current);
	    break;
	default:
	    break;
	}
	current = current->next;
    }
//    update_path_name ();
}

#undef CHECK_BUFFER

static void
unlink_entry (struct hotlist *entry)
{
    struct hotlist *current = current_group->head;

    if (current == entry)
	current_group->head = entry->next;
    else {
	while (current && current->next != entry)
	    current = current->next;
	if (current)
	    current->next = entry->next;
    }
    entry->next =
	entry->up = 0;
}
    
#ifdef USE_VFS
static void add_name_to_list (const char *path)
{
    listbox_add_item (l_hotlist, 0, 0, path, 0);
}
#endif /* !USE_VFS */

static int
hotlist_button_callback (int action)
{
    WListbox *list;

    switch (action) {
    case B_MOVE:
	{
	    struct hotlist *saved = current_group;
	    struct hotlist *item;
	    struct hotlist *iter;
	    struct hotlist *moveto_item = 0;
	    struct hotlist *moveto_group = 0;
	    int ret;

	    if (!l_hotlist->current || !l_hotlist->current->data)
		return MSG_NOT_HANDLED;	/* list is empty, or we are at parent_mark - nothing to do */
	    item = l_hotlist->current->data;
	    hotlist_state.moving = 1;
	    init_movelist (LIST_MOVELIST, item);
	    run_dlg (movelist_dlg);
	    ret = movelist_dlg->ret_value;
	    hotlist_state.moving = 0;
	    if (l_movelist->current && l_movelist->current->data)
		moveto_item = l_movelist->current->data;
	    else { /* we are at parent_mark */
		moveto_item = current_group->head;
		ret = B_INSERT; /* after parent_mark is the same as before first item */
	    }
	    moveto_group = current_group;
	    destroy_dlg (movelist_dlg);
	    current_group = saved;

	    if (item->type == HL_TYPE_GROUP) {
		for (iter = moveto_group; iter != iter->up; iter = iter->up)
		    if (iter == item) {
			message (D_ERROR, "Hotlist: Move item", "You cannot move a group into itself");
			return 0;
		    }
		}

	    if (ret == B_CANCEL)
		return MSG_NOT_HANDLED;
	    if (moveto_item == item)
		return MSG_NOT_HANDLED;	/* If we insert/append a before/after a
					   it hardly changes anything ;) */
	    unlink_entry (item);
	    listbox_remove_current (l_hotlist, 1);
	    item->up = moveto_group;
	    if (!moveto_group->head)    /* moving into an empty group */
		moveto_group->head = item;
	    else if (!moveto_item) {	/* we have group with just comments */
		struct hotlist *p = moveto_group->head;

		/* skip comments */
		while (p->next)
		    p = p->next;
		p->next = item;
	    } else if (ret == B_ENTER || ret == B_APPEND)
		if (!moveto_item->next)
		    moveto_item->next = item;
		else {
		    item->next = moveto_item->next;
		    moveto_item->next = item;
	    } else if (moveto_group->head == moveto_item) {
		moveto_group->head = item;
		item->next = moveto_item;
	    } else {
		struct hotlist *p = moveto_group->head;

		while (p->next != moveto_item)
		    p = p->next;
		item->next = p->next;
		p->next = item;
	    }
	    listbox_remove_list (l_hotlist);
	    fill_listbox ();
	    repaint_screen ();
	    hotlist_state.modified = 1;
	    return MSG_NOT_HANDLED;
	    break;
	}
    case B_REMOVE:
	if (l_hotlist->current && l_hotlist->current->data)
	    remove_from_hotlist (l_hotlist->current->data);
	return MSG_NOT_HANDLED;
	break;

    case B_NEW_GROUP:
	add_new_group_cmd ();
	return MSG_NOT_HANDLED;
	break;

    case B_ADD_CURRENT:
	add2hotlist_cmd ();
    case B_EDIT:
	edit_entry_cmd ();
	return MSG_NOT_HANDLED;
	break;

    case B_NEW_ENTRY:
	add_new_entry_cmd ();
	return MSG_NOT_HANDLED;
	break;

    case B_ENTER:
	{
	    char *parent_mark_formatted = g_strconcat (" ", hotkey_separator, parent_mark, NULL);
	    list = hotlist_state.moving ? l_movelist : l_hotlist;
	    if (list->current) {
		if (list->current->data) {
		    struct hotlist *hlp =
			(struct hotlist *) list->current->data;
		    if (hlp->type == HL_TYPE_ENTRY)
			return MSG_HANDLED;
		    else if (hlp->type == HL_TYPE_DOTDOT) {
			/* Fall through - go up */
			;
		    }
		    else {
			listbox_remove_list (list);
			current_group = hlp;
			fill_listbox ();
			return MSG_NOT_HANDLED;
		    }
	    } else
		if (!show_parent_mark || strncmp(list->current->text, parent_mark_formatted, strlen(parent_mark_formatted)))
		    return MSG_HANDLED;
		/* else fall through, i.e. go to the parent group */
	    }
	}
	/* Fall through if list empty - just go up */

    case B_UP_GROUP:
	{
	    struct hotlist  *saved = current_group;
	    struct hotlist *iter = current_group->up->head;
	    int i = 0;

	    /* if we are in the root group, do nothing */
	    if (current_group->up == current_group)
	    return 0;

	    /* calculate the position of the subgroup we are leaving */
	    while (iter != saved && iter)
	    {
		iter = iter->next;
		i++;
	    }
	    /* add one for the parent_mark entry in subgroups */
	    if (show_parent_mark)
		if (current_group->up->up != current_group->up)
		    i++;

	    /* reload parent group */
	    list = hotlist_state.moving ? l_movelist : l_hotlist;
	    listbox_remove_list (list);
	    current_group = current_group->up;
	    fill_listbox ();

	    /* set the pointer to the subgroup we've just left */
	    listbox_select_by_number (l_hotlist, i);
	    return MSG_NOT_HANDLED;
	    break;
	}

#ifdef	USE_VFS
    case B_FREE_ALL_VFS:
	vfs_expire (1);
	/* fall through */

    case B_REFRESH_VFS:
	listbox_remove_list (l_hotlist);
	listbox_add_item (l_hotlist, 0, 0, home_dir, 0);
	vfs_fill_names (add_name_to_list);
	return MSG_NOT_HANDLED;
#endif				/* USE_VFS */

    default:
	return MSG_HANDLED;
	break;

    }
}

static cb_ret_t
hotlist_callback (Dlg_head *h, dlg_msg_t msg, int parm)
{
    switch (msg) {
    case DLG_DRAW:
	hotlist_refresh (h);
	return MSG_HANDLED;

    case DLG_UNHANDLED_KEY:
	switch (parm) {
	case KEY_M_CTRL | '\n':
	    goto l1;
	case '\n':
	case KEY_ENTER:
	case KEY_RIGHT:
	    if (hotlist_button_callback (B_ENTER)) {
		h->ret_value = B_ENTER;
		dlg_stop (h);
	    };
	    return MSG_HANDLED;
	    break;
	case KEY_LEFT:
	    if (hotlist_state.type != LIST_VFSLIST)
		return !hotlist_button_callback (B_UP_GROUP);
	    else
		return MSG_NOT_HANDLED;
	    break;
	case KEY_DC:
	    if (!hotlist_state.moving) {
		hotlist_button_callback (B_REMOVE);
		return MSG_HANDLED;
	    }
	    break;
	case KEY_IC:
	    if (!hotlist_state.moving) {
		hotlist_button_callback (B_NEW_ENTRY);
		return MSG_HANDLED;
	    }
	    break;
	case KEY_F(4):
	    if (!hotlist_state.moving) {
		hotlist_button_callback (B_EDIT);
		return MSG_HANDLED;
	    }
	    break;
	case KEY_F(6):
	    if (!hotlist_state.moving) {
		hotlist_button_callback (B_MOVE);
		return MSG_HANDLED;
	    }
	    break;
	case KEY_F(7):
	    if (!hotlist_state.moving) {
		hotlist_button_callback (B_NEW_GROUP);
		return MSG_HANDLED;
	    }
	    break;

	  l1:
	case ALT ('\n'):
	case ALT ('\r'):
	    if (!hotlist_state.moving) {
		if (l_hotlist->current) {
		    if (l_hotlist->current->data) {
			struct hotlist *hlp =
			    (struct hotlist *) l_hotlist->current->data;
			if (hlp->type == HL_TYPE_ENTRY) {
			    char *tmp =
				g_strconcat ("cd ", hlp->directory, (char *) NULL);
			    stuff (cmdline, tmp, 0);
			    g_free (tmp);
			    dlg_stop (h);
			    h->ret_value = B_CANCEL;
			    return MSG_HANDLED;
			}
		    }
		}
	    }
	    return MSG_HANDLED;	/* ignore key */
	}
	return MSG_NOT_HANDLED;

    case DLG_POST_KEY:
	if (hotlist_state.moving)
	    dlg_select_widget (l_movelist);
	else
	    dlg_select_widget (l_hotlist);
	/* always stay on hotlist */
	/* fall through */

    case DLG_INIT:
	attrset (MENU_ENTRY_COLOR);
	update_path_name ();
	return MSG_HANDLED;

    default:
	return default_dlg_callback (h, msg, parm);
    }
}

static int l_call (WListbox *list)
{
    Dlg_head *dlg = list->widget.parent;

    if (list->current){
	if (list->current->data) {
	    struct hotlist *hlp = (struct hotlist*) list->current->data;
	    if (hlp->type == HL_TYPE_ENTRY) {
		dlg->ret_value = B_ENTER;
		dlg_stop (dlg);
		return LISTBOX_DONE;
	    } else {
		hotlist_button_callback (B_ENTER);
		hotlist_callback (dlg, DLG_POST_KEY, '\n');
		return LISTBOX_CONT;
	    }
	} else {
	    dlg->ret_value = B_ENTER;
	    dlg_stop (dlg);
	    return LISTBOX_DONE;
	}
    }

    hotlist_button_callback (B_UP_GROUP);
    hotlist_callback (dlg, DLG_POST_KEY, 'u');
    return LISTBOX_CONT;
}

/*
 * Expands all button names (once) and recalculates button positions.
 * returns number of columns in the dialog box, which is 10 chars longer
 * then buttonbar.
 *
 * If common width of the window (i.e. in xterm) is less than returned
 * width - sorry :)  (anyway this did not handled in previous version too)
 */
static int
init_i18n_stuff(int list_type, int cols)
{
	register int i;
//	static const char* cancel_but = N_("&Cancel");
	static char* cancel_but = N_("Cancel");

#ifdef ENABLE_NLS
	static int hotlist_i18n_flag = 0;

	if (!hotlist_i18n_flag)
	{
		i = sizeof (hotlist_but) / sizeof (hotlist_but [0]);
		while (i--)
			hotlist_but [i].text = _(hotlist_but [i].text);

		cancel_but = _(cancel_but);
		hotlist_i18n_flag = 1;
	}
#endif /* ENABLE_NLS */

	/* Dynamic resizing of buttonbars */
	{
		int len[2], count[2]; /* at most two lines of buttons */
		int cur_x[2], row;

		i = sizeof (hotlist_but) / sizeof (hotlist_but [0]);
		len[0] = len[1] = count[0] = count[1] = 0;

		/* Count len of buttonbars, assuming 2 extra space between buttons */
		while (i--)
		{
			if (! (hotlist_but[i].type & list_type))
				continue;

			row = hotlist_but [i].y;
			++count [row];
			len [row] += mbstrlen (hotlist_but [i].text) + 5;
			if (hotlist_but [i].flags == DEFPUSH_BUTTON)
				len [row] += 2;
		}
		len[0] -= 2;
		len[1] -= 2;

		cols = max(cols, max(len[0], len[1]));

		/* arrange buttons */

		cur_x[0] = cur_x[1] = 0;
		i = sizeof (hotlist_but) / sizeof (hotlist_but [0]);
		while (i--)
		{
			if (! (hotlist_but[i].type & list_type))
				continue;

			row = hotlist_but [i].y;

			if (hotlist_but [i].x != 0) 
			{
				/* not first int the row */
				if (!strcmp (hotlist_but [i].text, cancel_but))
					hotlist_but [i].x = 
						cols - mbstrlen (hotlist_but [i].text) - 14;
				else
					hotlist_but [i].x = cur_x [row];
			}

			cur_x [row] += mbstrlen (hotlist_but [i].text) + 2
				+ (hotlist_but [i].flags == DEFPUSH_BUTTON ? 5 : 3);
		}
	}
	
	return cols;
}

static void
init_hotlist (int list_type)
{
    size_t i;
    const char *title, *help_node;
    int hotlist_cols;

    hotlist_cols = init_i18n_stuff (list_type, COLS - 6);

    do_refresh ();

    hotlist_state.expanded =
	GetPrivateProfileInt ("HotlistConfig", "expanded_view_of_groups",
			      0, profile_name);

    if (list_type == LIST_VFSLIST) {
	title = _("Active VFS directories");
	help_node = "[vfshot]";	/* FIXME - no such node */
    } else {
	title = _("Directory hotlist");
	help_node = "[Hotlist]";
    }

    hotlist_dlg =
	create_dlg (0, 0, LINES - 2, hotlist_cols, dialog_colors,
		    hotlist_callback, help_node, title, DLG_CENTER | DLG_REVERSE);

    for (i = 0; i < BUTTONS; i++) {
	if (hotlist_but[i].type & list_type)
	    add_widget (hotlist_dlg,
			button_new (BY + hotlist_but[i].y,
				    BX + hotlist_but[i].x,
				    hotlist_but[i].ret_cmd,
				    hotlist_but[i].flags,
				    hotlist_but[i].text,
				    hotlist_button_callback));
    }

    /* We add the labels. 
     *    pname       will hold entry's pathname;
     *    pname_group will hold name of current group
     */
    pname = label_new (UY - 11 + LINES, UX + 2, "");
    add_widget (hotlist_dlg, pname);
    if (!hotlist_state.moving) {
	add_widget (hotlist_dlg,
		    label_new (UY - 12 + LINES, UX + 1,
			       _(" Directory path ")));

	/* This one holds the displayed pathname */
	pname_group = label_new (UY, UX + 1, _(" Directory label "));
	add_widget (hotlist_dlg, pname_group);
    }
    /* get new listbox */
    l_hotlist =
	listbox_new (UY + 1, UX + 1, COLS - 2 * UX - 8, LINES - 14,
		     l_call);

    /* Fill the hotlist with the active VFS or the hotlist */
#ifdef USE_VFS
    if (list_type == LIST_VFSLIST) {
	listbox_add_item (l_hotlist, 0, 0, home_dir, 0);
	vfs_fill_names (add_name_to_list);
    } else
#endif				/* !USE_VFS */
	fill_listbox ();

    add_widget (hotlist_dlg, l_hotlist);
    /* add listbox to the dialogs */
}

static void
init_movelist (int list_type, struct hotlist *item)
{
    size_t i;
    char *hdr = g_strdup_printf (_("Moving: %s"), item->label);
    int movelist_cols = init_i18n_stuff (list_type, COLS - 6);

    do_refresh ();

    movelist_dlg =
	create_dlg (0, 0, LINES - 6, movelist_cols, dialog_colors,
		    hotlist_callback, "[Hotlist]", hdr, DLG_CENTER | DLG_REVERSE);
    g_free (hdr);

    for (i = 0; i < BUTTONS; i++) {
	if (hotlist_but[i].type & list_type)
	    add_widget (movelist_dlg,
			button_new (BY - 4 + hotlist_but[i].y,
				    BX + hotlist_but[i].x,
				    hotlist_but[i].ret_cmd,
				    hotlist_but[i].flags,
				    hotlist_but[i].text,
				    hotlist_button_callback));
    }

    /* We add the labels.  We are interested in the last one,
     * that one will hold the path name label
     */
    movelist_group = label_new (UY, UX + 1, _(" Directory label "));
    add_widget (movelist_dlg, movelist_group);
    /* get new listbox */
    l_movelist =
	listbox_new (UY + 1, UX + 1, movelist_dlg->cols - 2 * UX - 2,
		     movelist_dlg->lines - 8, l_call);

    fill_listbox ();

    add_widget (movelist_dlg, l_movelist);
    /* add listbox to the dialogs */
}

/*
 * Destroy the list dialog.
 * Don't confuse with done_hotlist() for the list in memory.
 */
static void hotlist_done (void)
{
    destroy_dlg (hotlist_dlg);
    l_hotlist = NULL;
    if (0)
	update_panels (UP_OPTIMIZE, UP_KEEPSEL);
    repaint_screen ();
}

static inline char *
find_group_section (struct hotlist *grp)
{
    return g_strconcat (grp->directory, ".Group", (char *) NULL);
}


/* 1.11.96 bor: added pos parameter to control placement of new item.
   see widget.c, listbox_add_item()
                now hotlist is in unsorted mode
 */
enum {
    HL_BEFORE_CURRENT = 1
   ,HL_AFTER_CURRENT  = 2
};

static struct hotlist *
add2hotlist (char *label, char *directory, int hotkey, enum HotListType type, int pos)
{
    static struct hotlist *current = NULL;
    struct hotlist *hotlist_new = NULL;
    /*
     * Hotlist is neither loaded nor loading.
     * Must be called by "Ctrl-x a" before using hotlist.
     */
    if (!current_group)
	load_hotlist ();

    if (l_hotlist && l_hotlist->current) {
	if (l_hotlist->current->data) /* we are at a normal entry */
	    current = l_hotlist->current->data;
	else {/* we are at a parent_mark */
	    current = current_group->head;
	    if (pos == 2)
		pos = 1; /* after parent-mark == before first item */
	}
    }
	/* Make sure `..' stays at the top of the list. */
	if (current->type == HL_TYPE_DOTDOT)
	    pos = HL_AFTER_CURRENT;

    hotlist_new = new_hotlist ();

    hotlist_new->type      = type;
    hotlist_new->label     = label;
    hotlist_new->hotkey    = hotkey;
    hotlist_new->directory = directory;
    hotlist_new->up	   = current_group;

    if (type == HL_TYPE_GROUP) {
	current_group = hotlist_new;
	add_dotdot_to_list ();
	current_group = hotlist_new->up;
    }

    if (!current_group->head) { /* first element in group */
	current_group->head = hotlist_new;
    } else if (pos == HL_AFTER_CURRENT) {
	hotlist_new->next     = current->next;
	current->next = hotlist_new;
    } else if (pos == HL_BEFORE_CURRENT &&
	       current == current_group->head) {
			   /* should be inserted before first item */
	hotlist_new->next = current;
	current_group->head = hotlist_new;
    } else if (pos == HL_BEFORE_CURRENT) { /* before current */
	struct hotlist  *p = current_group->head;

	while (p->next != current)
	    p = p->next;

	hotlist_new->next = current;
	p->next   = hotlist_new;
    } else {		    /* append at the end */
	struct hotlist  *p = current_group->head;

	while (p->next)
	    p = p->next;

	p->next = hotlist_new;
    }

    if (hotlist_state.running && type != HL_TYPE_COMMENT &&
	type != HL_TYPE_DOTDOT) {

        char  *buf;

        /* in buf, construct the visible line for the hotlist */
	buf = g_strconcat (" ", hotkey_separator, hotlist_new->label, NULL);
	if (type == HL_TYPE_GROUP) 
	    strncpy (buf + 1 + strlen (hotkey_separator) - strlen (group_mark), group_mark, strlen (group_mark));
	if (hotlist_new->hotkey) 
	    buf [0] = (char) hotlist_new->hotkey;

	if (!l_hotlist->current->data && pos == 1) /* at the parent_mark? */
	    /* add after the _visible_ current item, i.e. below the parent_mark */
	    listbox_add_item (l_hotlist, 2, hotlist_new->hotkey, buf, hotlist_new);
	else
	    listbox_add_item (l_hotlist, pos, hotlist_new->hotkey, buf, hotlist_new);
	listbox_select_entry (l_hotlist, l_hotlist->current->next);
	g_free (buf);
    }
    return hotlist_new;

}

#ifdef ENABLE_NLS
/*
 * Support routine for add_new_entry_input()/add_new_group_input()
 * Change positions of buttons (first three widgets).
 *
 * This is just a quick hack. Accurate procedure must take care of
 * internationalized label lengths and total buttonbar length...assume
 * 64 is longer anyway.
 */
/* I don't think this can be fixed permanently, but I added the
   number_of_buttons argument so this could work for any number of
   buttons, not only 3       -- bb */
static void position_widgets_i18n(QuickWidget* qw, int len, int number_of_buttons)
{
	int i, *l, space, cur_x, sum = 0;
	l = g_malloc(sizeof(int)*number_of_buttons);
	
	for (i = 0; i < number_of_buttons; i++)
	{
		qw [i].text = _(qw [i].text);
		l[i] = mbstrlen (qw [i].text) + 3;
		sum += l[i];
	}
	space = (len - 4 - sum) / (number_of_buttons + 1);

	for (cur_x = 2 + space, i = number_of_buttons; i--; cur_x += l[i] + space)
	{
		qw [i].relative_x = cur_x;
		qw [i].x_divisions = len;
	}
	g_free(l);
}
#endif /* ENABLE_NLS */

static int
hotlist_entry_input (char *header, char *help, char *button_done, char *text1, char *text2, char *text3, char **r1, char **r2, char **r3)
{
  /* defining the order of the widget groups */
#define ENTRY_Y_LABEL		1
#define ENTRY_Y_PATH		2
#define ENTRY_Y_HOTKEY		3
#define ENTRY_Y_BUTTONS		4

    QuickDialog Quick_input;
    /* Note: the _reverse_ order of quick_widgets array determines the order of
       visiting the widgets in the dialog upon pressing arrows or tab; the last
       widget in the array gets focus first    -- bb */
    static QuickWidget quick_widgets [] = {
    { quick_button, 55, 80, ENTRY_Y_BUTTONS, 0, N_("Cancel"), 0, B_CANCEL, 0, 0, "button-cancel" },
    { quick_button, 30, 80, ENTRY_Y_BUTTONS, 0, "", 0, B_ENTER, 0, 0, "button-insert" },
    { quick_label, 4, 80, ENTRY_Y_HOTKEY, 0, 0, 0, 0, 0, 0, "label-htk" },
    { quick_input, 4, 80, ENTRY_Y_HOTKEY, 0, "", 2, 0, 0, 0, "input-htk" },
    { quick_label, 4, 80, ENTRY_Y_PATH, 0, 0, 0, 0, 0, 0, "label-pth" },
    { quick_input, 4, 80, ENTRY_Y_PATH, 0, "", 58, 0, 0, 0, "input-pth" },
    { quick_label, 4, 80, ENTRY_Y_LABEL, 0, 0, 0, 0, 0, 0, "label-lbl" },
    { quick_input, 4, 80, ENTRY_Y_LABEL, 0, "", 58, 0, 0, 0, "input-lbl" },
    NULL_QuickWidget };

    int len;
    int i;
    /* these hold the number of lines taken by each label - can they 
       really be more than one line high? */
    int lines1, lines2, lines3;
    int cols1, cols2, cols3;

#ifdef ENABLE_NLS
	static int i18n_flag = 0;
#endif /* ENABLE_NLS */

    msglen(text1, &lines1, &cols1);
    msglen(text2, &lines2, &cols2);
    msglen(text3, &lines3, &cols3);
    len = max ((int) mbstrlen (header), cols1);
    len = max (len, cols2) + 4;
    len = max (len, cols3) + 4;
    len = max (len, 64);

#ifdef ENABLE_NLS
	if (!i18n_flag)
	{
//		add_widgets_i18n(quick_widgets, len);
		position_widgets_i18n(quick_widgets, len, 2);
		i18n_flag = 1;
	}
#endif /* ENABLE_NLS */

    Quick_input.xlen  = len;
    Quick_input.xpos  = -1;
    Quick_input.title = header;
    Quick_input.help  = help;
    Quick_input.i18n  = 0;
    quick_widgets [6].text = text1;
    quick_widgets [7].text = *r1;
    quick_widgets [4].text = text2;
    quick_widgets [5].text = *r2;

/*    for (i = 0; i < 7; i++)
	quick_widgets [i].y_divisions = lines1+lines2+7;
    Quick_input.ylen  = lines1 + lines2 + 7;

    quick_widgets [0].relative_y = RELATIVE_Y_BUTTONS + (lines1 + lines2);
    quick_widgets [1].relative_y = RELATIVE_Y_BUTTONS + (lines1 + lines2);
    quick_widgets [2].relative_y = RELATIVE_Y_BUTTONS + (lines1 + lines2);
    quick_widgets [3].relative_y = RELATIVE_Y_INPUT_PTH + (lines1);
    quick_widgets [4].relative_y = RELATIVE_Y_LABEL_PTH + (lines1);
*/
    quick_widgets [2].text = text3;
    quick_widgets [3].text = *r3;

#define ENTRY_WIDGETS_COUNT 8
    /* the total number of quick_widgets; can this be calculated, not hard-coded? */
    for (i = 0; i < ENTRY_WIDGETS_COUNT; i++)
	quick_widgets [i].y_divisions = lines1 + lines2 + lines3 + ENTRY_WIDGETS_COUNT - 1;
    Quick_input.ylen  = lines1 + lines2 + lines3 + ENTRY_WIDGETS_COUNT;

    /* positioning widgets vertically, based on their order and the number of lines they take */
    quick_widgets [0].relative_y = ENTRY_Y_BUTTONS + (lines1 + lines2 + lines3 + 1);
    quick_widgets [1].relative_y = ENTRY_Y_BUTTONS + (lines1 + lines2 + lines3 + 1);
    quick_widgets [2].relative_y = ENTRY_Y_HOTKEY + (lines1 + lines2 + lines3);
    quick_widgets [3].relative_y = ENTRY_Y_HOTKEY + (lines1 + lines2 + lines3 + 1);
    quick_widgets [4].relative_y = ENTRY_Y_PATH + (lines1 + lines2);
    quick_widgets [5].relative_y = ENTRY_Y_PATH + (lines1 + lines2 + 1);
    quick_widgets [6].relative_y = ENTRY_Y_LABEL + (lines1);
    quick_widgets [7].relative_y = ENTRY_Y_LABEL + (lines1 + 1);

    quick_widgets [5].str_result = r1;
    quick_widgets [3].str_result = r2;
    quick_widgets [7].str_result = r3;
    
    Quick_input.widgets = quick_widgets;
    if ((i = quick_dialog (&Quick_input)) != B_CANCEL){
	 return i;
    } else
	 return 0;
}

static void add_new_entry_cmd (void)
{
    char *title, *url, *to_free, *hotkey = 0;
    int ret;

    /* Take current directory as default value for input fields */
    to_free = title = url = strip_password (g_strdup (current_panel->cwd), 1);

    do {
	ret = hotlist_entry_input (_("New hotlist entry"), "[Hotlist]", N_("&Add"), _("Entry label"), 
	    _("Directory path"), _("Hotkey"), &title, &url, &hotkey);
	if (!ret)
	    return;
	if (!title || !*title || !url || !*url){
	    g_free (title);
	    g_free (url);
	    return;
	}
    } while (alert_duplicate_hotkey ("Hotlist: Add entry", (int) *hotkey));
    g_free (to_free);

    add2hotlist (g_strdup (title), g_strdup (url), (int) *hotkey, HL_TYPE_ENTRY, 2); 
    hotlist_state.modified = 1;
}

/* This is very similar to hotlist_entry_input, but because of the
   awkward position calculations we need a separate function. Maybe
   someone sometime will implement a higher-level API that would
   allow to express all of this function in a single statement.   -- bb */
static int hotlist_group_input (char *header, char *help, char *button_done, char *text1, char *text2, char **r1, char **r2)
{
    int		ret;
    QuickDialog Quick_input;

  /* defining the order of the widget groups */
#define GROUP_Y_LABEL	     1
#define GROUP_Y_HOTKEY       2
#define GROUP_Y_BUTTONS      3

    static QuickWidget quick_widgets [] = {
    { quick_button, 55, 80, GROUP_Y_BUTTONS, 0, N_("Cancel"), 0, B_CANCEL, 0, 0,
	  NULL },
    { quick_button, 30, 80, GROUP_Y_BUTTONS, 0, "",           0, B_ENTER, 0, 0,
	  NULL },
    { quick_label, 4, 80, GROUP_Y_HOTKEY, 0,  0,  0, 0, 0, 0, "label-htk" },
    { quick_input, 4, 80, GROUP_Y_HOTKEY, 0, "",  2, 0, 0, 0, "input-htk" },
    { quick_label, 4, 80, GROUP_Y_LABEL,  0,  0,  0, 0, 0, 0, "label-lbl" },
    { quick_input, 4, 80, GROUP_Y_LABEL,  0, "", 58, 0, 0, 0, "input-lbl" },
    NULL_QuickWidget };

    int len;
    int i;
    int lines1, lines2;
    int cols1, cols2;

#ifdef ENABLE_NLS
	static int i18n_flag = 0;
#endif /* ENABLE_NLS */

    quick_widgets[1].text = button_done;
    msglen (text1, &lines1, &cols1);
    msglen (text2, &lines2, &cols2);
    len = strlen (header);
    len = max (len, cols1);
    len = max (len, cols2);
    len = max (len + 4, 64);

#ifdef ENABLE_NLS
	if (!i18n_flag)
	{
	    position_widgets_i18n(quick_widgets, len, 2);
	    i18n_flag = 1;
	}
#endif /* ENABLE_NLS */

    Quick_input.xlen  = len;
    Quick_input.xpos  = -1;
    Quick_input.title = header;
    Quick_input.help  = help;
    Quick_input.i18n  = 0;
    quick_widgets [2].text = text2;
    quick_widgets [3].text = *r2;

    quick_widgets [4].text = text1;
    quick_widgets [5].text = *r1;

#define GROUP_WIDGETS_COUNT 7
    for (i = 0; i < GROUP_WIDGETS_COUNT; i++)
	quick_widgets [i].y_divisions = lines1 + lines2 + GROUP_WIDGETS_COUNT - 1;
    Quick_input.ylen  = lines1 + lines2 + GROUP_WIDGETS_COUNT;

    quick_widgets [0].relative_y = GROUP_Y_BUTTONS + (lines1 + lines2 + 1);
    quick_widgets [1].relative_y = GROUP_Y_BUTTONS + (lines1 + lines2 + 1);
    quick_widgets [2].relative_y = GROUP_Y_HOTKEY + (lines1 + lines2);
    quick_widgets [3].relative_y = GROUP_Y_HOTKEY + (lines1 + lines2 + 1);
    quick_widgets [4].relative_y = GROUP_Y_LABEL + (lines1);
    quick_widgets [5].relative_y = GROUP_Y_LABEL + (lines1 + 1);

    quick_widgets [3].str_result = r2;
    quick_widgets [5].str_result = r1;

    Quick_input.widgets = quick_widgets;
    if ((ret = quick_dialog (&Quick_input)) != B_CANCEL){
	return ret;
    } else
	return 0;
}

static void add_new_group_cmd (void)
{
    char   *label, *hotkey;
    int    ret;

    /* no useful default values I can think of */
    label = hotkey = "";

    do {
	ret = hotlist_group_input (
		_("New hotlist group"), 
		"[Hotlist]", 
		N_("&Add"),
		 _("Group label"), 
		 _("Hotkey"), 
		 &label, 
		 &hotkey
	);
	if (!ret || !label || !*label)
	    return;
    } while (alert_duplicate_hotkey ("Hotlist: Add group", (int) *hotkey));
    if (ret == B_ENTER || ret == B_APPEND)
	add2hotlist (label, 0, (int) *hotkey, HL_TYPE_GROUP, HL_AFTER_CURRENT);
    else
	add2hotlist (label, 0, (int) *hotkey, HL_TYPE_GROUP, HL_BEFORE_CURRENT);

    hotlist_state.modified = 1;
}

void edit_entry_cmd ()
{
    WListbox *list = hotlist_state.moving ? l_movelist : l_hotlist;
    char *title, *url, *hotkey;
    char old_hotkey [2];
    int ret;
    struct hotlist *iter = current_group->head;
    int i = 0;

    if (list->current && list->current->data) {
	struct hotlist *entry = (struct hotlist*) list->current->data;
	if (entry->type == HL_TYPE_ENTRY) {

	    title = entry->label;
	    url = entry->directory;
	    old_hotkey[0] = (char) entry->hotkey;
	    old_hotkey[1] = 0;
	    hotkey = old_hotkey;

	    do {
		ret = hotlist_entry_input (_("Edit hotlist entry"), "[Hotlist]", N_("&Done"), _("Entry label"),
					   _("Directory path"), _("Hotkey"), &title, &url, &hotkey);
		if (!ret)
		    break;
	    } while (alert_duplicate_hotkey ("Hotlist: Edit entry", (int) *hotkey));
	
	    if (ret) {
		entry->label = title;
		entry->directory = url;
		entry->hotkey = (int) *hotkey;
		hotlist_state.modified = 1;
	    }

	} else if (entry->type == HL_TYPE_GROUP) {

	    title = entry->label;
	    old_hotkey[0] = (char) entry->hotkey;
	    old_hotkey[1] = 0;
	    hotkey = old_hotkey;

	    do {
		ret = hotlist_group_input (_("Edit hotlist group"), "[Hotlist]", N_("&Done"), _("Group label"),
					  _("Hotkey"), &title, &hotkey);
		if (!ret)
		    break;
	    } while (alert_duplicate_hotkey ("Hotlist: Edit group", (int) *hotkey));
	
	    if (ret) {
		entry->label = title;
		entry->hotkey = (int) *hotkey;
		hotlist_state.modified = 1;
	    }
	}

	/* calculate the position of the entry we've been editing */
	while (iter != entry && iter)
	{
	    iter = iter->next;
	    i++;
	}
	/* add one for the parent_mark entry in subgroups */
	if (show_parent_mark)
	    if (entry->up->up != entry->up)
		i++;

	/* reload list */
	listbox_remove_list (list);
	fill_listbox ();

	/* set the pointer to the edited entry */
	listbox_select_by_number (l_hotlist, i);

    } /* else do nothing */
}

void add2hotlist_cmd (void)
{
    char *promptl, *label;
    const char *cp = _("Label for \"%s\":");
    int l = mbstrlen (cp);
    char *label_string = g_strdup (current_panel->cwd);

    strip_password (label_string, 1);

    promptl = g_strdup_printf (cp, path_trunc (current_panel->cwd, COLS-2*UX-(l+8)));
    label = input_dialog (_(" Add to hotlist "), promptl, label_string);
    g_free (promptl);

    if (!label || !*label) {
	g_free (label_string);
	g_free (label);
	return;
    }
    add2hotlist (label, label_string, 0, HL_TYPE_ENTRY, 0);
    hotlist_state.modified = 1;
}

static void remove_group (struct hotlist *grp)
{
    struct hotlist *current = grp->head;

    while (current) {
	struct hotlist *next = current->next;

	if (current->type == HL_TYPE_GROUP)
	    remove_group (current);

	g_free (current->label);
	g_free (current->directory);
	g_free (current);

	current = next;
    }

}
	
static void remove_from_hotlist (struct hotlist *entry)
{
    if (entry->type == HL_TYPE_DOTDOT)
	return;

    if (confirm_directory_hotlist_delete) {
	char *title;
	int result;

	title = g_strconcat (_(" Remove: "),
				   name_trunc (entry->label, 30),
				   " ",
				   NULL);

	if (safe_delete)
	    query_set_sel (1);
	result = query_dialog (title,
			_("\n Are you sure you want to remove this entry?"),
			D_ERROR, 2, _("&Yes"), _("&No"));

	g_free (title);

	if (result != 0)
	    return;
    }

    if (entry->type == HL_TYPE_GROUP) {
	if (entry->head) {
	    char *header;
	    int   result;

	    header = g_strconcat (_(" Delete: "),
				   name_trunc (entry->label, 30),
				   " ",
				   NULL);
	    result = query_dialog (header, _("\n Group is not empty.\n Delete it?"),
				   D_ERROR, 2,
				   _("&Yes"), _("&No"));
	    g_free (header);

	    if (result == 0 || result == -1) /* 0 means "No" was selected, -1 means Esc was pressed */
		return;
	}

	remove_group (entry);
    }

    unlink_entry (entry);

    g_free (entry->label);
    g_free (entry->directory);
    g_free (entry);
    /* now remove list entry from screen */
    listbox_remove_current (l_hotlist, 1);
    hotlist_state.modified = 1;
}


static void
load_group (struct hotlist *grp)
{
    void *profile_keys;
    char *key, *value;
    char *group_section;
    struct hotlist *current = 0;
    
    group_section = find_group_section (grp);

    profile_keys = profile_init_iterator (group_section, profile_name);

    current_group = grp;

    while (profile_keys){
	profile_keys = profile_iterator_next (profile_keys, &key, &value);
	add2hotlist (g_strdup (value), g_strdup (key), 0, HL_TYPE_GROUP, 0);
    }
    g_free (group_section);

    profile_keys = profile_init_iterator (grp->directory, profile_name);

    while (profile_keys){
	profile_keys = profile_iterator_next (profile_keys, &key, &value);
	add2hotlist (g_strdup (value),g_strdup (key), 0, HL_TYPE_ENTRY, 0);
    }

    for (current = grp->head; current; current = current->next)
	load_group (current);
}

#define TKN_GROUP	0
#define TKN_ENTRY	1
#define TKN_STRING	2
#define TKN_URL		3
#define TKN_ENDGROUP	4
#define TKN_COMMENT	5
#define TKN_HOTKEY      6
#define TKN_EOL		125
#define TKN_EOF		126
#define TKN_UNKNOWN	127

static char *tkn_buf;
static int  tkn_buf_length;
static int  tkn_length;

static char *hotlist_file_name;
static FILE *hotlist_file;
static time_t hotlist_file_mtime;

static int hot_skip_blanks (void)
{
    int c;

    while ((c = getc (hotlist_file)) != EOF && c != '\n' && isspace (c))
	;
    return c;
    
}

/* stores the token so it will be retrieved by the hot_next_token */
void unhot_next_token (int token)
{
   unhot_token = token;
}

static int hot_next_token (void)
{
    int	c;
    int ret;

    /* if a token was saved, return it without reading from the file */
    if (unhot_token) {
      ret = unhot_token;
      unhot_token = 0;
      return ret;
    }

#define CHECK_BUF() \
do { \
    if (tkn_length == tkn_buf_length) \
	tkn_buf = tkn_buf ? ( g_realloc (tkn_buf, tkn_buf_length += 1024)) \
			  : ( g_malloc (tkn_buf_length = 1024)); \
} while (0)

    tkn_length = 0;

again:
    c = hot_skip_blanks ();
    switch (c) {
    case EOF:
	return TKN_EOF;
	break;
    case '\n':
	return TKN_EOL;
	break;
    case '#':
	while ((c = getc (hotlist_file)) != EOF && c != '\n') {
	    if (c == EOF)
		return TKN_EOF;
	    if (c != '\n') {
		CHECK_BUF();
		tkn_buf[tkn_length++] = c == '\n' ? ' ' : c;
	    }
	}
	CHECK_BUF();
	tkn_buf[tkn_length] = '\0';
	return TKN_COMMENT;
	break;
    case '"':
	while ((c = getc (hotlist_file)) != EOF && c != '"') {
	    if (c == '\\')
		if ((c = getc (hotlist_file)) == EOF)
		    return TKN_EOF;
	    CHECK_BUF();
	    tkn_buf[tkn_length++] = c == '\n' ? ' ' : c;
	}
	if (c == EOF)
	    return TKN_EOF;
	CHECK_BUF();
	tkn_buf[tkn_length] = '\0';
	return TKN_STRING;
	break;
    case '\\':
	if ((c = getc (hotlist_file)) == EOF)
	    return TKN_EOF;
	if (c == '\n')
	    goto again;

	/* fall through; it is taken as normal character */

    default:
	do {
	    CHECK_BUF();
	    tkn_buf[tkn_length++] = toupper(c);
	} while ((c = fgetc (hotlist_file)) != EOF && isalnum (c));
	if (c != EOF)
	    ungetc (c, hotlist_file);
	CHECK_BUF();
	tkn_buf[tkn_length] = '\0';
	if (strncmp (tkn_buf, "GROUP", tkn_length) == 0)
	    return TKN_GROUP;
	else if (strncmp (tkn_buf, "ENTRY", tkn_length) == 0)
	    return TKN_ENTRY;
	else if (strncmp (tkn_buf, "ENDGROUP", tkn_length) == 0)
	    return TKN_ENDGROUP;
	else if (strncmp (tkn_buf, "URL", tkn_length) == 0)
	    return TKN_URL;
	else if (strncmp (tkn_buf, "HOTKEY", tkn_length) == 0)
	    return TKN_HOTKEY;
	else
	    return TKN_UNKNOWN;
	break;
    }
}

#define SKIP_TO_EOL	{ \
int _tkn; \
while ((_tkn = hot_next_token ()) != TKN_EOF && _tkn != TKN_EOL) ; \
}

#define CHECK_TOKEN(_TKN_) \
if ((tkn = hot_next_token ()) != _TKN_) { \
    hotlist_state.readonly = 1; \
    hotlist_state.file_error = 1; \
    while (tkn != TKN_EOL && tkn != TKN_EOF) \
	tkn = hot_next_token (); \
break; \
}

static void
hot_load_group (struct hotlist * grp)
{
    int		tkn;
    struct hotlist *new_grp;
    char	*label, *url;
    int         hotkey = 0;

    current_group = grp;

    while ((tkn = hot_next_token()) != TKN_ENDGROUP)
	switch (tkn) {
	case TKN_GROUP:
	    CHECK_TOKEN(TKN_STRING);
	    label = g_strdup (tkn_buf);
	    if ((tkn = hot_next_token ()) == TKN_HOTKEY) { 
		CHECK_TOKEN(TKN_STRING);
		hotkey = (int) g_strdup (tkn_buf) [0];
	    } else {
		/* store back the wrong token - it does not belong in this entry */
		unhot_next_token (tkn);
		hotkey = 0;
	    }

	    hotkey = check_duplicate_hotkey (hotkey);

	    new_grp = add2hotlist (label, 0, hotkey, HL_TYPE_GROUP, 0);
	    SKIP_TO_EOL;
	    hot_load_group (new_grp);
	    current_group = grp;
	    break;
	case TKN_ENTRY:
	    CHECK_TOKEN(TKN_STRING);
	    label = g_strdup (tkn_buf);
	    CHECK_TOKEN(TKN_URL);
	    CHECK_TOKEN(TKN_STRING);
	    url = g_strdup (tkn_buf);
	    if ((tkn = hot_next_token ()) == TKN_HOTKEY) { 
		CHECK_TOKEN(TKN_STRING);
		hotkey = (int) g_strdup (tkn_buf) [0];
	    } else {
		/* store back the wrong token - it does not belong in this entry */                
		unhot_next_token (tkn);
		hotkey = 0;
	    }

	    hotkey = check_duplicate_hotkey (hotkey);

	    add2hotlist (label, url, hotkey, HL_TYPE_ENTRY, 0);
	    SKIP_TO_EOL;
	    break;
	case TKN_COMMENT:
	    label = g_strdup (tkn_buf);
	    add2hotlist (label, 0, 0, HL_TYPE_COMMENT, 0);
	    break;
	case TKN_EOF:
	    hotlist_state.readonly = 1;
	    hotlist_state.file_error = 1;
	    return;
	    break;
	case TKN_EOL:
	    /* skip empty lines */
	    break;
	default:
	    hotlist_state.readonly = 1;
	    hotlist_state.file_error = 1;
	    SKIP_TO_EOL;
	    break;
	}
    SKIP_TO_EOL;
}

static void
hot_load_file (struct hotlist * grp)
{
    int		tkn;
    struct hotlist *new_grp;
    char	*label, *url;

    int         hotkey = 0;

    current_group = grp;

    while ((tkn = hot_next_token())!= TKN_EOF)
	switch (tkn) {
	case TKN_GROUP:
	    CHECK_TOKEN(TKN_STRING);
	    label = g_strdup (tkn_buf);
	    if ((tkn = hot_next_token ()) == TKN_HOTKEY) { 
		CHECK_TOKEN(TKN_STRING);
		hotkey = (int) g_strdup (tkn_buf) [0];
	    } else {
		unhot_next_token (tkn);
		hotkey = 0;
	    }

	    hotkey = check_duplicate_hotkey (hotkey);

	    new_grp = add2hotlist (label, 0, hotkey, HL_TYPE_GROUP, 0);
	    SKIP_TO_EOL;
	    hot_load_group (new_grp);
	    current_group = grp;
	    break;
	case TKN_ENTRY:
	    CHECK_TOKEN(TKN_STRING);
	    label = g_strdup (tkn_buf);
	    CHECK_TOKEN(TKN_URL);
	    CHECK_TOKEN(TKN_STRING);
	    url = g_strdup (tkn_buf);
	    if ((tkn = hot_next_token ()) == TKN_HOTKEY) { 
		CHECK_TOKEN(TKN_STRING);
		hotkey = (int) g_strdup (tkn_buf) [0];
	    } else {
		unhot_next_token (tkn);
		hotkey = 0;
	    }

	    hotkey = check_duplicate_hotkey (hotkey);

	    add2hotlist (label, url, hotkey, HL_TYPE_ENTRY, 0);
	    SKIP_TO_EOL;
	    break;
	case TKN_COMMENT:
	    label = g_strdup (tkn_buf);
	    add2hotlist (label, 0, 0, HL_TYPE_COMMENT, 0);
	    break;
	case TKN_EOL:
	    /* skip empty lines */
	    break;
	default:
	    hotlist_state.readonly = 1;
	    hotlist_state.file_error = 1;
	    SKIP_TO_EOL;
	    break;
	}
}

static void
clean_up_hotlist_groups (const char *section)
{
    char	*grp_section;
    void	*profile_keys;
    char	*key, *value;

    grp_section = g_strconcat (section, ".Group", (char *) NULL);
    if (profile_has_section (section, profile_name))
	profile_clean_section (section, profile_name);
    if (profile_has_section (grp_section, profile_name)) {
	profile_keys = profile_init_iterator (grp_section, profile_name);

	while (profile_keys) {
	    profile_keys = profile_iterator_next (profile_keys, &key, &value);
	    clean_up_hotlist_groups (key);
	}
	profile_clean_section (grp_section, profile_name);
    }
    g_free (grp_section);
}



static void
load_hotlist (void)
{
    int		remove_old_list = 0;
    struct stat stat_buf;

    if (hotlist_state.loaded) {
	stat (hotlist_file_name, &stat_buf);
	if (hotlist_file_mtime < stat_buf.st_mtime) 
	    done_hotlist ();
	else
	    return;
    }

    if (!hotlist_file_name)
	hotlist_file_name = concat_dir_and_file (home_dir, HOTLIST_FILENAME);
    
    hotlist	       = new_hotlist ();
    hotlist->type      = HL_TYPE_GROUP;
    hotlist->label     = g_strdup (_("Top level group"));
    hotlist->up        = hotlist;
    /*
     * compatibility :-(
     */
    hotlist->directory = g_strdup ("Hotlist");

    if ((hotlist_file = fopen (hotlist_file_name, "r")) == 0) {
	int	result;

	load_group (hotlist);
	hotlist_state.loaded   = 1;
	/*
	 * just to be sure we got copy
	 */
	hotlist_state.modified = 1;
	result = save_hotlist ();
	hotlist_state.modified = 0;
	if (result) {
	    remove_old_list = 1;
	} else {
	    message (D_ERROR, _("Hotlist: Load"),
		     _("MC was unable to write ~/%s file, your old hotlist entries were not deleted"),
		     HOTLIST_FILENAME);
	}
    } else {
	hot_load_file (hotlist);
	fclose (hotlist_file);
	hotlist_state.loaded = 1;
    }

    if (remove_old_list) {
	clean_up_hotlist_groups ("Hotlist");
	sync_profiles ();
    }

    stat (hotlist_file_name, &stat_buf);
    hotlist_file_mtime = stat_buf.st_mtime;
    current_group = hotlist;

    if (hotkey_dupes) {
        hotkey_dupes = 0;
        message (D_ERROR, _("Hotlist: Load"), "Some groups contained duplicate hotkeys which were ignored.");
        hotlist_state.modified = 1;
    }
}
/* This function is never called - what is it for?   --bb */
/*

static void _ATTRIBUTE_UNUSED_ save_group (struct hotlist *grp)
{
    struct hotlist *current = grp->head;
    char           *group_section;

    group_section = find_group_section (grp);
    
    profile_clean_section (group_section, profile_name);
    for (;current && current->type == HL_TYPE_GROUP; current = current->next){
	WritePrivateProfileString (group_section,
				   current->directory,
				   current->label,
				   profile_name);
    }
    g_free (group_section);

    for (current = grp->head;
	 current && current->type == HL_TYPE_GROUP;
	 current = current->next)
	 save_group (current);
    
    profile_clean_section (grp->directory, profile_name);
    for (;current; current = current->next){
	WritePrivateProfileString (grp->directory,
				   current->directory,
				   current->label,
				   profile_name);
    }
}
*/
static int  list_level = 0;

static void
hot_save_group (struct hotlist *grp)
{
    struct hotlist *current = grp->head;
    int		    i;
    char	   *s;

#define INDENT(n) \
do { \
    for (i = 0; i < n; i++) \
	putc (' ', hotlist_file); \
} while (0)

    for (;current; current = current->next)
        switch (current->type) {
        case HL_TYPE_GROUP:
	    INDENT (list_level);
	    fputs ("GROUP \"", hotlist_file);
	    for (s = current->label; *s; s++) {
		if (*s == '"' || *s == '\\')
		    putc ('\\', hotlist_file);
		putc (*s, hotlist_file);
	    }
	    fputs ("\"", hotlist_file);
	    if (current->hotkey) {
		fputs (" HOTKEY \"", hotlist_file); 
		putc ((char) current->hotkey, hotlist_file);
		fputs ("\"", hotlist_file);
	    }
	    fputs ("\n", hotlist_file);
	    list_level += 2;
	    hot_save_group (current);
	    list_level -= 2;
	    INDENT (list_level);
	    fputs ("ENDGROUP\n", hotlist_file);
	    break;
	case HL_TYPE_ENTRY:
	    INDENT(list_level);
	    fputs ("ENTRY \"", hotlist_file);
	    for (s = current->label; *s; s++) {
		if (*s == '"' || *s == '\\')
		    putc ('\\', hotlist_file);
		putc (*s, hotlist_file);
	    }
	    fputs ("\" URL \"", hotlist_file);
	    for (s = current->directory; *s; s++) {
		if (*s == '"' || *s == '\\')
		    putc ('\\', hotlist_file);
		putc (*s, hotlist_file);
	    }
	    fputs ("\"", hotlist_file);
	    if (current->hotkey) {
		fputs (" HOTKEY \"", hotlist_file); 
		putc ((char) current->hotkey, hotlist_file);
		fputs ("\"", hotlist_file);
	    }
	    fputs ("\n", hotlist_file);
	    break;
	case HL_TYPE_COMMENT:
	    fprintf (hotlist_file, "#%s\n", current->label);
	    break;
	case HL_TYPE_DOTDOT:
	    /* do nothing */
	    break;
	}
}

int save_hotlist (void)
{
    int		saved = 0;
    struct      stat stat_buf;
    
    if (!hotlist_state.readonly && hotlist_state.modified && hotlist_file_name) {
	char	*fbak = g_strconcat (hotlist_file_name, ".bak", (char *) NULL);

	rename (hotlist_file_name, fbak);
	if ((hotlist_file = fopen (hotlist_file_name, "w")) != 0) {
	    if (stat (fbak, &stat_buf) == 0)
		chmod (hotlist_file_name, stat_buf.st_mode);
	    else
		chmod (hotlist_file_name, S_IRUSR | S_IWUSR);
	    hot_save_group (hotlist);
	    fclose (hotlist_file);
	    stat (hotlist_file_name, &stat_buf);
	    hotlist_file_mtime = stat_buf.st_mtime;
	    saved = 1;
	    hotlist_state.modified = 0;
	} else
	    rename (fbak, hotlist_file_name);
	g_free (fbak);
    }

    return saved;
}

/*
 * Unload list from memory.
 * Don't confuse with hotlist_done() for GUI.
 */
void done_hotlist (void)
{
    if (hotlist){
	remove_group (hotlist);
	g_free (hotlist->label);
	g_free (hotlist->directory);
	g_free (hotlist);
	hotlist = 0;
    }
    
    hotlist_state.loaded = 0;

    g_free (hotlist_file_name);
    hotlist_file_name = 0;
    l_hotlist = 0;
    current_group = 0;
    if (tkn_buf){
        g_free (tkn_buf);
	tkn_buf_length = 0;
	tkn_length = 0;
	tkn_buf = NULL;
    }
}

static void
add_dotdot_to_list (void)
{
    char *hotkey = "";
    if (current_group != hotlist) {
	if (hotlist_has_dot_dot != 0)
	    add2hotlist (g_strdup (".."), g_strdup (".."), (int) *hotkey, HL_TYPE_DOTDOT, 0);
    }
}
