/////////////////////////////////////////////////////////////////////////////
//
// sega - Top-level emulation for Saturn and DC
//
/////////////////////////////////////////////////////////////////////////////

#ifndef EMU_COMPILE
#error "Hi I forgot to set EMU_COMPILE"
#endif

#include "sega.h"

#include "satsound.h"
#include "dcsound.h"
#include "arm.h"
#include "yam.h"
#ifdef USE_STARSCREAM
#include "Starscream/starcpu.h"
#endif

/////////////////////////////////////////////////////////////////////////////
//
// Static init for the whole library
//
static uint8 library_was_initialized = 0;

//
// Deliberately create a NULL dereference
// Useful for calling attention to show-stopper problems like forgetting to
// call sega_init() or compiling with the wrong byte order
//
static void sega_hang(const char *message) {
  for(;;) { *((volatile char*)0) = *message; }
}

//
// Endian check
//
static void sega_endian_check(void) {
  uint32 num = 0x61626364;
  // Big
  if(!memcmp(&num, "abcd", 4)) {
#ifdef SEGA_BIG_ENDIAN
    return;
#endif
    sega_hang("endian check");
  }
  // Little
  if(!memcmp(&num, "dcba", 4)) {
#ifndef SEGA_BIG_ENDIAN
    return;
#endif
    sega_hang("endian check");
  }
  // Don't know what!
  sega_hang("endian check");
}

//
// Data type size check
//
static void sega_size_check(void) {
  if(sizeof(uint8 ) != 1) sega_hang("size check");
  if(sizeof(uint16) != 2) sega_hang("size check");
  if(sizeof(uint32) != 4) sega_hang("size check");
  if(sizeof(uint64) != 8) sega_hang("size check");
  if(sizeof(sint8 ) != 1) sega_hang("size check");
  if(sizeof(sint16) != 2) sega_hang("size check");
  if(sizeof(sint32) != 4) sega_hang("size check");
  if(sizeof(sint64) != 8) sega_hang("size check");
}

sint32 EMU_CALL sega_init(void) {
  sint32 r;
  sega_endian_check();
  sega_size_check();

  if(library_was_initialized) return 0;

#ifndef DISABLE_SSF
  r = satsound_init(); if(r) return r;
#endif
  r = dcsound_init(); if(r) return r;
  r = arm_init(); if(r) return r;
  r = yam_init(); if(r) return r;
#ifndef DISABLE_SSF
#ifdef USE_STARSCREAM
  r = s68000_init(); if(r) return r;
#endif
#endif

  library_was_initialized = 1;
  return 0;
}

/////////////////////////////////////////////////////////////////////////////
//
// Version information
//
const char* EMU_CALL sega_getversion(void) {
  static const char s[] = "SegaCore0001 (built " __DATE__ ")";
  static char rv[500];
  int sl = (int)strlen(s);
  memcpy(rv, s, sl);
#ifndef DISABLE_SSF
  rv[sl] = '\n';
#ifdef USE_STARSCREAM
  strcpy(rv+sl+1, s68000_get_version());
#elif defined(USE_M68K)
  strcpy(rv+sl+1, "M68K");
#else
  strcpy(rv+sl+1, "C68K");
#endif
#endif

  return rv;
}

/////////////////////////////////////////////////////////////////////////////
//
// State information
//

struct SEGA_STATE {
  uint32 offset_to_dcsound;
  uint32 offset_to_satsound;
};

#define SEGASTATE     ((struct SEGA_STATE*)(state))
#define DCSOUNDSTATE  ((void*)(((char*)(state))+(SEGASTATE->offset_to_dcsound)))
#define SATSOUNDSTATE ((void*)(((char*)(state))+(SEGASTATE->offset_to_satsound)))

#define HAVE_DCSOUND  (SEGASTATE->offset_to_dcsound!=0)
#define HAVE_SATSOUND (SEGASTATE->offset_to_satsound!=0)

uint32 EMU_CALL sega_get_state_size(uint8 version) {
  uint32 size = 0;
  if(version != 2) version = 1;
  size += sizeof(struct SEGA_STATE);
#ifndef DISABLE_SSF
  if(version == 1) size += satsound_get_state_size();
#endif
  if(version == 2) size += dcsound_get_state_size();
  return size;
}

void EMU_CALL sega_clear_state(void *state, uint8 version) {
  uint32 offset;

  if(version != 2) version = 1;
  //
  // If we haven't initialized, we really SHOULD have.
  //
  if(!library_was_initialized) sega_hang("library not initialized");

  // Clear local struct
  memset(state, 0, sizeof(struct SEGA_STATE));
  // Set up offsets
  offset = sizeof(struct SEGA_STATE);
#ifndef DISABLE_SSF
  if(version == 1) { SEGASTATE->offset_to_satsound = offset; offset += satsound_get_state_size(); }
#endif
  if(version == 2) { SEGASTATE->offset_to_dcsound  = offset; offset += dcsound_get_state_size(); }
  //
  // Take care of substructures
  //
#ifndef DISABLE_SSF
  if(HAVE_SATSOUND) satsound_clear_state(SATSOUNDSTATE);
#endif
  if(HAVE_DCSOUND) dcsound_clear_state(DCSOUNDSTATE);
  // Done
}

/////////////////////////////////////////////////////////////////////////////
//
// Obtain substates
//
void* EMU_CALL sega_get_satsound_state(void *state) {
#ifdef DISABLE_SSF
  return NULL;
#else
  if(!(HAVE_SATSOUND)) return NULL;
  return SATSOUNDSTATE;
#endif
}

void* EMU_CALL sega_get_dcsound_state(void *state) {
  if(!(HAVE_DCSOUND)) return NULL;
  return DCSOUNDSTATE;
}

/////////////////////////////////////////////////////////////////////////////
//
// Executes the given number of cycles or the given number of samples
// (whichever is less)
//
// Sets *sound_samples to the number of samples actually generated,
// which may be ZERO or LESS than the number requested, but never more.
//
// Return value:
// >= 0   The number of cycles actually executed, which may be ZERO, MORE,
//        or LESS than the number requested
// <= -1  Unrecoverable error
//
sint32 EMU_CALL sega_execute(
  void   *state,
  sint32  cycles,
  sint16 *sound_buf,
  uint32 *sound_samples
) {
#ifndef DISABLE_SSF
  if(HAVE_SATSOUND) {
    return satsound_execute(SATSOUNDSTATE, cycles, sound_buf, sound_samples);
  } else
#endif
  if(HAVE_DCSOUND) {
    return dcsound_execute(DCSOUNDSTATE, cycles, sound_buf, sound_samples);
  } else {
    return -1;
  }
}

/////////////////////////////////////////////////////////////////////////////

static uint32 get32lsb(uint8 *src) {
  return
    ((((uint32)(src[0])) & 0xFF) <<  0) |
    ((((uint32)(src[1])) & 0xFF) <<  8) |
    ((((uint32)(src[2])) & 0xFF) << 16) |
    ((((uint32)(src[3])) & 0xFF) << 24);
}

#if 0
static uint32 get32msb(uint8 *src) {
  return
    ((((uint32)(src[3])) & 0xFF) <<  0) |
    ((((uint32)(src[2])) & 0xFF) <<  8) |
    ((((uint32)(src[1])) & 0xFF) << 16) |
    ((((uint32)(src[0])) & 0xFF) << 24);
}
#endif

/////////////////////////////////////////////////////////////////////////////
//
// Upload program - must INCLUDE the 4-byte header.
// Returns nonzero on error.
//
sint32 EMU_CALL sega_upload_program(void *state, void *program, uint32 size) {
  uint32 start;
  if(size < 5) return -1;
  start = get32lsb((uint8*)program);
#ifndef DISABLE_SSF
  if(HAVE_SATSOUND) {
    satsound_upload_to_ram(SATSOUNDSTATE, start, ((uint8*)program) + 4, size - 4);
  } else
#endif
  if(HAVE_DCSOUND) {
    dcsound_upload_to_ram(DCSOUNDSTATE, start, ((uint8*)program) + 4, size - 4);
  } else {
    return -1;
  }
  return 0;
}

/////////////////////////////////////////////////////////////////////////////
//
// Get the current program counter
//
uint32 EMU_CALL sega_get_pc(void *state) {
#ifndef DISABLE_SSF
  if(HAVE_SATSOUND) return satsound_get_pc(SATSOUNDSTATE);
#endif
  if(HAVE_DCSOUND) return dcsound_get_pc(DCSOUNDSTATE);
  return 0;
}

/////////////////////////////////////////////////////////////////////////////

static void *getyamstate(struct SEGA_STATE *state) {
  void *yamstate = NULL;
#ifndef DISABLE_SSF
  if(HAVE_SATSOUND) { yamstate = satsound_get_yam_state(SATSOUNDSTATE); }
#endif
  if(HAVE_DCSOUND) { yamstate = dcsound_get_yam_state(DCSOUNDSTATE); }
  return yamstate;
}

/////////////////////////////////////////////////////////////////////////////
//
// Enable or disable various things
//
void EMU_CALL sega_enable_dry(void *state, uint8 enable) {
  void *yamstate = getyamstate(SEGASTATE);
  if(yamstate) yam_enable_dry(yamstate, enable);
}

void EMU_CALL sega_enable_dsp(void *state, uint8 enable) {
  void *yamstate = getyamstate(SEGASTATE);
  if(yamstate) yam_enable_dsp(yamstate, enable);
}

void EMU_CALL sega_enable_dsp_dynarec(void *state, uint8 enable) {
  void *yamstate = getyamstate(SEGASTATE);
  if(yamstate) yam_enable_dsp_dynarec(yamstate, enable);
}

/////////////////////////////////////////////////////////////////////////////
