// 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 "uiutil.h"
#include <ncurses.h>

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

enum clear_line { NORMAL, INVERSE };

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

static const char* SnowSortIgnore (const char* title);
static void SmartFeedNewitems (struct feed* smart_feed);
static void clearLine (unsigned line, enum clear_line how);

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

// Init the ncurses library.
void InitCurses (void)
{
    initscr();
    keypad (stdscr, TRUE);     // Activate keypad so we can read function keys with getch.
    cbreak();		       // No buffering.
    noecho();		       // Do not echo typed chars onto the screen.
    ESCDELAY = 10;	       // Only wait 10ms after ESC
    clear();
    curs_set (_settings.cursor_always_visible);
}

// Print text in statusbar.
void UIStatus (const char* text, int delay, int warning)
{
    int attr = WA_REVERSE;
    if (warning)
	attr |= COLOR_PAIR (10);
    attron (attr);
    clearLine (LINES - 1, INVERSE);
    attron (WA_REVERSE);
    mvaddn_utf8 (LINES - 1, 1, text, COLS - 2);
    attroff (attr);
    refresh();
    if (delay)
	sleep (delay);
}

// Swap all pointers inside a feed struct except next and prev
void SwapPointers (struct feed* one, struct feed* two)
{
    struct feed* two_next = two->next, *two_prev = two->prev;
    struct feed tmp = *one;
    *one = *two;
    *two = tmp;
    one->next = two->next;
    one->prev = two->prev;
    two->next = two_next;
    two->prev = two_prev;
}

// Ignore "A", "The", etc. prefixes when sorting feeds.
static const char* SnowSortIgnore (const char* title)
{
    if (strncasecmp (title, "a ", strlen ("a ")) == 0)
	return title + strlen ("a ");
    else if (strncasecmp (title, "the ", strlen ("the ")) == 0)
	return title + strlen ("the ");
    return title;
}

// Sort the struct list alphabetically.
// Sorting criteria is struct feed->title
void SnowSort (void)
{
    // If there is no element do not run sort or it'll crash!
    if (!_feed_list)
	return;
    UIStatus (_("Sorting, please wait..."), 0, 0);

    unsigned elements = 0;
    for (const struct feed* f = _feed_list; f; f = f->next)
	++elements;
    for (unsigned i = 0; i <= elements; ++i) {
	for (struct feed* f = _feed_list; f->next; f = f->next) {
	    const char* one = SnowSortIgnore (f->title);
	    const char* two = SnowSortIgnore (f->next->title);
	    if (strcasecmp (one, two) > 0)
		SwapPointers (f, f->next);
	}
    }
}

// Draw a filled box with WA_REVERSE at coordinates x1y1/x2y2
void UISupportDrawBox (unsigned x1, unsigned y1, unsigned x2, unsigned y2)
{
    attron (WA_REVERSE);
    for (unsigned y = y1; y <= y2; ++y) {
	move (y, x1);
	for (unsigned x = x1; x <= x2; ++x) {
	    int c = ' ';
	    if (y == y1) {
		if (x == x1)
		    c = ACS_ULCORNER;
		else if (x == x2)
		    c = ACS_URCORNER;
		else
		    c = ACS_HLINE;
	    } else if (y == y2) {
		if (x == x1)
		    c = ACS_LLCORNER;
		else if (x == x2)
		    c = ACS_LRCORNER;
		else
		    c = ACS_HLINE;
	    } else if (x == x1 || x == x2)
		c = ACS_VLINE;
	    addch (c);
	}
    }
    attroff (WA_REVERSE);
}

// Draw main program header.
void UISupportDrawHeader (const char* headerstring)
{
    if (!headerstring)
	headerstring = "* " SNOWNEWS_NAME " " SNOWNEWS_VERSTRING;
    clearLine (0, INVERSE);
    attron (WA_REVERSE);
    mvadd_utf8 (0, 1, headerstring);
    attroff (WA_REVERSE);
}

// Take a URL and execute in default browser.
// Apply all security restrictions before running system()!
void UISupportURLJump (const char* url)
{
    if (!url)
	return;		       // Should not happen. Nope, really should not happen.
    if (strncmp (url, "smartfeed", strlen ("smartfeed")) == 0)
	return;		       // Smartfeeds cannot be opened (for now).
    if (strchr (_settings.browser, '\'')) {
	UIStatus (_("Unsafe browser string (contains quotes)! See snownews.kcore.de/faq#toc2.4"), 5, 1);
	return;		       // Complain loudly if browser string contains a single quote.
    }
    if (strchr (url, '\'')) {
	UIStatus (_("Invalid URL passed. Possible shell exploit attempted!"), 3, 1);
	return;
    }
    char escapedurl[PATH_MAX];
    snprintf (escapedurl, sizeof (escapedurl), "'%s' 2>/dev/null", url);
    char browcall[PATH_MAX];
    snprintf (browcall, sizeof (browcall), _settings.browser, escapedurl);

    char execmsg[sizeof (browcall) + strlen ("Executing ")];
    snprintf (execmsg, sizeof (execmsg), _("Executing %s"), browcall);
    UIStatus (execmsg, 0, 0);

    endwin();
    system (browcall);
}

void SmartFeedsUpdate (void)
{
    for (struct feed* f = _feed_list; f; f = f->next)
	if (f->smartfeed)
	    SmartFeedNewitems (f);
}

static void SmartFeedNewitems (struct feed* smart_feed)
{
    // Be smart and don't leak the smart feed.
    // The items->data structures must not be freed, since a smart feed is only
    // a pointer collection and does not contain allocated memory.
    while (smart_feed->items) {
	struct newsitem* i2f = smart_feed->items;
	smart_feed->items = smart_feed->items->next;
	free (i2f);
    }
    for (struct feed* f = _feed_list; f; f = f->next) {
	// Do not add the smart feed recursively. 8)
	if (f == smart_feed)
	    continue;
	for (struct newsitem* item = f->items; item; item = item->next) {
	    if (item->data->readstatus)
		continue;

	    // If item is unread, add to smart feed.
	    struct newsitem* new_item = calloc (1, sizeof (struct newsitem));
	    new_item->data = item->data;

	    // Find insertion point, sorted with latest date first
	    struct newsitem* prev = NULL;
	    if (smart_feed->items && smart_feed->items->data->date >= new_item->data->date) {
		prev = smart_feed->items;
		while (prev->next && prev->data->date >= new_item->data->date)
		    prev = prev->next;
	    }

	    // Add to data structure.
	    if (!prev) {
		new_item->next = smart_feed->items;
		smart_feed->items = new_item; // Add to beginning of the list
	    } else {
		new_item->next = prev->next;
		new_item->prev = prev;
		prev->next = new_item;
	    }
	    if (new_item->next)
		new_item->next->prev = new_item;
	}
    }
    // Only fill out once.
    if (!smart_feed->title)
	smart_feed->title = strdup (_("(New headlines)"));
    if (!smart_feed->link)
	smart_feed->link = strdup (smart_feed->feedurl);
}

bool SmartFeedExists (const char* smartfeed)
{
    // Find our smart feed.
    if (strcmp (smartfeed, "newitems") == 0)
	for (const struct feed* f = _feed_list; f; f = f->next)
	    if (f->smartfeed == 1)
		return true;
    return false;
}

void DrawProgressBar (unsigned numobjects, unsigned titlestrlen)
{
    attron (WA_REVERSE);
    mvhline (LINES - 1, titlestrlen + 1, '=', numobjects);
    mvaddch (LINES - 1, COLS - 3, ']');
    attroff (WA_REVERSE);
    refresh();
}

static void clearLine (unsigned line, enum clear_line how)
{
    if (how == INVERSE)
	attron (WA_REVERSE);
    mvhline (line, 0, ' ', COLS);
    if (how == INVERSE)
	attron (WA_REVERSE);
}

// Returns the number of bytes in the character whose first char is c
static unsigned utf8_ibytes (char c)
{
    // Count the leading bits. Header bits are 1 * nBytes followed by a 0.
    //	0 - single byte character. Take 7 bits (0xff >> 1)
    //	1 - error, in the middle of the character. Take 6 bits (0xff >> 2)
    //	    so you will keep reading invalid entries until you hit the next character.
    //	>2 - multibyte character. Take remaining bits, and get the next bytes.
    //
    unsigned n = 0u;
    for (uint8_t mask = 0x80; c & mask; ++n)
	mask >>= 1;
    return n+!n; // A sequence is always at least 1 byte.
}

static wchar_t utf8_next (const char** pt)
{
    const char* i = *pt;
    unsigned n = utf8_ibytes (*i);
    wchar_t v = *i & (0xff >> n);	// First byte contains bits after the header.
    while (--n && *++i)			// Each subsequent byte has 6 bits.
	v = (v << 6) | (*i & 0x3f);
    *pt = ++i;
    return v;
}

unsigned utf8_length (const char* s)
{
    unsigned l = 0;
    while (utf8_next (&s))
	++l;
    return l;
}

unsigned utf8_range_length (const char* f, const char* l)
{
    unsigned rl = 0;
    while (f < l && utf8_next (&f))
	++rl;
    return rl;
}

void addn_utf8 (const char* s, unsigned n)
{
    #if NCURSES_WIDECHAR
	attr_t attr = 0;
	short cpair = 0;
	attr_get (&attr, &cpair, NULL);

	wchar_t wchzs[2] = { 0, 0 };
	cchar_t ch = {};

	while (n-- && (wchzs[0] = utf8_next (&s))) {
	    setcchar (&ch, wchzs, attr, cpair, NULL);
	    add_wch (&ch);
	}
    #else
	wchar_t wc;
	while (n-- && (wc = utf8_next (&s)))
	    addch (wc < CHAR_MAX ? (char) wc : '?');
    #endif
}

void add_utf8 (const char* s)
{
    int x = getcurx (stdscr);
    unsigned w = getmaxx (stdscr);
    addn_utf8 (s, w-x);
}

void mvaddn_utf8 (int y, int x, const char* s, unsigned n)
{
    move (y, x);
    addn_utf8 (s, n);
}

void mvadd_utf8 (int y, int x, const char* s)
{
    move (y, x);
    add_utf8 (s);
}

unsigned utf8_osize (wchar_t c)
{
    if (c > 0x1fffff)
	return 0;
    else if (c > 0xffff)
	return 4;
    else if (c > 0x7ff)
	return 3;
    else if (c > 0x7f)
	return 2;
    return 1;
}

char* utf8_write (wchar_t c, char* o)
{
    unsigned n = utf8_osize (c);
    if (!n)
	return o;
    if (n <= 1)
	*o++ = c;
    else {
	unsigned btw = n * 6;
	*o++ = ((c >> (btw -= 6)) & 0x3f) | (0xff << (8 - n));
	while (btw)
	    *o++ = ((c >> (btw -= 6)) & 0x3f) | 0x80;
    }
    return o;
}

void wrap_text (char* text, unsigned width)
{
    unsigned textlen = strlen (text);
    char* t = text;
    char* tend = t + textlen;
    char* sp = NULL;
    unsigned linelen = 0;
    while (t < tend) {
	char* pc = t;
	wchar_t c = utf8_next ((const char**) &t);
	if (c == '\n') {
	    linelen = 0;
	    sp = NULL;
	    continue;
	} else if (c == ' ')
	    sp = pc;
	if (++linelen >= width && sp) {
	    *sp = '\n';
	    linelen = pc - sp;
	}
    }
}
