/*
 * Copyright (c) 2021-2025 Rupert Carmichael
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include <stdio.h>
#include <stdint.h>
#include <string.h>

#include <jg/jg.h>
#include <jg/jg_gg.h>
#include <jg/jg_md.h>
#include <jg/jg_sms.h>
#if JG_VERSION_NUMBER < 10100
#include "jg_compat.h"
#endif

#include "cega.h"
#include "cega_db.h"
#include "cega_md.h"
#include "cega_md_io.h"
#include "cega_mixer.h"
#include "cega_sg.h"
#include "cega_sms.h"
#include "cega_sms_io.h"
#include "cega_z80.h"
#include "mdvdp.h"
#include "smsvdp.h"
#include "version.h"

#define SAMPLERATE 48000
#define FRAMERATE 60
#define FRAMERATE_PAL 50
#define ASPECT_PAL 1.4257812
#define NUMINPUTS NUMINPUTS_MD // Maximum number of input devices
#define TOTALINPUTS 12 // 7 SMS/SG + 1 GG + 4 MD

static jg_cb_audio_t jg_cb_audio;
static jg_cb_frametime_t jg_cb_frametime;
static jg_cb_log_t jg_cb_log;
static jg_cb_rumble_t jg_cb_rumble;

#if JG_VERSION_NUMBER >= 10100
static void *udata_audio;
static void *udata_frametime;
static void *udata_rumble;
#endif

static jg_coreinfo_t coreinfo_gg = {
    "cega", "Cega", JG_VERSION, "gg", 1, 0x00
};

static jg_coreinfo_t coreinfo_md = {
    "cega", "Cega", JG_VERSION, "md", NUMINPUTS_MD, 0x00
};

static jg_coreinfo_t coreinfo_sg = {
    "cega", "Cega", JG_VERSION, "sg", NUMINPUTS_SMS, 0x00
};

static jg_coreinfo_t coreinfo_sms = {
    "cega", "Cega", JG_VERSION, "sms", NUMINPUTS_SMS, 0x00
};

static jg_coreinfo_t *coreinfo = &coreinfo_sms;

static jg_videoinfo_t vidinfo;
static jg_audioinfo_t audinfo;
static jg_pathinfo_t pathinfo;
static jg_fileinfo_t gameinfo;
static jg_fileinfo_t biosinfo;
static jg_inputinfo_t inputinfo[NUMINPUTS];
static jg_inputinfo_t inputlist[TOTALINPUTS];
static jg_inputstate_t *input_device[NUMINPUTS];

// Emulator settings
static jg_setting_t settings_cega[] = {
    { "port1_sms", "SMS Control Port 1",
      "0 = Auto, 1 = SMS Control Pad, 2 = Paddle, 3 = Light Phaser",
      "Select the device plugged into controller port 1",
      0, 0, 3, JG_SETTING_INPUT
    },
    { "port2_sms", "SMS Control Port 2",
      "0 = Auto, 1 = SMS Control Pad, 2 = Paddle, 3 = Light Phaser",
      "Select the device plugged into controller port 2",
      0, 0, 3, JG_SETTING_INPUT
    },
    { "smsbios", "SMS BIOS",
      "0 = Disable, 1 = Enable",
      "Choose to use the SMS BIOS",
      0, 0, 1, JG_SETTING_RESTART
    },
    { "fmaudio", "SMS FM Audio",
      "0 = Disable, 1 = Enable",
      "Use the YM2413 FM Synthesizer in supported games",
      1, 0, 1, JG_SETTING_RESTART
    },
    { "smspalette", "SMS Mode 0-3 Palette",
      "0 = SMS, 1 = TeaTime",
      "Use either the accurate SMS or better looking SG-1000 palette "
      "(TeaTime) for SMS games when using Video Modes 0-3",
      0, 0, 1, 0
    },
    { "mdcolramp", "MD Colour Ramp",
      "0 = Hardware Accurate, 1 = Umbrage",
      "Hardware Accurate or Linear (Umbrage) Colour Ramp for Mega Drive",
      0, 0, 1, 0
    },
    { "rsqual", "Resampler Quality",
      "N = Resampler Quality",
      "Quality level for the internal resampler",
      3, 0, 10, JG_SETTING_RESTART
    },
    { "region", "Region",
      "0 = Auto, 1 = Japan, 2 = Americas, 3 = PAL",
      "Set the region to use when there is no internal database entry",
       REGION_US, 0, REGION_EU, JG_SETTING_RESTART
    }
};

enum {
    PORT1_SMS,
    PORT2_SMS,
    BIOS,
    FMAUDIO,
    PALETTE,
    COLRAMP,
    RSQUAL,
    REGION,
};

static jg_biosinfo_t bioslist[] = {
    { "bios.sms", "Alex Kidd in Miracle World (Korea) (Unl)",
      "e49e63328c78fe3d24ae32bf903583e0", 0, 1 },
    { "bios.sms", "Alex Kidd in Miracle World (USA, Europe)",
      "e8b26871629b938887757a64798df6dc", 0, 1 },
    { "bios.sms", "Hang On & Safari Hunt (USA, Europe, Brazil) (v2.4)",
      "02cbb2e348945c9ac41e37502a58ca76", 0, 1 },
    { "bios.sms", "Hang On (USA, Europe) (v3.4)",
      "5cd8f62cd8786af0226e6d2248279338", 0, 1 },
    { "bios.sms", "Missile Defense 3-D (USA, Europe) (v4.4)",
      "08b81aa6be18b92daef1b875deecf824", 0, 1 },
    { "bios.sms", "Sega Master System (Europe) (v2.0)",
      "bdb34f2c471e5d0c358eeec621f9d029", 0, 1 },
    { "bios.sms", "Sega Master System (Japan) (v2.1)",
      "24a519c53f67b00640d0048ef7089105", 0, 1 },
    { "bios.sms", "Sega Master System (USA) (M404) (Proto)",
      "3153447d155fb8bfc66a4d1c41e399b9", 0, 1 },
    { "bios.sms", "Sega Master System (USA) (Store Display Unit)",
      "b264bef9bda264ffe83afcebac21b81f", 0, 1 },
    { "bios.sms", "Sega Master System (USA) (v1.0) (Proto)",
      "626b3453613e43d5a47908a8944ba583", 0, 1 },
    { "bios.sms", "Sega Master System (USA, Europe) (v1.3)",
      "840481177270d5642a14ca71ee72844c", 0, 1 },
    { "bios.sms", "Sonic The Hedgehog (Europe)",
      "4187d96beaf36385e681a3cf3bd1663d", 0, 1 },
    { "bios.gg", "Sega Game Gear (USA) (Majesco)",
      "672e104c3be3a238301aceffc3b23fd6", 0, 0 },
};

static jg_systeminfo_t systemlist[] = {
    { "sg", "Sega SG-1000", "sg" },
    { "sms", "Sega Master System", "sms" },
    { "gg", "Sega Game Gear", "gg" },
    { "md", "Sega Mega Drive/Genesis", "md,gen" },
};

// Keep track of whether the input list has been built or not
static int inputlist_built = 0;

// System being emulated
static int sys = 0;

// Database flags
static uint32_t dbflags = 0;

/*
 * SG-1000 and SMS
 */
static uint8_t cega_input_poll_sms(int port) {
    uint8_t b = 0x00;
    if (input_device[port]->button[0]) b |= SMS_INPUT_U;
    if (input_device[port]->button[1]) b |= SMS_INPUT_D;
    if (input_device[port]->button[2]) b |= SMS_INPUT_L;
    if (input_device[port]->button[3]) b |= SMS_INPUT_R;
    if (input_device[port]->button[4]) b |= SMS_INPUT_1;
    if (input_device[port]->button[5]) b |= SMS_INPUT_2;
    return b;
}

static uint8_t cega_input_poll_smssys(int port) {
    uint8_t b = 0x00;
    if (input_device[port]->button[0]) b |= SMS_INPUT_RESET;
    if (input_device[port]->button[1]) b |= SMS_INPUT_PAUSE;
    return b;
}

static uint8_t cega_input_poll_paddle(int port) {
    if (port < 2)
        return ((input_device[port]->axis[0] + 32768) >> 8);

    return input_device[port - 2]->button[0];
}

static uint8_t cega_input_poll_phaser(int port) {
    uint8_t b = 0x00;

    if (input_device[port]->button[0]) {
        b |= SMS_INPUT_PHASER;
        smsvdp_phaser_coords(input_device[port]->coord[0],
            input_device[port]->coord[1]);
    }
    else if (input_device[port]->button[1]) {
        b |= SMS_INPUT_PHASER;
        smsvdp_phaser_coords(350, 350); // Offscreen coordinates
    }

    return b;
}

// Terebi Oekaki
static uint8_t cega_input_poll_terebi(uint8_t t) {
    switch (t) {
        case SG_TEREBI_X: return input_device[0]->coord[0] > 252 ?
            252 : input_device[0]->coord[0];
        case SG_TEREBI_Y: return input_device[0]->coord[1] + 31;
    }
    return input_device[0]->button[0];
}

static void cega_input_setup_sg_sms(void) {
    int port[2] = {
        settings_cega[PORT1_SMS].val,
        settings_cega[PORT2_SMS].val
    };

    for (int i = 0; i < 2; ++i) {
        // If set to Auto, use database to set input device
        if (port[i] == 0) {
            if (dbflags & SMS_DB_PADDLE) {
                port[i] = SMS_INPUT_TYPE_PADDLE;
            }
            else if (dbflags & SMS_DB_PHASER) {
                port[i] = i ? SMS_INPUT_TYPE_PAD : SMS_INPUT_TYPE_PHASER;
            }
            else if (dbflags & SMS_DB_SPORTS) {
                if (i) // Only print this once...
                    jg_cb_log(JG_LOG_WRN, "Sports Pad Unsupported\n");
                port[i] = SMS_INPUT_TYPE_PAD;
            }
        }

        // Connect the devices
        switch (port[i]) {
            default: case 0: case SMS_INPUT_TYPE_PAD: {
                cega_input_set_callback(i, &cega_input_poll_sms);
                inputinfo[i] = jg_sms_inputinfo(i, JG_SMS_PAD);
                cega_sms_io_set_port(i, SMS_INPUT_TYPE_PAD);
                break;
            }
            case SMS_INPUT_TYPE_PADDLE: {
                cega_input_set_callback(i, &cega_input_poll_paddle);
                inputinfo[i] = jg_sms_inputinfo(i, JG_SMS_PADDLE);
                cega_sms_io_set_port(i, SMS_INPUT_TYPE_PADDLE);
                break;
            }
            case SMS_INPUT_TYPE_PHASER: {
                cega_input_set_callback(i, &cega_input_poll_phaser);
                inputinfo[i] = jg_sms_inputinfo(i, JG_SMS_LIGHTPHASER);
                cega_sms_io_set_port(i, SMS_INPUT_TYPE_PHASER);
                break;
            }
        }
    }

    // Terebi Oekaki is a special case
    if ((sys == SYSTEM_SG) && (dbflags & SG_DB_TEREBI)) {
        inputinfo[0] = jg_sms_inputinfo(0, JG_SMS_TEREBIOEKAKI);
        inputinfo[1] = jg_sms_inputinfo(1, JG_SMS_UNCONNECTED);
        cega_sg_terebi_set_callback(cega_input_poll_terebi);
        cega_sms_io_set_port(0, SMS_INPUT_TYPE_NONE);
        cega_sms_io_set_port(1, SMS_INPUT_TYPE_NONE);
    }

    // Always assign the System's buttons
    inputinfo[2] = jg_sms_inputinfo(2, JG_SMS_SYS);
    cega_input_set_callback(2, &cega_input_poll_smssys);
}

static void cega_setup_sg_sms(void) {
    // Check the game database to enable special inputs and mappers
    dbflags = sys == SYSTEM_SMS ?
        cega_db_process_sms(gameinfo.md5) :
        cega_db_process_sg(gameinfo.md5);

    // Set the samples per frame and frame timing depending on region
    if ((settings_cega[REGION].val == REGION_EU) || (dbflags & DB_PAL)) {
        cega_set_region(REGION_EU);
        vidinfo.aspect = ASPECT_PAL;
        audinfo.spf = (SAMPLERATE / FRAMERATE_PAL);
#if JG_VERSION_NUMBER >= 10100
        jg_cb_frametime(udata_frametime, FRAMERATE_PAL);
#else
        jg_cb_frametime(FRAMERATE_PAL);
#endif
    }
    else if (dbflags & DB_JP) {
        cega_set_region(REGION_JP);
#if JG_VERSION_NUMBER >= 10100
        jg_cb_frametime(udata_frametime, FRAMERATE);
#else
        jg_cb_frametime(FRAMERATE);
#endif
    }
    else {
#if JG_VERSION_NUMBER >= 10100
        jg_cb_frametime(udata_frametime, FRAMERATE);
#else
        jg_cb_frametime(FRAMERATE);
#endif
    }

    cega_input_setup_sg_sms();

    // Try to load the BIOS as an auxiliary file
    if (sys == SYSTEM_SMS) {
        if (biosinfo.size) {
            cega_bios_load(biosinfo.data, biosinfo.size);
            cega_sms_set_bios(0x01);
        }
        else if (settings_cega[BIOS].val) { // Fall back to emulator settings
            char biospath[256];
            snprintf(biospath, sizeof(biospath), "%s/bios.sms", pathinfo.bios);
            if (cega_sms_bios_load_file(biospath))
                cega_sms_set_bios(0x81);
            else
                jg_cb_log(JG_LOG_WRN, "Failed to load BIOS: %s\n", biospath);
        }

        // Enable FM Audio when allowed
        if (!(dbflags & SMS_DB_FMDISABLE))
            cega_sms_set_fmaudio(settings_cega[FMAUDIO].val);
    }

    // Set any required VDP quirks if required
    unsigned quirks = 0;
    if (dbflags & SMS_DB_SMS1VDP) // SMS1 VDP behaviour required
        quirks |= SMS_VDP_QUIRK_SMS1VDP;
    if (dbflags & SMS_DB_FRINT) // Frame Interrupt bit set on boot
        quirks |= SMS_VDP_QUIRK_FRINT;
    if (dbflags & SMS_DB_PHOFFSET) // Phaser X coordinates must be offset
        quirks |= SMS_VDP_QUIRK_PHOFFSET;
    smsvdp_set_quirks(quirks);
}

/*
 * Game Gear
 */
static uint8_t cega_input_poll_gg(int port) {
    uint8_t b = 0x00;

    if (port == 0) {
        if (input_device[0]->button[0]) b |= GG_INPUT_U;
        if (input_device[0]->button[1]) b |= GG_INPUT_D;
        if (input_device[0]->button[2]) b |= GG_INPUT_L;
        if (input_device[0]->button[3]) b |= GG_INPUT_R;
        if (input_device[0]->button[5]) b |= GG_INPUT_1;
        if (input_device[0]->button[6]) b |= GG_INPUT_2;
    }
    else if (port == 1) {
        // What happens here?
    }
    else {
        if (input_device[0]->button[4]) b |= GG_INPUT_START;
    }

    return b;
}

static void cega_setup_gg(void) {
    // Check for any special handling requirements
    dbflags = cega_db_process_gg(gameinfo.md5);

    if (dbflags & GG_DB_SMSMODE) {
        vidinfo = (jg_videoinfo_t){
            JG_PIXFMT_XRGB8888,
            SMS_VDP_WIDTH, SMS_VDP_HEIGHT_EXT2,
            SMS_VDP_WIDTH, SMS_VDP_HEIGHT, 0, 0,
            SMS_VDP_WIDTH, 4.0/3.0, NULL
        };
        audinfo = (jg_audioinfo_t){
            JG_SAMPFMT_INT16, SAMPLERATE, 1, (SAMPLERATE / FRAMERATE), NULL
        };

        cega_deinit();
        cega_set_system(SYSTEM_SMS);
        cega_gg_set_smsmode(GG_DB_SMSMODE);
        cega_init();

        // Callbacks for SMS Mode games
        cega_input_set_callback(1, &cega_input_poll_gg);
        cega_input_set_callback(2, &cega_input_poll_gg); // Start button
    }

    // Set to Japanese region if required
    if (dbflags & DB_JP)
        cega_set_region(REGION_JP);

#if JG_VERSION_NUMBER >= 10100
    jg_cb_frametime(udata_frametime, FRAMERATE); // Game Gear is always 60Hz
#else
    jg_cb_frametime(FRAMERATE); // Game Gear is always 60Hz
#endif

    cega_input_set_callback(0, &cega_input_poll_gg);
    inputinfo[0] = jg_gg_inputinfo(0, 0);

    // Try to load the BIOS as an auxiliary file
    if (biosinfo.size) {
        cega_bios_load(biosinfo.data, biosinfo.size);
        cega_sms_set_bios(settings_cega[BIOS].val | 0x01);
    }
    else if (settings_cega[BIOS].val) { // Fall back to emulator settings
        char biospath[256];
        snprintf(biospath, sizeof(biospath), "%s/bios.gg", pathinfo.bios);
        if (cega_sms_bios_load_file(biospath))
            cega_sms_set_bios(settings_cega[BIOS].val | 0x80);
        else
            jg_cb_log(JG_LOG_WRN, "Failed to load BIOS: %s\n", biospath);
    }
}

/*
 * Mega Drive (Genesis)
 */
/*static uint16_t cega_input_poll_md3(int port) {
    uint16_t b = 0x0000;

    if (input_device[port]->button[0]) b |= MD_INPUT_U;
    if (input_device[port]->button[1]) b |= MD_INPUT_D;
    if (input_device[port]->button[2]) b |= MD_INPUT_L;
    if (input_device[port]->button[3]) b |= MD_INPUT_R;
    if (input_device[port]->button[4]) b |= MD_INPUT_S;
    if (input_device[port]->button[5]) b |= MD_INPUT_A;
    if (input_device[port]->button[6]) b |= MD_INPUT_B;
    if (input_device[port]->button[7]) b |= MD_INPUT_C;

    return b;
}*/

static uint16_t cega_input_poll_md6(int port) {
    uint16_t b = 0x0000;

    for (int i = 0; i < NDEFS_MDPAD6B; ++i)
        if (input_device[port]->button[i]) b |= (1 << i);

    return b;
}

static void cega_setup_md(void) {
    /* Set the samples per frame and frame timing depending on region - In the
       Mega Drive's case, the region can be detected from the ROM Header. The
       initial setting may have been overridden.
    */
    uint8_t region = cega_md_get_region();

    // Check for any special handling requirements
    dbflags = cega_db_process_md(gameinfo.md5);
    //jg_cb_log(JG_LOG_INF, "%s\n", gameinfo.md5);

    if ((settings_cega[REGION].val == REGION_EU) || region == REGION_EU ||
        (dbflags & DB_PAL)) {
        region = REGION_EU;
        vidinfo.aspect = ASPECT_PAL;
        audinfo.spf = (SAMPLERATE / FRAMERATE_PAL) * 2;
#if JG_VERSION_NUMBER >= 10100
        jg_cb_frametime(udata_frametime, FRAMERATE_PAL);
#else
        jg_cb_frametime(FRAMERATE_PAL);
#endif
    }
    else if (dbflags & DB_JP) {
        region = REGION_JP;
#if JG_VERSION_NUMBER >= 10100
        jg_cb_frametime(udata_frametime, FRAMERATE);
#else
        jg_cb_frametime(FRAMERATE);
#endif
    }
    else {
#if JG_VERSION_NUMBER >= 10100
        jg_cb_frametime(udata_frametime, FRAMERATE);
#else
        jg_cb_frametime(FRAMERATE);
#endif
    }

    cega_set_region(region);

    /* 3-button controllers may be needed for some games:
       Ms. Pac Man, Beast Wrestler, Sunset Riders, Golden Axe 2, Exile,
       John Madden Football, Forgotten Worlds
    */
    inputinfo[0] = jg_md_inputinfo(0, JG_MD_PAD6B);
    cega_md_input_set_callback(0, &cega_input_poll_md6);
    cega_md_io_set_port(0, MD_INPUT_TYPE_PAD6B);

    inputinfo[1] = jg_md_inputinfo(1, JG_MD_PAD6B);
    cega_md_input_set_callback(1, &cega_input_poll_md6);
    cega_md_io_set_port(1, MD_INPUT_TYPE_PAD6B);

    // Set the Mega Drive's Colour Ramp
    mdvdp_set_colramp(settings_cega[COLRAMP].val);
}

/*
 * Misc Callbacks
 */
static void cega_video_resize(int w, int h, int x, int y) {
    vidinfo.w = w;
    vidinfo.h = h;
    vidinfo.x = x;
    vidinfo.y = y;
}

/*
 * Jolly Good API Calls
 */
#if JG_VERSION_NUMBER >= 10100
static void cega_cb_audio(size_t samps) {
    jg_cb_audio(udata_audio, samps);
}

void jg_set_cb_audio(jg_cb_audio_t func, void *ud) {
    jg_cb_audio = func;
    udata_audio = ud;
}

void jg_set_cb_frametime(jg_cb_frametime_t func, void *ud) {
    jg_cb_frametime = func;
    udata_frametime = ud;
}
#else
void jg_set_cb_audio(jg_cb_audio_t func) {
    jg_cb_audio = func;
}

void jg_set_cb_frametime(jg_cb_frametime_t func) {
    jg_cb_frametime = func;
}
#endif

void jg_set_cb_log(jg_cb_log_t func) {
    jg_cb_log = func;
}

#if JG_VERSION_NUMBER >= 10100
void jg_set_cb_rumble(jg_cb_rumble_t func, void *ud) {
    jg_cb_rumble = func;
    udata_rumble = ud;
}
#else
void jg_set_cb_rumble(jg_cb_rumble_t func) {
    jg_cb_rumble = func;
}
#endif

int jg_init(void) {
    cega_set_region(settings_cega[REGION].val);

    cega_log_set_callback(jg_cb_log);

#if JG_VERSION_NUMBER >= 10100
    cega_mixer_set_callback(cega_cb_audio);
#else
    cega_mixer_set_callback(jg_cb_audio);
#endif
    cega_mixer_set_rate(SAMPLERATE);
    cega_mixer_set_rsqual(settings_cega[RSQUAL].val);

    smsvdp_set_size_callback(&cega_video_resize);
    smsvdp_set_palette(settings_cega[PALETTE].val);

    mdvdp_set_size_callback(&cega_video_resize);

    cega_init();

    return 1;
}

void jg_deinit(void) {
    cega_deinit();
}

void jg_reset(int hard) {
    cega_reset(hard);
}

void jg_exec_frame(void) {
    cega_exec(); // Execute a frame for the active system
}

int jg_game_load(void) {
    // Load the ROM
    if (!cega_rom_load(gameinfo.data, gameinfo.size))
        return 0;

    // Manage SRAM
    char savename[292];
    snprintf(savename, sizeof(savename),
        "%s/%s.srm", pathinfo.save, gameinfo.name);
    int sramstat = cega_sram_load((const char*)savename);

    if (sramstat == 1)
        jg_cb_log(JG_LOG_DBG, "SRAM Loaded: %s\n", savename);
    else if (sramstat == 2)
        jg_cb_log(JG_LOG_DBG, "SRAM file does not exist: %s\n", savename);
    else
        jg_cb_log(JG_LOG_DBG, "SRAM Load Failed: %s\n", savename);

    // Set up frame timing and input parameters depending on the system
    switch (cega_get_system()) {
        case SYSTEM_SG: case SYSTEM_SMS: {
            cega_setup_sg_sms();
            break;
        }
        case SYSTEM_GG: {
            cega_setup_gg();
            break;
        }
        case SYSTEM_MD: {
            cega_setup_md();
            break;
        }
        default:
            return 0;
    }

    return 1;
}

int jg_game_unload(void) {
    char savename[292];
    snprintf(savename, sizeof(savename),
        "%s/%s.srm", pathinfo.save, gameinfo.name);
    int srmstat = cega_sram_save((const char*)savename);

    if (srmstat == 1)
        jg_cb_log(JG_LOG_DBG, "SRAM Saved: %s\n", savename);
    else if (srmstat == 2)
        jg_cb_log(JG_LOG_DBG, "Cartridge does not contain SRAM\n");
    else
        jg_cb_log(JG_LOG_DBG, "SRAM Save Failed: %s\n", savename);

    return 1;
}

int jg_state_load(const char *filename) {
    return cega_state_load(filename);
}

void jg_state_load_raw(const void *data) {
    int ret = cega_state_load_raw(data, 0);
    (void)ret;
}

int jg_state_save(const char *filename) {
    return cega_state_save(filename);
}

const void* jg_state_save_raw(void) {
    return cega_state_save_raw();
}

size_t jg_state_size(void) {
    return cega_state_size();
}

void jg_media_select(void) {
}

void jg_media_insert(void) {
}

void jg_media_mount(unsigned mount) {
    (void)mount;
}

void jg_media_set(unsigned id) {
    (void)id;
}

void jg_cheat_clear(void) {
}

void jg_cheat_set(const char *code) {
    if (code) { }
}

void jg_rehash(void) {
    switch (sys) {
        case SYSTEM_SG: case SYSTEM_SMS: {
            smsvdp_set_palette(settings_cega[PALETTE].val);
            cega_input_setup_sg_sms();
            break;
        }
        case SYSTEM_MD: {
            mdvdp_set_colramp(settings_cega[COLRAMP].val);
            break;
        }
        default: {
            break;
        }
    }
}

void jg_data_push(uint32_t type, int port, const void *ptr, size_t size) {
    if (type || port || ptr || size) { }
}

jg_systeminfo_t* jg_get_systemlist(size_t *num) {
    *num = sizeof(systemlist) / sizeof(jg_systeminfo_t);
    return systemlist;
}

jg_coreinfo_t* jg_get_coreinfo(const char *subsys) {
    if (!strcmp(subsys, "sg")) {
        vidinfo = (jg_videoinfo_t){
            JG_PIXFMT_XRGB8888,
            SMS_VDP_WIDTH, SMS_VDP_HEIGHT,
            SMS_VDP_WIDTH, SMS_VDP_HEIGHT, 0, 0,
            SMS_VDP_WIDTH, 4.0/3.0, NULL
        };
        audinfo = (jg_audioinfo_t){
            JG_SAMPFMT_INT16, SAMPLERATE, 1, (SAMPLERATE / FRAMERATE), NULL
        };
        cega_set_system(SYSTEM_SG);
        sys = SYSTEM_SG;
        coreinfo = &coreinfo_sg;
    }
    else if (!strcmp(subsys, "sms")) {
        vidinfo = (jg_videoinfo_t){
            JG_PIXFMT_XRGB8888,
            SMS_VDP_WIDTH, SMS_VDP_HEIGHT_EXT2,
            SMS_VDP_WIDTH, SMS_VDP_HEIGHT, 0, 0,
            SMS_VDP_WIDTH, 4.0/3.0, NULL
        };
        audinfo = (jg_audioinfo_t){
            JG_SAMPFMT_INT16, SAMPLERATE, 1, (SAMPLERATE / FRAMERATE), NULL
        };
        cega_set_system(SYSTEM_SMS);
        sys = SYSTEM_SMS;
        coreinfo = &coreinfo_sms;
    }
    else if (!strcmp(subsys, "gg")) {
        vidinfo = (jg_videoinfo_t){
            JG_PIXFMT_XRGB8888,
            SMS_VDP_WIDTH, SMS_VDP_HEIGHT_EXT2,
            GG_VIEWPORT_WIDTH, GG_VIEWPORT_HEIGHT,
            GG_XOFFSET, GG_YOFFSET,
            SMS_VDP_WIDTH, 160.0/144.0, NULL
        };
        audinfo = (jg_audioinfo_t){
            JG_SAMPFMT_INT16, SAMPLERATE, 2, (SAMPLERATE / FRAMERATE) * 2, NULL
        };
        cega_set_system(SYSTEM_GG);
        sys = SYSTEM_GG;
        coreinfo = &coreinfo_gg;
    }
    else if (!strcmp(subsys, "md")) {
        vidinfo = (jg_videoinfo_t){
            JG_PIXFMT_XRGB8888,
            MD_VDP_WIDTH, MD_VDP_HEIGHT_ILC_EXT,
            MD_VDP_WIDTH, MD_VDP_HEIGHT, 0, 0,
            MD_VDP_WIDTH, 320.0/224.0, NULL
            //MD_VDP_WIDTH, 320.0/448.0, NULL
            //MD_VDP_WIDTH, 1.3061224, NULL
        };
        audinfo = (jg_audioinfo_t){
            JG_SAMPFMT_INT16, SAMPLERATE, 2, (SAMPLERATE / FRAMERATE) * 2, NULL
        };
        cega_set_system(SYSTEM_MD);
        sys = SYSTEM_MD;
        coreinfo = &coreinfo_md;
    }
    else if (!strcmp(subsys, "")) {
        vidinfo = (jg_videoinfo_t){
            JG_PIXFMT_XRGB8888,
            SMS_VDP_WIDTH, SMS_VDP_HEIGHT,
            SMS_VDP_WIDTH, SMS_VDP_HEIGHT, 0, 0,
            SMS_VDP_WIDTH, 4.0/3.0, NULL
        };
        audinfo = (jg_audioinfo_t){
            JG_SAMPFMT_INT16, SAMPLERATE, 1, (SAMPLERATE / FRAMERATE), NULL
        };
        coreinfo = &coreinfo_sms;
    }

    return coreinfo;
}

jg_videoinfo_t* jg_get_videoinfo(void) {
    return &vidinfo;
}

jg_audioinfo_t* jg_get_audioinfo(void) {
    return &audinfo;
}

jg_inputinfo_t* jg_get_inputinfo(int port) {
    return &inputinfo[port];
}

jg_inputinfo_t* jg_get_inputlist(size_t *num) {
    if (!inputlist_built) {
        unsigned listnum = 0;

        // SG-1000/SMS
        for (size_t i = 0; i < 2; ++i)
            inputlist[listnum++] = jg_sms_inputinfo(i, JG_SMS_PAD);

        inputlist[listnum++] = jg_sms_inputinfo(0, JG_SMS_SYS);

        for (size_t i = 0; i < 2; ++i)
            inputlist[listnum++] = jg_sms_inputinfo(i, JG_SMS_PADDLE);

        inputlist[listnum++] = jg_sms_inputinfo(0, JG_SMS_LIGHTPHASER);
        inputlist[listnum++] = jg_sms_inputinfo(0, JG_SMS_TEREBIOEKAKI);

        // Game Gear
        inputlist[listnum++] = jg_gg_inputinfo(0, 0);

        // Mega Drive
        for (size_t i = 0; i < 2; ++i)
            inputlist[listnum++] = jg_md_inputinfo(i, JG_MD_PAD6B);

        for (size_t i = 0; i < 2; ++i)
            inputlist[listnum++] = jg_md_inputinfo(i, JG_MD_PAD3B);

        inputlist_built = 1;
    }
    *num = TOTALINPUTS;
    return inputlist;
}

jg_setting_t* jg_get_settings(size_t *numsettings) {
    *numsettings = sizeof(settings_cega) / sizeof(jg_setting_t);
    return settings_cega;
}

jg_setting_t* jg_get_dips(size_t *num) {
    *num = 0;
    return NULL;
}

jg_mediainfo_t* jg_get_mediainfo(void) {
    return NULL;
}

jg_biosinfo_t* jg_get_bioslist(size_t *num) {
    *num = sizeof(bioslist) / sizeof(jg_biosinfo_t);
    return bioslist;
}

void jg_setup_video(void) {
    switch (sys) {
        case SYSTEM_SG: case SYSTEM_SMS: case SYSTEM_GG: {
            smsvdp_set_buffer(vidinfo.buf);
            break;
        }
        case SYSTEM_MD: {
            mdvdp_set_buffer(vidinfo.buf);
            break;
        }
    }
}

void jg_setup_audio(void) {
    cega_mixer_set_buffer(audinfo.buf);
}

void jg_set_inputstate(jg_inputstate_t *ptr, int port) {
    input_device[port] = ptr;
}

void jg_set_gameinfo(jg_fileinfo_t info) {
    gameinfo = info;
}

void jg_set_auxinfo(jg_fileinfo_t info, int index) {
    if (index) // Only one auxiliary file supported in this emulator
        return;
    biosinfo = info;
}

void jg_set_paths(jg_pathinfo_t paths) {
    pathinfo = paths;
}

unsigned jg_api_version(void) {
    return JG_VERSION_NUMBER;
}
