/*
 * 86Box    A hypervisor and IBM PC system emulator that specializes in
 *          running old operating systems and software designed for IBM
 *          PC systems and compatibles from 1981 through fairly recent
 *          system designs based on the PCI bus.
 *
 *          This file is part of the 86Box distribution.
 *
 *          Implementation of the PCjr cartridge emulation.
 *
 * Authors: Miran Grca, <mgrca8@gmail.com>
 *
 *          Copyright 2021 Miran Grca.
 */
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#define HAVE_STDARG_H
#include <86box/86box.h>
#include "cpu.h"
#include <86box/timer.h>
#include <86box/plat.h>
#include <86box/ui.h>
#include <86box/mem.h>
#include <86box/machine.h>
#include <86box/cartridge.h>

typedef struct cart_t {
    uint8_t *buf;
    uint32_t base;
} cart_t;

char cart_fns[2][512];
char *cart_image_history[2][CART_IMAGE_HISTORY];

static cart_t carts[2];

static mem_mapping_t cart_mappings[2];

#ifdef ENABLE_CARTRIDGE_LOG
int cartridge_do_log = ENABLE_CARTRIDGE_LOG;

static void
cartridge_log(const char *fmt, ...)
{
    va_list ap;

    if (cartridge_do_log) {
        va_start(ap, fmt);
        pclog_ex(fmt, ap);
        va_end(ap);
    }
}
#else
#    define cartridge_log(fmt, ...)
#endif

static uint8_t
cart_read(uint32_t addr, void *priv)
{
    const cart_t *dev = (cart_t *) priv;

    return dev->buf[addr - dev->base];
}

static void
cart_load_error(int drive, UNUSED(char *fn))
{
    cartridge_log("Cartridge: could not load '%s'\n", fn);
    memset(cart_fns[drive], 0, sizeof(cart_fns[drive]));
    ui_sb_update_icon_state(SB_CARTRIDGE | drive, 1);
}

static void
cart_image_close(int drive)
{
    if (carts[drive].buf != NULL) {
        free(carts[drive].buf);
        carts[drive].buf = NULL;
    }

    carts[drive].base = 0x00000000;

    mem_mapping_disable(&cart_mappings[drive]);
}

static void
cart_image_load(int drive, char *fn)
{
    FILE    *fp   = NULL;
    uint32_t size = 0;
    uint32_t base = 0x00000000;

    cart_image_close(drive);

    fp = fopen(fn, "rb");
    if (fseek(fp, 0, SEEK_END) == -1)
        fatal("cart_image_load(): Error seeking to the end of the file\n");
    size = ftell(fp);
    if (size < 0x1200) {
        cartridge_log("cart_image_load(): File size %i is too small\n", size);
        cart_load_error(drive, fn);
        fclose(fp);
        return;
    }
    if (size & 0x00000fff) {
        size -= 0x00000200;
        fseek(fp, 0x000001ce, SEEK_SET);
        (void) !fread(&base, 1, 2, fp);
        base <<= 4;
        fseek(fp, 0x00000200, SEEK_SET);
        carts[drive].buf = (uint8_t *) calloc(1, size);
        (void) !fread(carts[drive].buf, 1, size, fp);
        fclose(fp);
    } else {
        base = drive ? 0xe0000 : 0xd0000;
        if (size == 32768)
            base += 0x8000;
        fseek(fp, 0x00000000, SEEK_SET);
        carts[drive].buf = (uint8_t *) calloc(1, size);
        (void) !fread(carts[drive].buf, 1, size, fp);
        fclose(fp);
    }

    cartridge_log("cart_image_load(): %s at %08X-%08X\n", fn, base, base + size - 1);
    carts[drive].base = base;
    mem_mapping_set_addr(&cart_mappings[drive], base, size);
    mem_mapping_set_exec(&cart_mappings[drive], carts[drive].buf);
    mem_mapping_set_p(&cart_mappings[drive], &(carts[drive]));
}

static void
cart_load_common(int drive, char *fn, uint8_t hard_reset)
{
    FILE *fp = NULL;

    cartridge_log("Cartridge: loading drive %d with '%s'\n", drive, fn);

    if (!fn)
        return;
    fp = plat_fopen(fn, "rb");
    if (fp) {
        fclose(fp);
        strcpy(cart_fns[drive], fn);
        cart_image_load(drive, cart_fns[drive]);
        /* On the real PCjr, inserting a cartridge causes a reset
           in order to boot from the cartridge. */
        if (!hard_reset)
            resetx86();
    } else
        cart_load_error(drive, fn);
}

void
cart_load(int drive, char *fn)
{
    cart_load_common(drive, fn, 0);
}

void
cart_close(int drive)
{
    cartridge_log("Cartridge: closing drive %d\n", drive);

    cart_image_close(drive);
    cart_fns[drive][0] = 0;
    ui_sb_update_icon_state(SB_CARTRIDGE | drive, 1);
    resetx86();
}

void
cart_reset(void)
{
    cart_image_close(1);
    cart_image_close(0);

    if (!machine_has_cartridge(machine))
        return;

    for (uint8_t i = 0; i < 2; i++) {
        mem_mapping_add(&cart_mappings[i], 0x000d0000, 0x00002000,
                        cart_read, NULL, NULL,
                        NULL, NULL, NULL,
                        NULL, MEM_MAPPING_EXTERNAL, NULL);
        mem_mapping_disable(&cart_mappings[i]);
    }

    cart_load_common(0, cart_fns[0], 1);
    cart_load_common(1, cart_fns[1], 1);
}
