// This file is part of Snownews - A lightweight console RSS newsreader
//
// Copyright (c) 2003-2004 Oliver Feiler <kiza@kcore.de>
// Copyright (c) 2021 Mike Sharov <msharov@users.sourceforge.net>
//
// Snownews is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 3
// as published by the Free Software Foundation.
//
// Snownews 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 Snownews. If not, see http://www.gnu.org/licenses/.

#include "ui.h"
#include "main.h"
#include "about.h"
#include "cat.h"
#include "conv.h"
#include "dialog.h"
#include "feedio.h"
#include "setup.h"
#include "uiutil.h"
#include <ncurses.h>
#include <libxml/parser.h>
#ifdef UTF_8
#define xmlStrlen(s) xmlUTF8Strlen(s)
#endif

//----------------------------------------------------------------------

static bool resize_dirty = false;

//----------------------------------------------------------------------

void sig_winch (int p __attribute__((unused)))
{
    resize_dirty = true;
}

// View newsitem in scrollable window.
// Speed of this code has been greatly increased in 1.2.1.
static void UIDisplayItem (const struct newsitem* current_item, const struct feed* current_feed)
{
    unsigned topline = 0;	// First line on screen
    while (1) {
	const unsigned pagesz = LINES-4;
	const unsigned pagew = COLS-2;

	erase();

	const char* feed_title = current_feed->description;
	if (!feed_title || !feed_title[0])
	    feed_title = current_feed->title;
	if (!feed_title || !feed_title[0])
	    feed_title = "* " SNOWNEWS_NAME " " SNOWNEWS_VERSTRING;

	// Print feed title
	UISupportDrawHeader (feed_title);

	// Print publishing date if we have one.
	if (current_item->data->date) {
	    char* date_str = unixToPostDateString (current_item->data->date);
	    if (date_str) {
		move (0, COLS - strlen(date_str) - 2);
		attron (WA_REVERSE);
		addch (' ');
		addstr (date_str);
		addch (' ');
		attroff (WA_REVERSE);
		free (date_str);
	    }
	}

	// Print item title
	unsigned ydesc = 1, xdesc = 1;
	if (current_item->data->title) {
	    unsigned titlelen = xmlStrlen ((xmlChar*) current_item->data->title);
	    unsigned xtitle = xdesc;
	    if (titlelen < COLS - xdesc*2)
		xtitle = (COLS - titlelen) / 2u;
	    move (ydesc, xtitle);
	    attron (WA_BOLD);
	    add_utf8 (current_item->data->title);
	    attroff (WA_BOLD);
	    mvhline (++ydesc, 0, 0, COLS);
	    ++ydesc;
	}

	// Print item text
	unsigned desclines = 0; // and count lines
	if (!current_item->data->description || !current_item->data->description[0])
	    mvadd_utf8 (ydesc, xdesc, _("No description available."));
	else {
	    char* wrapped_dtext = strdup (current_item->data->description);
	    wrap_text (wrapped_dtext, pagew);
	    const char* dtext = wrapped_dtext;
	    const char* dtextend = dtext + strlen (dtext);
	    unsigned y = ydesc;
	    while (dtext < dtextend) {
		const char* pnl = strchr (dtext, '\n');
		if (!pnl)
		    pnl = dtextend;
		if (desclines >= topline && desclines < topline+pagesz) {
		    unsigned linelen = utf8_range_length (dtext, pnl);
		    if (linelen > pagew)
			linelen = pagew;
		    mvaddn_utf8 (y++, xdesc, dtext, linelen);
		}
		++desclines;
		dtext = pnl+strlen("\n");
	    }
	    free (wrapped_dtext);
	}

	char keyinfostr [256];
	if (current_item->data->link)
	    snprintf (keyinfostr, sizeof (keyinfostr), "-> %s", current_item->data->link);
	else
	    snprintf (keyinfostr, sizeof (keyinfostr), _("Press '%c' or Enter to return to previous screen. Hit '%c' for help screen."), _settings.keybindings.prevmenu, _settings.keybindings.help);
	UIStatus (keyinfostr, 0, 0);

	int uiinput = getch();
	if (uiinput == _settings.keybindings.help || uiinput == '?')
	    UIDisplayItemHelp();
	else if (uiinput == '\n' || uiinput == _settings.keybindings.prevmenu || uiinput == _settings.keybindings.enter)
	    return;
	else if (uiinput == _settings.keybindings.urljump)
	    UISupportURLJump (current_item->data->link);
	else if (uiinput == _settings.keybindings.next || uiinput == KEY_RIGHT) {
	    if (current_item->next != NULL) {
		current_item = current_item->next;
		topline = 0;
	    } else
		uiinput = ungetch (_settings.keybindings.prevmenu);
	} else if (uiinput == _settings.keybindings.prev || uiinput == KEY_LEFT) {
	    if (current_item->prev != NULL) {
		current_item = current_item->prev;
		topline = 0;
	    } else
		uiinput = ungetch (_settings.keybindings.prevmenu);
	} else if (uiinput == KEY_NPAGE || uiinput == ' ' || uiinput == _settings.keybindings.pdown) {
	    // Scroll by one page.
	    for (unsigned i = 0; i < pagesz; ++i)
		if (topline + pagesz < desclines)
		    ++topline;
	} else if (uiinput == KEY_PPAGE || uiinput == _settings.keybindings.pup) {
	    for (unsigned i = 0; i < pagesz; ++i)
		if (topline > 0)
		    --topline;
	} else if (uiinput == KEY_UP && topline > 0)
	    --topline;
	else if (uiinput == KEY_DOWN && topline + pagesz < desclines)
	    ++topline;
	else if (uiinput == KEY_HOME || uiinput == 'H')
	    topline = 0;
	else if (uiinput == KEY_END || uiinput == 'G')
	    topline = (desclines > pagesz ? desclines - pagesz : 0);
	else if (resize_dirty || uiinput == KEY_RESIZE) {
	    clear();
	    resize_dirty = false;
	} else if (uiinput == _settings.keybindings.about)
	    UIAbout();
	else if (uiinput == 12) // Redraw screen on ^L
	    clear();
    }
}

static int UIDisplayFeed (struct feed* current_feed)
{
    const unsigned ymax = LINES-1;
    const unsigned pagesz = LINES-3;

    // Set highlighted to _feed_list at the beginning.
    // Otherwise bad things (tm) will happen.
    const struct newsitem* first_scr_ptr = current_feed->items;
    const struct newsitem* tmp_first = first_scr_ptr;

    // Select first unread item if we enter feed view or
    // leave first item active if there is no unread.
    const struct newsitem* highlighted = current_feed->items;
    const struct newsitem* tmp_highlighted = highlighted;
    unsigned highlightline = ymax+1;

    // Moves highlight to next unread item.
    unsigned highlightnum = 1;	// Index of the highlighted item, 1 = first visible
    while (highlighted && highlighted->next && highlighted->data->readstatus) {
	highlighted = highlighted->next;
	if (++highlightnum > ymax) {
	    --highlightnum;
	    if (first_scr_ptr->next)
		first_scr_ptr = first_scr_ptr->next;
	}
    }
    // If *highlighted points to a read entry now we have hit the last
    // entry and there are no unread items anymore. Restore the original
    // location in this case.
    // Check if we have no items at all!
    if (highlighted && highlighted->data->readstatus) {
	highlighted = tmp_highlighted;
	first_scr_ptr = tmp_first;
    }
    // Typeahead enabled?
    bool typeahead = false;
    unsigned typeaheadskip = 0;	// # of lines to skip (for typeahead)
    char* searchstr = NULL;	// Typeahead searchstr string
    unsigned searchstrlen = 0;

    // Save first starting position. For typeahead.
    const struct newsitem* savestart = first_scr_ptr;	// Typeahead internal usage (tmp position save)
    const struct newsitem* savestart_first = NULL;

    // Put all categories of the current feed into a comma seperated list.
    char* categories = GetCategoryList (current_feed);

    bool reloaded = false;	// We need to signal the main function if we changed feed contents.
    while (1) {
	erase();

	// Print a title or something
	const char* title = current_feed->description;
	if (!title || !title[0])
	    title = current_feed->title;
	if (!title || !title[0])
	    title = _("No title");
	UISupportDrawHeader (title);

	// We start the item list below the header
	unsigned ypos = 2, itemnum = 1;

	if (typeahead) {
	    // This resets the offset for every typeahead loop.
	    first_scr_ptr = current_feed->items;

	    unsigned count = 0, skipper = 0;	// # of lines already skipped
	    bool found = false;
	    for (const struct newsitem * i = current_feed->items; i; ++count, i = i->next) {
		// count+1 > ymax:
		// If the _next_ line would go over the boundary.
		if (count + 1 > ymax)
		    if (first_scr_ptr->next)
			first_scr_ptr = first_scr_ptr->next;
		if (!searchstrlen)
		    continue;
		// Substring match.
		if (i->data->title && s_strcasestr (i->data->title, searchstr)) {
		    found = true;
		    highlighted = i;
		    if (typeaheadskip >= 1 && skipper != typeaheadskip) {
			++skipper;
			continue;
		    }
		    break;
		}
	    }
	    if (!found) {
		// Restore original position on no match.
		highlighted = savestart;
		first_scr_ptr = savestart_first;
	    }
	}

	// Print unread entries in bold.
	const struct newsitem* current_item = NULL;
	for (const struct newsitem* item = first_scr_ptr; item; item = item->next) {
	    // Set cursor to start of current line and clear it.
	    move (ypos, 0);
	    clrtoeol();

	    if (!item->data->readstatus)
		attron (WA_BOLD);

	    if (item == highlighted) {
		current_item = item;
		highlightline = ypos;
		highlightnum = itemnum;
		attron (WA_REVERSE);
		mvhline (ypos, 0, ' ', COLS);
	    }
	    // Check for empty <title>
	    if (item->data->title) {
		const char* item_title = item->data->title;
		char tmpstr[256];
		if (current_feed->smartfeed == 1) {
		    snprintf (tmpstr, sizeof (tmpstr), "(%s) %s", item->data->parent->title, item->data->title);
		    item_title = tmpstr;
		}

		int columns = COLS - 6;	// Cut max item length.
		mvaddn_utf8 (ypos, 1, item_title, columns);
		if (xmlStrlen ((xmlChar*) item_title) > columns)
		    mvaddstr (ypos, columns + 1, "...");
	    } else {
		if (current_feed->smartfeed == 1) {
		    char tmpstr[256];
		    snprintf (tmpstr, sizeof (tmpstr), "(%s) %s", item->data->parent->title, _("No title"));
		    mvadd_utf8 (ypos, 1, tmpstr);
		} else
		    mvadd_utf8 (ypos, 1, _("No title"));
	    }

	    ++ypos;
	    if (item == highlighted)
		attroff (WA_REVERSE);
	    if (!item->data->readstatus)
		attroff (WA_BOLD);

	    if (itemnum >= ymax)
		break;
	    ++itemnum;
	}

	char tmpstr [256];
	if (typeahead)
	    snprintf (tmpstr, sizeof (tmpstr), "-> %s", searchstr);
	else if (current_item && current_item->data->link)
	    snprintf (tmpstr, sizeof (tmpstr), "-> %s", current_item->data->link);
	else
	    snprintf (tmpstr, sizeof (tmpstr), _("Press '%c' to return to main menu, '%c' to show help."), _settings.keybindings.prevmenu, _settings.keybindings.help);
	UIStatus (tmpstr, 0, 0);

	move (highlightline, 0);
	int uiinput = getch();

	time_t curtime = time (NULL);
	if (typeahead) {
	    // Only match real characters.
	    if (uiinput >= ' ' && uiinput <= '~') {
		searchstr[searchstrlen] = uiinput;
		searchstr[searchstrlen + 1] = 0;
		++searchstrlen;
		searchstr = realloc (searchstr, searchstrlen + 2);
		// ASCII 127 is DEL, 263 is... actually I have
		// no idea, but it's what the text console returns.
	    } else if (uiinput == 127 || uiinput == 263) {
		// Don't let searchstrlen go below 0!
		if (searchstrlen > 0) {
		    --searchstrlen;
		    searchstr[searchstrlen] = 0;
		    searchstr = realloc (searchstr, searchstrlen + 2);
		} else {
		    typeahead = false;
		    free (searchstr);
		}
	    }
	} else {
	    if (uiinput == _settings.keybindings.help || uiinput == '?')
		UIDisplayFeedHelp();
	    else if (uiinput == _settings.keybindings.prevmenu) {
		free (categories);
		return reloaded;
	    } else if ((uiinput == KEY_UP || uiinput == _settings.keybindings.prev) && highlighted && highlighted->prev) {
		// Check if we have no items at all!
		highlighted = highlighted->prev;
		// Adjust first visible entry.
		if (--highlightnum < 1 && first_scr_ptr->prev) {
		    ++highlightnum;
		    first_scr_ptr = first_scr_ptr->prev;
		}
	    } else if ((uiinput == KEY_DOWN || uiinput == _settings.keybindings.next) && highlighted && highlighted->next) {
		highlighted = highlighted->next;
		// Adjust first visible entry.
		if (++highlightnum > pagesz && first_scr_ptr->next) {
		    --highlightnum;
		    first_scr_ptr = first_scr_ptr->next;
		}
	    } else if ((uiinput == KEY_NPAGE || uiinput == ' ' || uiinput == _settings.keybindings.pdown) && highlighted) {
		// Move highlight one page up/down == pagesz
		for (unsigned i = 0; i < pagesz && highlighted->next; ++i) {
		    highlighted = highlighted->next;
		    if (++highlightnum > pagesz && first_scr_ptr->next) {
			--highlightnum;
			first_scr_ptr = first_scr_ptr->next;
		    }
		}
	    } else if ((uiinput == KEY_PPAGE || uiinput == _settings.keybindings.pup) && highlighted) {
		for (unsigned i = 0; i < pagesz && highlighted->prev; ++i) {
		    highlighted = highlighted->prev;
		    if (--highlightnum < 1 && first_scr_ptr->prev) {
			++highlightnum;
			first_scr_ptr = first_scr_ptr->prev;
		    }
		}
	    } else if (uiinput == KEY_HOME || uiinput == _settings.keybindings.home) {
		highlighted = first_scr_ptr = current_feed->items;
		highlightnum = 0;
	    } else if (uiinput == KEY_END || uiinput == _settings.keybindings.end) {
		highlighted = first_scr_ptr = current_feed->items;
		highlightnum = 0;
		while (highlighted && highlighted->next) {
		    highlighted = highlighted->next;
		    if (++highlightnum >= pagesz && first_scr_ptr->next) {
			--highlightnum;
			first_scr_ptr = first_scr_ptr->next;
		    }
		}
	    } else if (uiinput == _settings.keybindings.reload || uiinput == _settings.keybindings.forcereload) {
		if (_unfiltered_feed_list) {
		    UIStatus (_("Please deactivate the category filter before using this function."), 2, 0);
		    continue;
		}
		if (current_feed->smartfeed == 1)
		    continue;

		if (uiinput == _settings.keybindings.forcereload) {
		    free (current_feed->lasterror);
		    current_feed->lasterror = NULL;
		}

		UpdateFeed (current_feed);
		highlighted = current_feed->items;
		// Reset first_scr_ptr if reloading.
		first_scr_ptr = current_feed->items;
		reloaded = true;
	    } else if (uiinput == _settings.keybindings.urljump)
		UISupportURLJump (current_feed->link);
	    else if (uiinput == _settings.keybindings.urljump2 && highlighted)
		UISupportURLJump (highlighted->data->link);
	    else if (uiinput == _settings.keybindings.markread) {	// Mark everything read.
		for (struct newsitem* i = current_feed->items; i; i = i->next) {
		    if (!i->data->readstatus) {
			i->data->readstatus = true;
			current_feed->mtime = curtime;
		    }
		}
	    } else if (uiinput == _settings.keybindings.markunread && highlighted) {
		highlighted->data->readstatus = !highlighted->data->readstatus;
		highlighted->data->parent->mtime = curtime;
		reloaded = true;
	    } else if (uiinput == _settings.keybindings.about)
		UIAbout();
	    else if (uiinput == _settings.keybindings.feedinfo)
		FeedInfo (current_feed);
	    else if (resize_dirty || uiinput == KEY_RESIZE) {
		clear();
		resize_dirty = false;
	    } else if (uiinput == 12)	// Redraw screen on ^L
		clear();
	}
	if (uiinput == '\n' || (uiinput == _settings.keybindings.enter && !typeahead)) {
	    // If typeahead is active clear it's state and free the structure.
	    if (typeahead) {
		free (searchstr);
		typeahead = false;
		if (!_settings.cursor_always_visible)
		    curs_set (0);
		typeaheadskip = 0;
	    }
	    // Check if we have no items at all!
	    // Don't even try to view a non existant item.
	    if (highlighted) {
		UIDisplayItem (highlighted, current_feed);
		if (!highlighted->data->readstatus) {
		    highlighted->data->readstatus = true;
		    current_feed->mtime = curtime;
		}
		tmp_highlighted = highlighted;
		tmp_first = first_scr_ptr;

		// highlighted = first_scr_ptr;
		// Moves highlight to next unread item.
		while (highlighted->next && highlighted->data->readstatus) {
		    highlighted = highlighted->next;
		    if (++highlightnum > ymax && first_scr_ptr->next) {
			--highlightnum;
			first_scr_ptr = first_scr_ptr->next;
		    }
		}

		// If *highlighted points to a read entry now we have hit the last
		// entry and there are no unread items anymore. Restore the original
		// location in this case.
		if (highlighted->data->readstatus) {
		    highlighted = tmp_highlighted;
		    first_scr_ptr = tmp_first;
		}
	    }
	}
	// TAB key is decimal 9.
	if (uiinput == 9 || uiinput == _settings.keybindings.typeahead) {
	    if (typeahead) {
		if (searchstrlen == 0) {
		    typeahead = false;
		    // Typeahead now off.
		    if (!_settings.cursor_always_visible)
			curs_set (0);
		    free (searchstr);
		    typeaheadskip = 0;
		    highlighted = savestart;
		    first_scr_ptr = savestart_first;
		} else {
		    unsigned found = 0;
		    for (const struct newsitem * i = current_feed->items; i; i = i->next)
			if (i->data->title && s_strcasestr (i->data->title, searchstr))
			    ++found;	// Substring match.
		    if (typeaheadskip == found - 1)
			typeaheadskip = 0;
		    else if (found > 1)
			++typeaheadskip;	// If more than one match was found and user presses tab we will skip matches.
		}
	    } else {
		typeahead = true;
		// Typeahead now on.
		curs_set (1);
		searchstr = malloc (2);
		memset (searchstr, 0, 2);
		searchstrlen = 0;
		// Save all start positions.
		savestart = highlighted;
		savestart_first = first_scr_ptr;
	    }
	}
	// ctrl+g clears typeahead.
	if (uiinput == 7) {
	    // But only if it was switched on previously.
	    if (typeahead) {
		typeahead = false;
		free (searchstr);
		typeaheadskip = 0;
		highlighted = savestart;
		first_scr_ptr = savestart_first;
	    }
	}
	// ctrl+u clears line.
	if (uiinput == 21) {
	    searchstrlen = 0;
	    searchstr = realloc (searchstr, 2);
	    memset (searchstr, 0, 2);
	}
    }
}

void UIMainInterface (void)
{
    struct feed* first_scr_ptr = _feed_list;	// First pointer on top of screen. Used for scrolling.
    struct feed* savestart = first_scr_ptr;

    struct feed* savestart_first = NULL;
    struct feed* highlighted = _feed_list;
    unsigned highlightnum = 1;
    unsigned highlightline = LINES-1;	// Line with current selected item cursor
    const unsigned pagesz = LINES-2;
    // will be moved to this line to have it in
    // the same line as the highlight. Visually
    // impaired users with screen readers seem
    // to need this.

    bool typeahead = false;
    unsigned typeaheadskip = 0;	// Number of typeahead matches to skip.
    char* search = NULL;	// Typeahead search string.
    unsigned searchlen = 0;	// " length.

    char* filters[8] = { };    // Category filters. Must be NULL when not used!
    unsigned numfilters = 0;	// Number of active filters.
    bool filteractivated = false;
    bool update_smartfeeds = true;
    bool andxor = false;	// Toggle for AND/OR combinations of filters.

    while (1) {
	if (update_smartfeeds) {
	    // This only needs to be done if new items are added, old removed.
	    // Reload, add, delete.
	    SmartFeedsUpdate();
	    update_smartfeeds = false;
	}
	erase();

	char* filterstring = NULL;
	if (filters[0]) {
	    numfilters = 0;
	    size_t len = 0;
	    for (unsigned i = 0; i < sizeof(filters)/sizeof(filters[0]) && filters[i]; ++i, ++numfilters)
		len += strlen (filters[i]) + strlen (", ");
	    filterstring = calloc (len, 1);
	    for (unsigned i = 0; i < numfilters; ++i) {
		if (i)
		    strcat (filterstring, ", ");
		strcat (filterstring, filters[i]);
	    }
	}
	UISupportDrawHeader (filterstring);
	if (filterstring)
	    free (filterstring);
	filterstring = NULL;

	// If a filter is defined we need to make copy of the pointers in
	// struct feed and work on that.
	// Build a new list only with items matching current filter.
	//
	// Never EVER set filteractivated=true if there is no filter defined!
	// This should be moved to its own function in ui-support.c!
	if (filteractivated) {
	    if (!_unfiltered_feed_list)
		_unfiltered_feed_list = _feed_list;
	    _feed_list = NULL;
	    for (const struct feed * cur_ptr = _unfiltered_feed_list; cur_ptr; cur_ptr = cur_ptr->next) {
		unsigned found = 0;
		if (!cur_ptr->feedcategories)
		    continue;
		for (const struct feedcategories * c = cur_ptr->feedcategories; c; c = c->next)
		    for (unsigned i = 0; i < 8 && filters[i]; ++i)
			if (strcasecmp (c->name, filters[i]) == 0)	// Match found
			    ++found;
		if (!found)
		    continue;

		bool addfilteritem = false;
		if (!andxor) { // AND
		    if (found == numfilters)
			addfilteritem = true;
		} else if (found)	// OR
		    addfilteritem = true;

		if (addfilteritem) {
		    struct feed* new_feed = newFeedStruct();
		    // These structs will get leaked!
		    // Yeah, yeah, it is on my todo list...
		    // Though valgrind doesn't report them as leaked.

		    // Duplicate pointers
		    new_feed->feedurl = cur_ptr->feedurl;
		    new_feed->xmltext = cur_ptr->xmltext;
		    new_feed->title = cur_ptr->title;
		    new_feed->link = cur_ptr->link;
		    new_feed->description = cur_ptr->description;
		    new_feed->lasterror = cur_ptr->lasterror;
		    new_feed->items = cur_ptr->items;
		    new_feed->problem = cur_ptr->problem;
		    new_feed->custom_title = cur_ptr->custom_title;
		    new_feed->original = cur_ptr->original;
		    new_feed->perfeedfilter = cur_ptr->perfeedfilter;
		    new_feed->execurl = cur_ptr->execurl;
		    new_feed->feedcategories = cur_ptr->feedcategories;

		    // Add to bottom of pointer chain.
		    AddFeedToList (new_feed);
		}
	    }
	    first_scr_ptr = _feed_list;
	    highlighted = first_scr_ptr;
	    filteractivated = false;
	}

	unsigned ypos = 2, itemnum = 1;

	if (typeahead) {
	    // This resets the offset for every typeahead loop.
	    first_scr_ptr = _feed_list;

	    unsigned count = 0, skipper = 0;
	    bool found = false;
	    for (struct feed* cur_ptr = _feed_list; cur_ptr; ++count, cur_ptr = cur_ptr->next) {
		// count+1 >= pagesz: if the _next_ line would go over the boundary.
		if (count + 1 > pagesz && first_scr_ptr->next)
		    first_scr_ptr = first_scr_ptr->next;
		// Exact match from beginning of line.
		if (searchlen > 0 && s_strcasestr (cur_ptr->title, search)) {
		    found = true;
		    highlighted = cur_ptr;
		    if (typeaheadskip >= 1 && skipper != typeaheadskip)
			++skipper;
		    break;
		}
	    }
	    if (!found) {
		// Restore original position on no match.
		highlighted = savestart;
		first_scr_ptr = savestart_first;
	    }
	}

	for (struct feed * cur_ptr = first_scr_ptr; cur_ptr; cur_ptr = cur_ptr->next) {
	    // Set cursor to start of current line and clear it.
	    move (ypos, 0);
	    clrtoeol();

	    // Determine number of new items in feed.
	    unsigned newcount = 0;
	    for (const struct newsitem * i = cur_ptr->items; i; i = i->next)
		if (!i->data->readstatus)
		    ++newcount;

	    // Make highlight if we are the highlighted feed
	    if (cur_ptr == highlighted) {
		highlightline = ypos;
		highlightnum = itemnum;
		attron (WA_REVERSE);
		mvhline (ypos, 0, ' ', COLS);
	    }

	    int columns;
	    if (cur_ptr->feedcategories != NULL)
		columns = COLS - 26 - strlen ("new");
	    else
		columns = COLS - 9 - strlen ("new");

	    mvaddn_utf8 (ypos, 1, cur_ptr->title, columns);
	    if (xmlStrlen ((xmlChar*) cur_ptr->title) > columns)
		mvaddstr (ypos, columns + 1, "...");

	    if (cur_ptr->problem)
		mvaddch (ypos, 0, '!');

	    // Execute this _after_ the for loop. Otherwise it'll suck CPU like crazy!
	    if (newcount) {
		const char* localized_msg = ngettext ("%3u new", "%3u new", newcount);
		char msgbuf[16];
		snprintf (msgbuf, sizeof (msgbuf), localized_msg, newcount);
		mvadd_utf8 (ypos, COLS - 1 - utf8_length (localized_msg), msgbuf);
	    }

	    if (cur_ptr->feedcategories != NULL)
		mvaddn_utf8 (ypos, COLS - 21 - strlen ("new"), cur_ptr->feedcategories->name, 15);

	    ++ypos;

	    if (cur_ptr == highlighted)
		attroff (WA_REVERSE);

	    if (itemnum >= pagesz)
		break;
	    ++itemnum;
	}

	if (typeahead) {
	    char msgbuf[128];
	    snprintf (msgbuf, sizeof (msgbuf), "-> %s", search);
	    UIStatus (msgbuf, 0, 0);
	} else {
	    char msgbuf[128];
	    snprintf (msgbuf, sizeof (msgbuf), _("Press '%c' for help window."), _settings.keybindings.help);
	    if (easterEgg())
		snprintf (msgbuf, sizeof (msgbuf), _("Press '%c' for help window. (Press '%c' to play Santa Hunta!)"), _settings.keybindings.help, _settings.keybindings.about);
	    UIStatus (msgbuf, 0, 0);
	}

	move (highlightline, 0);
	int uiinput = getch();

	if (typeahead) {
	    // Only match real characters.
	    if (uiinput >= ' ' && uiinput <= '~') {
		search[searchlen] = uiinput;
		search[++searchlen] = 0;
		search = realloc (search, searchlen + 1);
	    } else if (uiinput == 127 || uiinput == 263) {
		// Don't let searchlen go below 0!
		if (searchlen > 0)
		    search[--searchlen] = 0;
		else {
		    typeahead = false;
		    free (search);
		}
	    }
	} else {
	    if (uiinput == _settings.keybindings.quit) {
		// Restore original _feed_list if filter is defined!
		if (filters[0])
		    _feed_list = _unfiltered_feed_list;
		MainQuit (NULL, NULL);
	    } else if (uiinput == _settings.keybindings.reload || uiinput == _settings.keybindings.forcereload) {
		if (filters[0])
		    UIStatus (_("Please deactivate the category filter before using this function."), 2, 0);
		else {
		    if (highlighted && uiinput == _settings.keybindings.forcereload) {
			free (highlighted->lasterror);
			highlighted->lasterror = NULL;
		    }
		    UpdateFeed (highlighted);
		    update_smartfeeds = true;
		}
	    } else if (uiinput == _settings.keybindings.reloadall) {
		if (filters[0])
		    UIStatus (_("Please deactivate the category filter before using this function."), 2, 0);
		else {
		    UpdateAllFeeds();
		    update_smartfeeds = true;
		}
	    } else if (uiinput == _settings.keybindings.addfeed || uiinput == _settings.keybindings.newheadlines) {
		if (filters[0])
		    UIStatus (_("Please deactivate the category filter before using this function."), 2, 0);
		else if (uiinput == _settings.keybindings.addfeed) {
		    switch (UIAddFeed (NULL)) {
			case 0:
			    UIStatus (_("Successfully added new item..."), 1, 0);
			    _feed_list_changed = true;
			    break;
			case 2:
			    UIStatus (_("Invalid URL! Please add http:// if you forgot this."), 2, 1);
			    break;
			case -1:
			    UIStatus (_("There was a problem adding the feed!"), 2, 1);
			    break;
			default:
			    break;
		    }
		} else if (!SmartFeedExists ("newitems")) {
		    UIAddFeed ("smartfeed:/newitems");
		    _feed_list_changed = true;
		}

		// Scroll to top of screen and redraw everything.
		highlighted = _feed_list;
		first_scr_ptr = _feed_list;
		update_smartfeeds = true;
	    } else if (uiinput == _settings.keybindings.help || uiinput == '?')
		UIHelpScreen();
	    else if (uiinput == _settings.keybindings.deletefeed) {
		// This should be moved to its own function in ui-support.c!
		if (filters[0]) {
		    UIStatus (_("Please deactivate the category filter before using this function."), 2, 0);
		    continue;
		}
		// Move this code into its own function!

		// If the deleted feed was the last one of a specific category,
		// remove this category from the global list.
		if (highlighted) {
		    if (UIDeleteFeed (highlighted->title) == 1) {
			// Do it!
			struct feed* removed = highlighted;
			// Save current highlight position.
			struct feed* saved_highlighted = highlighted;

			// Remove cachefile from filesystem.
			char* hashme = Hashify (highlighted->feedurl);
			char cachefilename[PATH_MAX];
			CacheFilePath (hashme, cachefilename, sizeof(cachefilename));
			free (hashme);

			// Errors from unlink can be ignored. Worst thing that happens is that
			// we delete a file that doesn't exist.
			unlink (cachefilename);

			// Unlink pointer from chain.
			if (highlighted == _feed_list) {
			    // first element
			    if (_feed_list->next) {
				_feed_list = _feed_list->next;
				first_scr_ptr = _feed_list;
				highlighted->next->prev = NULL;
			    } else {
				_feed_list = NULL;
				first_scr_ptr = NULL;
			    }
			    // Set new highlighted to _feed_list again.
			    saved_highlighted = _feed_list;
			} else if (highlighted->next == NULL) {
			    // last element
			    // Set new highlighted to element before deleted one.
			    saved_highlighted = highlighted->prev;
			    // If highlighted was first line move first line upward in pointer chain.
			    if (highlighted == first_scr_ptr)
				first_scr_ptr = first_scr_ptr->prev;

			    highlighted->prev->next = NULL;
			} else {
			    // element inside list */
			    // Set new highlighted to element after deleted one.
			    saved_highlighted = highlighted->next;
			    // If highlighted was last line, move first line downward in pointer chain.
			    if (highlighted == first_scr_ptr)
				first_scr_ptr = first_scr_ptr->next;

			    highlighted->prev->next = highlighted->next;
			    highlighted->next->prev = highlighted->prev;
			}
			// Put highlight to new highlight position.
			highlighted = saved_highlighted;

			// free (removed) pointer
			if (!removed->smartfeed) {
			    if (removed->items) {
				while (removed->items->next) {
				    removed->items = removed->items->next;
				    free (removed->items->prev->data->title);
				    free (removed->items->prev->data->link);
				    free (removed->items->prev->data->description);
				    free (removed->items->prev);
				}
				free (removed->items->data->title);
				free (removed->items->data->link);
				free (removed->items->data->description);
				free (removed->items);
			    }
			    free (removed->feedurl);
			    free (removed->xmltext);
			    removed->xmltext = NULL;
			    removed->content_length = 0;
			    free (removed->title);
			    free (removed->link);
			    free (removed->description);
			    free (removed->lasterror);
			    free (removed->custom_title);
			    free (removed->original);
			    free (removed);
			}
			_feed_list_changed = true;
			update_smartfeeds = true;
		    }
		}
	    } else if ((uiinput == KEY_UP || uiinput == _settings.keybindings.prev) && highlighted && highlighted->prev) {
		highlighted = highlighted->prev;
		if (--highlightnum < 1 && first_scr_ptr->prev) {	// Reached first onscreen entry.
		    ++highlightnum;
		    first_scr_ptr = first_scr_ptr->prev;
		}
	    } else if ((uiinput == KEY_DOWN || uiinput == _settings.keybindings.next) && highlighted && highlighted->next) {
		highlighted = highlighted->next;
		if (++highlightnum > pagesz && first_scr_ptr->next) {	// If we fall off the screen, advance first_scr_ptr to next entry.
		    --highlightnum;
		    first_scr_ptr = first_scr_ptr->next;
		}
	    } else if (uiinput == KEY_NPAGE || uiinput == ' ' || uiinput == _settings.keybindings.pdown) {
		// Move highlight one page up/down == pagesz
		for (unsigned i = 0; i < pagesz && highlighted->next; ++i) {
		    highlighted = highlighted->next;
		    if (++highlightnum > pagesz && first_scr_ptr->next) {
			--highlightnum;
			first_scr_ptr = first_scr_ptr->next;
		    }
		}
	    } else if (uiinput == KEY_PPAGE || uiinput == _settings.keybindings.pup) {
		for (unsigned i = 0; i < pagesz && highlighted->prev; ++i) {
		    highlighted = highlighted->prev;
		    if (--highlightnum < 1 && first_scr_ptr->prev) {
			++highlightnum;
			first_scr_ptr = first_scr_ptr->prev;
		    }
		}
	    } else if (uiinput == KEY_HOME || uiinput == _settings.keybindings.home) {
		highlighted = first_scr_ptr = _feed_list;
		highlightnum = 0;
	    } else if (uiinput == KEY_END || uiinput == _settings.keybindings.end) {
		highlighted = first_scr_ptr = _feed_list;
		highlightnum = 0;
		while (highlighted && highlighted->next) {
		    highlighted = highlighted->next;
		    if (++highlightnum >= pagesz && first_scr_ptr->next) {
			--highlightnum;
			first_scr_ptr = first_scr_ptr->next;
		    }
		}
	    } else if (uiinput == _settings.keybindings.moveup) {
		// This function is deactivated when a filter is active.
		if (filters[0] != NULL) {
		    UIStatus (_("You cannot move items while a category filter is defined!"), 2, 0);
		    continue;
		}
		// Move item up.
		if (highlighted) {
		    if (highlighted->prev != NULL) {
			SwapPointers (highlighted, highlighted->prev);
			highlighted = highlighted->prev;
			if (highlightnum - 1 < 1 && first_scr_ptr->prev)
			    first_scr_ptr = first_scr_ptr->prev;
			_feed_list_changed = true;
		    }
		    update_smartfeeds = true;
		}
	    } else if (uiinput == _settings.keybindings.movedown) {
		// This function is deactivated when a filter is active.
		if (filters[0]) {
		    UIStatus (_("You cannot move items while a category filter is defined!"), 2, 0);
		    continue;
		}
		// Move item down.
		if (highlighted) {
		    if (highlighted->next) {
			SwapPointers (highlighted, highlighted->next);
			highlighted = highlighted->next;
			if (++highlightnum >= pagesz && first_scr_ptr->next) {
			    --highlightnum;
			    first_scr_ptr = first_scr_ptr->next;
			}
			_feed_list_changed = true;
		    }
		    update_smartfeeds = true;
		}
	    } else if (uiinput == _settings.keybindings.dfltbrowser) {
		UIChangeBrowser();
		SaveBrowserSetting();
	    } else if (uiinput == _settings.keybindings.markallread) {
		// This function is safe for using in filter mode, because it only
		// changes int values. It automatically marks the correct ones read
		// if a filter is applied since we are using a copy of the main data.
		time_t curtime = time (NULL);
		for (struct feed* f = _feed_list; f; f = f->next) {
		    for (struct newsitem* i = f->items; i; i = i->next) {
			if (!i->data->readstatus) {
			    i->data->readstatus = true;
			    f->mtime = curtime;
			}
		    }
		}
	    } else if (uiinput == _settings.keybindings.about)
		UIAbout();
	    else if (uiinput == _settings.keybindings.changefeedname && highlighted) {
		if (filters[0])	// This needs to be worked on before it works while a filter is applied!
		    UIStatus (_("Please deactivate the category filter before using this function."), 2, 0);
		else {
		    UIChangeFeedName (highlighted);
		    _feed_list_changed = true;
		    update_smartfeeds = true;
		}
	    } else if (uiinput == _settings.keybindings.perfeedfilter && highlighted) {
		if (filters[0])
		    UIStatus (_("Please deactivate the category filter before using this function."), 2, 0);
		else {
		    UIPerFeedFilter (highlighted);
		    _feed_list_changed = true;
		}
	    } else if (uiinput == _settings.keybindings.sortfeeds && highlighted) {
		if (filters[0])	// Deactivate sorting function if filter is applied.
		    UIStatus (_("Please deactivate the category filter before using this function."), 2, 0);
		else {
		    SnowSort();
		    _feed_list_changed = true;
		    update_smartfeeds = true;
		}
	    } else if (uiinput == _settings.keybindings.categorize && highlighted && !highlighted->smartfeed) {
		if (filters[0])	// This needs to be worked on before it works while a filter is applied!
		    UIStatus (_("Please deactivate the category filter before using this function."), 2, 0);
		else {
		    CategorizeFeed (highlighted);
		    _feed_list_changed = true;
		}
	    } else if (uiinput == _settings.keybindings.filter) {
		// GUI to set a filter
		char* catfilter = DialogGetCategoryFilter();
		if (catfilter) {
		    unsigned iunusedfilter = 0;
		    while (iunusedfilter < 8 && filters[iunusedfilter])
			++iunusedfilter;
		    if (iunusedfilter < 8) {
			filters[iunusedfilter] = strdup (catfilter);
			filteractivated = true;
		    }
		} else {
		    ResetFilters (filters);

		    // If DialogGetCategoryFilter() was used to switch off filter
		    // Restore _feed_list here.
		    if (_unfiltered_feed_list) {
			// Restore _feed_list
			_feed_list = _unfiltered_feed_list;
			first_scr_ptr = _feed_list;
			highlighted = _feed_list;
			_unfiltered_feed_list = NULL;
		    }
		}
		free (catfilter);
		_feed_list_changed = true;
	    } else if (uiinput == _settings.keybindings.filtercurrent) {
		// Set filter to primary filter of this feed.
		// Free filter if it's not empty to avoid memory leaks!
		if (filters[0]) {
		    ResetFilters (filters);

		    // Set new filter to primary category.
		    // highlighted can be null if there is no feed highlighted.
		    // First start, no feeds ever added, multiple filters applied that no feed matches.
		    if (highlighted && highlighted->feedcategories) {
			filters[0] = strdup (highlighted->feedcategories->name);
			filteractivated = true;
		    }
		    // Restore _feed_list
		    _feed_list = _unfiltered_feed_list;
		    first_scr_ptr = _feed_list;
		    highlighted = _feed_list;
		    _unfiltered_feed_list = NULL;
		} else if (highlighted && highlighted->feedcategories) {
		    filters[0] = strdup (highlighted->feedcategories->name);
		    filteractivated = true;
		}
	    } else if (uiinput == _settings.keybindings.nofilter) {
		// Remove all filters.
		if (filters[0]) {
		    ResetFilters (filters);

		    // Restore _feed_list
		    _feed_list = _unfiltered_feed_list;
		    first_scr_ptr = _feed_list;
		    highlighted = _feed_list;
		    _unfiltered_feed_list = NULL;
		}
	    } else if (uiinput == 'X' && filters[0]) {	// AND or OR matching for the filer.
		andxor = !andxor;
		filteractivated = true;	// Reconstruct filter.
	    } else if (resize_dirty || uiinput == KEY_RESIZE) {
		clear();
		resize_dirty = false;
	    } else if (uiinput == 12)	// Redraw screen on ^L
		clear();
	}
	if (uiinput == '\n' || (uiinput == _settings.keybindings.enter && !typeahead)) {
	    // If typeahead is active clear it's state and free the structure.
	    if (typeahead) {
		free (search);
		typeahead = false;
		// Clear typeaheadskip flag, otherwise type ahead breaks, because we
		// have a typeaheadskip "offset" when using it again.
		typeaheadskip = 0;
	    }
	    // Select this feed, open and view entries.
	    // If contents of the feed was changed during display (reload),
	    // regenerate the smartfeed.
	    if (highlighted && UIDisplayFeed (highlighted))
		update_smartfeeds = true;

	    // Clear screen after we return from here.
	    erase();
	}
	// TAB key is decimal 9.
	if (uiinput == 9 || uiinput == _settings.keybindings.typeahead) {
	    if (typeahead) {
		if (searchlen == 0) {
		    typeahead = false;
		    // Typeahead now off.
		    free (search);
		    typeaheadskip = 0;
		    highlighted = savestart;
		    first_scr_ptr = savestart_first;
		} else {
		    unsigned found = 0;
		    for (const struct feed* f = _feed_list; f; f = f->next)
			if (s_strcasestr (f->title, search))	// Substring match.
			    ++found;
		    if (typeaheadskip == found - 1)	// found-1 to avoid empty tab cycle.
			typeaheadskip = 0;
		    else if (found > 1)	// If more than one match was found and user presses tab we will typeaheadskip matches.
			++typeaheadskip;
		}
	    } else {
		typeahead = true;
		// Typeahead now on.
		search = malloc (2);
		memset (search, 0, 2);
		searchlen = 0;
		// Save all start positions.
		savestart = highlighted;
		savestart_first = first_scr_ptr;
	    }
	}
	// ctrl+g clears typeahead.
	if (uiinput == 7) {
	    // But only if it was switched on previously.
	    if (typeahead) {
		typeahead = false;
		free (search);
		typeaheadskip = 0;
		highlighted = savestart;
		first_scr_ptr = savestart_first;
	    }
	}
	// ctrl+u clears line.
	if (uiinput == 21) {
	    searchlen = 0;
	    search = realloc (search, 2);
	    memset (search, 0, 2);
	}
    }
}
