extern "C" {
#include "shared.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
}

#include <handy.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_565_BE
#else
#define FB_PIXEL_FORMAT RG_PIXEL_565_LE
#endif

static CSystem *lynx = NULL;

static int dpad_mapped_up;
static int dpad_mapped_down;
static int dpad_mapped_left;
static int dpad_mapped_right;

static rg_app_t *app;
static rg_surface_t *updates[2];
static rg_surface_t *currentUpdate;
// static bool netplay = false;
// --- MAIN

static void set_display_mode(void)
{
    display_rotation_t rotation = rg_display_get_rotation();
    int width, height;

    if (rotation == RG_DISPLAY_ROTATION_AUTO)
    {
        rotation = RG_DISPLAY_ROTATION_OFF;

        switch (lynx->mCart->CRC32())
        {
            case 0x97501709: // Centipede
            case 0x0271B6E9: // Lexis
            case 0x006FD398: // NFL Football
            case 0xBCD10C3A: // Raiden
                rotation = RG_DISPLAY_ROTATION_LEFT;
                break;
            case 0x7F0EC7AD: // Gauntlet
            case 0xAC564BAA: // Gauntlet - The Third Encounter
            case 0xA53649F1: // Klax
                rotation = RG_DISPLAY_ROTATION_RIGHT;
                break;
            default:
                if (lynx->mCart->CartGetRotate() == CART_ROTATE_LEFT)
                    rotation = RG_DISPLAY_ROTATION_LEFT;
                if (lynx->mCart->CartGetRotate() == CART_ROTATE_RIGHT)
                    rotation = RG_DISPLAY_ROTATION_RIGHT;
        }
    }

    switch(rotation)
    {
        case RG_DISPLAY_ROTATION_LEFT:
            width = HANDY_SCREEN_HEIGHT;
            height = HANDY_SCREEN_WIDTH;
            lynx->mMikie->SetRotation(MIKIE_ROTATE_L);
            dpad_mapped_up    = BUTTON_RIGHT;
            dpad_mapped_down  = BUTTON_LEFT;
            dpad_mapped_left  = BUTTON_UP;
            dpad_mapped_right = BUTTON_DOWN;
            break;
        case RG_DISPLAY_ROTATION_RIGHT:
            width = HANDY_SCREEN_HEIGHT;
            height = HANDY_SCREEN_WIDTH;
            lynx->mMikie->SetRotation(MIKIE_ROTATE_R);
            dpad_mapped_up    = BUTTON_LEFT;
            dpad_mapped_down  = BUTTON_RIGHT;
            dpad_mapped_left  = BUTTON_DOWN;
            dpad_mapped_right = BUTTON_UP;
            break;
        default:
            width = HANDY_SCREEN_WIDTH;
            height = HANDY_SCREEN_HEIGHT;
            lynx->mMikie->SetRotation(MIKIE_NO_ROTATE);
            dpad_mapped_up    = BUTTON_UP;
            dpad_mapped_down  = BUTTON_DOWN;
            dpad_mapped_left  = BUTTON_LEFT;
            dpad_mapped_right = BUTTON_RIGHT;
            break;
    }

    updates[0]->width = width;
    updates[0]->height = height;
    updates[1]->width = width;
    updates[1]->height = height;
}

static CSystem *new_lynx(void)
{
    long displayformat = FB_PIXEL_FORMAT  == RG_PIXEL_565_BE ? MIKIE_PIXEL_FORMAT_16BPP_565_BE : MIKIE_PIXEL_FORMAT_16BPP_565;
    if (rg_extension_match(app->romPath, "zip"))
    {
        void *data;
        size_t size;
        if (!rg_storage_unzip_file(app->romPath, NULL, &data, &size, 0))
            RG_PANIC("ROM file unzipping failed!");
        CSystem *lynx = new CSystem((UBYTE*)data, size, displayformat, app->sampleRate);
        free(data);
        return lynx;
    }
    return new CSystem(app->romPath, displayformat, app->sampleRate);
}


static rg_gui_event_t rotation_cb(rg_gui_option_t *option, rg_gui_event_t event)
{
    int rotation = (int)rg_display_get_rotation();

    if (event == RG_DIALOG_PREV) {
        if (--rotation < 0) rotation = RG_DISPLAY_ROTATION_COUNT - 1;
        rg_display_set_rotation((display_rotation_t)rotation);
        set_display_mode();
        return RG_DIALOG_REDRAW;
    }
    if (event == RG_DIALOG_NEXT) {
        if (++rotation > RG_DISPLAY_ROTATION_COUNT - 1) rotation = 0;
        rg_display_set_rotation((display_rotation_t)rotation);
        set_display_mode();
        return RG_DIALOG_REDRAW;
    }

    strcpy(option->value, _("Off"));
    if (rotation == RG_DISPLAY_ROTATION_AUTO)  strcpy(option->value, _("Auto"));
    if (rotation == RG_DISPLAY_ROTATION_LEFT)  strcpy(option->value, _("Left"));
    if (rotation == RG_DISPLAY_ROTATION_RIGHT) strcpy(option->value, _("Right"));

    return RG_DIALOG_VOID;
}

static void event_handler(int event, void *arg)
{
    if (event == RG_EVENT_REDRAW)
    {
        rg_display_submit(currentUpdate, 0);
    }
}

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)
{
    bool ret = false;
    FILE *fp;

    if ((fp = fopen(filename, "wb")))
    {
        ret = lynx->ContextSave(fp);
        fclose(fp);
    }

    return ret;
}

static bool load_state_handler(const char *filename)
{
    bool ret = false;
    FILE *fp;

    if ((fp = fopen(filename, "rb")))
    {
        ret = lynx->ContextLoad(fp);
        fclose(fp);
    }

    if (!ret) lynx->Reset();

    return ret;
}

static bool reset_handler(bool hard)
{
    // This isn't nice but lynx->Reset() crashes...
    delete lynx;
    lynx = new_lynx();
    return true;
}

static void options_handler(rg_gui_option_t *dest)
{
    *dest++ = (rg_gui_option_t){0, _("Rotation"), (char *)"-", RG_DIALOG_FLAG_NORMAL, &rotation_cb};
    *dest++ = (rg_gui_option_t)RG_DIALOG_END;
}

extern "C" void lynx_main(void)
{
    const rg_handlers_t handlers = {
        .loadState = &load_state_handler,
        .saveState = &save_state_handler,
        .reset = &reset_handler,
        .screenshot = &screenshot_handler,
        .event = &event_handler,
        .memRead = NULL,
        .memWrite = NULL,
        .options = &options_handler,
        .about = NULL,
    };

    app = rg_system_reinit(AUDIO_SAMPLE_RATE, &handlers, NULL);

    // the HANDY_SCREEN_WIDTH * HANDY_SCREEN_WIDTH is deliberate because of rotation
    updates[0] = rg_surface_create(HANDY_SCREEN_WIDTH, HANDY_SCREEN_WIDTH, FB_PIXEL_FORMAT , MEM_FAST);
    updates[1] = rg_surface_create(HANDY_SCREEN_WIDTH, HANDY_SCREEN_WIDTH, FB_PIXEL_FORMAT , MEM_FAST);
    currentUpdate = updates[0];

    // Init emulator
    lynx = new_lynx();

    if (lynx->mFileType == HANDY_FILETYPE_ILLEGAL)
    {
        RG_PANIC("ROM loading failed!");
    }

    gPrimaryFrameBuffer = (UBYTE*)currentUpdate->data;
    gAudioBuffer = new SWORD[AUDIO_BUFFER_LENGTH * 2];
    gAudioEnabled = 1;

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

    set_display_mode();

    long skipFrames = 0;
    bool slowFrame = false;

    // Start emulation
    while (1)
    {
        const int64_t startTime = rg_system_timer();
        uint32_t joystick = rg_input_read_gamepad();
        bool drawFrame = !skipFrames;

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

        ULONG buttons = 0;
    	if (joystick & RG_KEY_UP)     buttons |= dpad_mapped_up;
    	if (joystick & RG_KEY_DOWN)   buttons |= dpad_mapped_down;
    	if (joystick & RG_KEY_LEFT)   buttons |= dpad_mapped_left;
    	if (joystick & RG_KEY_RIGHT)  buttons |= dpad_mapped_right;
    	if (joystick & RG_KEY_A)      buttons |= BUTTON_A;
    	if (joystick & RG_KEY_B)      buttons |= BUTTON_B;
    	if (joystick & RG_KEY_START)  buttons |= BUTTON_OPT2; // BUTTON_PAUSE
    	if (joystick & RG_KEY_SELECT) buttons |= BUTTON_OPT1;
        lynx->SetButtonData(buttons);

        lynx->UpdateFrame(drawFrame);

        if (drawFrame)
        {
            slowFrame = rg_display_is_busy();
            rg_display_submit(currentUpdate, 0);
            currentUpdate = updates[currentUpdate == updates[0]];
            gPrimaryFrameBuffer = (UBYTE*)currentUpdate->data;
        }

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

        // The Lynx has a variable tick rate, I don't know of a better way to guess than from audio stream
        rg_system_set_tick_rate(AUDIO_SAMPLE_RATE / (gAudioBufferPointer / 2));
        rg_system_tick(rg_system_timer() - startTime);

        rg_audio_submit((const rg_audio_frame_t *)gAudioBuffer, gAudioBufferPointer / 2);
        gAudioBufferPointer = 0;

        // See if we need to skip a frame to keep up
        if (skipFrames == 0)
        {
            int elapsed = rg_system_timer() - startTime;
            if (app->frameskip > 0)
                skipFrames = app->frameskip;
            // The Lynx uses a variable framerate so we use the count of generated audio samples as reference instead
            else if (elapsed > app->frameTime + 1500)
                skipFrames = 1; // (elapsed / frameTime)
            else if (drawFrame && slowFrame)
                skipFrames = 1;
        }
        else if (skipFrames > 0)
        {
            skipFrames--;
        }
    }
}
