/* $Id$ */
/* Copyright (c) 2015-2020 Pierre Pronchery <khorben@defora.org> */
/* Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the authors nor the names of the contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 * THIS SOFTWARE IS PROVIDED BY ITS AUTHORS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE. */



#include <stdarg.h>
#include <string.h>
#include <libintl.h>
#include <gtk/gtk.h>
#include <System.h>
#include <Desktop.h>
#include <Devel/Asm.h>
#include "../backend.h"
#include "../debugger.h"
#include "../../config.h"
#define _(string) gettext(string)

#ifndef PREFIX
# define PREFIX	"/usr/local"
#endif
#ifndef LIBDIR
# define LIBDIR	PREFIX "/lib"
#endif


/* asm */
/* private */
typedef struct _DebuggerBackend AsmBackend;

struct _DebuggerBackend
{
	DebuggerBackendHelper const * helper;
	Asm * a;
	AsmCode * code;

	guint source;
};


/* prototypes */
/* plug-in */
static AsmBackend * _asm_init(DebuggerBackendHelper const * helper);
static void _asm_destroy(AsmBackend * backend);
static int _asm_open(AsmBackend * backend, char const * arch,
		char const * format, char const * filename);
static char * _asm_open_dialog(AsmBackend * backend, GtkWidget * window,
		char const * arch, char const * format);
static int _asm_close(AsmBackend * backend);
static char const * _asm_arch_get_name(AsmBackend * backend);
static char const * _asm_format_get_name(AsmBackend * backend);


/* constants */
DebuggerBackendDefinition backend =
{
	"asm",
	NULL,
	LICENSE_BSD3_FLAGS,
	_asm_init,
	_asm_destroy,
	_asm_open,
	_asm_open_dialog,
	_asm_close,
	_asm_arch_get_name,
	_asm_format_get_name
};


/* protected */
/* functions */
/* plug-in */
/* asm_init */
static AsmBackend * _asm_init(DebuggerBackendHelper const * helper)
{
	AsmBackend * backend;

	if((backend = object_new(sizeof(*backend))) == NULL)
		return NULL;
	backend->helper = helper;
	backend->a = NULL;
	backend->code = NULL;
	backend->source = 0;
	return backend;
}


/* asm_destroy */
static void _asm_destroy(AsmBackend * backend)
{
	_asm_close(backend);
	object_delete(backend);
}


/* asm_open */
/* callbacks */
static gboolean _open_on_idle(gpointer data);

static int _asm_open(AsmBackend * backend, char const * arch,
		char const * format, char const * filename)
{
	if(_asm_close(backend) != 0)
		return -1;
	if((backend->a = asm_new(arch, format)) == NULL)
		return -1;
	if((backend->code = asm_open_deassemble(backend->a, filename, TRUE))
			== NULL)
	{
		asm_delete(backend->a);
		backend->a = NULL;
		return -1;
	}
	else
		backend->source = g_idle_add(_open_on_idle, backend);
	return 0;
}

static gboolean _open_on_idle(gpointer data)
{
	AsmBackend * backend = data;
	AsmArchRegister const * registers;
	size_t cnt = 0;

	backend->source = 0;
	if((registers = asmcode_get_arch_registers(backend->code)) != NULL)
		for(cnt = 0; registers[cnt].name != NULL; cnt++);
	backend->helper->set_registers(backend->helper->debugger, registers,
			cnt);
	return FALSE;
}


/* asm_open_dialog */
static void _open_dialog_type(GtkWidget * combobox, char const * type,
		char const * value);

static char * _asm_open_dialog(AsmBackend * backend, GtkWidget * window,
		char const * arch, char const * format)
{
	GtkWidget * dialog;
	GtkWidget * vbox;
	GtkWidget * hbox;
	GtkWidget * awidget;
	GtkWidget * fwidget;
	GtkWidget * widget;
	GtkFileFilter * filter;
	char * a = NULL;
	char * f = NULL;
	char * filename = NULL;
	(void) backend;

	dialog = gtk_file_chooser_dialog_new(_("Open file..."),
			(window != NULL) ? GTK_WINDOW(window) : NULL,
			GTK_FILE_CHOOSER_ACTION_OPEN,
			GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
			GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL);
#if GTK_CHECK_VERSION(2, 14, 0)
	vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
#else
	vbox = GTK_DIALOG(dialog)->vbox;
#endif
	/* arch */
	hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
#if GTK_CHECK_VERSION(2, 24, 0)
	awidget = gtk_combo_box_text_new();
	gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(awidget),
			_("Auto-detect"));
#else
	awidget = gtk_combo_box_new_text();
	gtk_combo_box_append_text(GTK_COMBO_BOX(awidget), _("Auto-detect"));
#endif
	_open_dialog_type(awidget, "arch", arch);
	gtk_box_pack_end(GTK_BOX(hbox), awidget, FALSE, TRUE, 0);
	widget = gtk_label_new(_("Architecture:"));
	gtk_box_pack_end(GTK_BOX(hbox), widget, FALSE, TRUE, 0);
	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0);
	/* format */
	hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
#if GTK_CHECK_VERSION(2, 24, 0)
	fwidget = gtk_combo_box_text_new();
	gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(fwidget),
			_("Auto-detect"));
#else
	fwidget = gtk_combo_box_new_text();
	gtk_combo_box_append_text(GTK_COMBO_BOX(fwidget), _("Auto-detect"));
#endif
	_open_dialog_type(fwidget, "format", format);
	gtk_box_pack_end(GTK_BOX(hbox), fwidget, FALSE, TRUE, 0);
	widget = gtk_label_new(_("File format:"));
	gtk_box_pack_end(GTK_BOX(hbox), widget, FALSE, TRUE, 0);
	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0);
	gtk_widget_show_all(vbox);
	/* core files */
	filter = gtk_file_filter_new();
	gtk_file_filter_set_name(filter, _("Core files"));
	gtk_file_filter_add_mime_type(filter, "application/x-core");
	gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
	/* executable files */
	filter = gtk_file_filter_new();
	gtk_file_filter_set_name(filter, _("Executable files"));
	gtk_file_filter_add_mime_type(filter, "application/x-executable");
	gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
	gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
	/* java classes */
	filter = gtk_file_filter_new();
	gtk_file_filter_set_name(filter, _("Java classes"));
	gtk_file_filter_add_mime_type(filter, "application/x-java");
	gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
	/* objects */
	filter = gtk_file_filter_new();
	gtk_file_filter_set_name(filter, _("Objects"));
	gtk_file_filter_add_mime_type(filter, "application/x-object");
	gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
	/* shared objects */
	filter = gtk_file_filter_new();
	gtk_file_filter_set_name(filter, _("Shared objects"));
	gtk_file_filter_add_mime_type(filter, "application/x-sharedlib");
	gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
	/* all files */
	filter = gtk_file_filter_new();
	gtk_file_filter_set_name(filter, _("All files"));
	gtk_file_filter_add_pattern(filter, "*");
	gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
	if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
	{
		if(gtk_combo_box_get_active(GTK_COMBO_BOX(awidget)) == 0)
			a = NULL;
		else
#if GTK_CHECK_VERSION(2, 24, 0)
			a = gtk_combo_box_text_get_active_text(
					GTK_COMBO_BOX_TEXT(awidget));
#else
			a = gtk_combo_box_get_active_text(GTK_COMBO_BOX(
						awidget));
#endif
		if(gtk_combo_box_get_active(GTK_COMBO_BOX(fwidget)) == 0)
			f = NULL;
		else
#if GTK_CHECK_VERSION(2, 24, 0)
			f = gtk_combo_box_text_get_active_text(
					GTK_COMBO_BOX_TEXT(fwidget));
#else
			f = gtk_combo_box_get_active_text(GTK_COMBO_BOX(
						fwidget));
#endif
		filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(
					dialog));
	}
	gtk_widget_destroy(dialog);
	g_free(a);
	g_free(f);
	return filename;
}

static void _open_dialog_type(GtkWidget * combobox, char const * type,
		char const * value)
{
	char * path;
	DIR * dir;
	struct dirent * de;
#if defined(__APPLE__)
	char const ext[] = ".dylib";
#elif defined(__WIN32__)
	char const ext[] = ".dll";
#else
	char const ext[] = ".so";
#endif
	size_t len;
	int i;
	int active = 0;

	if((path = g_build_filename(LIBDIR, "Asm", type, NULL)) == NULL)
		return;
#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(%s) \"%s\"\n", __func__, type, path);
#endif
	dir = opendir(path);
	g_free(path);
	if(dir == NULL)
		return;
	for(i = 0; (de = readdir(dir)) != NULL; i++)
	{
		if(strcmp(de->d_name, ".") == 0
				|| strcmp(de->d_name, "..") == 0)
			continue;
		if((len = strlen(de->d_name)) < sizeof(ext))
			continue;
		if(strcmp(&de->d_name[len - sizeof(ext) + 1], ext) != 0)
			continue;
		de->d_name[len - sizeof(ext) + 1] = '\0';
#if GTK_CHECK_VERSION(2, 24, 0)
		gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combobox),
				de->d_name);
#else
		gtk_combo_box_append_text(GTK_COMBO_BOX(combobox), de->d_name);
#endif
		if(value != NULL && strcmp(de->d_name, value) == 0)
			active = i;
	}
	closedir(dir);
	gtk_combo_box_set_active(GTK_COMBO_BOX(combobox), active);
}


/* asm_close */
static int _asm_close(AsmBackend * backend)
{
	if(backend->source != 0)
		g_source_remove(backend->source);
	backend->source = 0;
	if(backend->a != NULL)
		asm_delete(backend->a);
	backend->a = NULL;
	return 0;
}


/* asm_arch_get_name */
static char const * _asm_arch_get_name(AsmBackend * backend)
{
	return asmcode_get_arch(backend->code);
}


/* asm_format_get_name */
static char const * _asm_format_get_name(AsmBackend * backend)
{
	return asmcode_get_format(backend->code);
}
