/*
 * The memory module:
 *     basic I/O to memory
 *     mapping module I/O to specific memory locations
 *
 * This is tightly interwoven with the manager and perhaps they should be
 * merged.
 */

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>
#include <errno.h>

#include "memory.h"
#include "module_inc.h"

t_memory i_memory = {
    init, update
};

/*
 * Some nice wasteful memory arrays.
 * On most machines this'll probably consume around 960K, which is pretty poor
 * considering most beebs only have 64K.
 * The trade off (hopefully) is not too drastic a speed loss.
 */
#define MEM_SIZE 65536
#define MEM_BANKS 16
#define BANK_SIZE 16384 

byte mem[MEM_SIZE];
byte *swrmem_r;
byte *swrmem_w;
byte mem_swr[MEM_BANKS][BANK_SIZE];
byte blank_swr[BANK_SIZE]; /* throw away bank to simplify read only sideways
			    * RAM banks */

int bank_writable[16] = {
    0, 0, 0, 0,
    1, 1, 1, 1,
    0, 0, 0, 0,
    1, 1, 1, 1
};

/*
 * Astoundingly reasonable assumption here that we're never going to memory
 * map IO into some, but not all, sideways RAM banks. :)
 */
byte (*special_mem_r[MEM_SIZE])(word addr);
byte (*special_mem_w[MEM_SIZE])(word addr, byte val);

/* some static variables and function declarations */
static byte mem_rd(word addr);
static byte mem_wr(word addr, byte val);
static byte mem_nord(word addr);
static byte mem_nowr(word addr, byte val);
static byte mem_rdswr(word addr);
static byte mem_wrswr(word addr, byte val);
static byte write_fe30(word addr, byte val);
static int load(char *file, byte *addr);

static byte latch = 0x0b; /* where BASIC is */

static void init() {
    int i;

    /*
     * clear memory. Ought to be done anyway as they're static - but just
     * to make sure.
     */
    memset(mem, 0, MEM_SIZE * sizeof(byte));
    for (i = 0; i < MEM_BANKS; i++) {
	memset(mem_swr[i], 0, BANK_SIZE * sizeof(byte));
    }

    /*
     * Initialise simple_mem_r/w array. (RO = Read Only)
     * 0000 - 7FFF is simple
     * 8000 - BFFF is simple (read); maybe non simple (RO write)
     * C000 - FBFF is simple (read); non simple (RO write)
     * FC00 - FDFF is non simple ?
     * FE00 - FEFF is non simple (read & write - chip specific)
     * FF00 - FFFF is simple (read); non simple (RO write)
     */
    for (i = 0x0000; i <= 0x7FFF; i++) {
	special_mem_r[i] = mem_rd;
	special_mem_w[i] = mem_wr;
    }

    for (i = 0x8000; i <= 0xBFFF; i++) {
	special_mem_r[i] = mem_rdswr;
	special_mem_w[i] = mem_wrswr;
    }

    for (i = 0xC000; i <= 0xFBFF; i++) {
	special_mem_r[i] = mem_rd;
	special_mem_w[i] = mem_nowr;
    }

    for (i = 0xFC00; i <= 0xFDFF; i++) {
	special_mem_r[i] = mem_nord;
	special_mem_w[i] = mem_nowr;
    }

    for (i = 0xFE00; i <= 0xFEFF; i++) {
	special_mem_r[i] = mem_nord;
	special_mem_w[i] = mem_nowr;
    }
    special_mem_w[0xfe30] = write_fe30;

    for (i = 0xFF00; i <= 0xFFFF; i++) {
	special_mem_r[i] = mem_rd;
	special_mem_w[i] = mem_nowr;
    }

    /* Load in the ROMs etc */
    (void)load("data/basic.bbc", mem_swr[latch]);
    /* DFS currently does horrid 8271 checks - hangs without it! */
    /* (void)load("data/dfs143.bbc", mem_swr[1]); */
    (void)load("data/6502ass.bbc", mem_swr[8]);
    (void)load("data/HACKROM", mem_swr[9]);
/*    memcpy(&mem[0x8000], mem_swr[latch], 0x4000); */
    (void)load("data/newos.bbc", &mem[0xc000]);
    (void)load("data/0000-0DFF.dump", &mem[0x0000]);
    write_fe30(0, latch);

    /*
     * And now some hackery of beeb OS to redirect various IO functions
     * to our own UNIX ones.
     */

    /* osfsc */
    mem[0xf1b1] = 0xd4;
    mem[0xf1b2] = 0x1e;
    mem[0xf1b3] = 0x60;

    /* osfile */
    mem[0xf27d] = 0xd4;
    mem[0xf27e] = 0xdd;
    mem[0xf27f] = 0x60;
    
    /* oswrch */
    mem[0xe0a4] = 0xd4;
    mem[0xe0a5] = 0xee;
    mem[0xe0a6] = 0x60;
    /* osrdch */
    mem[0xdec5] = 0xd4;
    mem[0xdec6] = 0xe0;
    mem[0xdec7] = 0x60;

    /* osbyte
     * mem[0xe772] = 0xd4;
     * mem[0xe773] = 0xf4;
     * mem[0xe774] = 0x60;
     */

    /* osword
     * mem[0xe7eb] = 0xd4;
     * mem[0xe7ec] = 0xf1;
     * mem[0xe7ed] = 0x60;
     */

    /* irq1
     * mem[0xdc93] = 0xd4;
     * mem[0xdc94] = 0xfe;
     * mem[0xdc95] = 0x60;
     */

    /* irq2
     * mem[0xde89] = 0xd4;
     * mem[0xde8a] = 0xff;
     * mem[0xde8b] = 0x40;
     */

    /* interrupt address
     * mem[0xfffe] = 0x02;
     * mem[0xffff] = 0xb4;
     */
}

static time_t update(time_t inc) {
    static time_t time = 0;

    return time += inc;
}

static int load(char *file, byte *addr) {
    int fd;
    struct stat statbuf;

    if (-1 == stat(file, &statbuf)) {
	perror(file);
	return -1;
    }

    if (-1 == (fd = open(file, O_RDONLY))) {
	perror(file);
	return -1;
    }

    if (read(fd, addr, statbuf.st_size) != statbuf.st_size) {
	perror(file);
	return -1;
    }

    return 0;
}

/*
 * memory IO with some simple modules.
 * more complex ones have their own files
 */

byte mem_rd(word addr) {
    return mem[addr];
}

byte mem_rdswr(word addr) {
    return swrmem_r[addr];
}

byte mem_nord(word addr) {
    return 0xff;
}

byte mem_wr(word addr, byte val) {
    mem[addr] = val;
    return 0x00;
}

byte mem_wrswr(word addr, byte val) {
    swrmem_w[addr] = val;
    return 0x00;
}

byte mem_nowr(word addr, byte val) {
    return 0x00;
}

byte write_fe30(word addr, byte val) {
    latch = val & 0x0f;
    swrmem_r = mem_swr[latch] - 0x8000;
    swrmem_w = (bank_writable[latch])
	? mem_swr[latch] - 0x8000
	: blank_swr - 0x8000;

    /* tmp hack!. mmap() can do this better? */
    memcpy(&mem[0x8000], mem_swr[latch], 0x4000);

    return 0;
}
