/*
 * GOATTRACKER sound routines
 */

#ifdef __WIN32__
#include <windows.h>
#include <winioctl.h>
#else
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include "cwsid.h"
#endif

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "bme.h"
#include "gsid.h"
#include "goattrk.h"

#define MINSPEED 11025
#define MAXSPEED 48000

unsigned framerate = 50;

Sint16 *buffer = NULL;

/*
 * HardSID stuff
 * from http://homepages.fh-giessen.de/~hg11001/
 */
#ifdef __WIN32__

static void InitHardDLL(void);

HINSTANCE hardsiddll = 0;
typedef void (CALLBACK* lpWriteToHardSID)(Uint8 DeviceID, Uint8 SID_reg, Uint8 data);
typedef Uint8 (CALLBACK* lpReadFromHardSID)(Uint8 DeviceID, Uint8 SID_reg);
typedef void (CALLBACK* lpInitHardSID_Mapper)(void);
typedef void (CALLBACK* lpMuteHardSID_Line)(int mute);

lpWriteToHardSID WriteToHardSID;
lpReadFromHardSID ReadFromHardSID;
lpInitHardSID_Mapper InitHardSID_Mapper;
lpMuteHardSID_Line MuteHardSID_Line;

int dll_initialized = FALSE;

/* defined for CatWeasel MK3 PCI device driver */
#define SID_SID_PEEK_POKE   CTL_CODE(FILE_DEVICE_SOUND,0x0800UL + 1,METHOD_BUFFERED,FILE_ANY_ACCESS)

static HANDLE catweaselhandle;

#else

int hardsidfd = -1;

static int catweaselfd=-1;

#endif

/* Configurable from command line */
int outsamplesize = 2;
int initted = 0;
int playspeed;
int usehardsid = 0;
static int usecatweasel=0;

static FILE *writehandle = NULL;

int sound_init(unsigned b, unsigned mr, unsigned writer, unsigned hardsid, unsigned m, unsigned ntsc, unsigned multiplier, unsigned catweasel);
void sound_uninit(void);
static void sound_playrout(void);
static void sound_mixer(Sint32 *dest, unsigned samples);
static Uint32 sound_timer(Uint32 interval);

int sound_init(unsigned b, unsigned mr, unsigned writer, unsigned hardsid, unsigned m, unsigned ntsc, unsigned multiplier, unsigned catweasel)
{
  int c;

  framerate = 50 * multiplier;
  snd_bpmtempo = 125 * multiplier;

  if (ntsc)
    {
      framerate = 60 * multiplier;
      snd_bpmtempo = 150 * multiplier;
    }

  if (!buffer)
    {
      buffer = malloc(65536 * sizeof(Sint16));
      if (!buffer) return 0;
    }

  if (hardsid)
    {
#ifdef __WIN32__
      InitHardDLL();
      if (dll_initialized)
	{
	  usehardsid = hardsid;
	  for (c = 0; c <= 0x18; c++)
	    {
	      sidreg[c] = 0;
	      WriteToHardSID(usehardsid-1, c, 0x00);
	    }
	  MuteHardSID_Line(FALSE);
	}
      else return 0;
#else
      char filename[80];
      sprintf(filename, "/dev/sid%d", hardsid-1);
      hardsidfd = open(filename, O_WRONLY, S_IREAD|S_IWRITE);
      if (hardsidfd != -1)
	{
	  usehardsid = hardsid;
	  for (c = 0; c <= 0x18; c++)
	    {
	      Uint32 dataword = c << 8;
	      write(hardsidfd, &dataword, 4);
	    }
	}
      else
	{
	  return 0;
	}
#endif

      SDL_SetTimer(1000/framerate, sound_timer);
      goto SOUNDOK;
    }

  if (catweasel)
    {
#ifdef __WIN32__
      catweaselhandle=CreateFile("\\\\.\\SID6581_1",GENERIC_READ,FILE_SHARE_WRITE|FILE_SHARE_READ,0L,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL/*|FILE_FLAG_OVERLAPPED*/,0L);
      if(catweaselhandle==INVALID_HANDLE_VALUE)
	return 0;
#else
      catweaselfd=open("/dev/sid", O_WRONLY);
      if(catweaselfd<0)
	catweaselfd=open("/dev/misc/sid", O_WRONLY);
      if(catweaselfd<0)
	return 0;

      if(ntsc)
	ioctl(catweaselfd, CWSID_IOCTL_NTSC);
      else
	ioctl(catweaselfd, CWSID_IOCTL_PAL);
#endif

      usecatweasel=1;
      SDL_SetTimer(1000/framerate, sound_timer);
      goto SOUNDOK;
    }

  if (writer)
    {
      writehandle = fopen("sidaudio.raw", "wb");
    }

  playspeed = mr;
  if (playspeed < MINSPEED) playspeed = MINSPEED;
  if (playspeed > MAXSPEED) playspeed = MAXSPEED;
  if (b < 20) b = 20;
  if (b > 2000) b = 2000;

  if (!snd_init(mr, SIXTEENBIT|MONO, b, 1, 0)) return 0;
  playspeed = snd_mixrate;
  sid_init(playspeed, m, ntsc);

  snd_player = &sound_playrout;
  snd_setcustommixer(sound_mixer);

 SOUNDOK:
  initted = 1;

  return 1;
}

void sound_uninit(void)
{
  int c;

  if (!initted) return;

  if (usehardsid || usecatweasel)
    {
      SDL_SetTimer(0,NULL);
    }
  if (writehandle)
    {
      fclose(writehandle);
      writehandle = NULL;
    }

  snd_uninit();

#ifdef __WIN32__
  if (usehardsid)
    {
      for (c = 0; c <= 0x18; c++)
	{
	  WriteToHardSID(usehardsid-1, c, 0x00);
	}
      MuteHardSID_Line(TRUE);
      initted = 0;
      return;
    }
#else
  if (hardsidfd != -1)
    {
      for (c = 0; c <= 0x18; c++)
	{
	  Uint32 dataword = c << 8;
	  write(hardsidfd, &dataword, 4);
	}
    }
  close(hardsidfd);
  hardsidfd = -1;
#endif

  if(usecatweasel)
    {
#ifdef __WIN32__
      DWORD w;
      unsigned char buf[0x19*2];
      for(w=0; w<=0x18; w++)
	buf[w*2]=w, buf[w*2+1]=0;
      DeviceIoControl(catweaselhandle,SID_SID_PEEK_POKE,buf,sizeof(buf),0L,0UL,&w,0L);
      CloseHandle(catweaselhandle), catweaselhandle=INVALID_HANDLE_VALUE;
#else
      if(catweaselfd>=0)
	{
	  unsigned char buf[0x19];
	  memset(buf, 0, sizeof(buf));
	  lseek(catweaselfd, 0, SEEK_SET);
	  write(catweaselfd, buf, sizeof(buf));
	  close(catweaselfd);
	}
      catweaselfd=-1;
#endif
    }

  initted = 0;
}

static Uint32 sound_timer(Uint32 interval)
{
  sound_playrout();
  return interval;
}

static void sound_playrout(void)
{
  int c;

  playroutine();
  if(usehardsid)
    {
#ifdef __WIN32__
      for (c = 0; c <= 0x18; c++)
	{
	  WriteToHardSID(usehardsid-1, c, sidreg[c]);
	}
#else
      for (c = 0; c <= 0x18; c++)
	{
	  Uint32 dataword = (c << 8) | sidreg[c];
	  write(hardsidfd, &dataword, 4);
	}
#endif
    }
  else if(usecatweasel)
    {
#ifdef __WIN32__
      DWORD w;
      unsigned char buf[0x19*2];
      for(w=0; w<=0x18; w++)
	buf[w*2]=w, buf[w*2+1]=sidreg[w];
      DeviceIoControl(catweaselhandle,SID_SID_PEEK_POKE,buf,sizeof(buf),0L,0UL,&w,0L);
#else
      lseek(catweaselfd, 0, SEEK_SET);
      write(catweaselfd, sidreg, 0x19);
#endif
    }
  else
    sid_dumpregs();
}

static void sound_mixer(Sint32 *dest, unsigned samples)
{
  int c;

  if (samples > 65536) return;

  sid_fillbuffer(buffer, samples);
  if (writehandle)
    {
      fwrite(buffer, samples * outsamplesize, 1, writehandle);
    }
  for (c = 0; c < samples; c++)
    {
      dest[c] = buffer[c];
    }
}

#ifdef __WIN32__

// Intializing the DLL
static void InitHardDLL()
{
  if (!(hardsiddll=LoadLibrary("HARDSID.DLL"))) return;

  WriteToHardSID = (lpWriteToHardSID) GetProcAddress(hardsiddll, "WriteToHardSID");
  ReadFromHardSID = (lpReadFromHardSID) GetProcAddress(hardsiddll, "ReadFromHardSID");
  InitHardSID_Mapper = (lpInitHardSID_Mapper) GetProcAddress(hardsiddll, "InitHardSID_Mapper");
  MuteHardSID_Line = (lpMuteHardSID_Line) GetProcAddress(hardsiddll, "MuteHardSID_Line");

  if (!WriteToHardSID) return;

  InitHardSID_Mapper();
  dll_initialized = TRUE;
}

#endif
