/*******************************************************
 ** mem.h                                             **
 ** Memory-Handler                                    **
 ** Speicherzugriffe, RAM,ROM,Hardware                **
 *******************************************************/

#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include "mem.h"
#include "atari.h"

#ifndef O_BINARY
#define O_MODE O_RDONLY
#else
#define O_MODE (O_RDONLY | O_BINARY)
#endif

#ifdef VMS
#include <unixio.h>
#include <file.h>
#else
#include <fcntl.h>
#endif

mtype memory[65536];
mtype *XE_Banks[5];  /* #5 is a temporary buffer */
MemGet mget[65536];
MemPut mput[65536];

static struct Cart *current=NULL;  /* currently inserted LEFT cartridge */
static struct Cart *basic=NULL;    /* XL,XE basic */
static struct Cart *os=NULL;       /* main OS, 0xe000,0xffff */
static struct Cart *mpack=NULL;    /* math pack, 0xd800,0xdfff */
static struct Cart *os_x=NULL;     /* XL OS extension 0xc000,0xcfff */
static struct Cart *selftest=NULL; /* XL selftest 0xd000,0xd7ff */

static int basic_on=FALSE;

static char *rcsid = "$Id: mem.c,v 1.00 1998/02/17 thor";

static struct Cart *ReadCart(char *filename, int fd,int addr, int nbytes);



mtype NoGet(void)
{
  return ~0;
}

int NoPut(mtype byte)
{
  return 0;
}

void SetRAM(int lower,int upper)
{
int i;

  for(i=lower;i<=upper;i++) {
     mput[i] = NULL;
     mget[i] = NULL;
   }
}

void SetROM(int lower,int upper)
{
int i;

   for(i=lower;i<=upper;i++) {
     mput[i] = &NoPut;
     mget[i] = NULL;
   }
}

void SetHW(int addr,int mask,MemGet read,MemPut write)
{
int i;
int lo,hi;

  lo = addr & 0xff00;
  hi = lo + 0x100;

  for(i=lo;i<hi;i++) {
    if ((i & mask)==addr) {
      if (read)  mget[i] = read;
      else       mget[i] = &NoGet;

      if (write) mput[i] = write;
      else       mput[i] = &NoPut;
    }
  }
}


void SetBlank(int lower,int upper)
{
int i;

   for(i=lower;i<=upper;i++) {
     mget[i] = &NoGet;
     mput[i] = &NoPut;
   }
}


void EnablePILL (void)
{
  SetROM (0x8000, 0xbfff);
}

void CopyFromMem(ATPtr from,UBYTE *to,int size)
{
  memcpy(to,from+memory,size);
}


void CopyToMem(UBYTE *from,ATPtr to,int size)
{
int i;

   for(i=0;i<size;i++) {
     Poke(to,*from);
     from++,to++;
   }
}

struct Cart *AllocCart(int size)
{
struct Cart *ct;
int i;

  if ((ct=malloc(sizeof(struct Cart)))!=0) {
    if ((ct->image=malloc(size*sizeof(mtype)))!=0) {
      if ((ct->saveback=malloc(size*sizeof(mtype)))!=0) {
	ct->type = 0;
	ct->active = FALSE;
	ct->num_secs = 1;  /* Defaults to one section */
	for (i=0;i<0x4;i++) {
	  ct->secs[i].lo = ct->secs[i].hi = 0;
	  ct->secs[i].offset = 0;
	  ct->secs[i].active = FALSE;
	}
	ct->secs[0].active = TRUE;  /* section zero is active by default */

	return ct;
      }
      free(ct->image);
    }
    free(ct);
  }

  return NULL;
}

void FreeCart(struct Cart *ct)
{
  if (ct) {
    if (ct->image)
      free(ct->image);
    if (ct->saveback)
      free(ct->saveback);
    free(ct);
  }
}

static int ReadImage(int fd,mtype *to,int nbytes)
{
UBYTE *buffer,*ptr;
int i;
int status=0;

  if ((buffer=malloc(nbytes*sizeof(UBYTE)))!=0) {
    if (read(fd,buffer,nbytes)==nbytes) {
      for(i=0,ptr=buffer;i<nbytes;i++,ptr++,to++)
	*to=*ptr;
      status = 1;
    }
    free(buffer);
  }

  return status;
}

int Read5200Rom(char *filename,int fd)
{
int fdo = -1;
int status = 0;
  
  if ((os=AllocCart(0xffff-0xf800+1))!=0) {
    if (filename) {
      fd = fdo = open(filename, O_MODE, 0777);
    }
    if (fd >= 0) {
      if (ReadImage(fd,os->image,0xffff-0xf800+1)) {
	status = 1;
      }
    }
  }

  if (fdo >= 0)
    close(fdo);

  if (status) {
    os->secs[0].lo=0xf800;
    os->secs[0].hi=0xffff;
    return TRUE;
  }

  if (verbose)
    perror(filename);

  FreeCart(os);

  os=NULL;
 
  return FALSE;
}

int ReadOSABRom(char *filename,int fd)
{
int fdo = -1;
int status = 0;
  
  if ((os=AllocCart(0xffff-0xe000+1))!=0) {
    if ((mpack=AllocCart(0xdfff-0xd800+1))!=0) {
      if (filename) {
	fd = fdo = open(filename, O_MODE, 0777);
      }
      if (fd >= 0) {
	if (ReadImage(fd,mpack->image,0xdfff-0xd800+1)) {
	  if (ReadImage(fd,os->image,0xffff-0xe000+1)) {
	    status = 1;
	  }
	}
      }
    }
  }

  if (fdo >= 0)
    close(fdo);

  if (status) {
    os->secs[0].lo=0xe000;
    os->secs[0].hi=0xffff;
    mpack->secs[0].lo=0xd800;
    mpack->secs[0].hi=0xdfff;
    return TRUE;
  }

  if (verbose)
    perror(filename);

  FreeCart(os);
  FreeCart(mpack);

  os=mpack=NULL;
 
  return FALSE;
}

int ReadXLRom(char *filename,int fd)
{
int fdo = -1;
int status = 0;
  
  if ((os=AllocCart(0xffff-0xe000+1))!=0) {
    if ((mpack=AllocCart(0xdfff-0xd800+1))!=0) {
      if ((os_x=AllocCart(0xcfff-0xc000+1))!=0) {
	if ((selftest=AllocCart(0xd7ff-0xd000+1))!=0) {
	  if (filename) {
	    fd = fdo = open(filename, O_MODE, 0777);
	  }
	  if (fd>=0) {
	    if (ReadImage(fd,os_x->image,0xcfff-0xc000+1)) {
	      if (ReadImage(fd,selftest->image,0xd7ff-0xd000+1)) {
		if (ReadImage(fd,mpack->image,0xdfff-0xd800+1)) {
		  if (ReadImage(fd,os->image,0xffff-0xe000+1)) {
		    status = 1;
		  }
		}
	      }
	    }
	  }
	}
      }
    }
  }

  if (fdo>=0)
    close(fdo);

  if (status) {
    os->secs[0].lo=0xe000;
    os->secs[0].hi=0xffff;
    mpack->secs[0].lo=0xd800;
    mpack->secs[0].hi=0xdfff;
    selftest->secs[0].lo=0x5000;
    selftest->secs[0].hi=0x57ff;
    os_x->secs[0].lo=0xc000;
    os_x->secs[0].hi=0xcfff;
    return TRUE;
  }

  if (verbose)
    perror(filename);

  FreeCart(os);
  FreeCart(mpack);
  FreeCart(os_x);
  FreeCart(selftest);

  os=mpack=os_x=selftest=NULL;
 
  return FALSE;
}

int ReadBasic(char *filename,int fd)
{
struct Cart *ct;

   if ((ct=ReadCart(filename,fd,0xa000,0xbfff-0xa000+1))!=0) {
     basic=ct;
     return TRUE;
   }

   return FALSE;
}

/*
 * Load a standard 8K ROM from the specified file
 */
int Insert_8K_ROM (char *filename,int fd)
{
struct Cart *ct;

   if ((ct=ReadCart(filename,fd,0xa000,0xbfff-0xa000+1))!=0) {
     ct->type = NORMAL8_CART;
     RemoveCurrent();
     InsertCart(ct);
     return TRUE;
   }

   return FALSE; 
}

/*
 * Load a standard 16K ROM from the specified file
 */
int Insert_16K_ROM (char *filename,int fd)
{
struct Cart *ct;

   if ((ct=ReadCart(filename,fd,0x8000,0xbfff-0x8000+1))!=0) {
     ct->type = NORMAL16_CART;
     RemoveCurrent();
     InsertCart(ct);
     return TRUE;
   }

   return FALSE; 
}

 /*
 * Load an OSS Supercartridge from the specified file
 * The OSS cartridge is a 16K bank switched cartridge
 * that occupies 8K of address space between $a000
 * and $bfff
 */

int Insert_OSS_ROM (char *filename,int fd)
{
struct Cart *ct;

   if ((ct=ReadCart(filename,fd,0x8000,0xbfff-0x8000+1))!=0) {
     ct->type = OSS_SUPERCART;
     ct->secs[0].lo     = 0xa000;
     ct->secs[0].hi     = 0xafff;
     ct->secs[1].lo     = 0xa000;
     ct->secs[1].hi     = 0xafff;
     ct->secs[1].offset = 0x1000;
     ct->secs[2].lo     = 0xa000;
     ct->secs[2].hi     = 0xafff;
     ct->secs[2].offset = 0x2000;
     ct->secs[3].lo     = 0xb000;
     ct->secs[3].hi     = 0xbfff;
     ct->secs[3].offset = 0x3000;
     ct->secs[3].active = TRUE;
     RemoveCurrent();
     InsertCart(ct);
     return TRUE;
   }

   return FALSE; 
}


/*
 * Load a DB Supercartridge from the specified file
 * The DB cartridge is a 32K bank switched cartridge
 * that occupies 16K of address space between $8000
 * and $bfff
 */
int Insert_DB_ROM (char *filename,int fd)
{
struct Cart *ct;

   if ((ct=ReadCart(filename,fd,0x2000,0xbfff-0x2000+1))!=0) {
     ct->type = DB_SUPERCART;
     ct->secs[0].lo     = 0x8000;
     ct->secs[0].hi     = 0x9fff;
     ct->secs[1].lo     = 0x8000;
     ct->secs[1].hi     = 0x9fff;
     ct->secs[1].offset = 0x2000;
     ct->secs[2].lo     = 0x8000;
     ct->secs[2].hi     = 0x9fff;
     ct->secs[2].offset = 0x4000;
     ct->secs[3].lo     = 0xa000;
     ct->secs[3].hi     = 0xbfff;
     ct->secs[3].offset = 0x6000;
     ct->secs[3].active = TRUE;
     RemoveCurrent();
     InsertCart(ct);
     return TRUE;
   }

   return FALSE; 
}


/*
 * Load a 32K 5200 ROM from the specified file
 * 
 * still to be checked
 */

int Insert_32K_5200ROM (char *filename,int fd)
{
struct Cart *ct;

   if ((ct=ReadCart(filename,fd,0x4000,0xbfff-0x4000+1))!=0) {
     ct->type = AGS32_CART;
     RemoveCurrent();
     InsertCart(ct);
     return TRUE;
   }

   return FALSE; 
}

int Insert_16K_5200ROM (char *filename,int fd)
{
struct Cart *ct;

   if ((ct=ReadCart(filename,fd,0x4000,0x7fff-0x4000+1))!=0) {
     ct->type = AGS16_CART;
     /* setup the magic mirroring. Invalidates RAM saveback, but who cares...
	the 5200 doesn't have RAM there anyways */
     ct->secs[0].lo = 0x4000;
     ct->secs[0].hi = 0x5fff;
     ct->secs[0].offset = 0x0000;
     ct->secs[0].active = TRUE;
     ct->secs[1].lo = 0x8000;
     ct->secs[1].hi = 0x9fff;
     ct->secs[1].offset = 0x2000;
     ct->secs[1].active = TRUE;
     ct->secs[2].lo = 0xa000;
     ct->secs[2].hi = 0xbfff;
     ct->secs[2].offset = 0x2000;
     ct->secs[2].active = TRUE;
     ct->secs[3].lo = 0x6000;
     ct->secs[3].hi = 0x7fff;
     ct->secs[3].offset = 0x0000;
     ct->secs[3].active = TRUE;
     RemoveCurrent();
     InsertCart(ct);
     return TRUE;
   }

   return FALSE; 
}

static struct Cart *ReadCart(char *filename, int fd,int addr, int nbytes)
{
struct Cart *ct;
int fdo = -1;
int status=0;

  if ((ct=AllocCart(nbytes))!=0) {
    if (filename) {
      fd = fdo = open(filename, O_MODE,0777);
    }
    if (fd>=0) {
      status = ReadImage(fd,ct->image,nbytes);
    } 
  }

  if (fdo>=0)
    close(fdo);

  if (status)  {
    ct->secs[0].lo = addr;
    ct->secs[0].hi = addr+nbytes-1;
    return ct;
  }

  if (verbose)
    perror(filename);

  FreeCart(ct);
  return NULL;
}
  
       
int lof(char *filename)
{
struct stat buf;

  if (stat(filename,&buf)==0) {
    return buf.st_size;
  }

  if (verbose)
    perror(filename);

  return -1;
}


static void DisableCart(struct Cart *ct)
{
int i;

  if (ct) {
    if (ct->active) {
      for(i=0;i<4;i++) {
	if (ct->secs[i].active) {
	  memcpy(memory+ct->secs[i].lo,ct->saveback+ct->secs[i].offset,(ct->secs[i].hi-ct->secs[i].lo+1)*sizeof(mtype));
	  SetRAM(ct->secs[i].lo,ct->secs[i].hi);
#ifdef DEBUG
	  printf("Disabling a cart between %x and %x\n",ct->secs[i].lo,ct->secs[i].hi);
#endif
	}
      }
      ct->active = FALSE;
    }
  }
}

static void EnableCart(struct Cart *ct)
{
int i;
int len;

  if (ct) {
    if (!(ct->active)) {
      for(i=0;i<4;i++) {
	if (ct->secs[i].active) {
	  len = (ct->secs[i].hi-ct->secs[i].lo+1)*sizeof(mtype);
	  memcpy(ct->saveback+ct->secs[i].offset,memory+ct->secs[i].lo,len);
	  memcpy(memory+ct->secs[i].lo,ct->image+ct->secs[i].offset,len);
	  SetROM(ct->secs[i].lo,ct->secs[i].hi);
#ifdef DEBUG
	  printf("Enabling a cart between %x and %x\n",ct->secs[i].lo,ct->secs[i].hi);
#endif
	}
      }
      ct->active=TRUE;
    }
  }
}


void ChangeMapping(int map0,int map1,int map2, int map3)
{
struct Cart *ct=current;

  if (ct) {
    /* first, remove all images */
    if (!(ct->active)) {
      if (basic_on) {
	DisableCart(basic);  /* if it wasn't active before, we've to turn the
				basic off */
      }
    } else DisableCart(ct);
    ct->active = TRUE;   /* enables cartridge as well */
    ct->secs[0].active = map0;
    ct->secs[1].active = map1;
    ct->secs[2].active = map2;
    ct->secs[3].active = map3;
    EnableCart(ct);
  }
}

void RemoveCart(void)
{
  
  DisableCart(current);
  current = NULL;
  
  if (basic_on)
    EnableCart(basic);
}
    
void RemoveCurrent(void)
{
struct Cart *old=current;

   if (old) {
     RemoveCart();
     FreeCart(old);
   }
}

void TurnOffCart(void)
{
struct Cart *ct=current;

  if (ct) {
    if (ct->active) {
      DisableCart(ct);
    }
    if (basic_on) {
      EnableCart(basic);
    }
  }
}

void TurnOnCart(void)
{
struct Cart *ct=current;

  if (ct) {
    if (!(ct->active)) {
      if (basic_on) {
	DisableCart(basic);
      }

      EnableCart(ct);
    }
  }
}

void InsertCart(struct Cart *ct)
{
  if (ct) {
    DisableCart(basic);    
    if (current!=ct) {
      DisableCart(current);
      EnableCart(ct);
      current=ct;
    }
  }
}

  
int CartInserted(void)
{
  if (current && current->active)
    return TRUE;

  return FALSE;
}

int CartType(void)
{
  if (current)
    return current->type;
  else return -1;
}

void EnableBasic(void)
{
  basic_on=TRUE;

  if (!(CartInserted()))
      EnableCart(basic);
}
  
void DisableBasic(void)
{
  basic_on=FALSE;

  if (!(CartInserted()))
    DisableCart(basic);
}

void EnableOs(void)
{
  EnableCart(os);
  EnableCart(mpack);
  EnableCart(os_x);
}

void DisableOs(void)
{
  DisableCart(os);
  DisableCart(mpack);
  DisableCart(os_x);
}

void EnableSelftest(void)
{
  EnableCart(selftest);
}

void DisableSelftest(void)
{
  DisableCart(selftest);
}

void FreeCarts(void)
{
  FreeCart(basic);
  FreeCart(os);
  FreeCart(os_x);
  FreeCart(selftest);
  FreeCart(mpack);
  FreeCart(current);
  basic=os=os_x=selftest=mpack=current=NULL;
}

void SelectXEBank(int byte)
{      
int cpu_flag = (byte & 0x10);
#ifdef DEBUG
int antic_flag = (byte & 0x20);
#endif
int bank = (byte & 0x0c) >> 2;
static int xe_bank = -1;

#ifdef DEBUG
   printf ("CPU = %d, ANTIC = %d, XE BANK = %d\n",
	      cpu_flag, antic_flag, bank);
#endif

/*
 * Possible Bank Transitions
 *
 * Main        -> Main
 * Main        -> Bank1,2,3,4
 * Bank1,2,3,4 -> Main
 * Bank1,2,3,4 -> Bank1,2,3,4
 */

  if (cpu_flag) {  /* turn off bank */
     if (xe_bank != -1) {
       memcpy (XE_Banks[xe_bank],memory + 0x4000,0x4000);
       memcpy (memory + 0x4000,XE_Banks[5],0x4000);
       xe_bank = -1;
     }
   } else if (bank != xe_bank) { /* turn on bank */
     if (xe_bank == -1) {
       memcpy (XE_Banks[5],memory + 0x4000,0x4000);  /* saveback mem */
     } else {
       memcpy (XE_Banks[xe_bank],memory + 0x4000,0x4000); /* saveback bank */
     }
     memcpy (memory+0x4000,XE_Banks[bank],0x4000); /* swap in bank */
     xe_bank = bank;
   }
}


int Free_XE_Banks(void)
{
int i;

   for(i=0;i<4;i++) {
     if (XE_Banks[i])
       free(XE_Banks[i]);
   }

   return TRUE;
}

int Build_XE_Banks(void)
{
int i;

  for(i=0;i<4;i++) {
    if (!(XE_Banks[i] = malloc(0x4000*sizeof(mtype))))
      return FALSE;
  }

  return TRUE;
}


static int InCart(struct Cart *ct,ATPtr ad,mtype **where)
{
int i;

  if (ct) {
    for (i=0;i<4;i++) {
      if (ct->secs[i].active) {
	if (ad>=ct->secs[i].lo && ad<=ct->secs[i].hi) {
	  *where=ct->image+ct->secs[i].offset+(ad-ct->secs[i].lo);
	  return TRUE;
	}
      }
    }
  }

  return FALSE;
}
	  
static mtype *LocationOf(ATPtr ad)
{
mtype *in;

  if (InCart(os_x,ad,&in))
    return in;

  if (InCart(selftest,ad,&in))
    return in;

  if (InCart(mpack,ad,&in))
    return in;

  if (InCart(os,ad,&in))
    return in;

  printf("Invalid ROM address %x while patching.\n",ad);
  Atari800_Exit (FALSE,20);
  return 0;
}

int RomGet(ATPtr ad)
{
  return *LocationOf(ad);
}

int RomDGet(ATPtr ad)
{
  return RomGet(ad)|(RomGet(ad+1)<<8);
}

void RomSet(ATPtr ad,int v)
{
  *LocationOf(ad)=v;
}

