#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <errno.h>
#include <gtk/gtk.h>

#include <stlink.h>
#include "stlink-gui.h"

#define MEM_READ_SIZE 1024

#ifndef G_VALUE_INIT
#define G_VALUE_INIT {0, {{0}}}
#endif

G_DEFINE_TYPE (STlinkGUI, stlink_gui, G_TYPE_OBJECT);

static void
stlink_gui_dispose (GObject *gobject)
{
    G_OBJECT_CLASS (stlink_gui_parent_class)->dispose (gobject);
}

static void
stlink_gui_finalize (GObject *gobject)
{
    G_OBJECT_CLASS (stlink_gui_parent_class)->finalize (gobject);
}

static void
stlink_gui_class_init (STlinkGUIClass *klass)
{
    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

    gobject_class->dispose  = stlink_gui_dispose;
    gobject_class->finalize = stlink_gui_finalize;
}

static void
stlink_gui_init (STlinkGUI *self)
{
    self->sl       = NULL;
    self->filename = NULL;

    self->progress.activity_mode = FALSE;
    self->progress.fraction      = 0;

    self->flash_mem.memory = NULL;
    self->flash_mem.size   = 0;
    self->flash_mem.base   = 0;

    self->file_mem.memory = NULL;
    self->file_mem.size   = 0;
    self->file_mem.base   = 0;
}

static gboolean
set_info_error_message_idle (STlinkGUI *gui)
{
    if (gui->error_message != NULL) {
        gchar *markup;

        markup = g_markup_printf_escaped ("<b>%s</b>", gui->error_message);
        gtk_label_set_markup (gui->infolabel, markup);
        gtk_info_bar_set_message_type (gui->infobar, GTK_MESSAGE_ERROR);
        gtk_widget_show (GTK_WIDGET (gui->infobar));

        g_free (markup);
        g_free (gui->error_message);
        gui->error_message = NULL;
    }
    return FALSE;
}

static void
stlink_gui_set_info_error_message (STlinkGUI *gui, const gchar *message)
{
    gui->error_message = g_strdup (message);
    g_idle_add ((GSourceFunc) set_info_error_message_idle, gui);
}

static void
stlink_gui_set_sensitivity (STlinkGUI *gui, gboolean sensitivity)
{
    gtk_widget_set_sensitive (GTK_WIDGET (gui->open_button), sensitivity);

    if (sensitivity && gui->sl)
        gtk_widget_set_sensitive (GTK_WIDGET (gui->disconnect_button), sensitivity);

    if (sensitivity && !gui->sl)
        gtk_widget_set_sensitive (GTK_WIDGET (gui->connect_button), sensitivity);

    if (sensitivity && gui->sl && gui->filename)
        gtk_widget_set_sensitive (GTK_WIDGET (gui->flash_button), sensitivity);

    gtk_widget_set_sensitive (GTK_WIDGET (gui->export_button), sensitivity && (gui->sl != NULL));
}

static void
mem_view_init_headers (GtkTreeView *view)
{
    GtkCellRenderer *renderer;
    gint             i;

    g_return_if_fail (view != NULL);

    renderer = gtk_cell_renderer_text_new ();
    gtk_tree_view_insert_column_with_attributes (view,
            -1,
            "Address",
            renderer,
            "text",
            0, /* column */
            NULL);
    for (i = 0; i < 4; i++) {
        gchar *label;

        label = g_strdup_printf ("%X", i * 4);
        renderer = gtk_cell_renderer_text_new ();
        gtk_tree_view_insert_column_with_attributes (view,
                -1,
                label,
                renderer,
                "text",
                (i + 1), /* column */
                NULL);
        g_free (label);
    }

    for (i = 0; i < 5; i++) {
        GtkTreeViewColumn *column = gtk_tree_view_get_column (view, i);
        gtk_tree_view_column_set_expand (column, TRUE);
    }
}

static void
mem_view_add_as_hex (GtkListStore *store,
                     GtkTreeIter  *iter,
                     gint          column,
                     guint32       value)
{
    gchar     *hex_str;

    hex_str = g_strdup_printf ("0x%08X", value);
    gtk_list_store_set (store, iter, column, hex_str, -1);
    g_free (hex_str);
}

static void
mem_view_add_buffer (GtkListStore *store,
                     GtkTreeIter  *iter,
                     guint32       address,
                     guchar       *buffer,
                     gint          len)
{
    guint32 *word;
    gint     i, step;
    gint     column = 0;

    step = sizeof (*word);

    for (i = 0; i < len; i += step) {
        word = (guint *) &buffer[i];

        if (column == 0) {
            /* new row */
            gtk_list_store_append (store, iter);

            /* add address */
            mem_view_add_as_hex (store, iter, column, (address + i));
        }
        mem_view_add_as_hex (store, iter, (column + 1), *word);
        column = (column + 1) % step;
    }
}

static guint32
hexstr_to_guint32 (const gchar *str, GError **err)
{
    guint32  val;
    gchar   *end_ptr;

    val = (guint32) strtoul (str, &end_ptr, 16);
    if ((errno == ERANGE && val == UINT_MAX) || (errno != 0 && val == 0)) {
        g_set_error (err,
                g_quark_from_string ("hextou32"),
                1,
                "Invalid hexstring");
        return UINT32_MAX;
    }
    if (end_ptr == str) {
        g_set_error (err,
                g_quark_from_string ("hextou32"),
                2,
                "Invalid hexstring");
        return UINT32_MAX;
    }
    return val;
}


static void
stlink_gui_update_mem_view (STlinkGUI *gui, struct mem_t *mem, GtkTreeView *view) {
    GtkListStore *store;
    GtkTreeIter   iter;

    store = GTK_LIST_STORE (gtk_tree_view_get_model (view));

    mem_view_add_buffer (store,
            &iter,
            mem->base,
            mem->memory,
            (gint) mem->size);

    gtk_widget_hide (GTK_WIDGET (gui->progress.bar));
    gtk_progress_bar_set_fraction (gui->progress.bar, 0);
    stlink_gui_set_sensitivity (gui, TRUE);
}

static gboolean
stlink_gui_update_devmem_view (STlinkGUI *gui)
{
    stlink_gui_update_mem_view (gui, &gui->flash_mem, gui->devmem_treeview);
    return FALSE;
}



static gpointer
stlink_gui_populate_devmem_view (gpointer data)
{
    guint            off;
    stm32_addr_t     addr;

    g_return_val_if_fail (STLINK_IS_GUI (data), NULL);
    STlinkGUI *gui = (STlinkGUI *)data;

    g_return_val_if_fail ((gui != NULL), NULL);
    g_return_val_if_fail ((gui->sl != NULL), NULL);

    addr = gui->sl->flash_base;

    if (gui->flash_mem.memory) {
        g_free (gui->flash_mem.memory);
    }
    gui->flash_mem.memory = g_malloc (gui->sl->flash_size);
    gui->flash_mem.size   = gui->sl->flash_size;
    gui->flash_mem.base   = gui->sl->flash_base;

    for (off = 0; off < gui->sl->flash_size; off += MEM_READ_SIZE) {
        guint   n_read = MEM_READ_SIZE;

        if (off + MEM_READ_SIZE > gui->sl->flash_size) {
            n_read = (guint) gui->sl->flash_size - off;

            /* align if needed */
            if (n_read & 3) {
                n_read = (n_read + 4) & ~(3);
            }
        }
        /* reads to sl->q_buf */
        stlink_read_mem32(gui->sl, addr + off, n_read);
        if (gui->sl->q_len < 0) {
            stlink_gui_set_info_error_message (gui, "Failed to read memory");
            g_free (gui->flash_mem.memory);
            gui->flash_mem.memory = NULL;
            return NULL;
        }
        memcpy (gui->flash_mem.memory + off, gui->sl->q_buf, n_read);
        gui->progress.fraction = (gdouble) (off + n_read) / gui->sl->flash_size;
    }
    g_idle_add ((GSourceFunc) stlink_gui_update_devmem_view, gui);

    return NULL;
}

static gboolean
stlink_gui_update_filemem_view (STlinkGUI *gui)
{
    gchar *basename;

    basename = g_path_get_basename (gui->filename);
    gtk_notebook_set_tab_label_text (gui->notebook,
            GTK_WIDGET (gtk_notebook_get_nth_page (gui->notebook, 1)),
            basename);
    g_free (basename);

    stlink_gui_update_mem_view (gui, &gui->file_mem, gui->filemem_treeview);

    return FALSE;
}

static gpointer
stlink_gui_populate_filemem_view (gpointer data)
{
    guchar        buffer[MEM_READ_SIZE];
    GFile        *file;
    GFileInfo    *file_info;
    GInputStream *input_stream;
    gint          off;
    GError       *err = NULL;

    g_return_val_if_fail (STLINK_IS_GUI (data), NULL);
    STlinkGUI *gui = (STlinkGUI *)data;

    g_return_val_if_fail (gui != NULL, NULL);
    g_return_val_if_fail (gui->filename != NULL, NULL);

	if (g_str_has_suffix (gui->filename, ".hex")) {
		// If the file has prefix .hex - try to interpret it as Intel-HEX.
		// It's difficult to merge the world of standard functions and GLib,
		// so do it simple - load whole file into buffer and copy the data
		// to the destination afterwards.
		// We loose meanwhile the displaying of the progress and need
		// double memory.

		uint8_t* mem   = NULL;
		size_t   size  = 0;
		uint32_t begin = 0;
		int res = stlink_parse_ihex (gui->filename, 0, &mem, &size, &begin);

		if (res == 0) {
			if (gui->file_mem.memory) {
				g_free (gui->file_mem.memory);
			}
			gui->file_mem.size   = size;
			gui->file_mem.memory = g_malloc (size);
			gui->file_mem.base   = begin;

			memcpy (gui->file_mem.memory, mem, size);
		}
		else {
			stlink_gui_set_info_error_message (gui, "Cannot interpret the file as Intel-HEX");
		}

		free(mem);
	}
	else {
		file = g_file_new_for_path (gui->filename);
		input_stream = G_INPUT_STREAM (g_file_read (file, NULL, &err));
		if (err) {
			stlink_gui_set_info_error_message (gui, err->message);
			g_error_free (err);
			goto out;
		}

		file_info = g_file_input_stream_query_info (G_FILE_INPUT_STREAM (input_stream),
				G_FILE_ATTRIBUTE_STANDARD_SIZE, NULL, &err);
		if (err) {
			stlink_gui_set_info_error_message (gui, err->message);
			g_error_free (err);
			goto out_input;
		}
		if (gui->file_mem.memory) {
			g_free (gui->file_mem.memory);
		}
		gui->file_mem.size   = g_file_info_get_size (file_info);
		gui->file_mem.memory = g_malloc (gui->file_mem.size);

		for (off = 0; off < (gint) gui->file_mem.size; off += MEM_READ_SIZE) {
			guint   n_read = MEM_READ_SIZE;

			if (off + MEM_READ_SIZE > (gint) gui->file_mem.size) {
				n_read = (guint) gui->file_mem.size - off;
			}

			if (g_input_stream_read (G_INPUT_STREAM (input_stream),
						&buffer, n_read, NULL, &err) == -1) {
				stlink_gui_set_info_error_message (gui, err->message);
				g_error_free (err);
				goto out_input;
			}
			memcpy (gui->file_mem.memory + off, buffer, n_read);
			gui->progress.fraction = (gdouble) (off + n_read) / gui->file_mem.size;
		}

out_input:
		g_object_unref (input_stream);
out:
		g_object_unref (file);
	}

	g_idle_add ((GSourceFunc) stlink_gui_update_filemem_view, gui);
    return NULL;
}

static void mem_jmp (GtkTreeView *view,
                     GtkEntry    *entry,
                     guint32      base_addr,
                     gsize        size,
                     GError     **err)
{
    GtkTreeModel *model;
    guint32       jmp_addr;
    GtkTreeIter   iter;

    jmp_addr = hexstr_to_guint32 (gtk_entry_get_text (entry), err);
    if (err && *err) {
        return;
    }

    if (jmp_addr < base_addr || jmp_addr > base_addr + size) {
        g_set_error (err,
                g_quark_from_string ("mem_jmp"),
                1,
                "Invalid address");
        return;
    }

    model = gtk_tree_view_get_model (view);
    if (!model) {
        return;
    }

    if (gtk_tree_model_get_iter_first (model, &iter)) {
        do {
            guint32 addr;
            GValue  value = G_VALUE_INIT;

            gtk_tree_model_get_value (model, &iter, 0, &value);
            if (G_VALUE_HOLDS_STRING (&value)) {
                addr = hexstr_to_guint32 (g_value_get_string (&value), err);
                if (!*err) {
                    if (addr == (jmp_addr & 0xFFFFFFF0)) {
                        GtkTreeSelection *selection;
                        GtkTreePath      *path;

                        selection = gtk_tree_view_get_selection (view);
                        path      = gtk_tree_model_get_path (model, &iter);

                        gtk_tree_selection_select_iter (selection, &iter);
                        gtk_tree_view_scroll_to_cell (view,
                                path,
                                NULL,
                                TRUE,
                                0.0,
                                0.0);
                        gtk_tree_path_free (path);
                    }
                }
            }
            g_value_unset (&value);
        } while (gtk_tree_model_iter_next (model, &iter));
    }
}

static void
devmem_jmp_cb (GtkWidget *widget, gpointer data)
{
    STlinkGUI *gui;
    GError    *err = NULL;
    (void) widget;

    gui = STLINK_GUI (data);

    mem_jmp (gui->devmem_treeview,
            gui->devmem_jmp_entry,
            gui->sl->flash_base,
            gui->sl->flash_size,
            &err);

    if (err) {
        stlink_gui_set_info_error_message (gui, err->message);
        g_error_free (err);
    }
}

static void
filemem_jmp_cb (GtkWidget *widget, gpointer data)
{
    STlinkGUI *gui;
    GError    *err = NULL;
    (void) widget;

    gui = STLINK_GUI (data);

    g_return_if_fail (gui->filename != NULL);

    mem_jmp (gui->filemem_treeview,
            gui->filemem_jmp_entry,
            0,
            gui->file_mem.size,
            &err);

    if (err) {
        stlink_gui_set_info_error_message (gui, err->message);
        g_error_free (err);
    }
}

static gchar *
dev_format_chip_id (guint32 chip_id)
{
    const struct stlink_chipid_params *params;

    params = stlink_chipid_get_params(chip_id);
    if (!params)
        return g_strdup_printf ("0x%x", chip_id);

    return g_strdup (params->description);
}

static gchar *
dev_format_mem_size (gsize flash_size)
{
  return g_strdup_printf ("%u kB", (unsigned int)(flash_size / 1024));
}


static void
stlink_gui_set_connected (STlinkGUI *gui)
{
    gchar        *tmp_str;
    GtkListStore *store;
    GtkTreeIter   iter;

    gtk_statusbar_push (gui->statusbar,
            gtk_statusbar_get_context_id (gui->statusbar, "conn"),
            "Connected");

    gtk_widget_set_sensitive (GTK_WIDGET (gui->device_frame), TRUE);
    gtk_widget_set_sensitive (GTK_WIDGET (gui->devmem_box), TRUE);
    gtk_widget_set_sensitive (GTK_WIDGET (gui->connect_button), FALSE);

    if (gui->filename) {
        gtk_widget_set_sensitive (GTK_WIDGET (gui->flash_button), TRUE);
    }

    tmp_str = dev_format_chip_id (gui->sl->chip_id);
    gtk_label_set_text (gui->chip_id_label, tmp_str);
    g_free (tmp_str);

    tmp_str = g_strdup_printf ("0x%x", gui->sl->core_id);
    gtk_label_set_text (gui->core_id_label, tmp_str);
    g_free (tmp_str);

    tmp_str = dev_format_mem_size (gui->sl->flash_size);
    gtk_label_set_text (gui->flash_size_label, tmp_str);
    g_free (tmp_str);

    tmp_str = dev_format_mem_size (gui->sl->sram_size);
    gtk_label_set_text (gui->ram_size_label, tmp_str);
    g_free (tmp_str);

    tmp_str = g_strdup_printf ("0x%08X", gui->sl->flash_base);
    gtk_entry_set_text (gui->devmem_jmp_entry, tmp_str);
    gtk_editable_set_editable (GTK_EDITABLE (gui->devmem_jmp_entry), TRUE);
    g_free (tmp_str);

    store = GTK_LIST_STORE (gtk_tree_view_get_model (gui->devmem_treeview));
    if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter)) {
        gtk_list_store_clear (store);
    }

    stlink_gui_set_sensitivity (gui, FALSE);
    gtk_notebook_set_current_page (gui->notebook, PAGE_DEVMEM);
    gtk_widget_show (GTK_WIDGET (gui->progress.bar));
    gtk_progress_bar_set_text (gui->progress.bar, "Reading memory");

    g_thread_new ("devmem", (GThreadFunc) stlink_gui_populate_devmem_view, gui);
}

static void
connect_button_cb (GtkWidget *widget, gpointer data)
{
    STlinkGUI *gui;
    gint       i;
    (void) widget;

    gui = STLINK_GUI (data);

    if (gui->sl != NULL)
        return;

    /* try version 1 then version 2 */
    gui->sl = stlink_v1_open(0, 1);
    if (gui->sl == NULL) {
	    gui->sl = stlink_open_usb(0, 1, NULL);
    }
    if (gui->sl == NULL) {
        stlink_gui_set_info_error_message (gui, "Failed to connect to STLink.");		return;
    }

    /* code below taken from flash/main.c, refactoring might be in order */
    if (stlink_current_mode(gui->sl) == STLINK_DEV_DFU_MODE)
        stlink_exit_dfu_mode(gui->sl);

    if (stlink_current_mode(gui->sl) != STLINK_DEV_DEBUG_MODE)
        stlink_enter_swd_mode(gui->sl);

    /* Disable DMA - Set All DMA CCR Registers to zero. - AKS 1/7/2013 */
    if (gui->sl->chip_id == STLINK_CHIPID_STM32_F4) {
        memset(gui->sl->q_buf, 0, 4);
        for (i = 0; i < 8; i++) {
            stlink_write_mem32(gui->sl, 0x40026000 + 0x10 + 0x18 * i, 4);
            stlink_write_mem32(gui->sl, 0x40026400 + 0x10 + 0x18 * i, 4);
            stlink_write_mem32(gui->sl, 0x40026000 + 0x24 + 0x18 * i, 4);
            stlink_write_mem32(gui->sl, 0x40026400 + 0x24 + 0x18 * i, 4);
        }
    }
    stlink_gui_set_connected (gui);
}

static void stlink_gui_set_disconnected (STlinkGUI *gui)
{
    gtk_statusbar_push (gui->statusbar,
            gtk_statusbar_get_context_id (gui->statusbar, "conn"),
            "Disconnected");

    gtk_widget_set_sensitive (GTK_WIDGET (gui->device_frame), FALSE);
    gtk_widget_set_sensitive (GTK_WIDGET (gui->flash_button), FALSE);
    gtk_widget_set_sensitive (GTK_WIDGET (gui->export_button), FALSE);
    gtk_widget_set_sensitive (GTK_WIDGET (gui->disconnect_button), FALSE);
    gtk_widget_set_sensitive (GTK_WIDGET (gui->connect_button), TRUE);
}

static void
disconnect_button_cb (GtkWidget *widget, gpointer data)
{
    STlinkGUI *gui;
    (void) widget;

    gui = STLINK_GUI (data);

    if (gui->sl != NULL) {
        stlink_exit_debug_mode(gui->sl);
        stlink_close(gui->sl);
        gui->sl = NULL;
    }
    stlink_gui_set_disconnected (gui);
}


static void
stlink_gui_open_file (STlinkGUI *gui)
{
    GtkWidget    *dialog;
    GtkListStore *store;
    GtkTreeIter   iter;

    dialog = gtk_file_chooser_dialog_new ("Open file",
            gui->window,
            GTK_FILE_CHOOSER_ACTION_OPEN,
            "_Cancel", GTK_RESPONSE_CANCEL,
            "_Open", GTK_RESPONSE_ACCEPT,
            NULL);

    if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) {
        gui->filename =
            gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));

        store = GTK_LIST_STORE (gtk_tree_view_get_model (gui->filemem_treeview));
        if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter)) {
            gtk_list_store_clear (store);
        }

        stlink_gui_set_sensitivity (gui, FALSE);
        gtk_notebook_set_current_page (gui->notebook, PAGE_FILEMEM);
        gtk_widget_show (GTK_WIDGET (gui->progress.bar));
        gtk_progress_bar_set_text (gui->progress.bar, "Reading file");
        g_thread_new ("file", (GThreadFunc) stlink_gui_populate_filemem_view, gui);
    }
    gtk_widget_destroy (dialog);
}

static void
open_button_cb (GtkWidget *widget, gpointer data)
{
    STlinkGUI    *gui;
    (void) widget;

    gui = STLINK_GUI (data);

    stlink_gui_open_file (gui);
}

static gboolean
stlink_gui_write_flash_update (STlinkGUI *gui)
{
    stlink_gui_set_sensitivity (gui, TRUE);
    gui->progress.activity_mode = FALSE;
    gtk_widget_hide (GTK_WIDGET (gui->progress.bar));

    return FALSE;
}

static gpointer
stlink_gui_write_flash (gpointer data)
{
    g_return_val_if_fail (STLINK_IS_GUI (data), NULL);
    STlinkGUI *gui = (STlinkGUI *)data;

    g_return_val_if_fail ((gui->sl != NULL), NULL);
    g_return_val_if_fail ((gui->filename != NULL), NULL);

    if (stlink_mwrite_flash(gui->sl, gui->file_mem.memory, (uint32_t)gui->file_mem.size, gui->sl->flash_base) < 0) {
        stlink_gui_set_info_error_message (gui, "Failed to write to flash");
    }

    g_idle_add ((GSourceFunc) stlink_gui_write_flash_update, gui);
    return NULL;
}

static void
flash_button_cb (GtkWidget *widget, gpointer data)
{
    STlinkGUI *gui;
    gchar     *tmp_str;
    guint32    address;
    gint       result;
    GError    *err = NULL;
    (void) widget;

    gui = STLINK_GUI (data);
    g_return_if_fail (gui->sl != NULL);

    if (!g_strcmp0 (gtk_entry_get_text (gui->flash_dialog_entry), "")) {
        tmp_str = g_strdup_printf ("0x%08X", gui->sl->flash_base);
        gtk_entry_set_text (gui->flash_dialog_entry, tmp_str);
        g_free (tmp_str);
    }

    result = gtk_dialog_run (gui->flash_dialog);
    if (result == GTK_RESPONSE_OK) {
        address = hexstr_to_guint32 (gtk_entry_get_text (gui->flash_dialog_entry),
                &err);
        if (err) {
            stlink_gui_set_info_error_message (gui, err->message);
        } else {
            if (address > gui->sl->flash_base + gui->sl->flash_size ||
                    address < gui->sl->flash_base) {
                stlink_gui_set_info_error_message (gui, "Invalid address");
            }
            else if (address + gui->file_mem.size >
                    gui->sl->flash_base + gui->sl->flash_size) {
                stlink_gui_set_info_error_message (gui, "Binary overwrites flash");
            } else {
                stlink_gui_set_sensitivity (gui, FALSE);
                gtk_progress_bar_set_text (gui->progress.bar,
                        "Writing to flash");
                gui->progress.activity_mode = TRUE;
                gtk_widget_show (GTK_WIDGET (gui->progress.bar));
                g_thread_new ("flash",
                        (GThreadFunc) stlink_gui_write_flash, gui);
            }
        }
    }
}

int export_to_file(const char*filename, const struct mem_t flash_mem)
{
    printf("%s\n", filename);
    FILE * f=fopen(filename, "w");
    if(f==NULL)
        return -1;
    for(gsize i=0;i<flash_mem.size;i++)
        if(fputc(flash_mem.memory[i], f)==EOF)
        return -1;
    fclose(f);
    return 0;
}

static void
export_button_cb (GtkWidget *widget, gpointer data)
{
    (void)widget;
    STlinkGUI * gui = STLINK_GUI (data);
    GtkWidget *dialog;
    dialog = gtk_file_chooser_dialog_new ("Save as",
            gui->window,
            GTK_FILE_CHOOSER_ACTION_SAVE,
            "_Cancel",
            GTK_RESPONSE_CANCEL,
            "_Open",
            GTK_RESPONSE_ACCEPT,
            NULL);
    GtkFileChooser *chooser = GTK_FILE_CHOOSER (dialog);
    gtk_file_chooser_set_do_overwrite_confirmation (chooser, TRUE);
    gint res = gtk_dialog_run (GTK_DIALOG (dialog));
    if (res == GTK_RESPONSE_ACCEPT)
    {
        char *filename;

        filename = gtk_file_chooser_get_filename (chooser);
        if(export_to_file (filename, gui->flash_mem)!=0)
            stlink_gui_set_info_error_message(gui, "Failed to export flash");
        else
            stlink_gui_set_info_error_message(gui, "Export successful");
        g_free (filename);
    }

    gtk_widget_destroy (dialog);
}

static gboolean
progress_pulse_timeout (STlinkGUI *gui) {
    if (gui->progress.activity_mode) {
        gtk_progress_bar_pulse (gui->progress.bar);
    } else {
        gtk_progress_bar_set_fraction (gui->progress.bar, gui->progress.fraction);
    }
    return TRUE;
}

static void
notebook_switch_page_cb (GtkNotebook *notebook,
                         GtkWidget   *widget,
                         guint        page_num,
                         gpointer     data)
{
    STlinkGUI *gui;
    (void) notebook;
    (void) widget;

    gui = STLINK_GUI (data);

    if (page_num == 1) {
        if (gui->filename == NULL) {
            stlink_gui_open_file (gui);
        }
    }
}

static void
dnd_received_cb (GtkWidget *widget,
                 GdkDragContext *context,
                 gint x,
                 gint y,
                 GtkSelectionData *selection_data,
                 guint target_type,
                 guint timestamp,
                 gpointer data)
{
    GFile        *file_uri;
    gchar       **file_list;
    const guchar *file_data;
    STlinkGUI    *gui = STLINK_GUI (data);
    GtkListStore *store;
    GtkTreeIter   iter;
    (void) widget;
    (void) x;
    (void) y;

    if (selection_data != NULL &&
            gtk_selection_data_get_length (selection_data) > 0) {
        switch (target_type) {
        case TARGET_FILENAME:

            if (gui->filename) {
                g_free (gui->filename);
            }

            file_data = gtk_selection_data_get_data (selection_data);
            file_list = g_strsplit ((gchar *)file_data, "\r\n", 0);

            file_uri = g_file_new_for_uri (file_list[0]);
            gui->filename = g_file_get_path (file_uri);

            g_strfreev (file_list);
            g_object_unref (file_uri);


            store = GTK_LIST_STORE (gtk_tree_view_get_model (gui->devmem_treeview));
            if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter)) {
                gtk_list_store_clear (store);
            }

            stlink_gui_set_sensitivity (gui, FALSE);
            gtk_notebook_set_current_page (gui->notebook, PAGE_FILEMEM);
            gtk_widget_show (GTK_WIDGET (gui->progress.bar));
            gtk_progress_bar_set_text (gui->progress.bar, "Reading file");
            g_thread_new ("file", (GThreadFunc) stlink_gui_populate_filemem_view, gui);
            break;
        }
    }
    gtk_drag_finish (context,
                     TRUE,
                     gdk_drag_context_get_suggested_action (context) == GDK_ACTION_MOVE,
                     timestamp);
}

void
stlink_gui_init_dnd (STlinkGUI *gui)
{
    GtkTargetEntry target_list[] = {
        { "text/uri-list", 0, TARGET_FILENAME },
    };

    gtk_drag_dest_set (GTK_WIDGET (gui->window),
                       GTK_DEST_DEFAULT_ALL,
                       target_list,
                       G_N_ELEMENTS (target_list),
                       GDK_ACTION_COPY);

    g_signal_connect (gui->window, "drag-data-received",
                      G_CALLBACK (dnd_received_cb), gui);
}

static void
stlink_gui_build_ui (STlinkGUI *gui) {
    GtkBuilder   *builder;
    GtkListStore *devmem_store;
    GtkListStore *filemem_store;
    gchar *ui_file = STLINK_UI_DIR "/stlink-gui.ui";

    if (!g_file_test (ui_file, G_FILE_TEST_EXISTS)) {
        ui_file = "stlink-gui.ui";
    }
    builder = gtk_builder_new ();
    if (!gtk_builder_add_from_file (builder, ui_file, NULL)) {
        g_printerr ("Failed to load UI file: %s\n", ui_file);
        exit (1);
    }

    gui->window = GTK_WINDOW (gtk_builder_get_object (builder, "window"));
    g_signal_connect (G_OBJECT (gui->window), "destroy",
            G_CALLBACK (gtk_main_quit), NULL);

    /* set up toolutton clicked callbacks */
    gui->open_button =
        GTK_TOOL_BUTTON (gtk_builder_get_object (builder, "open_button"));
    g_signal_connect (G_OBJECT (gui->open_button), "clicked",
            G_CALLBACK (open_button_cb), gui);

    gui->connect_button =
        GTK_TOOL_BUTTON (gtk_builder_get_object (builder, "connect_button"));
    g_signal_connect (G_OBJECT (gui->connect_button), "clicked",
            G_CALLBACK (connect_button_cb), gui);

    gui->disconnect_button =
        GTK_TOOL_BUTTON (gtk_builder_get_object (builder, "disconnect_button"));
    g_signal_connect (G_OBJECT (gui->disconnect_button), "clicked",
            G_CALLBACK (disconnect_button_cb), gui);

    gui->flash_button =
        GTK_TOOL_BUTTON (gtk_builder_get_object (builder, "flash_button"));
    g_signal_connect (G_OBJECT (gui->flash_button), "clicked",
            G_CALLBACK (flash_button_cb), gui);

    gui->export_button =
        GTK_TOOL_BUTTON (gtk_builder_get_object (builder, "export_button"));
    g_signal_connect (G_OBJECT (gui->export_button), "clicked",
            G_CALLBACK (export_button_cb), gui);

    gui->devmem_treeview =
        GTK_TREE_VIEW (gtk_builder_get_object (builder, "devmem_treeview"));
    mem_view_init_headers (gui->devmem_treeview);
    devmem_store = gtk_list_store_new (5,
            G_TYPE_STRING,
            G_TYPE_STRING,
            G_TYPE_STRING,
            G_TYPE_STRING,
            G_TYPE_STRING);
    gtk_tree_view_set_model (gui->devmem_treeview, GTK_TREE_MODEL (devmem_store));
    g_object_unref (devmem_store);

    gui->filemem_treeview =
        GTK_TREE_VIEW (gtk_builder_get_object (builder, "filemem_treeview"));
    mem_view_init_headers (gui->filemem_treeview);
    filemem_store = gtk_list_store_new (5,
            G_TYPE_STRING,
            G_TYPE_STRING,
            G_TYPE_STRING,
            G_TYPE_STRING,
            G_TYPE_STRING);
    gtk_tree_view_set_model (gui->filemem_treeview, GTK_TREE_MODEL (filemem_store));
    g_object_unref (filemem_store);

    gui->core_id_label =
        GTK_LABEL (gtk_builder_get_object (builder, "core_id_value"));

    gui->chip_id_label =
        GTK_LABEL (gtk_builder_get_object (builder, "chip_id_value"));

    gui->flash_size_label =
        GTK_LABEL (gtk_builder_get_object (builder, "flash_size_value"));

    gui->ram_size_label =
        GTK_LABEL (gtk_builder_get_object (builder, "ram_size_value"));

    gui->device_frame =
        GTK_FRAME (gtk_builder_get_object (builder, "device_frame"));

    gui->notebook =
        GTK_NOTEBOOK (gtk_builder_get_object (builder, "mem_notebook"));
    g_signal_connect (gui->notebook, "switch-page",
            G_CALLBACK (notebook_switch_page_cb), gui);

    gui->devmem_box =
        GTK_BOX (gtk_builder_get_object (builder, "devmem_box"));

    gui->filemem_box =
        GTK_BOX (gtk_builder_get_object (builder, "filemem_box"));

    gui->devmem_jmp_entry =
        GTK_ENTRY (gtk_builder_get_object (builder, "devmem_jmp_entry"));
    g_signal_connect (gui->devmem_jmp_entry, "activate",
            G_CALLBACK (devmem_jmp_cb), gui);

    gui->filemem_jmp_entry =
        GTK_ENTRY (gtk_builder_get_object (builder, "filemem_jmp_entry"));
    g_signal_connect (gui->filemem_jmp_entry, "activate",
            G_CALLBACK (filemem_jmp_cb), gui);
    gtk_editable_set_editable (GTK_EDITABLE (gui->filemem_jmp_entry), TRUE);

    gui->progress.bar =
        GTK_PROGRESS_BAR (gtk_builder_get_object (builder, "progressbar"));
    gtk_progress_bar_set_show_text (gui->progress.bar, TRUE);
    gui->progress.timer = g_timeout_add (100,
            (GSourceFunc) progress_pulse_timeout,
            gui);

    gui->statusbar =
        GTK_STATUSBAR (gtk_builder_get_object (builder, "statusbar"));

    gui->infobar =
        GTK_INFO_BAR (gtk_builder_get_object (builder, "infobar"));
    gtk_info_bar_add_button (gui->infobar, "_OK", GTK_RESPONSE_OK);
    gui->infolabel = GTK_LABEL (gtk_label_new (""));
    gtk_container_add (GTK_CONTAINER (gtk_info_bar_get_content_area (gui->infobar)),
            GTK_WIDGET (gui->infolabel));
    g_signal_connect (gui->infobar, "response", G_CALLBACK (gtk_widget_hide), NULL);

    /* flash dialog */
    gui->flash_dialog =
        GTK_DIALOG (gtk_builder_get_object (builder, "flash_dialog"));
    g_signal_connect_swapped (gui->flash_dialog, "response",
            G_CALLBACK (gtk_widget_hide), gui->flash_dialog);

    gui->flash_dialog_ok =
        GTK_BUTTON (gtk_builder_get_object (builder, "flash_dialog_ok_button"));

    gui->flash_dialog_cancel =
        GTK_BUTTON (gtk_builder_get_object (builder, "flash_dialog_cancel_button"));

    gui->flash_dialog_entry =
        GTK_ENTRY (gtk_builder_get_object (builder, "flash_dialog_entry"));

    /* make it so */
    gtk_widget_show_all (GTK_WIDGET (gui->window));
    gtk_widget_hide (GTK_WIDGET (gui->infobar));
    gtk_widget_hide (GTK_WIDGET (gui->progress.bar));

    stlink_gui_set_disconnected (gui);
}

int
main (int argc, char **argv)
{
    STlinkGUI *gui;

    gtk_init (&argc, &argv);

    gui = g_object_new (STLINK_TYPE_GUI, NULL);
    stlink_gui_build_ui (gui);
    stlink_gui_init_dnd (gui);

    gtk_main ();

    return 0;
}
