// 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 "main.h"
#include "ui.h"
#include "feedio.h"
#include "setup.h"
#include "uiutil.h"
#include <ncurses.h>
#include <signal.h>
#include <sys/wait.h>

//{{{ Global variables -------------------------------------------------

struct feed* _feed_list = NULL;
struct feed* _unfiltered_feed_list = NULL;	// Backup first pointer for filter mode.
				// Needs to be global so it can be used in the signal handler.
				// Must be set to NULL by default and whenever it's not used anymore!
bool _feed_list_changed = false;

struct settings _settings = {
    .keybindings = {
		    //{{{2 Default values for keybindings.
		    // If some are defined differently in the keybindings file
		    // they will be overwritten. If some are missing or broken
		    // these are sane defaults.
		    .about = 'A',
		    .addfeed = 'a',
		    .andxor = 'X',
		    .categorize = 'C',
		    .changefeedname = 'c',
		    .deletefeed = 'D',
		    .dfltbrowser = 'B',
		    .end = '>',
		    .enter = 'l',
		    .feedinfo = 'i',
		    .filter = 'f',
		    .filtercurrent = 'g',
		    .forcereload = 'T',
		    .help = 'h',
		    .home = '<',
		    .markallread = 'm',
		    .markread = 'm',
		    .markunread = 'M',
		    .movedown = 'N',
		    .moveup = 'P',
		    .newheadlines = 'H',
		    .next = 'n',
		    .nofilter = 'F',
		    .pdown = ' ',
		    .perfeedfilter = 'e',
		    .prev = 'p',
		    .prevmenu = 'q',
		    .pup = 'b',
		    .quit = 'q',
		    .reload = 'r',
		    .reloadall = 'R',
		    .sortfeeds = 's',
		    .typeahead = '/',
		    .urljump = 'o',
		    .urljump2 = 'O'
		    //}}}2
		     },
    .color = {
	      .feedtitle = -1,
	      .feedtitlebold = 0,
	      .newitems = 5,
	      .newitemsbold = 0,
	      .urljump = 4,
	      .urljumpbold = 0 },
};

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

static int last_signal = 0;

//}}}-------------------------------------------------------------------
//{{{ PID file management

enum EPIDAction { pid_file_delete, pid_file_create };

static void make_pidfile_name (char* fnbuf, size_t fnbufsz)
{
    const char* rundir = getenv ("XDG_RUNTIME_DIR");
    if (!rundir)
	rundir = getenv ("TMPDIR");
    if (!rundir)
	rundir = "/tmp";
    snprintf (fnbuf, fnbufsz, "%s/snownews.pid", rundir);
}

static void modifyPIDFile (enum EPIDAction action)
{
    char pid_path[PATH_MAX];
    make_pidfile_name (pid_path, sizeof (pid_path));
    if (action == pid_file_create) {
	FILE* file = fopen (pid_path, "w");
	if (!file) {
	    printf (_("Unable to write PID file %s!"), pid_path);
	    return;
	}
	fprintf (file, "%d", getpid());
	fclose (file);
    } else
	unlink (pid_path);
}

static void checkPIDFile (void)
{
    char pid_path[PATH_MAX];
    make_pidfile_name (pid_path, sizeof (pid_path));

    FILE* pidfile = fopen (pid_path, "r");
    if (!pidfile)
	return;
    char pidbuf[12];
    fgets (pidbuf, sizeof (pidbuf), pidfile);
    fclose (pidfile);
    pid_t pid = atoi (pidbuf);

    if (kill (pid, 0) == 0) {
	printf (_("Snownews seems to be already running with process ID %d.\n"
		"A pid file exists at \"%s\".\n"), pid, pid_path);
	exit (2);
    } else {
	printf (_("A pid file exists at \"%s\",\n"
		"but snownews doesn't seem to be running.\n"
		"Delete that file and start it again.\n"
		"Continue anyway? (y/n) "), pid_path);
	char ybuf[2] = { };
	fgets (ybuf, sizeof (ybuf), stdin);
	if (ybuf[0] != 'y')
	    exit (2);
	modifyPIDFile (pid_file_delete);
    }
}

//}}}-------------------------------------------------------------------
//{{{ stderr log redirection

static int MakeStderrLogFileName (char* logfile, size_t logfilesz)
{
    const char* tmpdir = getenv ("TMPDIR");
    if (!tmpdir)
	tmpdir = "/tmp";
    unsigned r = snprintf (logfile, logfilesz, "%s/" SNOWNEWS_NAME ".log", tmpdir);
    return r >= logfilesz ? -1 : 0;
}

// If the log file is empty at exit, delete it
static void CleanupStderrLog (void)
{
    char logfile[PATH_MAX];
    if (0 == MakeStderrLogFileName (logfile, sizeof (logfile))) {
	struct stat st;
	if (0 == stat (logfile, &st) && st.st_size == 0)
	    unlink (logfile);
    }
}

// Redirects stderr into a log file in /tmp
static void RedirectStderrToLog (void)
{
    char logfile[PATH_MAX];
    if (0 > MakeStderrLogFileName (logfile, sizeof (logfile)))
	return;

    int fd = open (logfile, O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR);
    if (fd < 0)
	return;

    dup2 (fd, STDERR_FILENO);
    close (fd);
    atexit (CleanupStderrLog);
}

//}}}-------------------------------------------------------------------
//{{{ Signal handling

// Deinit ncurses and quit.
_Noreturn void MainQuit (const char* func, const char* error)
{
    if (!error)		       // Only save settings if we didn't exit on error.
	WriteCache();
    endwin();		       // Make sure no ncurses function is called after this point!
    modifyPIDFile (pid_file_delete);

    if (!error)
	exit (EXIT_SUCCESS);
    if (last_signal) {
	#if __linux__
	    printf ("Exiting via signal %s.\n", strsignal (last_signal));
	#else
	    printf ("Exiting via signal %d.\n", last_signal);
	#endif
    }
    printf (_("Aborting program execution!\n"
	    "snownews quit without saving changes due to internal error!\n"
	    "Please submit a bugreport at %s\n"), SNOWNEWS_BUGTRACKER);
    printf ("----\n");
    printf (_("While executing: %s\nError: %s\n\n"), func, error);
    exit (EXIT_FAILURE);
}

// Signal handler function.
static _Noreturn void MainSignalHandler (int sig)
{
    last_signal = sig;

    // If there is a _unfiltered_feed_list!=NULL a filter is set. Reset _feed_list
    // so the correct list gets written on the disk when exiting via SIGINT.
    if (_unfiltered_feed_list)
	_feed_list = _unfiltered_feed_list;
    MainQuit ("MainSignalHandler", (sig == SIGINT || sig == SIGQUIT || sig == SIGTERM) ? NULL : "Signal");
}

// Automatic child reaper.
static void sigChildHandler (int sig __attribute__((unused)))
{
    // Wait for any child without blocking
    waitpid (-1, NULL, WNOHANG);
}

static void InstallSignalHandlers (void)
{
    signal (SIGHUP, MainSignalHandler);
    signal (SIGINT, MainSignalHandler);
    signal (SIGQUIT, MainSignalHandler);
    signal (SIGTERM, MainSignalHandler);
    signal (SIGABRT, MainSignalHandler);
    signal (SIGSEGV, MainSignalHandler);

    // Un-broken pipify
    signal (SIGPIPE, SIG_IGN);
    signal (SIGCHLD, sigChildHandler);
#ifdef SIGWINCH
    signal (SIGWINCH, sig_winch);
#endif
}

//}}}-------------------------------------------------------------------
//{{{ Command line options help

static void printHelp (void)
{
    printf ("Snownews " SNOWNEWS_VERSTRING "\n\n");
    printf (_("Usage: snownews [-chuV]\n\n"
	"  -h, --help\t\tPrint this help message.\n"
	"  -V, --version\t\tPrint version number and exit.\n"
	"  -c, --cursor-on\tForce cursor always visible.\n"
	"  -u, --update\t\tAutomatically update every feed.\n"));
}

//}}}-------------------------------------------------------------------
//{{{ srandrand

inline static uint32_t ror32 (uint32_t v, unsigned n)
{
    return (v >> n) | (v << (32 - n));
}

// Randomly initializes the random number generator
static void srandrand (void)
{
    struct timespec now;
    clock_gettime (CLOCK_REALTIME, &now);
    uint32_t seed = now.tv_sec;
    seed ^= getppid();
    seed = ror32 (seed, 16);
    seed ^= getpid();
    seed ^= now.tv_nsec;
    srand (seed);
}

//}}}-------------------------------------------------------------------

int main (int argc, char* argv[])
{
    InstallSignalHandlers();
    srandrand();
    setlocale (LC_ALL, "");
#ifdef LOCALEPATH
    bindtextdomain (SNOWNEWS_NAME, LOCALEPATH);
    textdomain (SNOWNEWS_NAME);
#endif
    RedirectStderrToLog();

    bool autoupdate = false;	// Automatically update feeds on app start... or not if set to 0.
    for (int i = 1; i < argc; ++i) {
	char* arg = argv[i];
	if (strcmp (arg, "-u") == 0 || strcmp (arg, "--update") == 0)
	    autoupdate = true;
	else if (strcmp (arg, "-c") == 0 || strcmp (arg, "--cursor-on") == 0)
	    _settings.cursor_always_visible = true;
	else if (strcmp (arg, "--version") == 0 || strcmp (arg, "-V") == 0) {
	    printf ("snownews " SNOWNEWS_VERSTRING "\n");
	    return EXIT_SUCCESS;
	} else if (strcmp (arg, "-h") == 0 || strcmp (arg, "--help") == 0) {
	    printHelp();
	    return EXIT_SUCCESS;
	} else {
	    printf (_("Unknown option \"%s\"\n"), arg);
	    printHelp();
	    return EXIT_SUCCESS;
	}
    }

    // Create PID file.
    checkPIDFile();
    modifyPIDFile (pid_file_create);

    InitCurses();

    // Check if configfiles exist and create/read them.
    LoadAllFeeds (Config());
    if (autoupdate)
	UpdateAllFeeds();

    // Give control to main program loop.
    UIMainInterface();

    // We really shouldn't be here at all... ah well.
    return EXIT_SUCCESS;
}
