/*
 * This file is part of roccat-tools.
 *
 * roccat-tools 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 2 of the License, or
 * (at your option) any later version.
 *
 * roccat-tools 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 roccat-tools. If not, see <http://www.gnu.org/licenses/>.
 */

#include "roccat_secure.h"
#include "roccat_firmware.h"
#include "ryos_device.h"
#include "ryos_profile.h"
#include "ryos_reset.h"
#include "ryos_rkp.h"
#include "ryos_info.h"
#include "ryos_config.h"
#include "ryos_default_rkp.h"
#include "ryos_dbus_services.h"
#include "dbus_roccat_helper.h"
#include "config.h"
#include <stdlib.h>

static gchar *parameter_just_update_firmware = NULL;
static gboolean parameter_just_print_version = FALSE;
static gboolean parameter_just_print_actual_profile = FALSE;
static gboolean parameter_just_reset = FALSE;
static gboolean parameter_read_firmware = FALSE;
static gint parameter_activate_profile = -1;
static gint parameter_default_profile = -1;
static gint parameter_load = -1;
static gint parameter_save = -1;
static gchar *parameter_in_rkp = NULL;
static gchar *parameter_out_rkp = NULL;

static GOptionEntry entries[] = {
	{ "activate-profile", 'a', 0, G_OPTION_ARG_INT, &parameter_activate_profile, "activate profile NUMBER", "NUMBER" },
	{ "default-profile", 'd', 0, G_OPTION_ARG_INT, &parameter_default_profile, "set NUMBER as default profile", "NUMBER" },
	{ "read-firmware", 'f', 0, G_OPTION_ARG_NONE, &parameter_read_firmware, "read firmware", NULL },
	{ "load", 'l', 0, G_OPTION_ARG_INT, &parameter_load, "load profile NUMBER", "NUMBER" },
	{ "read-rkp", 'r', 0, G_OPTION_ARG_FILENAME, &parameter_in_rkp, "read from rkp-file FILENAME", "FILENAME" },
	{ "save", 's', 0, G_OPTION_ARG_INT, &parameter_save, "save as profile NUMBER", "NUMBER" },
	{ "update", 'u', 0, G_OPTION_ARG_FILENAME, &parameter_just_update_firmware, "update firmware with FILENAME", "FILENAME" },
	{ "version", 'v', 0, G_OPTION_ARG_NONE, &parameter_just_print_version, "print version", NULL },
	{ "write-rkp", 'w', 0, G_OPTION_ARG_FILENAME, &parameter_out_rkp, "write to rkp-file FILENAME", "FILENAME" },
	{ "actual-profile", 0, 0, G_OPTION_ARG_NONE, &parameter_just_print_actual_profile, "print actual profile", NULL },
	{ "reset", 0, 0, G_OPTION_ARG_NONE, &parameter_just_reset, "reset to factory defaults", NULL },
	{ NULL }
};

static gboolean parse_profile_number_parameter(gint number, GError **error) {
	if (number < 1 || number > RYOS_PROFILE_NUM) {
		g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "profile numbers have to be in range of 1-5");
		return FALSE;
	}
	return TRUE;
}

static gboolean post_parse_func(GOptionContext *context, GOptionGroup *group, gpointer data, GError **error) {
	guint just_counter = 0;

	if (parameter_just_update_firmware) ++just_counter;
	if (parameter_just_print_version) ++just_counter;
	if (parameter_just_print_actual_profile) ++just_counter;
	if (parameter_just_reset) ++just_counter;
	if (just_counter > 1) {
		g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, "just give one of -uv, --actual-profile, --reset");
		return FALSE;
	}

	/* additionally allow 0 as off */
	if (parameter_default_profile != -1 &&
			parameter_default_profile != 0 &&
			!parse_profile_number_parameter(parameter_default_profile, error))
		return FALSE;

	if (parameter_activate_profile != -1 && !parse_profile_number_parameter(parameter_activate_profile, error))
		return FALSE;

	if (parameter_load != -1 && !parse_profile_number_parameter(parameter_load, error))
		return FALSE;

	if (parameter_save != -1 && !parse_profile_number_parameter(parameter_save, error))
		return FALSE;

	if ((parameter_load != -1 || parameter_in_rkp) && !(parameter_out_rkp || parameter_save != -1)) {
		g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, "if -r or -l is given, -w or -s is needed");
		return FALSE;
	}

	if (parameter_load != -1 && parameter_in_rkp) {
		g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, "-r and -l are mutual exclusive");
		return FALSE;
	}

	return TRUE;
}

static GOptionContext *commandline_parse(int *argc, char ***argv) {
	GOptionContext *context = NULL;
	GError *error = NULL;

	context = g_option_context_new("- control extended capabilities of Roccat Ryos MK keyboard");
	g_option_context_add_main_entries(context, entries, NULL);

	g_option_group_set_parse_hooks(g_option_context_get_main_group(context), NULL, post_parse_func);

	if (!g_option_context_parse(context, argc, argv, &error)) {
		g_critical("option parsing failed: %s", error->message);
		exit(EXIT_FAILURE);
	}
	return context;
}

static void commandline_free(GOptionContext *context) {
	g_free(parameter_in_rkp);
	g_free(parameter_out_rkp);
	g_option_context_free(context);
}

static gboolean print_actual_profile(RoccatDevice *ryos, GError **error) {
	guint profile = ryos_profile_read(ryos, error);
	if (*error)
		return FALSE;
	g_print("%i\n", profile + 1);
	return TRUE;
}

static gboolean print_firmware(RoccatDevice *ryos, GError **error) {
	guint firmware_version;
	gchar *firmware_string;

	firmware_version = ryos_firmware_version_read(ryos, error);
	if (*error)
		return FALSE;

	firmware_string = roccat_firmware_version_to_string(firmware_version);
	g_print("%s\n", firmware_string);
	g_free(firmware_string);

	return TRUE;
}

static gboolean reset(RoccatDevice *ryos, GError **error) {
	DBusGProxy *proxy;
	guint i;

	if (!ryos_reset(ryos, RYOS_RESET_FUNCTION_RESET, error))
		return FALSE;

	proxy = ryos_dbus_proxy_new();
	if (!proxy)
		return TRUE;

	for (i = 0; i < RYOS_PROFILE_NUM; ++i)
		ryos_dbus_emit_profile_data_changed_outside(proxy, i);
	dbus_roccat_proxy_free(proxy);

	return TRUE;
}

static gboolean update_firmware(RoccatDevice *device, gchar const *path, GError **error) {
	RoccatFirmwareState *state;
	gboolean retval = FALSE;

	state = roccat_firmware_state_new(device, RYOS_FIRMWARE_SIZE);

	if (!roccat_firmware_state_read_firmware(state, path, error))
		goto error;

	while (roccat_firmware_state_tick(state, error))
		g_print(".");
	g_print("\n");

	if (!*error)
		retval = TRUE;

error:
	roccat_firmware_state_free(state);
	return retval;
}

int main(int argc, char **argv) {
	GOptionContext *context;
	RoccatDevice *ryos;
	GError *error = NULL;
	int retval = EXIT_SUCCESS;
	RyosRkp *rkp = NULL;
	RoccatKeyFile *configuration;

	roccat_secure();

	context = commandline_parse(&argc, &argv);

	if (parameter_just_print_version) {
		g_print(VERSION_STRING "\n");
		goto exit1;
	}

#if !(GLIB_CHECK_VERSION(2, 36, 0))
	g_type_init();
#endif

	ryos = ryos_device_first();
	if (ryos == NULL) {
		g_critical("No Ryos found.");
		retval = EXIT_FAILURE;
		goto exit1;
	}

	if (parameter_just_update_firmware) {
		if (!update_firmware(ryos, parameter_just_update_firmware, &error)) {
			g_critical("firmware update failed: %s", error->message);
			retval = EXIT_FAILURE;
		} else
			g_message("Firmware updated successfully. Please reconnect device.");
		goto exit2;
	}

	if (parameter_just_print_actual_profile) {
		if (!print_actual_profile(ryos, &error)) {
			g_critical("print actual profile failed: %s", error->message);
			retval = EXIT_FAILURE;
		}
		goto exit2;
	}

	if (parameter_just_reset) {
		if (!reset(ryos, &error)) {
			g_critical("reset failed: %s", error->message);
			retval = EXIT_FAILURE;
		}
		goto exit2;
	}

	if (parameter_read_firmware) {
		if (!print_firmware(ryos, &error)) {
			g_critical("Print firmware version failed: %s", error->message);
			retval = EXIT_FAILURE;
			goto exit2;
		}
	}

	if (parameter_load != -1) {
		rkp = ryos_rkp_load(ryos, parameter_load - 1, &error);
		if (!rkp) {
			g_critical("Error loading profile %i: %s", parameter_load, error->message);
			retval = EXIT_FAILURE;
			goto exit2;
		}
	} else if (parameter_in_rkp) {
		rkp = ryos_rkp_read_with_path(parameter_in_rkp, &error);
		if (!rkp) {
			g_critical("Error reading rkp %s: %s", parameter_in_rkp, error->message);
			retval = EXIT_FAILURE;
			goto exit2;
		}
	} else
		rkp = ryos_rkp_default();

	ryos_rkp_set_modified(rkp);

	if (parameter_save != -1) {
		if (!ryos_rkp_save(ryos, rkp, parameter_save - 1, &error)) {
			g_critical("Error saving profile %i: %s", parameter_load, error->message);
			retval = EXIT_FAILURE;
			goto exit3;
		}
		ryos_dbus_emit_profile_data_changed_outside_instant(parameter_save);
	}

	if (parameter_out_rkp) {
		if (!ryos_rkp_write_with_path(parameter_out_rkp, rkp, &error)) {
			g_critical("Error writing rmp: %s", error->message);
			retval = EXIT_FAILURE;
			goto exit3;
		}
	}

	if (parameter_default_profile != -1) {
		configuration = ryos_configuration_load();
		ryos_configuration_set_default_profile_number(configuration, parameter_default_profile);
		if (!ryos_configuration_save(configuration, &error)) {
			g_critical("Error setting default profile: %s", error->message);
			ryos_configuration_free(configuration);
			retval = EXIT_FAILURE;
			goto exit3;
		}
		ryos_configuration_free(configuration);
		ryos_dbus_emit_configuration_changed_outside_instant();
	}

	if (parameter_activate_profile != -1) {
		if (!ryos_profile_write(ryos, parameter_activate_profile -1, &error)) {
			g_critical("Error activating profile: %s", error->message);
			retval = EXIT_FAILURE;
			goto exit3;
		}
		ryos_dbus_emit_profile_changed_outside_instant(parameter_activate_profile -1);
	}

exit3:
	ryos_rkp_free(rkp);
exit2:
	g_object_unref(G_OBJECT(ryos));
exit1:
	commandline_free(context);
	g_clear_error(&error);
	exit(retval);
}
