/******************************************************************************/
/*                                                                            */
/*                     RAINE - STARSCREAM 68000 INTERFACE                     */
/*                                                                            */
/******************************************************************************/

#include "starhelp.h"
#include "raine.h"
#include "savegame.h"

struct S68000CONTEXT            M68000_context[MAX_68000];
struct STARSCREAM_PROGRAMREGION M68000_programregion[MAX_68000][MAX_PROGRAM];
struct STARSCREAM_DATAREGION    M68000_dataregion_rb[MAX_68000][MAX_DATA];
struct STARSCREAM_DATAREGION    M68000_dataregion_rw[MAX_68000][MAX_DATA];
struct STARSCREAM_DATAREGION    M68000_dataregion_wb[MAX_68000][MAX_DATA];
struct STARSCREAM_DATAREGION    M68000_dataregion_ww[MAX_68000][MAX_DATA];
struct STARSCREAM_DATAREGION    MC68000A_memoryall[128];
void *M68000_resethandler[MAX_68000];

int StarScreamEngine;

/*
 *  Fill in the basic structures via these functions...
 */

// FIRST EMULATED 68000

static UINT32 ma;

static UINT32 program_count[MAX_68000];
static UINT32 data_count_rb[MAX_68000];
static UINT32 data_count_rw[MAX_68000];
static UINT32 data_count_wb[MAX_68000];
static UINT32 data_count_ww[MAX_68000];

void AddMemoryList(UINT32 d0, UINT32 d1, void *d2, UINT8 *d3)
{
   MC68000A_memoryall[ma].lowaddr    = d0;
   MC68000A_memoryall[ma].highaddr   = d1;
   MC68000A_memoryall[ma].memorycall = d2;
   MC68000A_memoryall[ma].userdata   = d3;
   ma++;
}

static void add_s68000_program_region(UINT32 cpu, UINT32 d0, UINT32 d1, UINT8 *d2)
{
   M68000_programregion[cpu][program_count[cpu]].lowaddr  = d0;
   M68000_programregion[cpu][program_count[cpu]].highaddr = d1;
   M68000_programregion[cpu][program_count[cpu]].offset   = (UINT32) d2;
   program_count[cpu]++;

   // Normally this is totally useless, since ma is cleared by LoadDefault
   // I hope so since I tend to add AddMemFetch(-1,-1,NULL)
   // at the end !
/*     if(cpu == 0) */
/*        ma = 0; */
}

static void add_s68000_data_region_rb(UINT32 cpu, UINT32 d0, UINT32 d1, void *d2, UINT8 *d3)
{
   M68000_dataregion_rb[cpu][data_count_rb[cpu]].lowaddr    = d0;
   M68000_dataregion_rb[cpu][data_count_rb[cpu]].highaddr   = d1;
   M68000_dataregion_rb[cpu][data_count_rb[cpu]].memorycall = d2;
   M68000_dataregion_rb[cpu][data_count_rb[cpu]].userdata   = d3 - d0;
   if (data_count_rb[cpu]++ >= MAX_DATA) {
     fprintf(stderr,"overflow rb\n");
     exit(1);
   }

   if(cpu == 0)
      AddMemoryList(d0,d1,d2,d3);
}

static void add_s68000_data_region_rw(UINT32 cpu, UINT32 d0, UINT32 d1, void *d2, UINT8 *d3)
{
   M68000_dataregion_rw[cpu][data_count_rw[cpu]].lowaddr    = d0;
   M68000_dataregion_rw[cpu][data_count_rw[cpu]].highaddr   = d1;
   M68000_dataregion_rw[cpu][data_count_rw[cpu]].memorycall = d2;
   M68000_dataregion_rw[cpu][data_count_rw[cpu]].userdata   = d3 - d0;
   if (data_count_rw[cpu]++ >= MAX_DATA) {
     fprintf(stderr,"overflow rw\n");
     exit(1);
   }

   if(cpu == 0)
      AddMemoryList(d0,d1,d2,d3);
}

static void add_s68000_data_region_wb(UINT32 cpu, UINT32 d0, UINT32 d1, void *d2, UINT8 *d3)
{
   M68000_dataregion_wb[cpu][data_count_wb[cpu]].lowaddr    = d0;
   M68000_dataregion_wb[cpu][data_count_wb[cpu]].highaddr   = d1;
   M68000_dataregion_wb[cpu][data_count_wb[cpu]].memorycall = d2;
   M68000_dataregion_wb[cpu][data_count_wb[cpu]].userdata   = d3 - d0;
   if (data_count_wb[cpu]++ >= MAX_DATA) {
     fprintf(stderr,"overflow wb\n");
     exit(1);
   }

   if(cpu == 0)
      AddMemoryList(d0,d1,d2,d3);
}

static void add_s68000_data_region_ww(UINT32 cpu, UINT32 d0, UINT32 d1, void *d2, UINT8 *d3)
{
#ifdef RAINE_DEBUG
  if (d1 < d0)
    fprintf(stderr,"stupidity ww\n");
#endif

   M68000_dataregion_ww[cpu][data_count_ww[cpu]].lowaddr    = d0;
   M68000_dataregion_ww[cpu][data_count_ww[cpu]].highaddr   = d1;
   M68000_dataregion_ww[cpu][data_count_ww[cpu]].memorycall = d2;
   M68000_dataregion_ww[cpu][data_count_ww[cpu]].userdata   = d3 - d0;
   if (data_count_ww[cpu]++ >= MAX_DATA) {
     fprintf(stderr,"overflow add_s68000_data_region_ww\n");
     exit(1);
   }

   if(cpu == 0)
      AddMemoryList(d0,d1,d2,d3);
}

void set_s68000_data_region_rb(UINT32 cpu, UINT32 d0, UINT32 d1, void *d2, UINT8 *d3)
{
  int i;
  for (i=0; i<data_count_rb[cpu]; i++) {
    if (M68000_dataregion_rb[cpu][i].lowaddr == d0 &&
	M68000_dataregion_rb[cpu][i].highaddr == d1) {
      M68000_dataregion_rb[cpu][i].memorycall = d2;
      M68000_dataregion_rb[cpu][i].userdata = d3 - d0;
      break;
    }
  }
  if (i>=data_count_rb[cpu]) {
    fprintf(stderr,"couldn't set rb %x - %x\n",d0,d1);
    exit(1);
  }
}

void set_s68000_data_region_rw(UINT32 cpu, UINT32 d0, UINT32 d1, void *d2, UINT8 *d3)
{
  int i;
  for (i=0; i<data_count_rw[cpu]; i++) {
    if (M68000_dataregion_rw[cpu][i].lowaddr == d0 &&
	M68000_dataregion_rw[cpu][i].highaddr == d1) {
      M68000_dataregion_rw[cpu][i].memorycall = d2;
      M68000_dataregion_rw[cpu][i].userdata = d3 - d0;
      break;
    }
  }
  if (i>=data_count_rw[cpu]) {
    fprintf(stderr,"couldn't set rw %x - %x\n",d0,d1);
    exit(1);
  }
}

void set_s68000_data_region_wb(UINT32 cpu, UINT32 d0, UINT32 d1, void *d2, UINT8 *d3)
{
  int i;
  for (i=0; i<data_count_wb[cpu]; i++) {
    if (M68000_dataregion_wb[cpu][i].lowaddr == d0 &&
	M68000_dataregion_wb[cpu][i].highaddr == d1) {
      M68000_dataregion_wb[cpu][i].memorycall = d2;
      M68000_dataregion_wb[cpu][i].userdata = d3 - d0;
      break;
    }
  }
  if (i>=data_count_wb[cpu]) {
    fprintf(stderr,"couldn't set wb %x - %x\n",d0,d1);
    exit(1);
  }
}

void set_s68000_data_region_ww(UINT32 cpu, UINT32 d0, UINT32 d1, void *d2, UINT8 *d3)
{
  int i;
  for (i=0; i<data_count_ww[cpu]; i++) {
    if (M68000_dataregion_ww[cpu][i].lowaddr == d0 &&
	M68000_dataregion_ww[cpu][i].highaddr == d1) {
      M68000_dataregion_ww[cpu][i].memorycall = d2;
      M68000_dataregion_ww[cpu][i].userdata = d3 - d0;
      break;
    }
  }
  if (i>=data_count_ww[cpu]) {
    fprintf(stderr,"couldn't set ww %x - %x\n",d0,d1);
    exit(1);
  }
}

void set_s68000_data_region_io(UINT32 cpu, UINT32 d0, UINT32 d1, void *d2, UINT8 *d3)
{
  set_s68000_data_region_rb(cpu, d0, d1, d2, d3);
  set_s68000_data_region_rw(cpu, d0, d1, d2, d3);
  set_s68000_data_region_wb(cpu, d0, d1, d2, d3);
  set_s68000_data_region_ww(cpu, d0, d1, d2, d3);
}

void AddMemFetch(UINT32 d0, UINT32 d1, UINT8 *d2)
{
   add_s68000_program_region(0, d0, d1, d2);
}

void AddReadByte(UINT32 d0, UINT32 d1, void *d2, UINT8 *d3)
{
   add_s68000_data_region_rb(0, d0, d1, d2, d3);
}

void AddReadWord(UINT32 d0, UINT32 d1, void *d2, UINT8 *d3)
{
   add_s68000_data_region_rw(0, d0, d1, d2, d3);
}

void AddReadBW (UINT32 d0, UINT32 d1, void *d2, UINT8 *d3) {
   add_s68000_data_region_rb(0, d0, d1, d2, d3);
   add_s68000_data_region_rw(0, d0, d1, d2, d3);
}


void AddWriteByte(UINT32 d0, UINT32 d1, void *d2, UINT8 *d3)
{
   add_s68000_data_region_wb(0, d0, d1, d2, d3);
}

void AddWriteWord(UINT32 d0, UINT32 d1, void *d2, UINT8 *d3)
{
   add_s68000_data_region_ww(0, d0, d1, d2, d3);
}

void AddWriteBW(UINT32 d0, UINT32 d1, void *d2, UINT8 *d3)
{
   add_s68000_data_region_wb(0, d0, d1, d2, d3);
   add_s68000_data_region_ww(0, d0, d1, d2, d3);
}

void AddRWBW (UINT32 d0, UINT32 d1, void *d2, UINT8 *d3)
{
   add_s68000_data_region_rb(0, d0, d1, d2, d3);
   add_s68000_data_region_rw(0, d0, d1, d2, d3);
   add_s68000_data_region_wb(0, d0, d1, d2, d3);
   add_s68000_data_region_ww(0, d0, d1, d2, d3);
}

void quiet_reset_handler() {
  print_debug("RESET\n");
}

void AddResetHandler(void *d0)
{
   M68000_resethandler[0] = d0;
}

/*

darn, neill moves things around in the context, so it's not
safe to save starscream contexts incase they change in future
releases, have to 'pack' them into our own save buffer then.

*/

typedef struct SAVE_BUFFER
{
   UINT32 id;
   UINT32 interrupts[8];
   UINT32 dreg[8];
   UINT32 areg[8];
   UINT32 asp;
   UINT32 pc;
   UINT32 odometer;
   UINT16 sr;
   UINT8 stopped;
} SAVE_BUFFER;

static struct SAVE_BUFFER save_buffer[2];

void AddInitMemory(void)
{
   M68000_context[0].fetch       = M68000_programregion[0];
   M68000_context[0].readbyte    = M68000_dataregion_rb[0];
   M68000_context[0].readword    = M68000_dataregion_rw[0];
   M68000_context[0].writebyte   = M68000_dataregion_wb[0];
   M68000_context[0].writeword   = M68000_dataregion_ww[0];
   M68000_context[0].s_fetch     = M68000_programregion[0];
   M68000_context[0].s_readbyte  = M68000_dataregion_rb[0];
   M68000_context[0].s_readword  = M68000_dataregion_rw[0];
   M68000_context[0].s_writebyte = M68000_dataregion_wb[0];
   M68000_context[0].s_writeword = M68000_dataregion_ww[0];
   M68000_context[0].u_fetch     = M68000_programregion[0];
   M68000_context[0].u_readbyte  = M68000_dataregion_rb[0];
   M68000_context[0].u_readword  = M68000_dataregion_rw[0];
   M68000_context[0].u_writebyte = M68000_dataregion_wb[0];
   M68000_context[0].u_writeword = M68000_dataregion_ww[0];
   M68000_context[0].resethandler = M68000_resethandler[0];
   M68000_context[0].sr = 0x2700;

   AddSaveCallback_Internal(M68000A_save_update);
   AddLoadCallback_Internal(M68000A_load_update);
   AddSaveData(SAVE_68K_0, (UINT8 *) &save_buffer[0], sizeof(SAVE_BUFFER));

   StarScreamEngine = 1;
}

void Clear68000List(void)
{
   UINT32 ta;

   for(ta = 0; ta < MAX_68000; ta ++){
      program_count[ta] = 0;
      data_count_rb[ta] = 0;
      data_count_rw[ta] = 0;
      data_count_wb[ta] = 0;
      data_count_ww[ta] = 0;
      M68000_resethandler[ta] = NULL;
   }

   ma = 0;
}

void WriteStarScreamByte(UINT32 address, UINT8 data)
{
   int ta;

   for(ta=0;(UINT32)ta<ma;ta++){
      if((MC68000A_memoryall[ta].lowaddr)==-1){
         ta=ma;
            print_debug("Wb(%06x,%02x) [Via WriteStarScreamByte]\n",address,data);
      }
      else{
         if((address>=MC68000A_memoryall[ta].lowaddr)&&(MC68000A_memoryall[ta].highaddr>=address)){
            if(MC68000A_memoryall[ta].memorycall==NULL && MC68000A_memoryall[ta].userdata){
               WriteByte( ((UINT8 *) MC68000A_memoryall[ta].userdata) + ((address^1)-MC68000A_memoryall[ta].lowaddr),data);
               ta=ma;
            }
            //else{
            //   *MC68000A_memoryall[ta].memorycall(address,data);
            //}
         }
      }
   }
}

UINT8 ReadStarScreamByte(UINT32 address)
{
   int ta;

   for(ta=0;(UINT32)ta<ma;ta++){
      if((MC68000A_memoryall[ta].lowaddr)==-1){
            print_debug("Rb(%06x) [Via ReadStarScreamByte]\n",address);
         return(0x00);
      }
      else{
         if((address>=MC68000A_memoryall[ta].lowaddr)&&(MC68000A_memoryall[ta].highaddr>=address)){
            if(MC68000A_memoryall[ta].memorycall==NULL && MC68000A_memoryall[ta].userdata){
               return ReadByte( ((UINT8 *) MC68000A_memoryall[ta].userdata) + ((address^1)-MC68000A_memoryall[ta].lowaddr) );
            }
         }
      }
   }

   return 0x00;
}

// SECOND EMULATED 68000

void AddMemFetchMC68000B(UINT32 d0, UINT32 d1, UINT8 *d2)
{
   add_s68000_program_region(1, d0, d1, d2);
}

void AddReadByteMC68000B(UINT32 d0, UINT32 d1, void *d2, UINT8 *d3)
{
   add_s68000_data_region_rb(1, d0, d1, d2, d3);
}

void AddReadWordMC68000B(UINT32 d0, UINT32 d1, void *d2, UINT8 *d3)
{
   add_s68000_data_region_rw(1, d0, d1, d2, d3);
}

void AddWriteByteMC68000B(UINT32 d0, UINT32 d1, void *d2, UINT8 *d3)
{
   add_s68000_data_region_wb(1, d0, d1, d2, d3);
}

void AddWriteWordMC68000B(UINT32 d0, UINT32 d1, void *d2, UINT8 *d3)
{
   add_s68000_data_region_ww(1, d0, d1, d2, d3);
}

void AddInitMemoryMC68000B(void)
{
   M68000_context[1].fetch       = M68000_programregion[1];
   M68000_context[1].readbyte    = M68000_dataregion_rb[1];
   M68000_context[1].readword    = M68000_dataregion_rw[1];
   M68000_context[1].writebyte   = M68000_dataregion_wb[1];
   M68000_context[1].writeword   = M68000_dataregion_ww[1];
   M68000_context[1].s_fetch     = M68000_programregion[1];
   M68000_context[1].s_readbyte  = M68000_dataregion_rb[1];
   M68000_context[1].s_readword  = M68000_dataregion_rw[1];
   M68000_context[1].s_writebyte = M68000_dataregion_wb[1];
   M68000_context[1].s_writeword = M68000_dataregion_ww[1];
   M68000_context[1].u_fetch     = M68000_programregion[1];
   M68000_context[1].u_readbyte  = M68000_dataregion_rb[1];
   M68000_context[1].u_readword  = M68000_dataregion_rw[1];
   M68000_context[1].u_writebyte = M68000_dataregion_wb[1];
   M68000_context[1].u_writeword = M68000_dataregion_ww[1];
   M68000_context[1].resethandler = M68000_resethandler[1];
   M68000_context[1].sr = 0x2700;

   AddSaveCallback_Internal(M68000B_save_update);
   AddLoadCallback_Internal(M68000B_load_update);
   AddSaveData(SAVE_68K_1, (UINT8 *) &save_buffer[1], sizeof(SAVE_BUFFER));

   StarScreamEngine = 2;
}

/*
 *  Helper Functions for Starscream memory read/write structs
 *  ---------------------------------------------------------
 */

void Stop68000(UINT32 address, UINT8 data)
{
	(void)(address);
	(void)(data);
   s68000releaseTimeslice();
   print_debug("[Stop68000]\n");
}

UINT8 DefBadReadByte(UINT32 address)
{
  print_debug("RB(%06x) [%06x]\n",address,s68000readPC());
  return 0xFF;
}

UINT16 DefBadReadWord(UINT32 address)
{
       print_debug("RW(%06x) [%06x]\n",address,s68000readPC());
   return 0x0000;
}

void DefBadWriteByte(UINT32 address, UINT8 data)
{
      print_debug("WB(%06x,%02x) [%06x]\n",address,data,s68000readPC());
}

void DefBadWriteWord(UINT32 address, UINT16 data)
{
      print_debug("WW(%06x,%04x) [%06x]\n",address,data,s68000readPC());
}

void ByteSwap(UINT8 *MEM, UINT32 size)
{
   UINT32 ta;
   for(ta=0;ta<size;ta+=2){
      WriteWord(&MEM[ta],ReadWord68k(&MEM[ta]));
   }
}

typedef struct OLD_CONTEXT
{
   UINT8 *memoryfetch;
   UINT8 *memoryreadbyte;
   UINT8 *memoryreadword;
   UINT8 *memorywritebyte;
   UINT8 *memorywriteword;
   void (*resethandler)(void);
   UINT32 dreg[8];
   UINT32 areg[8];
   UINT32 asp;
   UINT32 pc;
   UINT32 odometer;
   UINT16 sr;
   UINT8 stopped;
   UINT8 contextfiller;
} OLD_CONTEXT;

static void do_save_packing(int cpu)
{
   UINT32 ta;

   save_buffer[cpu].id = ASCII_ID('S','0','2','6');

   for(ta=0;ta<8;ta++){
   save_buffer[cpu].dreg[ta]       = M68000_context[cpu].dreg[ta];
   save_buffer[cpu].areg[ta]       = M68000_context[cpu].areg[ta];
   save_buffer[cpu].interrupts[ta] = M68000_context[cpu].interrupts[ta];
   }

   save_buffer[cpu].asp         = M68000_context[cpu].asp;
   save_buffer[cpu].pc          = M68000_context[cpu].pc;
   save_buffer[cpu].odometer    = M68000_context[cpu].odometer;
   save_buffer[cpu].sr          = M68000_context[cpu].sr;

}

void M68000A_save_update(void)
{
   do_save_packing(0);
}

void M68000B_save_update(void)
{
   do_save_packing(1);
}

static void do_load_unpacking(int cpu)
{
   UINT32 ta;

   if( save_buffer[cpu].id == ASCII_ID('S','0','2','6') ){

      for(ta=0;ta<8;ta++){
      M68000_context[cpu].dreg[ta]       = save_buffer[cpu].dreg[ta];
      M68000_context[cpu].areg[ta]       = save_buffer[cpu].areg[ta];
      M68000_context[cpu].interrupts[ta] = save_buffer[cpu].interrupts[ta];
      }

      M68000_context[cpu].asp         = save_buffer[cpu].asp;
      M68000_context[cpu].pc          = save_buffer[cpu].pc;
      M68000_context[cpu].odometer    = save_buffer[cpu].odometer;
      M68000_context[cpu].sr          = save_buffer[cpu].sr;

   }
   else{

      // this hack will make old saves work

      OLD_CONTEXT *old_context;

      old_context = (OLD_CONTEXT *) &save_buffer[cpu];

      for(ta=0;ta<8;ta++){
      M68000_context[cpu].dreg[ta]       = old_context->dreg[ta];
      M68000_context[cpu].areg[ta]       = old_context->areg[ta];
      M68000_context[cpu].interrupts[ta] = 0x00;
      }

      M68000_context[cpu].asp         = old_context->asp;
      M68000_context[cpu].pc          = old_context->pc;
      M68000_context[cpu].odometer    = old_context->odometer;
      M68000_context[cpu].sr          = old_context->sr;
   }
}

void M68000A_load_update(void)
{
   do_load_unpacking(0);
}

void M68000B_load_update(void)
{
   do_load_unpacking(1);
}

void finish_conf_starscream() {
   AddReadByte(0x000000, 0xFFFFFFFF, DefBadReadByte, NULL);		// <Bad Reads>
   AddReadByte(-1, -1, NULL, NULL);
   AddReadWord(0x000000, 0xFFFFFFFF, DefBadReadWord, NULL);		// <Bad Reads>
   AddReadWord(-1, -1,NULL, NULL);
   AddWriteByte(0x000000, 0xFFFFFFFF, DefBadWriteByte, NULL);		// <Bad Writes>
   AddWriteByte(-1, -1, NULL, NULL);
   AddWriteWord(0x000000, 0xFFFFFFFF, DefBadWriteWord, NULL);		// <Bad Writes>
   AddWriteWord(-1, -1, NULL, NULL);

   AddInitMemory();	// Set Starscream mem pointers...
}
