#include "shared.h"

#include <nofrendo.h>

// #define AUDIO_SAMPLE_RATE   (32000)
// #define AUDIO_BUFFER_LENGTH (AUDIO_SAMPLE_RATE / 50 + 1)

#if RG_SCREEN_PIXEL_FORMAT == 0
#define FB_PIXEL_FORMAT RG_PIXEL_PAL565_BE
#else
#define FB_PIXEL_FORMAT RG_PIXEL_PAL565_LE
#endif

static int overscan = true;
static int autocrop = 0;
static int palette = 0;
static bool slowFrame = false;
static bool nsfPlayer = false;
static nes_t *nes;

static rg_app_t *app;
static rg_surface_t *updates[2];
static rg_surface_t *currentUpdate;

static const char *SETTING_AUTOCROP = "autocrop";
static const char *SETTING_OVERSCAN = "overscan";
static const char *SETTING_PALETTE = "palette";
static const char *SETTING_SPRITELIMIT = "spritelimit";
// --- MAIN


static void event_handler(int event, void *arg)
{
    if (event == RG_EVENT_REDRAW)
    {
        if (nsfPlayer)
            rg_display_clear(C_BLACK);
        else if (nes)
            (nes->blit_func)(NULL);
    }
}

static bool screenshot_handler(const char *filename, int width, int height)
{
	return rg_surface_save_image_file(currentUpdate, filename, width, height);
}

static bool save_state_handler(const char *filename)
{
    return state_save(filename) == 0;
}

static bool load_state_handler(const char *filename)
{
    if (state_load(filename) != 0)
    {
        nes_reset(true);
        return false;
    }
    return true;
}

static bool reset_handler(bool hard)
{
    nes_reset(hard);
    return true;
}

static void build_palette(int n)
{
    uint16_t *pal = nofrendo_buildpalette(n, 16);
    for (int i = 0; i < 256; i++)
    {
        uint16_t color = pal[i];
        if (FB_PIXEL_FORMAT  == RG_PIXEL_PAL565_BE)
            color = (pal[i] >> 8) | ((pal[i]) << 8);
        updates[0]->palette[i] = color;
        updates[1]->palette[i] = color;
    }
    free(pal);
}

static rg_gui_event_t sprite_limit_cb(rg_gui_option_t *option, rg_gui_event_t event)
{
    bool spritelimit = ppu_getopt(PPU_LIMIT_SPRITES);

    if (event == RG_DIALOG_PREV || event == RG_DIALOG_NEXT)
    {
        spritelimit = !spritelimit;
        rg_settings_set_number(NS_APP, SETTING_SPRITELIMIT, spritelimit);
        ppu_setopt(PPU_LIMIT_SPRITES, spritelimit);
    }

    strcpy(option->value, spritelimit ? _("On") : _("Off"));

    return RG_DIALOG_VOID;
}

static rg_gui_event_t overscan_update_cb(rg_gui_option_t *option, rg_gui_event_t event)
{
    if (event == RG_DIALOG_PREV || event == RG_DIALOG_NEXT)
    {
        overscan = !overscan;
        rg_settings_set_number(NS_APP, SETTING_OVERSCAN, overscan);
        return RG_DIALOG_REDRAW;
    }

    strcpy(option->value, overscan ? _("Auto") : _("Off"));

    return RG_DIALOG_VOID;
}

static rg_gui_event_t autocrop_update_cb(rg_gui_option_t *option, rg_gui_event_t event)
{
    int val = autocrop;
    int max = 2;

    if (event == RG_DIALOG_PREV) val = val > 0 ? val - 1 : max;
    if (event == RG_DIALOG_NEXT) val = val < max ? val + 1 : 0;

    if (val != autocrop)
    {
        autocrop = val;
        rg_settings_set_number(NS_APP, SETTING_AUTOCROP, val);
        return RG_DIALOG_REDRAW;
    }

    if (val == 0) strcpy(option->value, _("Never"));
    if (val == 1) strcpy(option->value, _("Auto"));
    if (val == 2) strcpy(option->value, _("Always"));

    return RG_DIALOG_VOID;
}

static rg_gui_event_t palette_update_cb(rg_gui_option_t *option, rg_gui_event_t event)
{
    int pal = palette;
    int max = NES_PALETTE_COUNT - 1;

    if (event == RG_DIALOG_PREV) pal = pal > 0 ? pal - 1 : max;
    if (event == RG_DIALOG_NEXT) pal = pal < max ? pal + 1 : 0;

    if (pal != palette)
    {
        palette = pal;
        rg_settings_set_number(NS_APP, SETTING_PALETTE, pal);
        build_palette(pal);
        return RG_DIALOG_REDRAW;
    }

    if (pal == NES_PALETTE_NOFRENDO)    strcpy(option->value, _("Nofrendo"));
    if (pal == NES_PALETTE_COMPOSITE)   strcpy(option->value, _("Composite"));
    if (pal == NES_PALETTE_NESCLASSIC)  strcpy(option->value, _("NES Classic"));
    if (pal == NES_PALETTE_NTSC)        strcpy(option->value, _("NTSC"));
    if (pal == NES_PALETTE_PVM)         strcpy(option->value, _("PVM"));
    if (pal == NES_PALETTE_SMOOTH)      strcpy(option->value, _("Smooth"));

    return RG_DIALOG_VOID;
}


static void blit_screen(uint8 *bmp)
{
    slowFrame = bmp && rg_display_is_busy();
    // A rolling average should be used for autocrop == 1, it causes jitter in some games...
    // int crop_h = (autocrop == 2) || (autocrop == 1 && nes->ppu->left_bg_counter > 210) ? 8 : 0;
    int crop_v = (overscan) ? nes->overscan : 0;
    int crop_h = (autocrop) ? 8 : 0;
    // crop_h = (autocrop == 2) || (autocrop == 1 && nes->ppu->left_bg_counter > 210) ? 8 : 0;
    currentUpdate->width = NES_SCREEN_WIDTH - crop_h * 2;
    currentUpdate->height = NES_SCREEN_HEIGHT - crop_v * 2;
    currentUpdate->offset = crop_v * currentUpdate->stride + crop_h + 8;
    rg_display_submit(currentUpdate, 0);
}

static void nsf_draw_overlay(void)
{
    extern int nsf_current_song;
    char song[32];
    const nsfheader_t *header = (nsfheader_t *)nes->cart->data_ptr;
    const rg_gui_option_t options[] = {
        {0, _("Name"),      (char*)header->name,      RG_DIALOG_FLAG_NORMAL, NULL},
        {0, _("Artist"),    (char*)header->artist,    RG_DIALOG_FLAG_NORMAL, NULL},
        {0, _("Copyright"), (char*)header->copyright, RG_DIALOG_FLAG_NORMAL, NULL},
        {0, _("Playing"),   (char*)song,              RG_DIALOG_FLAG_NORMAL, NULL},
        RG_DIALOG_END,
    };
    snprintf(song, sizeof(song), "%d / %d", nsf_current_song, header->total_songs);
    rg_gui_draw_dialog("NSF Player", options, 4, -1);
}


static void options_handler(rg_gui_option_t *dest)
{
    *dest++ = (rg_gui_option_t){0, _("Palette"),      "-", RG_DIALOG_FLAG_NORMAL, &palette_update_cb};
    *dest++ = (rg_gui_option_t){0, _("Overscan"),     "-", RG_DIALOG_FLAG_NORMAL, &overscan_update_cb};
    *dest++ = (rg_gui_option_t){0, _("Crop sides"),   "-", RG_DIALOG_FLAG_NORMAL, &autocrop_update_cb};
    *dest++ = (rg_gui_option_t){0, _("Sprite limit"), "-", RG_DIALOG_FLAG_NORMAL, &sprite_limit_cb};
    *dest++ = (rg_gui_option_t)RG_DIALOG_END;
}

void nes_main(void)
{
    const rg_handlers_t handlers = {
        .loadState = &load_state_handler,
        .saveState = &save_state_handler,
        .reset = &reset_handler,
        .event = &event_handler,
        .screenshot = &screenshot_handler,
        .options = &options_handler,
    };

    app = rg_system_reinit(AUDIO_SAMPLE_RATE, &handlers, NULL);
    // 强制禁用跳帧
    app->frameskip = 0;

    overscan = rg_settings_get_number(NS_APP, SETTING_OVERSCAN, 1);
    autocrop = rg_settings_get_number(NS_APP, SETTING_AUTOCROP, 0);
    palette = rg_settings_get_number(NS_APP, SETTING_PALETTE, NES_PALETTE_PVM);

    updates[0] = rg_surface_create(NES_SCREEN_PITCH, NES_SCREEN_HEIGHT, FB_PIXEL_FORMAT , MEM_FAST);
    updates[1] = rg_surface_create(NES_SCREEN_PITCH, NES_SCREEN_HEIGHT, FB_PIXEL_FORMAT , MEM_FAST);
    currentUpdate = updates[0];

    nes = nes_init(SYS_DETECT, app->sampleRate, true, RG_BASE_PATH_BIOS "/fds_bios.bin");
    if (!nes)
        RG_PANIC("Init failed.");

    int ret = -1;

    if (rg_extension_match(app->romPath, "zip"))
    {
        void *data;
        size_t size;
        if (!rg_storage_unzip_file(app->romPath, NULL, &data, &size, RG_FILE_ALIGN_8KB))
            RG_PANIC("ROM file unzipping failed!");
        ret = nes_insertcart(rom_loadmem(data, size));
    }
    else
    {
        ret = nes_loadfile(app->romPath);
    }

    if (ret == -1)
        RG_PANIC("ROM load failed.");
    else if (ret == -2)
        RG_PANIC("Unsupported mapper.");
    else if (ret == -3)
        RG_PANIC("BIOS file required.");
    else if (ret < 0)
        RG_PANIC("Unsupported ROM.");

    nes->blit_func = blit_screen;

    nsfPlayer = nes->cart->type == ROM_TYPE_NSF;

    ppu_setopt(PPU_LIMIT_SPRITES, rg_settings_get_number(NS_APP, SETTING_SPRITELIMIT, 1));

    build_palette(palette);

    // This is necessary for successful state restoration
    // I have not yet investigated why that is...
    nes_emulate(false);
    nes_emulate(false);

    if (app->bootFlags & RG_BOOT_RESUME)
    {
        rg_emu_load_state(app->saveSlot);
    }

    rg_system_set_tick_rate(nes->refresh_rate);

    // int skipFrames = 0;

    while (true)
    {
        const int64_t startTime = rg_system_timer();
        uint32_t joystick = rg_input_read_gamepad();
        // bool drawFrame = !skipFrames && !nsfPlayer;
        bool drawFrame = true; 

        if (joystick & (RG_KEY_MENU|RG_KEY_OPTION))
        {
            if (joystick & RG_KEY_MENU)
                rg_gui_game_menu();
            else
                rg_gui_options_menu();
            continue;
        }

        // NSF播放器，与游戏无关
        if (nsfPlayer)
        {
            nsf_draw_overlay();
        }

        int buttons = 0;
        if (joystick & RG_KEY_START)  buttons |= NES_PAD_START;
        if (joystick & RG_KEY_SELECT) buttons |= NES_PAD_SELECT;
        if (joystick & RG_KEY_UP)     buttons |= NES_PAD_UP;
        if (joystick & RG_KEY_RIGHT)  buttons |= NES_PAD_RIGHT;
        if (joystick & RG_KEY_DOWN)   buttons |= NES_PAD_DOWN;
        if (joystick & RG_KEY_LEFT)   buttons |= NES_PAD_LEFT;
        if (joystick & RG_KEY_A)      buttons |= NES_PAD_A;
        if (joystick & RG_KEY_B)      buttons |= NES_PAD_B;
        input_update(0, buttons);

        if (drawFrame)
            currentUpdate = updates[currentUpdate == updates[0]];
        nes_setvidbuf(currentUpdate->data);
        nes_emulate(drawFrame);

        //音量控制
        rg_volume_handle(joystick, startTime);

        // Tick before submitting audio/syncing
        rg_system_tick(rg_system_timer() - startTime);

        // Audio is used to pace emulation :)
        rg_audio_submit((void*)nes->apu->buffer, nes->apu->samples_per_frame);

        // if (skipFrames == 0)
        // {
        //     int elapsed = rg_system_timer() - startTime;
        //     if (nsfPlayer)
        //         skipFrames = 10, nsf_draw_overlay();
        //     else if (app->frameskip > 0)
        //         skipFrames = app->frameskip;
        //     else if (elapsed > app->frameTime + 1500) // Allow some jitter
        //         skipFrames = 1; // (elapsed / frameTime)
        //     else if (drawFrame && slowFrame)
        //         skipFrames = 1;
        // }
        // else if (skipFrames > 0)
        // {
        //     skipFrames--;
        // }
    }

    RG_PANIC("Nofrendo died!");
}
