/* ======================================================================== */
/*  Title:    Intellicart Emulation                                         */
/*  Author:   J. Zbiciak                                                    */
/* ------------------------------------------------------------------------ */
/*  This module implements Intellicart Emulation.  We use this emulation    */
/*  to emulate ALL Intellivision cartridges because it's much more          */
/*  convenient that way.                                                    */
/* ======================================================================== */

#include <assert.h>
#include "../config.h"
#include "periph/periph.h"
#include "cp1600/cp1600.h"
#include "lzoe/lzoe.h"
#include "file/file.h"
#include "icart/icart.h"
#include "metadata/metadata.h"

/* ======================================================================== */
/*  ICART_CALC_BS    -- Calculate an address for a bank-switched address.   */
/* ======================================================================== */
LOCAL INLINE uint_32 icart_calc_bs(icart_t *const ic, uint_32 const addr)
{
    return 0xFFFF & (addr + ic->bs_tbl[addr >> 11]);
}

/* ======================================================================== */
/*  ICART_RD_F       -- Read flat 16-bit Intellicart memory.                */
/*  ICART_RD_FN      -- Read flat 8-bit Intellicart memory.                 */
/*  ICART_RD_B       -- Read bank-switched 16-bit Intellicart memory.       */
/*  ICART_RD_BN      -- Read bank-switched 8-bit Intellicart memory.        */
/* ======================================================================== */
uint_32 icart_rd_NULL(periph_t *const per, periph_t *const ign, 
                      const uint_32 addr, const uint_32 data)
{
    UNUSED(per); UNUSED(ign); UNUSED(addr); UNUSED(data);
    return ~0U;
}
uint_32 icart_rd_f(periph_t *const per, periph_t *const ign,
                   const uint_32 addr, const uint_32 data)
{
    icart_t *const ic = (icart_t *)(per->parent);
    UNUSED(ign); UNUSED(data);
    return ic->rom.image[addr];
}
uint_32 icart_rd_fn(periph_t *const per, periph_t *const ign,
                    const uint_32 addr, const uint_32 data)
{
    icart_t *const ic = (icart_t *)(per->parent);
    UNUSED(ign); UNUSED(data);
    return ic->rom.image[addr] & 0xFF;
}
uint_32 icart_rd_b(periph_t *const per, periph_t *const ign,
                   const uint_32 addr, const uint_32 data)
{
    icart_t *const ic = (icart_t *)(per->parent);
    UNUSED(ign); UNUSED(data);
    return ic->rom.image[icart_calc_bs(ic, addr)];
}
uint_32 icart_rd_bn(periph_t *const per, periph_t *const ign,
                    const uint_32 addr, const uint_32 data)
{
    icart_t *const ic = (icart_t *)(per->parent);
    UNUSED(ign); UNUSED(data);
    return ic->rom.image[icart_calc_bs(ic, addr)] & 0xFF;
}

/* ======================================================================== */
/*  ICART_WR_F       -- Write flat 16-bit Intellicart memory.               */
/*  ICART_WR_FN      -- Write flat 8-bit Intellicart memory.                */
/*  ICART_WR_B       -- Write bank-switched 16-bit Intellicart memory.      */
/*  ICART_WR_BN      -- Write bank-switched 8-bit Intellicart memory.       */
/*  ICART_WR_BI      -- Write bank-switch 16-bit I-cart mem, w/ invalidate  */
/*  ICART_WR_BNI     -- Write bank-switch 8-bit I-cart mem, w/ invalidate   */
/* ======================================================================== */

void icart_wr_NULL(periph_t *const per, periph_t *const ign,
                   const uint_32 addr, const uint_32 data)
{
    UNUSED(per); UNUSED(ign); UNUSED(addr); UNUSED(data);
}
void icart_wr_f(periph_t *const per, periph_t *const ign,
                const uint_32 addr, const uint_32 data)
{
    icart_t *const ic = (icart_t *)(per->parent);
    UNUSED(ign);
    ic->rom.image[addr] = data;
}
void icart_wr_fn(periph_t *const per, periph_t *const ign,
                 const uint_32 addr, const uint_32 data)
{
    icart_t *const ic = (icart_t *)(per->parent);
    UNUSED(ign);
    ic->rom.image[addr] = data & 0xFF;
}
void icart_wr_b(periph_t *const per, periph_t *const ign,
                const uint_32 addr, const uint_32 data)
{
    icart_t *const ic = (icart_t *)(per->parent);
    UNUSED(ign);
    ic->rom.image[icart_calc_bs(ic, addr)] = data;
}
void icart_wr_bn(periph_t *const per, periph_t *const ign,
                 const uint_32 addr, const uint_32 data)
{
    icart_t *const ic = (icart_t *)(per->parent);
    UNUSED(ign);
    ic->rom.image[addr] = data & 0xFF;
}

/* ======================================================================== */
/*  ICART_RD_BS      -- Read from bankswitch registers.                     */
/*  ICART_WR_BS      -- Write to bankswitch registers.                      */
/* ======================================================================== */
uint_32 icart_rd_bs(periph_t *const per, periph_t *const ign,
                    const uint_32 addr, const uint_32 data)
{
    UNUSED(per); UNUSED(ign); UNUSED(addr); UNUSED(data);
    return ~0U;
}

void icart_wr_bs(periph_t *const per, periph_t *const ign,
                 const uint_32 w_addr, const uint_32 data)
{
    icart_t *const ic = (icart_t *)(per->parent);

    UNUSED(ign);

    /* Convert the register write address to an Intellivision address */
    const uint_32 i_addr = ((w_addr & 0xF) << 12) | ((w_addr & 0x10) << 7);

    const uint_32 b_idx =  i_addr >> 13;
    const uint_32 b_shf = (i_addr >> 8) & 31;
    if (((ic->rom.dobanksw[b_idx] >> b_shf) & 1) == 0)
        return;

    /* Convert the write data + Intellivision address to a bankswitch entry. */
    const uint_32 t_idx = i_addr >> 11;
    const uint_32 bs    = ((data << 8) - (i_addr & ~0x0700)) & 0xFFFF;

    ic->bs_tbl[t_idx] = bs;

    if (ic->cache_bs && ((ic->rom.readable[b_idx] >> b_shf) & 1) == 1)
    {
        cp1600_invalidate(ic->cpu, i_addr, i_addr + 0x00FF);
    }
}

/* ======================================================================== */
/*  These handy tables simplify initializing the Intellicart.               */
/* ======================================================================== */
typedef struct
{
    periph_rd_t read;
    periph_wr_t write;
    periph_rd_t peek;
    periph_wr_t poke;
} ic_init_t;

LOCAL const ic_init_t ic_init[] =
{
    {   icart_rd_f,     icart_wr_NULL,  icart_rd_f,     icart_wr_f      },
    {   icart_rd_NULL,  icart_wr_f,     icart_rd_f,     icart_wr_f      },
    {   icart_rd_f,     icart_wr_f,     icart_rd_f,     icart_wr_f      },

    {   icart_rd_fn,    icart_wr_NULL,  icart_rd_fn,    icart_wr_fn     },
    {   icart_rd_NULL,  icart_wr_fn,    icart_rd_fn,    icart_wr_fn     },
    {   icart_rd_fn,    icart_wr_fn,    icart_rd_fn,    icart_wr_fn     },

    {   icart_rd_b,     icart_wr_NULL,  icart_rd_b,     icart_wr_b      },
    {   icart_rd_NULL,  icart_wr_b,     icart_rd_b,     icart_wr_b      },
    {   icart_rd_b,     icart_wr_b,     icart_rd_b,     icart_wr_b      },

    {   icart_rd_bn,    icart_wr_NULL,  icart_rd_bn,    icart_wr_bn     },
    {   icart_rd_NULL,  icart_wr_bn,    icart_rd_bn,    icart_wr_bn     },
    {   icart_rd_bn,    icart_wr_bn,    icart_rd_bn,    icart_wr_bn     },
};

/* Legend: R == Readable, W == Writable, N == Narrow, B == Bankswitchable */

LOCAL const int ic_attr_map[16] =
{
/*       R       R         */
/*           W   W         */
    -1,  0,  1,  2, /*     */
    -1,  3,  4,  5, /* N   */
    -1,  6,  7,  8, /*   B */
    -1,  9, 10, 11, /* N B */
};

/* ======================================================================== */
/*  ICART_DTOR   -- Tear down the ICART_T                                   */
/* ======================================================================== */
LOCAL void icart_dtor(periph_p const p)
{
    icart_t *const icart = (icart_t *)p;

    if (icart->rom.metadata)
    {
        free_game_metadata(icart->rom.metadata);
        icart->rom.metadata = NULL;
    }
}

/* ======================================================================== */
/*  ICART_INIT       -- Initialize the Intellicart w/ a ROM image.          */
/* ======================================================================== */
int icart_init
(
    icart_t *const ic,
    LZFILE  *const rom,
    int      const randomize
)
{
    uint_8      *rom_img;
    periph_p    p[12];
    long        size;
    int         err;

    /* -------------------------------------------------------------------- */
    /*  Sanity checks.                                                      */
    /* -------------------------------------------------------------------- */
    if (!ic || !rom)
        return -1;

    /* -------------------------------------------------------------------- */
    /*  Clean things up.                                                    */
    /* -------------------------------------------------------------------- */
    memset(ic, 0, sizeof(icart_t));

    /* -------------------------------------------------------------------- */
    /*  If asked to randomize memory, do so.                                */
    /* -------------------------------------------------------------------- */
    if (randomize)
        for (int i = 0; i <= 0xFFFF; i++)
            ic->rom.image[i] = rand_jz();

    /* -------------------------------------------------------------------- */
    /*  The Intellicart offers multiple "periph" interfaces, each tuned     */
    /*  for a different type of memory.  This is done for performance.      */
    /*                                                                      */
    /*                  Read        Write       8-bit       Bankswitch      */
    /*      ic->r       Yes         No          No          No              */
    /*      ic->w       No          Yes         No          No              */
    /*      ic->rw      Yes         Yes         No          No              */
    /*      ic->rn      Yes         No          Yes         No              */
    /*      ic->wn      No          Yes         Yes         No              */
    /*      ic->rwn     Yes         Yes         Yes         No              */
    /*      ic->rb      Yes         No          No          Yes             */
    /*      ic->wb      No          Yes         No          Yes             */
    /*      ic->rwb     Yes         Yes         No          Yes             */
    /*      ic->rnb     Yes         No          Yes         Yes             */
    /*      ic->wnb     No          Yes         Yes         Yes             */
    /*      ic->rwnb    Yes         Yes         Yes         Yes             */
    /* -------------------------------------------------------------------- */
    p[ 0] = &(ic->r);               p[ 6] = &(ic->rb);
    p[ 1] = &(ic->w);               p[ 7] = &(ic->wb);
    p[ 2] = &(ic->rw);              p[ 8] = &(ic->rwb);
    p[ 3] = &(ic->rn);              p[ 9] = &(ic->rnb);
    p[ 4] = &(ic->wn);              p[10] = &(ic->wnb);
    p[ 5] = &(ic->rwn);             p[11] = &(ic->rwnb);

    for (int i = 0; i < 12; i++)
    {
        p[i]->read      = ic_init[i].read;
        p[i]->write     = ic_init[i].write;
        p[i]->peek      = ic_init[i].peek;
        p[i]->poke      = ic_init[i].poke;
        p[i]->tick      = NULL;
        p[i]->min_tick  = ~0U;
        p[i]->max_tick  = ~0U;
        p[i]->addr_base = 0x000;
        p[i]->addr_mask = 0xFFFF;
        p[i]->parent    = (void*)ic;
    }

    /* -------------------------------------------------------------------- */
    /*  Now, read in the .ROM file.                                         */
    /* -------------------------------------------------------------------- */
    size = file_length(rom);
    if (size <= 52)
    {
        fprintf(stderr, "icart:  Short file?\n");
        return -1;      /* Short file, or seek failed */
    }

    if ((rom_img = CALLOC(uint_8, size)) == NULL)
    {
        fprintf(stderr, "icart:  Out of memory.\n");
        return -1;
    }

    lzoe_rewind(rom);
    if ((long)lzoe_fread(rom_img, 1, size, rom) != size)
    {
        fprintf(stderr, "icart:  Short read while reading ROM.\n");
        return -1;
    }

    if ((err = icartrom_decode(&(ic->rom), rom_img, size, 0, 0)) < 0)
    {
        fprintf(stderr, "icart:  Error %d while decoding ROM.\n", -err);
        return -1;
    }

    free(rom_img);

    /* -------------------------------------------------------------------- */
    /*  If asked to randomize, also randomize the bankswitch table.         */
    /*  This happens /after/ loading the ROM, since we need to know which   */
    /*  segments are bankswitched.                                          */
    /* -------------------------------------------------------------------- */
    if (randomize)
        for (int i = 0x00; i < 0x1F; i++)
            icart_wr_bs((periph_t*)ic, NULL, i, rand_jz());

    /* -------------------------------------------------------------------- */
    /*  Base Intellicart peripheral, responsible for cleanup.               */
    /* -------------------------------------------------------------------- */
    ic->base.read      = NULL;
    ic->base.write     = NULL;
    ic->base.peek      = NULL;
    ic->base.poke      = NULL;
    ic->base.tick      = 0;
    ic->base.max_tick  = ~0U;
    ic->base.min_tick  = ~0U;
    ic->base.addr_base = 0;
    ic->base.addr_mask = 0;
    ic->base.parent    = ic;
    ic->base.dtor      = icart_dtor;

    return 0;
}

/* ======================================================================== */
/*  ICART_REGISTER   -- The Intellicart is unique in that it will register  */
/*                      itself on the peripheral bus.                       */
/* ======================================================================== */
int icart_register
(
    icart_t     *const ic,
    periph_bus_p const bus,
    cp1600_t    *const cpu,
    uint_32      const cache_flags
)
{
    int         attr, p_attr;
    char        buf[17];
    int         has_banksw = 0;
    uint_32     addr_lo, addr_hi;
    periph_p    p[12];
    int         warn_jlp = 0;
    const char* name = "ICart";

    /* -------------------------------------------------------------------- */
    /*  Sanity checks.                                                      */
    /* -------------------------------------------------------------------- */
    if (!ic || !bus)
        return -1;

    /* -------------------------------------------------------------------- */
    /*  See table above in icart_init for explanation.                      */
    /* -------------------------------------------------------------------- */
    p[ 0] = &(ic->r);               p[ 6] = &(ic->rb);
    p[ 1] = &(ic->w);               p[ 7] = &(ic->wb);
    p[ 2] = &(ic->rw);              p[ 8] = &(ic->rwb);
    p[ 3] = &(ic->rn);              p[ 9] = &(ic->rnb);
    p[ 4] = &(ic->wn);              p[10] = &(ic->wnb);
    p[ 5] = &(ic->rwn);             p[11] = &(ic->rwnb);

    /* -------------------------------------------------------------------- */
    /*  If someone selected JLP support, at least warn when the memory map  */
    /*  may do something bad to us.                                         */
    /* -------------------------------------------------------------------- */
    const int has_jlp = 
        (ic->rom.metadata && ic->rom.metadata->jlp_accel > JLP_DISABLED);

    /* -------------------------------------------------------------------- */
    /*  Register ourself on the peripheral bus.  Also, set up the CPU       */
    /*  cacheability of each range we register.                             */
    /* -------------------------------------------------------------------- */
    has_banksw = 0;
    ic->cpu = cpu;
    p_attr  = -1;
    addr_lo = addr_hi = -1;
    for (unsigned i = 0; i <= 256; i++)
    {
        const unsigned idx = i >> 5;
        const unsigned shf = i & 31;

        attr = i >= 256 ? -1
                        : (int)((1 & ((ic->rom.readable[idx] >> shf) << 0)) |
                                (2 & ((ic->rom.writable[idx] >> shf) << 1)) |
                                (4 & ((ic->rom.narrow  [idx] >> shf) << 2)) |
                                (8 & ((ic->rom.dobanksw[idx] >> shf) << 3)));

        /* Check for overlap between ICart and JLP in JLP's special RAM area */
        if (has_jlp && (attr & 1) == 1 && i >= 0x80 && i <= 0x9F)
            warn_jlp = 1;

        if (attr != p_attr)
        {
            if (p_attr > 0 && ic_attr_map[p_attr] >= 0)
            {
                snprintf(buf, sizeof(buf),
                        "ICart   [%c%c%c%c]",
                        p_attr & 1 ? 'R' : ' ',
                        p_attr & 2 ? 'W' : ' ',
                        p_attr & 4 ? 'N' : ' ',
                        p_attr & 8 ? 'B' : ' ');

                periph_register(bus, p[ic_attr_map[p_attr]],
                                addr_lo, addr_hi, buf);
                if (p_attr & ICARTROM_BANKSW)
                    has_banksw = 1;

                if (cache_flags & (1u << p_attr))
                {
                    int snoop;

                    snoop = (cache_flags >> (p_attr + 16)) & 1;
                    cp1600_cacheable(cpu, addr_lo, addr_hi, snoop);
                }
            }
            addr_lo = i << 8;
            addr_hi = addr_lo + 0xFF;
            p_attr  = attr;
        } else
        {
            addr_hi = (i << 8) + 0xFF;
        }
    }

    /* -------------------------------------------------------------------- */
    /*  Did we see a problem with JLP?  Print a warning to stderr.          */
    /* -------------------------------------------------------------------- */
    if (warn_jlp)
        fprintf(stderr, 
"icart: JLP is enabled, but Intellicart ROM mapped part of $8000 - $9FFF\n"
"       as readable memory.  This may result in incorrect JLP behavior.\n"
"       Remove the readable Intellicart ROM mapping in this address range\n"
"       when using JLP accelerators.  Mark this region's memory attributes\n"
"       as \"-R\" or \"-RWBN\", for example.\n"
"\n"
"       See http://spatula-city.org/~im14u2c/intv/icart_jlp.html .\n");

    /* -------------------------------------------------------------------- */
    /*  If this cartridge image uses bankswitching, reconfigure the base    */
    /*  peripheral to monitor the bankswitch registers.                     */
    /* -------------------------------------------------------------------- */
    if (has_banksw)
    {
        if (cache_flags & (IC_C_B__R | IC_C_B_WR | IC_C_BN_R | IC_C_BNWR))
            ic->cache_bs = 1;

        ic->base.read      = icart_rd_bs;
        ic->base.write     = icart_wr_bs;
        ic->base.peek      = icart_rd_bs;
        ic->base.poke      = icart_wr_bs;
        ic->base.tick      = 0;
        ic->base.max_tick  = ~0U;
        ic->base.min_tick  = ~0U;
        ic->base.addr_base = 0x40;
        ic->base.addr_mask = 0x1F;
        name = "ICart BankSw";
    }

    periph_register(bus, &(ic->base), ic->base.addr_base,
                    ic->base.addr_base + ic->base.addr_mask, name);

    return 0;
}

/* ======================================================================== */
/*  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 2 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, write to the Free Software             */
/*  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.               */
/* ======================================================================== */
/*                 Copyright (c) 2001-+Inf, Joseph Zbiciak                  */
/* ======================================================================== */
