/***************************************************************************
 *   Copyright (C) 2021 - 2023 by Alessio Caiazza IU5BON                   *
 *                                                                         *
 *   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 3 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, see <http://www.gnu.org/licenses/>   *
 ***************************************************************************/

#include <stdlib.h>
#include <pthread.h>
#include <state.h>
#include "sdl_engine.h"
#include "emulator.h"

chan_t fb_sync;                 // Shared channel to receive frame buffer updates
Uint32 SDL_Screenshot_Event;    // Shared custom SDL event to request a screenshot
Uint32 SDL_Backlight_Event;     // Shared custom SDL event to change backlight

static SDL_Window   *window;
static SDL_Renderer *renderer;
static SDL_Texture  *displayTexture;

static bool       ready = false;  // Signal if the main loop is ready
static keyboard_t sdl_keys;       // Store the keyboard status


static bool sdk_key_code_to_key(SDL_Keycode sym, keyboard_t *key)
{
    switch (sym)
    {
        case SDLK_0:
            *key = KEY_0;
            return true;

        case SDLK_1:
            *key = KEY_1;
            return true;

        case SDLK_2:
            *key = KEY_2;
            return true;

        case SDLK_3:
            *key = KEY_3;
            return true;

        case SDLK_4:
            *key = KEY_4;
            return true;

        case SDLK_5:
            *key = KEY_5;
            return true;

        case SDLK_6:
            *key = KEY_6;
            return true;

        case SDLK_7:
            *key = KEY_7;
            return true;

        case SDLK_8:
            *key = KEY_8;
            return true;

        case SDLK_9:
            *key = KEY_9;
            return true;

        case SDLK_ASTERISK:
            *key = KEY_STAR;
            return true;

        case SDLK_ESCAPE:
            *key = KEY_ESC;
            return true;

        case SDLK_LEFT:
            *key = KEY_LEFT;
            return true;

        case SDLK_RIGHT:
            *key = KEY_RIGHT;
            return true;

        case SDLK_RETURN:
            *key = KEY_ENTER;
            return true;

        case SDLK_HASH:
            *key = KEY_HASH;
            return true;

        case SDLK_n:
            *key = KEY_F1;
            return true;

        case SDLK_m:
            *key = KEY_MONI;
            return true;

        case SDLK_PAGEUP:
            *key = KNOB_LEFT;
            return true;

        case SDLK_PAGEDOWN:
            *key = KNOB_RIGHT;
            return true;

        case SDLK_UP:
            *key = KEY_UP;
            return true;

        case SDLK_DOWN:
            *key = KEY_DOWN;
            return true;

        default:
            return false;
    }
}

static int screenshot_display(const char *filename)
{
    /*
     * https://stackoverflow.com/a/48176678
     * user1902824
     * modified to keep renderer and display texture references in the body
     * rather than as a parameter
     */
    SDL_Renderer *ren = renderer;
    SDL_Texture  *tex = displayTexture;
    int err = 0;

    SDL_Texture *ren_tex;
    SDL_Surface *surf;
    int st;
    int w;
    int h;
    int format;
    void *pixels;

    pixels  = NULL;
    surf    = NULL;
    ren_tex = NULL;
    format  = SDL_PIXELFORMAT_RGBA32;

    /* Get information about texture we want to save */
    st = SDL_QueryTexture(tex, NULL, NULL, &w, &h);

    if (st != 0)
    {
        SDL_Log("Failed querying texture: %s\n", SDL_GetError());
        err++;
        goto cleanup;
    }

    ren_tex = SDL_CreateTexture(ren, format, SDL_TEXTUREACCESS_TARGET, w, h);

    if (!ren_tex)
    {
        SDL_Log("Failed creating render texture: %s\n", SDL_GetError());
        err++;
        goto cleanup;
    }

    /*
     * Initialize our canvas, then copy texture to a target whose pixel data we
     * can access
     */
    st = SDL_SetRenderTarget(ren, ren_tex);

    if (st != 0)
    {
        SDL_Log("Failed setting render target: %s\n", SDL_GetError());
        err++;
        goto cleanup;
    }

    SDL_SetRenderDrawColor(ren, 0x00, 0x00, 0x00, 0x00);
    SDL_RenderClear(ren);
    st = SDL_RenderCopy(ren, tex, NULL, NULL);

    if (st != 0)
    {
        SDL_Log("Failed copying texture data: %s\n", SDL_GetError());
        err++;
        goto cleanup;
    }

    /* Create buffer to hold texture data and load it */
    pixels = malloc(w * h * SDL_BYTESPERPIXEL(format));

    if (!pixels)
    {
        SDL_Log("Failed allocating memory\n");
        err++;
        goto cleanup;
    }

    st = SDL_RenderReadPixels(ren, NULL, format, pixels,
                              w * SDL_BYTESPERPIXEL(format));
    if (st != 0)
    {
        SDL_Log("Failed reading pixel data: %s\n", SDL_GetError());
        err++;
        goto cleanup;
    }

    /* Copy pixel data over to surface */
    surf = SDL_CreateRGBSurfaceWithFormatFrom(pixels, w, h,
                                              SDL_BITSPERPIXEL(format),
                                              w * SDL_BYTESPERPIXEL(format),
                                              format);
    if (!surf)
    {
        SDL_Log("Failed creating new surface: %s\n", SDL_GetError());
        err++;
        goto cleanup;
    }

    /* Save result to an image */
    st = SDL_SaveBMP(surf, filename);

    if (st != 0)
    {
        SDL_Log("Failed saving image: %s\n", SDL_GetError());
        err++;
        goto cleanup;
    }

    SDL_Log("Saved texture as BMP to \"%s\"\n", filename);

cleanup:
    SDL_FreeSurface(surf);
    free(pixels);
    SDL_DestroyTexture(ren_tex);
    return err;
}

static bool set_brightness(uint8_t brightness)
{
    /*
     * When this texture is rendered, during the copy operation each source
     * color channel is modulated by the appropriate color value according to
     * the following formula:
     * srcC = srcC * (color / 255)
     *
     * Color modulation is not always supported by the renderer;
     * it will return -1 if color modulation is not supported.
     */
    int colMod = SDL_SetTextureColorMod(displayTexture,
                                        brightness,
                                        brightness,
                                        brightness) == 0;

    SDL_RenderCopy(renderer, displayTexture, NULL, NULL);
    SDL_RenderPresent(renderer);

    return colMod;
}



void sdlEngine_init()
{
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) < 0)
    {
        printf("SDL video init error!!\n");
        exit(1);
    }

    // Register SDL custom events to handle screenshot requests and backlight
    SDL_Screenshot_Event = SDL_RegisterEvents(2);
    SDL_Backlight_Event = SDL_Screenshot_Event+1;

    chan_init(&fb_sync);

    window = SDL_CreateWindow("OpenRTX",
                              SDL_WINDOWPOS_UNDEFINED,
                              SDL_WINDOWPOS_UNDEFINED,
                              SCREEN_WIDTH * 3, SCREEN_HEIGHT * 3,
                              SDL_WINDOW_SHOWN );

    renderer = SDL_CreateRenderer(window, -1, 0);
    SDL_RenderSetLogicalSize(renderer, SCREEN_WIDTH, SCREEN_HEIGHT);
    displayTexture = SDL_CreateTexture(renderer,
                                       PIXEL_FORMAT,
                                       SDL_TEXTUREACCESS_STREAMING,
                                       SCREEN_WIDTH,
                                       SCREEN_HEIGHT);
    SDL_RenderClear(renderer);

    // Setting brightness also triggers a render
    set_brightness(state.settings.brightness);
}

/*
 * SDL main loop. Due to macOS restrictions, this must run on the Main Thread.
 */
void sdlEngine_run()
{
    ready = true;

    SDL_Event ev = { 0 };

    while (!emulator_state.powerOff)
    {
        keyboard_t key = 0;

        if (SDL_PollEvent(&ev) == 1)
        {
            switch (ev.type)
            {
                case SDL_QUIT:
                    emulator_state.powerOff = true;
                    break;

                case SDL_KEYDOWN:
                    if (sdk_key_code_to_key(ev.key.keysym.sym, &key))
                    {
                        sdl_keys |= key;
                    }
                    break;

                case SDL_KEYUP:
                    if (sdk_key_code_to_key(ev.key.keysym.sym, &key))
                    {
                        sdl_keys ^= key;
                    }
                    break;
            }

            if (ev.type == SDL_Screenshot_Event)
            {
                char *filename = (char *)ev.user.data1;
                screenshot_display(filename);
                free(ev.user.data1);
            }
            else if (ev.type == SDL_Backlight_Event)
            {
                set_brightness(*((uint8_t*)ev.user.data1));
                free(ev.user.data1);
            }
        }

        // we update the window only if there is a something ready to render
        if (chan_can_send(&fb_sync))
        {
            PIXEL_SIZE *pixels;
            int pitch = 0;

            if (SDL_LockTexture(displayTexture, NULL,
                                (void **) &pixels, &pitch) < 0)
            {
                SDL_Log("SDL_lock failed: %s", SDL_GetError());
            }

            chan_send(&fb_sync, pixels);
            chan_recv(&fb_sync, NULL);

            SDL_UnlockTexture(displayTexture);
            SDL_RenderCopy(renderer, displayTexture, NULL, NULL);
            SDL_RenderPresent(renderer);
        }
    }

    printf("Terminating SDL display emulator, goodbye!\n");

    SDL_DestroyTexture(displayTexture);
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();
}

bool sdlEngine_ready()
{
    /*
     * bool is an atomic data type for x86 and it can be returned safely without
     * incurring in data races between threads.
     */
    return ready;
}

keyboard_t sdlEngine_getKeys()
{
    /*
     * keyboard_t is an atomic data type for x86 and it can be returned safely
     * without incurring in data races between threads.
     */
    return sdl_keys;
}
