/** \file   uicart.c
 * \brief   Widget to attach carts
 *
 * \author  Bas Wassink <b.wassink@ziggo.nl>
 */

/*
 * This file is part of VICE, the Versatile Commodore Emulator.
 * See README for copyright notice.
 *
 *  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 2 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, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
 *  02111-1307  USA.
 *
 */

#include "vice.h"
#include <gtk/gtk.h>
#include <string.h>

#include "machine.h"
#include "resources.h"
#include "debug_gtk3.h"
#include "basewidgets.h"
#include "widgethelpers.h"
#include "basedialogs.h"
#include "cartimagewidget.h"
#include "filechooserhelpers.h"
#include "lastdir.h"
#include "log.h"
#include "openfiledialog.h"
#include "savefiledialog.h"
#include "cartridge.h"
#include "vsync.h"
#include "ui.h"
#include "uimachinewindow.h"
#include "crtpreviewwidget.h"

#include "uicart.h"


/** \brief  Enum with various cart types, independent from cartridge.h
 *
 * The define's in cartridge.h don't provide the values I need, so this will
 * have to do.
 */
typedef enum ui_cart_type_e {

    /* C64 cart types */
    UICART_C64_SMART = 0,
    UICART_C64_8KB,
    UICART_C64_16KB,
    UICART_C64_ULTIMAX,
    UICART_C64_FREEZER,
    UICART_C64_GAME,
    UICART_C64_UTIL,

    /* VIC20 cart types */
    UICART_VIC20_SMART,
    UICART_VIC20_BEHRBONZ,
    UICART_VIC20_MEGACART,
    UICART_VIC20_FINALEXP,
    UICART_VIC20_ULTIMEM,
    UICART_VIC20_FLASHPLUGIN,
    UICART_VIC20_GENERIC,
    UICART_VIC20_ADD_GENERIC,
    /* FIXME: add the groups */

    /* Plus4 cart types */
    UICART_PLUS4_SMART,
    UICART_PLUS4_16KB_C1LO,
    UICART_PLUS4_16KB_C1HI,
    UICART_PLUS4_16KB_C2LO,
    UICART_PLUS4_16KB_C2HI,
    UICART_PLUS4_32KB_C1,
    UICART_PLUS4_32KB_C2,
    UICART_PLUS4_FREEZER,
    UICART_PLUS4_GAME,
    UICART_PLUS4_UTIL,

    /* CBM2 cart types */
    /*UICART_CBM2_SMART,*/
    UICART_CBM2_8KB_1000,
    UICART_CBM2_8KB_2000,
    UICART_CBM2_16KB_4000,
    UICART_CBM2_16KB_6000
    /* FIXME: add the groups */

} ui_cart_type_t;


/** \brief  Indici of filename patterns
 */
enum {
    UICART_PATTERN_CRT = 0, /* '*.crt' */
    UICART_PATTERN_BIN,     /* '*.bin' */
    UICART_PATTERN_BIN_PRG, /* '*.bin;*.prg' */
    UICART_PATTERN_ALL      /* '*' */
};


/** \brief  Simple (text,id) data structure for the cart type model
 */
typedef struct cart_type_list_s {
    const char *name;   /**< cartridge name */
    int id;             /**< cartridge id */
} cart_type_list_t;


/** \brief  Available C64 cart types
 *
 * When the 'type' is freezer, games or utilities, a second combo box will
 * be populated with cartridges which fall in that category.
 */
static const cart_type_list_t c64_cart_types[] = {
    { "Smart-attach",   UICART_C64_SMART },
    { "Raw 8KiB",       UICART_C64_8KB },
    { "Raw 16iKB",      UICART_C64_16KB },
    { "Raw Ultimax",    UICART_C64_ULTIMAX },
    { "Freezer",        UICART_C64_FREEZER },
    { "Games",          UICART_C64_GAME },
    { "Utilities",      UICART_C64_UTIL },
    { NULL,             -1 }
};


/** \brief  List of VIC-20 'main' cart types
 *
 * The generic type will use the generic carts list in #vic20_cart_types_generic
 */
static const cart_type_list_t vic20_cart_types[] = {
    { "Smart-attach",               UICART_VIC20_SMART },
    { "Behr Bonz",                  UICART_VIC20_BEHRBONZ },
    { "Mega Cart",                  UICART_VIC20_MEGACART },
    { "Final Expansion",            UICART_VIC20_FINALEXP },
    { "UltiMem",                    UICART_VIC20_ULTIMEM },
    { "Vic Flash Plugin",           UICART_VIC20_FLASHPLUGIN },
    { "Generic",                    UICART_VIC20_GENERIC },
    { "Add to generic cartridge",   UICART_VIC20_ADD_GENERIC },
    /* FIXME: add the groups */
    { NULL, -1 }
};


/** \brief  List of Plus4/C16/C116 cart types
 */
static const cart_type_list_t plus4_cart_types[] = {
    { "Smart-attach",               UICART_PLUS4_SMART },
    { "Raw 16KiB C1 Low",           UICART_PLUS4_16KB_C1LO },
    { "Raw 16KiB C1 High",          UICART_PLUS4_16KB_C1HI },
    { "Raw 16KiB C2 Low",           UICART_PLUS4_16KB_C2LO },
    { "Raw 16KiB C2 High",          UICART_PLUS4_16KB_C2HI },
    { "Raw 32KiB C1",               UICART_PLUS4_32KB_C1 },
    { "Raw 32KiB C2",               UICART_PLUS4_32KB_C2 },
    { "Freezer",                    UICART_PLUS4_FREEZER },
    { "Games",                      UICART_PLUS4_GAME },
    { "Utilities",                  UICART_PLUS4_UTIL },
    { NULL, -1 }
};


/** \brief  List of CBM-II cart types
 */
static const cart_type_list_t cbm2_cart_types[] = {
    /*{ "Smart-attach",               UICART_CBM2_SMART },*/
    { "8KiB at $1000",              UICART_CBM2_8KB_1000 },
    { "8KiB at $2000",              UICART_CBM2_8KB_2000 },
    { "16KiB at $4000",             UICART_CBM2_16KB_4000 },
    { "16KiB at $6000",             UICART_CBM2_16KB_6000 },
    /* FIXME: add the groups */
    { NULL, -1 }
};


/** \brief  List of VIC-20 cart types of the 'generic' variety
 */
static const cart_type_list_t vic20_cart_types_generic[] = {
    { "Smart-attach cartridge image",   CARTRIDGE_VIC20_DETECT },
    { "32KiB cartridge at $2000",       CARTRIDGE_VIC20_32KB_2000 },
    { "4/8/16KiB cartridge at $2000",   CARTRIDGE_VIC20_16KB_2000 },
    { "4/8/16KiB cartridge at $4000",   CARTRIDGE_VIC20_16KB_4000 },
    { "4/8/16KiB cartridge at $6000",   CARTRIDGE_VIC20_16KB_6000 },
    { "4/8KiB cartridge at $A000",      CARTRIDGE_VIC20_8KB_A000 },
    { "4KiB cartridge at $B000",        CARTRIDGE_VIC20_4KB_B000 },
    { NULL, -1 }
};


/** \brief  File filter pattern for CRT images */
static const char *pattern_crt[] = { "*.crt", NULL };


/** \brief  File filter pattern for raw images */
static const char *pattern_bin[] = { "*.bin", NULL };

/** \brief  File filter pattern for raw images */
static const char *pattern_bin_prg[] = { "*.bin", "*.prg", NULL };

/* FIXME: for vic20 we should add .vrt(?), for plus4 .prt(?) */

/** \brief  File type filters for the dialog
 */
static ui_file_filter_t filters[] = {
    { "CRT images", pattern_crt },
    { "Raw images", pattern_bin },
    { "Raw images", pattern_bin_prg },  /* VIC20 */
    { "All files", file_chooser_pattern_all },
    { NULL, NULL }
};


/** \brief  Last used directory
 */
static gchar *last_dir = NULL;

/** \brief  Last used filename
 */
static gchar *last_file = NULL;


/* list of cartridge handling functions (to avoid vsid link errors) */

/** \brief  Machine-specific cart type detection function pointer */
static int  (*crt_detect_func)(const char *filename) = NULL;

/** \brief  Machine-specific cart attach function pointer */
static int  (*crt_attach_func)(int type, const char *filename) = NULL;

/** \brief  Machine-specific cart freeze function pointer */
static void (*crt_freeze_func)(void) = NULL;

/** \brief  Machine-specific cart detach function pointer */
static void (*crt_detach_func)(int type) = NULL;

/** \brief  Machine-specific cart info retrieval function pointer */
static cartridge_info_t *(*crt_list_func)(void) = NULL;

/** \brief  Machine-specific cart set-default function pointer */
static void (*crt_set_default_func)(void) = NULL;

/** \brief  Machine-specific cart unset-default function pointer */
static void (*crt_unset_default_func)(void) = NULL;


/* References to widgets used in various event handlers */

/** \brief  Reference to the cart dialog */
static GtkWidget *cart_dialog = NULL;


/** \brief  Reference to the cart dialog */
static GtkWidget *cart_type_widget = NULL;


/** \brief  Reference to the cart-id widget */
static GtkWidget *cart_id_widget = NULL;

/** \brief  Reference to the cart content 'preview' widget
 *
 * This widget shows the contents of the cart selected in the dialog.
 */
static GtkWidget *cart_preview_widget = NULL;


/** \brief  Reference to the cart-set-default widget */
static GtkWidget *cart_set_default_widget = NULL;


/** \brief  Reference to the cart ID widget */
static GtkWidget *cart_id_label = NULL;

/** \brief  Reference to the dialog file filter showing only .crt images */
 GtkFileFilter *flt_crt = NULL;


/** \brief  Reference to the dialog file filter showing only .bin images */
static GtkFileFilter *flt_bin = NULL;

/** \brief  Reference to the dialog file filter showing .bin + .prg images */
static GtkFileFilter *flt_bin_prg = NULL;

/** \brief  Reference to the dialog file filter showing all files */
static GtkFileFilter *flt_all = NULL;


/* forward declarations of functions */
static GtkListStore *create_cart_id_model(unsigned int flags);
static int get_cart_type(void);
static int get_cart_id(void);
static int attach_cart_image(int type, int id, const char *path);
static GtkListStore *create_cart_id_model_vic20(void);

/** \brief  Optional extra callback
 *
 * This function is triggered when a cartidge is attached.
 */
static void (*extra_attach_callback)(void) = NULL;



/** \brief  Callback for the detach confirm dialog
 *
 * If \a result is TRUE, detach all carts.
 *
 * \param[in]   dialog  dialog reference (unused)
 * \param[in]   result  dialog result
 */
static void uicart_confirm_detach_callback(GtkDialog *dialog, gboolean result)
{
    if (result) {
        crt_unset_default_func();
    }
}


/** \brief  Handler for the "response" event of the dialog
 *
 * \param[in]   dialog      dialog
 * \param[in]   response_id response ID
 * \param[in]   data        extra event data (unused)
 */
static void on_response(GtkWidget *dialog, gint response_id, gpointer data)
{
    gchar *filename;

    switch (response_id) {
        case GTK_RESPONSE_DELETE_EVENT:
            gtk_widget_destroy(dialog);
            break;
        case GTK_RESPONSE_ACCEPT:
            lastdir_update(dialog, &last_dir, &last_file);
            filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
            if (filename != NULL) {
                gchar *filename_locale = file_chooser_convert_to_locale(filename);
                gboolean result;

                result = attach_cart_image(get_cart_type(), get_cart_id(), filename_locale);
                if (!result) {
                    vice_gtk3_message_error("VICE Error",
                            "Failed to smart-attach '%s'", filename);
                } else {
                    /* call optional extra callback */
                    if (extra_attach_callback != NULL) {
                        extra_attach_callback();
                    }
                }
                g_free(filename);
                g_free(filename_locale);

           }
            gtk_widget_destroy(dialog);
            break;
    }

    /* FIXME: hack to avoid the 'normal' dialog (via the menu) triggering the
     *        extra callback meant for the default cart setting page.
     *
     *      Didn't I fix this? (cpx-2021-03-14)
     */
    extra_attach_callback = NULL;
}


/** \brief  Set the file filter pattern for the dialog
 *
 * \param[in]   pattern UICART_PATTERN_\* enum value
 */
static void set_pattern(int pattern)
{
    GtkFileFilter *filter = NULL;

    switch (pattern) {
        case UICART_PATTERN_CRT:
            filter = flt_crt;
            break;
        case UICART_PATTERN_BIN:
            filter = flt_bin;
            break;
        default:
            filter = flt_all;
            break;
    }
    gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(cart_dialog), filter);
}


/** \brief  Handler for the "changed" event of the cart type combo box
 *
 * \param[in]   combo   cart type combo
 * \param[in]   data    extra event data (unused)
 */
static void on_cart_type_changed(GtkComboBox *combo, gpointer data)
{
    GtkListStore *id_model;     /* cart 'ID' model */
    unsigned int mask = ~0;
    int pattern = UICART_PATTERN_BIN;
    int crt_type;

    crt_type = get_cart_type();
    if (crt_type < 0) {
        return;
    }

    switch (machine_class) {
        case VICE_MACHINE_C64:      /* fall through */
        case VICE_MACHINE_C64SC:    /* fall through */
        case VICE_MACHINE_C128:     /* fall through */
        case VICE_MACHINE_SCPU64:   /* fall through */
        case VICE_MACHINE_PLUS4:
            switch (crt_type) {
                case UICART_C64_SMART:      /* fall through */
                case UICART_PLUS4_SMART:
                    pattern = UICART_PATTERN_CRT;
                    break;
                case UICART_C64_FREEZER:    /* fall through */
                case UICART_PLUS4_FREEZER:
                    mask = CARTRIDGE_GROUP_FREEZER;
                    break;
                case UICART_C64_GAME:       /* fall through */
                case UICART_PLUS4_GAME:
                    mask = CARTRIDGE_GROUP_GAME;
                    break;
                case UICART_C64_UTIL:       /* fall through */
                case UICART_PLUS4_UTIL:
                    mask = CARTRIDGE_GROUP_UTIL;
                    break;
                default:
                    mask = 0x0;
                    break;
            }

            /* update cart ID model and set it */
            id_model = create_cart_id_model(mask);
            gtk_combo_box_set_model(GTK_COMBO_BOX(cart_id_widget), GTK_TREE_MODEL(id_model));
            gtk_combo_box_set_active(GTK_COMBO_BOX(cart_id_widget), 0);
            /* only show cart ID list when needed */
            if ((pattern == UICART_PATTERN_CRT) || (mask == 0x0)) {
                gtk_widget_hide(GTK_WIDGET(cart_id_widget));
                gtk_widget_hide(GTK_WIDGET(cart_id_label));
            } else {
                gtk_widget_show(GTK_WIDGET(cart_id_widget));
                gtk_widget_show(GTK_WIDGET(cart_id_label));
            }

            /* update filename pattern */
            set_pattern(pattern);

            break;
        case VICE_MACHINE_VIC20:
            if ((crt_type == UICART_VIC20_GENERIC) ||
                (crt_type == UICART_VIC20_ADD_GENERIC)) {
                id_model = create_cart_id_model_vic20();
                /* gtk_widget_set_sensitive(cart_id_widget, TRUE); */
                gtk_widget_show(GTK_WIDGET(cart_id_widget));
                gtk_widget_show(GTK_WIDGET(cart_id_label));
            } else {
                /* empty model */
                id_model = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_INT);
                /* gtk_widget_set_sensitive(cart_id_widget, FALSE); */
                gtk_widget_hide(GTK_WIDGET(cart_id_widget));
                gtk_widget_hide(GTK_WIDGET(cart_id_label));
            }
            gtk_combo_box_set_model(GTK_COMBO_BOX(cart_id_widget), GTK_TREE_MODEL(id_model));
            gtk_combo_box_set_active(GTK_COMBO_BOX(cart_id_widget), 0);

            break;

        default:
            break;
    }
}



/** \brief  Get the ID of the model for the 'cart type' combo box
 *
 * \return  ID or -1 on error
 */
static int get_cart_type(void)
{
    GtkTreeModel *model;
    GtkTreeIter iter;
    GtkComboBox *combo;
    int crt_type = -1;

    combo = GTK_COMBO_BOX(cart_type_widget);

    if (gtk_combo_box_get_active(combo) >= 0) {
        model = gtk_combo_box_get_model(combo);
        if (gtk_combo_box_get_active_iter(combo, &iter)) {
            gtk_tree_model_get(model, &iter, 1, &crt_type, -1);
        }
    }
    return crt_type;
}



/** \brief  Get the ID of the model for the 'cart ID' combo box
 *
 * \return  ID or -1 on error
 */
static int get_cart_id(void)
{
    GtkTreeModel *model;
    GtkTreeIter iter;
    GtkComboBox *combo;
    int crt_id = -1;

    if (cart_id_widget == NULL) {
        return crt_id;
    }

    combo = GTK_COMBO_BOX(cart_id_widget);
    if (gtk_combo_box_get_active(combo) >= 0) {
        model = gtk_combo_box_get_model(combo);
        if (gtk_combo_box_get_active_iter(combo, &iter)) {
            gtk_tree_model_get(model, &iter, 1, &crt_id, -1);
        }
    }
    return crt_id;
}


/** \brief  Cart attach handler
 *
 * \param[in]   type    cartridge type
 * \param[in]   id      cartridge ID
 * \param[in]   path    path to cartrige image
 *
 * \return  bool
 */
static int attach_cart_image(int type, int id, const char *path)
{
    /* printf("attach_cart_image type: %d id: %d path: %s\n", type, id, path); */
    switch (machine_class) {
        case VICE_MACHINE_C64:      /* fall through */
        case VICE_MACHINE_C64SC:    /* fall through */
        case VICE_MACHINE_C128:     /* fall through */
        case VICE_MACHINE_SCPU64:
            switch (type) {
                case UICART_C64_SMART:
                    id = CARTRIDGE_CRT;
                    break;
                case UICART_C64_FREEZER:    /* fall through */
                case UICART_C64_GAME:       /* fall through */
                case UICART_C64_UTIL:
                    /* id is correct */
                    break;
                default:
                    debug_gtk3("error: shouldn't get here.");
                    break;
            }
            break;

        case VICE_MACHINE_VIC20:
            switch (type) {
                case UICART_VIC20_SMART:
                    /* id = CARTRIDGE_VIC20_DETECT; */
                    id = CARTRIDGE_CRT;
                    break;
                case UICART_VIC20_GENERIC:
                    /* we also want to select an id for generic type. some day
                       we need to fix "generic" vs "add to generic" */
                    /* id = CARTRIDGE_VIC20_GENERIC; */
                    /* id is correct */
                case UICART_VIC20_ADD_GENERIC:
                    /* id is correct */
                    break;
                case UICART_VIC20_BEHRBONZ:
                    id = CARTRIDGE_VIC20_BEHRBONZ;
                    break;
               case UICART_VIC20_MEGACART:
                    id = CARTRIDGE_VIC20_MEGACART;
                    break;
               case UICART_VIC20_FINALEXP:
                    id = CARTRIDGE_VIC20_FINAL_EXPANSION;
                    break;
               case UICART_VIC20_ULTIMEM:
                    id = CARTRIDGE_VIC20_UM;
                    break;
               case UICART_VIC20_FLASHPLUGIN:
                    id = CARTRIDGE_VIC20_FP;
                    break;
                /* FIXME: add groups */
                default:
                    debug_gtk3("error: shouldn't get here.");
                    break;
            }
            break;

        case VICE_MACHINE_PLUS4:
            switch (type) {
                case UICART_PLUS4_SMART:
                    id = CARTRIDGE_CRT;
                    break;
                case UICART_PLUS4_16KB_C1LO:
                    id = CARTRIDGE_PLUS4_GENERIC_C1LO;
                    break;
                case UICART_PLUS4_16KB_C1HI:
                    id = CARTRIDGE_PLUS4_GENERIC_C1HI;
                    break;
                case UICART_PLUS4_16KB_C2LO:
                    id = CARTRIDGE_PLUS4_GENERIC_C2LO;
                    break;
                case UICART_PLUS4_16KB_C2HI:
                    id = CARTRIDGE_PLUS4_GENERIC_C2HI;
                    break;
                case UICART_PLUS4_32KB_C1:
                    id = CARTRIDGE_PLUS4_GENERIC_C1;
                    break;
                case UICART_PLUS4_32KB_C2:
                    id = CARTRIDGE_PLUS4_GENERIC_C2;
                    break;
                case UICART_PLUS4_FREEZER:    /* fall through */
                case UICART_PLUS4_GAME:       /* fall through */
                case UICART_PLUS4_UTIL:
                    /* id is correct */
                    break;
                default:
                    /* oops */
                    debug_gtk3("error: shouldn't get here.");
                    break;
            }
            break;

        case VICE_MACHINE_CBM5x0:   /* fall through */
        case VICE_MACHINE_CBM6x0:
            switch (type) {
                /*case UICART_CBM2_SMART:
                    return (crt_attach_func(CARTRIDGE_CBM2_DETECT, path) == 0);*/
                case UICART_CBM2_8KB_1000:
                    id = CARTRIDGE_CBM2_8KB_1000;
                    break;
                case UICART_CBM2_8KB_2000:
                    id = CARTRIDGE_CBM2_8KB_2000;
                    break;
                case UICART_CBM2_16KB_4000:
                    id = CARTRIDGE_CBM2_16KB_4000;
                    break;
                case UICART_CBM2_16KB_6000:
                    id = CARTRIDGE_CBM2_16KB_6000;
                    break;
                /* FIXME: add groups */
                default:
                    /* oops */
                    debug_gtk3("error: shouldn't get here.");
                    break;
            }
            break;

        default:
            debug_gtk3("very oops: type = %d, id = %d, path = '%s'.",
                    type, id, path);
            return 0;
            break;
    }
    /* printf("id:%d path:%s\n", id, path); */
    if ((crt_attach_func(id, path) == 0)) {
        /* check 'set default' */
        if ((cart_set_default_widget != NULL)
                & (gtk_toggle_button_get_active(
                        GTK_TOGGLE_BUTTON(cart_set_default_widget)))) {
            /* set cart as default, there's no return value, so let's assume
             * this works */
            crt_set_default_func();
        }
        return 1;
    }
    return 0;
}



/** \brief  Create model for the 'cart type' combo box
 *
 * This depends on the `machine_class`, so for some machines, this may return
 * an empty (useless) model
 *
 * \return  model
 */
static GtkListStore *create_cart_type_model(void)
{
    GtkListStore *model;
    GtkTreeIter iter;
    const cart_type_list_t *types;
    int i;

    model = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_INT);
    switch (machine_class) {
        case VICE_MACHINE_C64:      /* fall through */
        case VICE_MACHINE_C64SC:    /* fall through */
        case VICE_MACHINE_C128:     /* fall through */
        case VICE_MACHINE_SCPU64:
            types = c64_cart_types;
            break;
        case VICE_MACHINE_VIC20:
            types = vic20_cart_types;
            break;
        case VICE_MACHINE_PLUS4:
            types = plus4_cart_types;
            break;
        case VICE_MACHINE_CBM5x0:
        case VICE_MACHINE_CBM6x0:
            types = cbm2_cart_types;
            break;
        default:
            return model;
    }

    for (i = 0; types[i].name != NULL; i++) {
        gtk_list_store_append(model, &iter);
        gtk_list_store_set(model, &iter, 0, types[i].name, 1, types[i].id, -1);
    }
    return model;
}


/** \brief  Create a list of cartridges, filtered with \a flags
 *
 * Only valid for c64/c128/scpu
 *
 * \param[in]   flags   flags determining what cart types to use
 *
 * \return  Three-column list store (name, crtid, flags)
 */
static GtkListStore *create_cart_id_model(unsigned int flags)
{
    GtkListStore *model;
    GtkTreeIter iter;
    cartridge_info_t *list;
    int i;

    model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_UINT);

    if (crt_list_func != NULL) {
        list = crt_list_func();
    } else {
        return model;
    }


    for (i = 0; list[i].name != NULL; i++) {
        if (list[i].flags & flags) {
            gtk_list_store_append(model, &iter);
            gtk_list_store_set(model, &iter,
                    0, list[i].name,        /* cart name */
                    1, list[i].crtid,       /* cart ID */
                    2, list[i].flags,       /* cart flags */
                    -1);
        }
    }
    return model;
}


/** \brief  Create a list of cartridges for VIC-20
 *
 * Only valid for VIC-20
 *
 * \return  Two-column list store (name, id)
 */
static GtkListStore *create_cart_id_model_vic20(void)
{
    GtkListStore *model;
    GtkTreeIter iter;
    int i;

    model = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_INT);

    for (i = 0; vic20_cart_types_generic[i].name != NULL; i++) {
        gtk_list_store_append(model, &iter);
        gtk_list_store_set(model, &iter,
                0, vic20_cart_types_generic[i].name,    /* cart name */
                1, vic20_cart_types_generic[i].id,      /* cart ID */
                -1);
    }
    return model;
}


/** \brief  Create combo box with main cartridge types
 *
 * \return  GtkComboBox
 */
static GtkWidget *create_cart_type_combo_box(void)
{
    GtkWidget *combo;
    GtkListStore *model;
    GtkCellRenderer *renderer;

    model = create_cart_type_model();
    if (model == NULL) {
        return gtk_combo_box_new();
    }
    combo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(model));
    g_object_unref(model);

    renderer = gtk_cell_renderer_text_new();
    gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, TRUE);
    gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo), renderer,
            "text", 0, NULL);

    gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);

    g_signal_connect(combo, "changed", G_CALLBACK(on_cart_type_changed), NULL);
    return combo;
}


/** \brief  Create combo box with cartridges with adhere to \a mask
 *
 * \param[in]   mask    bitmask to filter cartridges
 *
 * \return  GtkComboBox
 *
 * \note    Only for x64/x64sc/xscp64/x128/plus4
 */
static GtkWidget *create_cart_id_combo_box(unsigned int mask)
{
    GtkWidget *combo;
    GtkListStore *model;
    GtkCellRenderer *renderer;

    model = create_cart_id_model(mask);
    if (model == NULL) {
        return gtk_combo_box_new();
    }
    combo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(model));
    g_object_unref(model);

    renderer = gtk_cell_renderer_text_new();
    gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, TRUE);
    gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo), renderer,
            "text", 0, NULL);

    gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
    return combo;
}


/** \brief  Create combo box with generic VIC-20 cartridges
 *
 * \return  GtkComboBox
 */
static GtkWidget *create_cart_id_combo_box_vic20(void)
{
    GtkWidget *combo;
    GtkListStore *model;
    GtkCellRenderer *renderer;

    model = create_cart_id_model_vic20();
    if (model == NULL) {
        return gtk_combo_box_new();
    }
    combo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(model));
    g_object_unref(model);

    renderer = gtk_cell_renderer_text_new();
    gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, TRUE);
    gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo), renderer,
            "text", 0, NULL);

    gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
    return combo;
}


/** \brief  Create the 'extra' widget for the dialog
 *
 * \param[in]   set_default initial state of the 'set as default' checkbox
 *
 * \return  GtkGrid
 */
static GtkWidget *create_extra_widget(gboolean set_default)
{
    GtkWidget *grid;
    GtkWidget *label;

    grid = gtk_grid_new();
    gtk_grid_set_column_spacing(GTK_GRID(grid), 16);
    gtk_grid_set_row_spacing(GTK_GRID(grid), 8);

    label = gtk_label_new("cartridge type");
    gtk_widget_set_halign(label, GTK_ALIGN_START);
    cart_type_widget = create_cart_type_combo_box();
    gtk_grid_attach(GTK_GRID(grid), label, 0, 0, 1, 1);
    gtk_grid_attach(GTK_GRID(grid), cart_type_widget, 1, 0, 1, 1);

    /* create "Set cartridge as default" check button */
    switch (machine_class) {
        case VICE_MACHINE_C64:      /* fall through */
        case VICE_MACHINE_C64SC:    /* fall through */
        case VICE_MACHINE_SCPU64:   /* fall through */
        case VICE_MACHINE_PLUS4:    /* fall through */
        case VICE_MACHINE_VIC20:
            cart_set_default_widget = gtk_check_button_new_with_label(
                    "Set cartridge as default");
            gtk_toggle_button_set_active(
                    GTK_TOGGLE_BUTTON(cart_set_default_widget), set_default);

            gtk_grid_attach(GTK_GRID(grid), cart_set_default_widget, 0, 1, 4, 1);
            break;
        default:
            /* Set cart as default is not supported for the current machine */
            break;
    }

    /* only for c64/c128/plus4 */
    switch (machine_class) {
        case VICE_MACHINE_C64:      /* fall through */
        case VICE_MACHINE_C64SC:    /* fall through */
        case VICE_MACHINE_C128:     /* fall through */
        case VICE_MACHINE_SCPU64:   /* fall through */
        case VICE_MACHINE_PLUS4:

            cart_id_label = gtk_label_new("cartridge ID");
            gtk_widget_set_halign(cart_id_label, GTK_ALIGN_START);
            cart_id_widget = create_cart_id_combo_box(0x0);
            gtk_grid_attach(GTK_GRID(grid), cart_id_label, 2, 0, 1, 1);
            gtk_grid_attach(GTK_GRID(grid), cart_id_widget, 3, 0, 1, 1);
            break;
        case VICE_MACHINE_VIC20:
            cart_id_label = gtk_label_new("cartridge class");
            gtk_widget_set_halign(cart_id_label, GTK_ALIGN_START);
            cart_id_widget = create_cart_id_combo_box_vic20();
            gtk_grid_attach(GTK_GRID(grid), cart_id_label, 2, 0, 1, 1);
            gtk_grid_attach(GTK_GRID(grid), cart_id_widget, 3, 0, 1, 1);
            break;

        default:
            break;
    }

    gtk_widget_show_all(grid);
    return grid;
}


/** \brief  Create the 'preview' widget for the dialog
 *
 * \return  GtkGrid
 */
static GtkWidget *create_preview_widget(void)
{
    if ((machine_class != VICE_MACHINE_C64)
            && (machine_class != VICE_MACHINE_C64SC)
            && (machine_class != VICE_MACHINE_C128)) {
        GtkWidget *grid = NULL;
        GtkWidget *label;
        grid = gtk_grid_new();
        gtk_grid_set_column_spacing(GTK_GRID(grid), 16);
        gtk_grid_set_row_spacing(GTK_GRID(grid), 8);

        label = gtk_label_new(NULL);
        gtk_label_set_markup(GTK_LABEL(label), "<b>Cartridge info</b>");
        gtk_grid_attach(GTK_GRID(grid), label, 0, 0, 1, 1);

        label = gtk_label_new("Error: groepaz was here!");
        g_object_set(label, "margin-left", 16, NULL);
        gtk_grid_attach(GTK_GRID(grid), label, 0, 1, 1, 1);

        gtk_widget_show_all(grid);
        return grid;
    } else {
        return crt_preview_widget_create();
    }

}


/** \brief  Update the 'preview' widget for the dialog
 *
 * \param[in,out]   file_chooser    GtkFileChooser instance
 * \param[in]       data            extra event data (unused)
 *
 * \return  GtkGrid
 */
static void  update_preview(GtkFileChooser *file_chooser, gpointer data)
{
    gchar *path = NULL;

    path = gtk_file_chooser_get_filename(file_chooser);
    if (path != NULL) {
        gchar *path_locale = file_chooser_convert_to_locale(path);
        crt_preview_widget_update(path_locale);
        g_free(path);
        g_free(path_locale);
    }
}


/** \brief  Set function to get a list of cartridges
 *
 * \param[in]   func    list function
 */
void ui_cart_set_list_func(cartridge_info_t *(*func)(void))
{
    crt_list_func = func;
}




/** \brief  Set function to detect a cartridge's type
 *
 * Appears to be CBM2/Plus4 only
 *
 * \param[in]   func    detect function
 */
void ui_cart_set_detect_func(int (*func)(const char *))
{
    crt_detect_func = func;
}


/** \brief  Set function to attach a cartridge image
 *
 * \param[in]   func    attach function
 */
void ui_cart_set_attach_func(int (*func)(int, const char *))
{
    crt_attach_func = func;
}


/** \brief  Set function to trigger a cartridge freeze-button click
 *
 * \param[in]   func    freeze function
 */
void ui_cart_set_freeze_func(void (*func)(void))
{
    crt_freeze_func = func;
}


/** \brief  Set function to detach a/all cartridges
 *
 * \param[in]   func    freeze function
 */
void ui_cart_set_detach_func(void (*func)(int))
{
    crt_detach_func = func;
}


/** \brief  Set function to set active cart as default
 *
 * \param[in]   func    default func
 */
void ui_cart_set_set_default_func(void (*func)(void))
{
    crt_set_default_func = func;
}

/** \brief  Set function to set active cart as default
 *
 * \param[in]   func    default func
 */
void ui_cart_set_unset_default_func(void (*func)(void))
{
    crt_unset_default_func = func;
}


/** \brief  Trigger cartridge freeze
 *
 * Called from the menu
 *
 *
 * \return  TRUE
 */
gboolean ui_cart_trigger_freeze(void)
{
    if (crt_freeze_func != NULL) {
        crt_freeze_func();
    }
    return TRUE;
}


/** \brief  Detach all cartridge images
 *
 * TODO: The question about removing the default enabled cartridge doesn't
 *       work for carts not on "slot 1", these seem to their own "enabled"
 *       resources and do not use the CartridgeType/CartridgeFile resources.
 *
 * \return  TRUE
 */
gboolean ui_cart_detach(void)
{
    int cartid = CARTRIDGE_NONE;

    if (crt_detach_func != NULL) {

        /* determine if one of the attached cartridges is set as default
           cartridge, and if so ask if it should be removed from default also */

        /* first check if the set_default and unset_default functions exist. some
           emulators do not have this feature, in this case we just use detach */
        if (crt_set_default_func != NULL) {
            if (crt_unset_default_func != NULL) {
                /* when both functions exist, check if the default is actually set */
                /* FIXME: perhaps we should have a dedicated function for getting the,
                          id of slot1 default cart, however this will work for a start */
                resources_get_int("CartridgeType", &cartid);
                if (cartid != CARTRIDGE_NONE) {
                    /* default is set, ask to remove it */
                    vice_gtk3_message_confirm(
                            uicart_confirm_detach_callback,
                            "Detach cartridge",
                            "You're detaching the default cartridge.\n\n"
                            "Would you also like to unregister this cartridge"
                            " as the default cartridge?");
#if 0
                    if (result) {
                        crt_unset_default_func();
                    }
#endif
                }
                /* FIXME: the above will only check/ask for "slot1" cartridges. other
                          cartridges have seperate "enable" resources which would
                          have to be checked individually. perhaps make a dedicated
                          function for this later */
            } else {
                log_message(LOG_DEFAULT, "FIXME: cartridge_set_default exists, but cartridge_unset_default is not implemented");
            }
        }

        crt_detach_func(-1);    /* detach all cartridges */
    }
    return TRUE;
}



/** \brief  Create attach dialog
 *
 * \param[in]   widget          parent widget
 * \param[in]   set_as_default  initial state of the 'set as default' checkbox
 * \param[in]   callback        extra callback when attach succeeds
 *
 * \return  GtkDialog
 */
static GtkWidget *cart_dialog_internal(GtkWidget *widget,
                                       gboolean set_as_default,
                                       void (*callback)(void))
{
    GtkWidget *dialog;

    dialog = gtk_file_chooser_dialog_new(
            "Attach a cartridge image",
            ui_get_active_window(),
            GTK_FILE_CHOOSER_ACTION_OPEN,
            /* buttons */
            "Attach", GTK_RESPONSE_ACCEPT,
            "Close", GTK_RESPONSE_DELETE_EVENT,
            NULL, NULL);

    /* set modal so mouse-grab doesn't get triggered */
    gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);

    /* set last directory */
    lastdir_set(dialog, &last_dir, &last_file);

    /* add extra widget */
    gtk_file_chooser_set_extra_widget(GTK_FILE_CHOOSER(dialog),
            create_extra_widget(set_as_default));

    /* add preview widget */
    cart_preview_widget = create_preview_widget();
    gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(dialog),
            cart_preview_widget);
    gtk_file_chooser_set_use_preview_label(GTK_FILE_CHOOSER(dialog), FALSE);

    /* add filters */
    flt_crt = create_file_chooser_filter(filters[UICART_PATTERN_CRT], FALSE);
    flt_bin = create_file_chooser_filter(filters[UICART_PATTERN_BIN], FALSE);
    flt_bin_prg = create_file_chooser_filter(filters[UICART_PATTERN_BIN_PRG], FALSE);
    flt_all = create_file_chooser_filter(filters[UICART_PATTERN_ALL], TRUE);

    switch (machine_class) {
        case VICE_MACHINE_C64:      /* fall through */
        case VICE_MACHINE_C64SC:    /* fall through */
        case VICE_MACHINE_C128:     /* fall through */
        case VICE_MACHINE_SCPU64:   /* fall through */
        case VICE_MACHINE_PLUS4:
            gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), flt_crt);
            gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), flt_bin);
            gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), flt_all);
            break;
        case VICE_MACHINE_VIC20:
            gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), flt_crt);
            gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), flt_bin_prg);
            gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), flt_all);
            break;
        default:
            break;
    }

    extra_attach_callback = callback;
    cart_dialog = dialog;

    g_signal_connect(dialog, "response", G_CALLBACK(on_response), NULL);
    g_signal_connect(dialog, "update-preview", G_CALLBACK(update_preview), NULL);

    /* those should be hidden by default */
    if (cart_id_label) {
        gtk_widget_hide(cart_id_label);
    }
    if (cart_id_widget) {
        gtk_widget_hide(cart_id_widget);
    }

    return dialog;
}


/** \brief  Pop up the cart-attach dialog
 *
 * \param[in]   widget  parent widget (unused)
 * \param[in]   data    initial state of the 'set as default' checkbox
 *
 * \return  TRUE
 */
gboolean ui_cart_show_dialog(GtkWidget *widget, gpointer data)
{
    GtkWidget *dialog;

    dialog = cart_dialog_internal(widget, GPOINTER_TO_INT(data), NULL);

    gtk_widget_show(dialog);
    return TRUE;
}


/** \brief  Attach dialog for the settings->default cart page
 *
 * \param[in]   widget      parent widget (unused)
 * \param[in]   callback    function to call on succesfull attach
 *
 */
void ui_cart_default_attach(GtkWidget *widget, void (*callback)(void))
{
    GtkWidget *dialog;

    dialog = cart_dialog_internal(widget, GPOINTER_TO_INT(1), callback);
    gtk_widget_show(dialog);
}


/** \brief  Clean up the last directory string
 */
void ui_cart_shutdown(void)
{
    lastdir_shutdown(&last_dir, &last_file);
}
