/*
 * TR8 VM Player
 * Copyright (C) 2023 by Juan J. Martinez <jjm@usebox.net>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 */

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

#include "SDL.h"

#include "vm.h"

#define ARGB(r, g, b) ((uint32_t)(((r)<<16)|((g)<<8)|(b)))

#define APP_NAME "TR8 VM Player"
#define APP_VERSION "0.1-alpha"

#define TR8_W 128
#define TR8_H 128

#define WINDOW_SCALE 4
#define WINDOW_W (TR8_W * WINDOW_SCALE)
#define WINDOW_H (TR8_H * WINDOW_SCALE)

static void resize_full_screen(SDL_Window *win, SDL_Renderer *renderer, int fullscreen, SDL_Rect *s)
{
    if (!fullscreen)
    {
        SDL_SetWindowFullscreen(win, 0);
        s->x = 0;
        s->y = 0;
        s->w = WINDOW_W;
        s->h = WINDOW_H;
    }
    else
    {
        SDL_SetWindowFullscreen(win, SDL_WINDOW_FULLSCREEN_DESKTOP);

        int sw, sh, w, h, scale;

        SDL_GetRendererOutputSize(renderer, &w, &h);

        sw = w / TR8_W;
        sh = h / TR8_H;
        scale = sw > sh ? sh : sw;

        s->x = (w - (TR8_W * scale)) / 2;
        s->y = (h - (TR8_H * scale)) / 2;
        s->w = TR8_W * scale;
        s->h = TR8_H * scale;
    }
}

static uint32_t palette[] =
{
    ARGB(0x00, 0x00, 0x00),
    ARGB(0x00, 0x00, 0xaa),
    ARGB(0x00, 0xaa, 0x00),
    ARGB(0x00, 0xaa, 0xaa),
    ARGB(0xaa, 0x00, 0x00),
    ARGB(0xaa, 0x00, 0xaa),
    ARGB(0xaa, 0x55, 0x00),
    ARGB(0xaa, 0xaa, 0xaa),
    ARGB(0x55, 0x55, 0x55),
    ARGB(0x55, 0x55, 0xff),
    ARGB(0x55, 0xff, 0x55),
    ARGB(0x55, 0xff, 0xff),
    ARGB(0xff, 0x55, 0x55),
    ARGB(0xff, 0x55, 0xff),
    ARGB(0xff, 0xff, 0x55),
    ARGB(0xff, 0xff, 0xff),
};

uint8_t ram[UINT16_MAX + 1] = { 0 };
uint32_t *fb_data = NULL;

void write_m(uint16_t addr, uint8_t b)
{
    if (addr >= VIDEO_RAM && addr < (VIDEO_RAM + 16384))
        fb_data[addr - VIDEO_RAM] = palette[b & 15];

    ram[addr] = b;
}

uint8_t read_m(uint16_t addr)
{
    return ram[addr];
}

int main(int argc, char *argv[])
{
    FILE *fd;
    size_t size;

    if (argc < 2)
    {
        fprintf(stderr, "Usage: input.prg\n");
        return 1;
    }
    fd = fopen(argv[1], "rb");
    if (!fd)
    {
        fclose(fd);
        fprintf(stderr, "Error opening input\n");
        return 1;
    }
    fseek(fd, 0, SEEK_END);
    size = ftell(fd);
    if (size > UINT16_MAX + 1)
    {
        fclose(fd);
        fprintf(stderr, "Input is too large\n");
        return 1;
    }
    fseek(fd, 0, SEEK_SET);
    if (fread(ram, 1, size, fd) != size)
    {
        fclose(fd);
        fprintf(stderr, "Error reading input\n");
        return 1;
    }
    fclose(fd);

    SDL_Window *screen = SDL_CreateWindow(APP_NAME " " APP_VERSION,
                                          SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
                                          WINDOW_W, WINDOW_H,
                                          SDL_WINDOW_OPENGL);
    if (!screen)
    {
        fprintf(stderr, "Failed to create a window: %s\n", SDL_GetError());
        return 1;
    }

    SDL_Renderer *renderer = SDL_CreateRenderer(screen, -1, SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_ACCELERATED);
    if (!renderer)
    {
        fprintf(stderr, "Failed to create the renderer: %s\n", SDL_GetError());
        return 1;
    }
    SDL_SetHint("SDL_HINT_RENDER_SCALE_QUALITY", "0");

    SDL_Texture *canvas = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, TR8_W, TR8_H);
    if (!canvas)
    {
        fprintf(stderr, "Failed to create the canvas: %s\n", SDL_GetError());
        return 1;
    }

    SDL_Texture *fb = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, TR8_W, TR8_H);
    if (!fb)
    {
        fprintf(stderr, "Failed to create the frame-buffer: %s\n", SDL_GetError());
        return 1;
    }
    SDL_SetTextureBlendMode(fb, SDL_BLENDMODE_NONE);

    int fullscreen = 0;
    SDL_Rect dst;
    resize_full_screen(screen, renderer, fullscreen, &dst);

    SDL_RenderClear(renderer);
    SDL_RenderPresent(renderer);

    Tr8 vm;
    tr8_init(&vm, write_m, read_m);
    int pitch = 0;
    uint8_t rc;

    SDL_Event ev;
    while (1)
    {
        if (SDL_PollEvent(&ev))
        {
            if (ev.type == SDL_QUIT)
                break;

            if (ev.type == SDL_KEYDOWN)
            {
                if (ev.key.keysym.sym == SDLK_ESCAPE)
                {
                    if (fullscreen)
                        resize_full_screen(screen, renderer, 0, &dst);
                    break;
                }
                if (ev.key.keysym.sym == SDLK_RETURN && (SDL_GetModState() & KMOD_LALT))
                {
                    fullscreen ^= 1;
                    resize_full_screen(screen, renderer, fullscreen, &dst);
                    continue;
                }
            }
        }

        pitch = 0;
        fb_data = NULL;
        SDL_LockTexture(fb, NULL, (void **)&fb_data, &pitch);
        rc = tr8_eval(&vm);
        SDL_UnlockTexture(fb);

        if (!rc)
            break;

        /* render to the canvas */
        SDL_SetRenderTarget(renderer, canvas);
        SDL_RenderClear(renderer);

        SDL_RenderCopy(renderer, fb, NULL, NULL);

        /* now target the screen */
        SDL_SetRenderTarget(renderer, NULL);
        SDL_RenderClear(renderer);
        SDL_RenderCopy(renderer, canvas, NULL, &dst);

        SDL_RenderPresent(renderer);

        pitch = 0;
        fb_data = NULL;
        SDL_LockTexture(fb, NULL, (void **)&fb_data, &pitch);
        rc = tr8_frame_int(&vm);
        SDL_UnlockTexture(fb);

        if (!rc)
            break;
    }

    if (canvas)
        SDL_DestroyTexture(canvas);
    if (fb)
        SDL_DestroyTexture(fb);

    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(screen);

    return 0;
}