/*
 * 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 <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>

#include "cega.h"
#include "cega_md.h"
#include "cega_sg.h"
#include "cega_sms.h"
#include "cega_mixer.h"
#include "cega_z80.h"
#include "mdvdp.h"
#include "smsvdp.h"
#include "sn76496.h"
#include "ym2413.h"
#include "ymfm_opn.h"
#include "ymfm_shim.h"

// Function pointer for execution of one frame of emulation
void (*cega_exec)(void);

// Function Pointers for loading ROM Images
int (*cega_bios_load)(void*, size_t);
int (*cega_rom_load)(void*, size_t);

// Function Pointers for States
size_t (*cega_state_size)(void);
int (*cega_state_load_raw)(const void*, size_t);
int (*cega_state_load)(const char*);
const void* (*cega_state_save_raw)(void);
int (*cega_state_save)(const char*);

// Function Pointers for SRAM
int (*cega_sram_load)(const char*);
int (*cega_sram_save)(const char*);

// Log callback
void (*cega_log)(int, const char *, ...);

// Input poll callbacks
uint8_t (*cega_input_cb[NUMINPUTS_SMS])(int);
uint16_t (*cega_md_input_cb[NUMINPUTS_MD])(int);

// System being emulated
static int sys = 0;

// Set the log callback
void cega_log_set_callback(void (*cb)(int, const char *, ...)) {
    cega_log = cb;
}

// Set the Input Callback to allow the emulator to strobe the input state
void cega_input_set_callback(int port, uint8_t (*cb)(int)) {
    cega_input_cb[port] = cb;
}

void cega_md_input_set_callback(int port, uint16_t (*cb)(int)) {
    cega_md_input_cb[port] = cb;
}

// Set the region
void cega_set_region(unsigned region) {
    switch (sys) {
        default:
        case SYSTEM_SG: {
            // SYSTEM_SG is always Domestic/Japan
            cega_mixer_set_region(REGION_JP);
            smsvdp_set_region(0);
            break;
        }
        case SYSTEM_SMS: {
            cega_sms_set_region(region);
            cega_mixer_set_region(region);
            smsvdp_set_region(region > REGION_US);
            break;
        }
        case SYSTEM_GG: {
            // SYSTEM_GG is always 60Hz
            cega_sms_set_region(region > REGION_US ? REGION_US : region);
            smsvdp_set_region(0);
            break;
        }
        case SYSTEM_MD: {
            cega_md_set_region(region);
            cega_mixer_set_region(region);
            mdvdp_set_region(region);
            break;
        }
    }
}

// Set the system to be emulated
void cega_set_system(int system) {
    sys = system;
}

// Get the system currently being emulated
int cega_get_system(void) {
    return sys;
}

// Initialize
void cega_init(void) {
    switch (sys) {
        case SYSTEM_SG: {
            cega_sg_init();
            cega_mixer_init(0);
            break;
        }
        case SYSTEM_SMS: {
            cega_sms_init();
            cega_mixer_init(0);
            ym2413_init();
            break;
        }
        case SYSTEM_GG: {
            cega_sms_init(); // Many things are common to SMS and Game Gear
            cega_gg_init(); // Game Gear specific tasks
            cega_mixer_init(1);
            break;
        }
        case SYSTEM_MD: {
            cega_md_init();
            cega_mixer_init(0);
            ymfm_shim_init();
            break;
        }
    }
}

// Deinitialize
void cega_deinit(void) {
    switch (sys) {
        case SYSTEM_SG: {
            cega_sg_deinit();
            break;
        }
        case SYSTEM_SMS: {
            ym2413_deinit();
            cega_sms_deinit();
            break;
        }
        case SYSTEM_GG: {
            cega_sms_deinit();
            break;
        }
        case SYSTEM_MD: {
            cega_md_deinit();
            break;
        }
    }

    cega_mixer_deinit();
}

// Reset the system
void cega_reset(int hard) {
    if (hard) { } // Currently unused

    switch (sys) {
        case SYSTEM_SG: {
            cega_sg_reset();
            break;
        }
        case SYSTEM_SMS: {
            cega_sms_reset();
            ym2413_reset();
            break;
        }
        case SYSTEM_GG: {
            cega_sms_reset();
            break;
        }
        case SYSTEM_MD: {
            cega_md_reset();
            ym2612_reset();
            break;
        }
    }
}
