/*
    Copyright (C) 2008-2010 Stefan Haller

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <config.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <dirent.h>
#include <errno.h>
#include <pthread.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/resource.h>

#include "history.h"
#include "wallpapers.h"

#include "../misc/general.h"
#include "../misc/xml.h"
#include "../misc/module.h"
#include "../dbus/dbus-server.h"
#include "../modules/desktopnova_module.h"

GPtrArray * modules;

gboolean changebyinterval;
guint interval;
gboolean changeeverylaunch;
gboolean use_cache;

gboolean HUP = FALSE;
gboolean quit = FALSE;

GMainLoop * main_loop = NULL;

GTimer * timer = NULL;

void signals_handler(int sig)
{
	g_message("Received signal \"%s.\"", g_strsignal(sig));
	if (sig == SIGHUP)
	{
		HUP = TRUE;
	}
	if (main_loop != NULL)
	{
		g_main_loop_quit(main_loop);
	}
	else
	{
		quit = TRUE;
	}
}

void config_signals()
{
	struct sigaction sig;
	sig.sa_handler = signals_handler;
	sig.sa_flags = SA_RESTART;
	sigemptyset(&sig.sa_mask);

	sigaction(SIGHUP, &sig, NULL);
	sigaction(SIGTERM, &sig, NULL);
	sigaction(SIGINT, &sig, NULL);
	sigaction(SIGQUIT, &sig, NULL);
}

void read_modules(struct module_node * m_node)
{
	while (m_node != NULL)
	{
		gchar * abs_fname = g_build_filename(PKGLIBDIR, m_node->name, NULL);
		/* mustn't be unloaded, because we use gconf (and glib) with static
		   variables in the module! */
		struct library_entry * lib_ent = load_module(abs_fname);
		if (lib_ent != NULL)
		{
			g_ptr_array_add(modules, lib_ent);
		}
		m_node = m_node->next;
		g_free(abs_fname);
	}
}

gboolean change_wallpaper(guint nr)
{
	if (nr >= wallpapers->len)
	{
		return FALSE;
	}

	g_timer_start(timer);

	gchar * file = g_strdup((const gchar *)g_ptr_array_index(wallpapers, nr));

	#ifdef DEBUG
	g_debug("change_wallpaper(%s)", file);
	#endif

	struct stat * stbuf = g_new(struct stat, 1);
	if (stat(file, stbuf) != 0)
	{
		g_free(stbuf);
		return FALSE;
	}
	g_free(stbuf);

	guint i;
	for (i = 0; i < modules->len; i++)
	{
		struct library_entry * lib = g_ptr_array_index(modules, i);
		if (lib->change_wallpaper != NULL)
		{
			(*lib->change_wallpaper)(file);
		}
	}

	#ifdef USE_DBUS
	emit_changed(file);
	#endif

	g_free(file);

	return TRUE;
}

gboolean next_wallpaper_timer(gpointer data)
{
	/* 5 seconds tolerance */
	if (g_timer_elapsed(timer, NULL) > (interval * 60) - 5)
	{
		next_wallpaper();
	}
	return TRUE;
}

gboolean change_launch_timer(gpointer data)
{
	next_wallpaper();

	#ifndef USE_DBUS
	if (! changebyinterval)
	{
		g_main_loop_quit(main_loop);
	}
	#endif

	return FALSE;
}

/* create / check pid lock in ~/.config/desktopnova */
gboolean check_pid_file(char ** fname)
{
	*fname = NULL;
	gchar * app_dir = get_app_dir();

	if (app_dir == NULL)
	{
		g_critical("Could not get application directory. Maybe environment " \
		           "variable $HOME isn't set.");
		return FALSE;
	}	

	gchar * filename = g_build_filename(app_dir, PIDLOCKFILE, NULL);
	g_free(app_dir);
	*fname = filename;

	int pidlock = open(filename, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR |
	                             S_IWUSR);

	if (pidlock == -1)
	{
		if (errno == EEXIST)
		{
			/* file exists already, another desktopnova-daemon must be running
			   or a desktopnova-daemon did not shutdown correctly             */
			/* get pid from pid lock file
			   (read only 10 bytes of pidfile -> should be sufficient         */
			FILE * f;
			gboolean quit = FALSE;
			gboolean valid_pid = FALSE;
			gchar * pid = g_malloc(11);

			f = fopen(filename, "r");
			if (f != NULL)
			{
				size_t bytes_read = fread(pid, 1, 10, f);
				fclose(f);

				pid[bytes_read] = '\0';

				guint i;
				for (i = 0; i < bytes_read; i++)
				{
					valid_pid = g_ascii_isdigit(pid[i]);
					if (! valid_pid)
					{
						break;
					}
				}
			}

			if (valid_pid)
			{
				/* check if process with this pid is running */
				gboolean running = FALSE;
				gchar * procfilename = g_build_filename("/proc/", pid, "cmdline", NULL);
				f = fopen(procfilename, "r");
				g_free(procfilename);

				if (f != NULL)
				{
					/* get cmdline of process and check if it's our daemon */
					gchar * cmdline = g_malloc(256);
					cmdline[0] = '\0';

					fgets(cmdline, 256, f);
					fclose(f);

					if (g_strstr_len(cmdline, -1, "desktopnova-daemon") != NULL)
					{
						running = TRUE;
					}
				}

				if (running)
				{
					quit = TRUE;
					g_message("%s already exists. Another DesktopNova-daemon " \
					          "is running (PID=%s)! Terminating...", filename, pid);
				}
				else
				{
					g_message("%s already exists, but process with PID %s "\
					          "is not DesktopNova. Daemon will continue "\
					          "starting.", filename, pid);
				}
			}
			else
			{
				/* no valid pid in pidfile
				   nevertheless start daemon */
				g_message("%s already exists, but couldn't read valid " \
				          "PID. Daemon will continue starting.",
				          filename);
			}

			g_free(pid);

			if (quit)
			{
				return FALSE;
			}
		}
		else
		{
			g_warning("Could not open pid lock file readably.");
			error("open");
			return FALSE;
		}
	}
	else
	{
		close(pidlock);
	}

	FILE * file = fopen(filename, "w");
	if (file == NULL)
	{
		error("fopen");
		return FALSE;
	}
	fprintf(file, "%u", getpid());
	fclose(file);

	return TRUE;
}

void initial_setup()
{
	/* load settings from xml file */
	struct settings * set = load_settings();

	/* set global settings */
	changebyinterval = set->changebyinterval;
	interval = set->interval;
	changeeverylaunch = set->changeeverylaunch;

	init_filters(set->filters);

	/* list of all wallpapers - no need to lock, no other thread are running */
	wallpapers = g_ptr_array_new();
	
	struct profile_node * profile = set->profiles;

	while (profile != NULL)
	{
		if (g_strcmp0(profile->name, set->default_profile) == 0)
		{
			scan_files(profile->files);
		}
		profile = profile->next;
	}

	modules = g_ptr_array_new();
	read_modules(set->modules);

	free_settings(set);

	init_history();

	timer = g_timer_new();
}

int main (int argc, gchar *argv[])
{
	#ifdef USE_DBUS
	g_thread_init(NULL);
	g_type_init();
	dbus_g_thread_init();
	init_dbus();
	#endif

	srand(time(0) * getpid());

	config_signals();

	/* be nice to other processes */
	setpriority(PRIO_PROCESS, 0, 19);

	gchar * pid_filename;
	if (! check_pid_file(&pid_filename))
	{
		g_critical("Error in check_pid_file() function. Aborting...");
		return 1;
	}

	initial_setup();

	if (modules->len > 0)
	{
		if (changebyinterval)
		{
			if (interval < 1)
			{
				changebyinterval = FALSE;
			}
		}

		if (wallpapers->len > 1)
		{
			/* main loop - changes wallpaper */
			#ifndef USE_DBUS
			if ((! changeeverylaunch) &&
				(! changebyinterval))
			{
				g_message("Neither Change-By-Interval nor Change-Every-Launch is activated. The program terminates.");
			}
			else
			{
			#endif

			if (changeeverylaunch)
				g_timeout_add_seconds(0, &change_launch_timer, NULL);

			if (changebyinterval)
				g_timeout_add_seconds(15, &next_wallpaper_timer, NULL);

			main_loop = g_main_loop_new(NULL, FALSE);
			if (!quit)
			{
				g_main_loop_run(main_loop);
			}
			g_main_loop_unref(main_loop);

			#ifndef USE_DBUS
			}
			#endif
		}
		else
		{
			g_critical("%u wallpapers were found. The program terminates.",
			           wallpapers->len);
		}

		clear_wallpapers(wallpapers);
	}
	else
	{
		g_critical("No modules loaded. The program terminates.");
	}

	g_timer_destroy(timer);

	free_filters();

	guint i;
	/* free used modules */
	for (i = 0; i < modules->len; i++)
	{
		struct library_entry * lib = (struct library_entry *)
		                              g_ptr_array_index(modules, i);
		/* mustn't be unloaded, because we use gconf (and glib) with static
		variables in the module! */
		//dlclose(lib->lib);
		g_free(lib->filename);
		g_free(lib);
	}
	g_ptr_array_free(modules, TRUE);

	remove(pid_filename);
	g_free(pid_filename);

	if (HUP)
	{
		if (execv(argv[0], argv) == -1);
	}

	return 0;
}
