/**
 *	This file is part of devilspie2
 *	Copyright (C) 2005 Ross Burton, 2011-2017 Andreas Rönnquist
 *
 *	devilspie2 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.
 *
 *	devilspie2 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 devilspie2.
 *	If not, see <http://www.gnu.org/licenses/>.
 */

#include <string.h>

#include <stdlib.h>
#include <glib.h>
#include <glib/gstdio.h>
#include <gdk/gdk.h>
#include <gdk/gdkx.h>
#include <glib/gi18n.h>

#define WNCK_I_KNOW_THIS_IS_UNSTABLE
#include <libwnck/libwnck.h>

#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>

#include <locale.h>

#include "script.h"
#include "script_functions.h"

#include "error_strings.h"

#include "config.h"


#if (GTK_MAJOR_VERSION >= 3)
#define HAVE_GTK3
#endif

/**
 *
 */
GMainLoop *loop = NULL;

static gboolean debug = FALSE;
static gboolean emulate = FALSE;
static gboolean show_version = FALSE;

// libwnck Version Information is only availible if you have
// libwnck 3.0 or later
static gboolean show_wnck_version = FALSE;

static gboolean show_lua_version = FALSE;

static gchar *script_folder = NULL;
static gchar *temp_folder = NULL;

GFileMonitor *mon = NULL;

gchar *config_filename = NULL;

WnckHandle *my_wnck_handle = NULL;

/**
 *
 */
static void load_list_of_scripts(WnckScreen *screen G_GNUC_UNUSED, WnckWindow *window,
                                 GSList *file_list)
{
	GSList *temp_file_list = file_list;
	// set the window to work on
	set_current_window(window);

	// for every file in the folder - load the script
	if (event_lists[W_OPEN] != NULL) {

		while(temp_file_list) {
			gchar *filename = (gchar*)temp_file_list->data;

			// is it a Lua file?
			if (g_str_has_suffix((gchar*)filename, ".lua")) {

				// init the script, run it
				if (!load_script(global_lua_state, filename)) {
				}

				run_script(global_lua_state);

			}
			temp_file_list=temp_file_list->next;
		}
	}
	return;

}


static void window_name_changed_cb(WnckWindow *window)
{
	WnckScreen * screen = wnck_window_get_screen(window);
	if(screen == NULL) return;

	// Handle duplicate name-change events
	// Simple method: just track the most recent event regardless of window
	static WnckWindow *previous = NULL;
	static char *prevname = NULL;

	const char *newname = wnck_window_get_name(window);
	if (window == previous && prevname && !strcmp (prevname, newname))
		return;
	// Store the info for the next event
	free(prevname);
	prevname = strdup(newname);
	previous = window;

	load_list_of_scripts(screen, window, event_lists[W_NAME_CHANGED]);
}

/**
 *
 */
static void window_opened_cb(WnckScreen *screen, WnckWindow *window)
{
	load_list_of_scripts(screen, window, event_lists[W_OPEN]);
	/*
	Attach a listener to each window for window-specific changes
	Safe to do this way as long as the 'user data' parameter is NULL
	*/
	g_signal_connect(window, "name-changed", (GCallback)window_name_changed_cb, NULL);
}


/**
 *
 */
static void window_closed_cb(WnckScreen *screen, WnckWindow *window)
{
	load_list_of_scripts(screen, window, event_lists[W_CLOSE]);
}


/**
 *
 */
static void window_changed_cb(WnckScreen *screen, WnckWindow *window)
{
	WnckWindow *cur;

	load_list_of_scripts(screen, window, event_lists[W_BLUR]);
	cur = wnck_screen_get_active_window(screen);
	load_list_of_scripts(screen, cur, event_lists[W_FOCUS]);
}


/**
 *
 */
void init_screens()
{
	int i;
	int num_screens;

#ifndef GDK_VERSION_3_10
	num_screens = gdk_display_get_n_screens(gdk_display_get_default());
#else
	num_screens = 1;
#endif

	for (i=0; i<num_screens; i++) {
		WnckScreen *screen = wnck_handle_get_screen(my_wnck_handle, i);

		g_signal_connect(screen, "window-opened",
		                 (GCallback)window_opened_cb, NULL);
		g_signal_connect(screen, "window-closed",
		                 (GCallback)window_closed_cb, NULL);
		g_signal_connect(screen, "active-window-changed",
		                 (GCallback)window_changed_cb, NULL);
	}
}


/**
 * atexit handler - kill the script
 */
void devilspie_exit()
{
	clear_file_lists();
	g_free(temp_folder);
	if (mon)
		g_object_unref(mon);
	g_free(config_filename);
}


/**
 * handle signals that are sent to the application
 */
static void signal_handler(int sig)
{
	printf("\n%s %d (%s)\n", _("Received signal:"), sig, strsignal(sig));

	done_script_error_messages();

	if (sig == SIGINT) {
		exit(EXIT_FAILURE);
	}
}


/**
 *
 */
void print_list(GSList *list)
{
	GSList *temp_list;
	if (list != NULL) {
		temp_list = list;

		while(temp_list) {
			gchar *file_name = temp_list->data;

			if (file_name) {
				if (g_str_has_suffix((gchar*)file_name, ".lua")) {
					printf("%s\n", (gchar*)file_name);
				}
			}
			temp_list = temp_list->next;
		}
	}
}


/**
 *
 */
void print_script_lists()
{
	gboolean have_any_files = FALSE;
	win_event_type i;

	if (debug)
		printf("------------\n");

	for (i = 0; i < W_NUM_EVENTS; i++) {
		if (event_lists[i])
			have_any_files = TRUE;
		// If we are running debug mode - print the list of files:
		if (debug) {
			printf(_("List of Lua files handling \"%s\" events in folder:"),
				   event_names[i]);
			printf("\n");
			if (event_lists[i]) {
				print_list(event_lists[i]);
			}
		}
	}

	if (!have_any_files) {
		printf("%s\n\n", _("No script files found in the script folder - exiting."));
		exit(EXIT_SUCCESS);
	}
}


/**
 *
 */
void folder_changed_callback(GFileMonitor *mon G_GNUC_UNUSED,
                             GFile *first_file,
                             GFile *second_file G_GNUC_UNUSED,
                             GFileMonitorEvent event,
                             gpointer user_data)
{
	gchar *our_filename = (gchar*)(user_data);

	// If a file is created or deleted, we need to check the file lists again
	if ((event == G_FILE_MONITOR_EVENT_CREATED) ||
	    (event == G_FILE_MONITOR_EVENT_DELETED)) {

		clear_file_lists();

		set_current_window(NULL);
		load_config(our_filename);

		if (debug)
			printf("Files in folder updated!\n - new lists:\n\n");

		print_script_lists();

		if (debug)
			printf("-----------\n");
	}

	// Also monitor if our devilspie2.lua file is changed - since it handles
	// which files are window close or window open scripts.
	if (event == G_FILE_MONITOR_EVENT_CHANGED) {
		if (first_file) {
			gchar *short_filename = g_file_get_basename(first_file);

			if (g_strcmp0(short_filename, "devilspie2.lua")==0) {

				clear_file_lists();

				set_current_window(NULL);
				load_config(our_filename);

				print_script_lists();

				if (debug)
					printf("----------");
			}
		}
	}
}


/**
 * Program main entry
 */
int main(int argc, char *argv[])
{
	static const GOptionEntry options[]= {
		{ "debug",        'd', 0, G_OPTION_ARG_NONE,   &debug,
		  N_("Print debug info to stdout"), NULL
		},
		{ "emulate",      'e', 0, G_OPTION_ARG_NONE,   &emulate,
		  N_("Don't apply any rules, only emulate execution"), NULL
		},
		{ "folder",       'f', 0, G_OPTION_ARG_STRING, &script_folder,
		  N_("Search for scripts in this folder"), N_("FOLDER")
		},
		{ "version",      'v', 0, G_OPTION_ARG_NONE,   &show_version,
		  N_("Show Devilspie2 version and quit"), NULL
		},
		// libwnck Version Information is only availible if you have
		// libwnck 3.0 or later
		{ "wnck-version", 'w', 0, G_OPTION_ARG_NONE,   &show_wnck_version,
		  N_("Show libwnck version and quit"), NULL
		},
		{ "lua-version",  'l', 0, G_OPTION_ARG_NONE,   &show_lua_version,
		  N_("Show Lua version and quit"), NULL
		},
		{ NULL }
	};

	GError *error = NULL;
	GOptionContext *context;

	// Init gettext stuff
	setlocale(LC_ALL, "");

	bindtextdomain(PACKAGE, LOCALEDIR);
	bind_textdomain_codeset(PACKAGE, "");
	textdomain(PACKAGE);

	gchar *devilspie2_description = g_strdup_printf(_("apply rules on windows"));

	gchar *full_desc_string = g_strdup_printf("- %s", devilspie2_description);

	context = g_option_context_new(full_desc_string);
	g_option_context_add_main_entries(context, options, NULL);
	if (!g_option_context_parse(context, &argc, &argv, &error)) {
		g_print(_("option parsing failed: %s"), error->message);
		printf("\n");
		exit(EXIT_FAILURE);
	}

	gdk_init(&argc, &argv);

	g_free(full_desc_string);
	g_free(devilspie2_description);

	// if the folder is NULL, default to ~/.config/devilspie2/
	if (script_folder == NULL) {

		temp_folder = g_build_path(G_DIR_SEPARATOR_S,
		                           g_get_user_config_dir(),
		                           "devilspie2",
		                           NULL);

		// check if the folder does exist
		if (!g_file_test(temp_folder, G_FILE_TEST_IS_DIR)) {

			// - and if it doesn't, create it.
			if (g_mkdir(temp_folder, 0700) != 0) {
				printf("%s\n", _("Couldn't create the default folder for devilspie2 scripts."));
				exit(EXIT_FAILURE);
			}
		}

		script_folder = temp_folder;
	}

	gboolean shown = FALSE;
	if (show_version) {
		printf("Devilspie2 v%s\n", DEVILSPIE2_VERSION);
		shown = TRUE;
	}
	// libwnck Version Information is only availible if you have
	// libwnck 3.0 or later
	if (show_wnck_version) {
#ifdef _DEBUG
		printf("GTK v%d.%d.%d\n",
		       GTK_MAJOR_VERSION,
		       GTK_MINOR_VERSION,
		       GTK_MICRO_VERSION);
#endif
#ifdef HAVE_GTK3
		printf("libwnck v%d.%d.%d\n",
		       WNCK_MAJOR_VERSION,
		       WNCK_MINOR_VERSION,
		       WNCK_MICRO_VERSION);
#else
		printf("libwnck v2.x\n");
#endif
		shown = TRUE;
	}
	if (show_lua_version) {
		puts(LUA_VERSION);
		shown = TRUE;
	}
	if (shown)
		exit(0);

#if (GTK_MAJOR_VERSION >= 3)
	if (!GDK_IS_X11_DISPLAY(gdk_display_get_default())) {
		printf("%s\n\n", _("An X11 display is required for devilspie2."));
		return EXIT_FAILURE;
	}

#endif

	if (init_script_error_messages()!=0) {
		printf("%s\n", _("Couldn't init script error messages!"));
		exit(EXIT_FAILURE);
	}

	// set the current window to NULL, we don't need to be able to modify
	// the windows when reading the config
	set_current_window(NULL);

	config_filename =
	    g_build_filename(script_folder, "devilspie2.lua", NULL);

	if (load_config(config_filename) != 0) {

		devilspie_exit();
		return EXIT_FAILURE;
	}

	if (debug) {

		if (emulate) {
			printf("%s\n\n", _("Running devilspie2 in debug and emulate mode."));
		} else {
			printf("%s\n\n", _("Running devilspie2 in debug mode."));
		}

		printf(_("Using scripts from folder: %s"), script_folder);

		printf("\n");

		devilspie2_debug = TRUE;
	}

	// Should we only run an emulation (don't modify any windows)
	if (emulate) devilspie2_emulate = emulate;

	GFile *directory_file;
	directory_file = g_file_new_for_path(script_folder);
//	mon = g_file_monitor_directory(directory_file, G_FILE_MONITOR_WATCH_MOUNTS,
	mon = g_file_monitor_directory(directory_file, G_FILE_MONITOR_NONE,
	                               NULL, NULL);
	if (!mon) {
		printf("%s\n", _("Couldn't create directory monitor!"));
		return EXIT_FAILURE;
	}

	g_signal_connect(mon, "changed", G_CALLBACK(folder_changed_callback),
	                 (gpointer)(config_filename));

	global_lua_state = init_script();
	print_script_lists();

	if (debug) printf("------------\n");

	// remove stuff cleanly
	atexit(devilspie_exit);

	struct sigaction signal_action;

	sigemptyset(&signal_action.sa_mask);
	signal_action.sa_flags = 0;
	signal_action.sa_handler = signal_handler;

	if (sigaction(SIGINT, &signal_action, NULL) == -1) {
		exit(EXIT_FAILURE);
	}

	my_wnck_handle = wnck_handle_new(WNCK_CLIENT_TYPE_PAGER);
	init_screens();

	loop=g_main_loop_new(NULL, TRUE);
	g_main_loop_run(loop);

	return EXIT_SUCCESS;
}
