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

#include "iw.h"
#include "iwapi.h"
#if defined(__WATCOMC__) && defined(__FLAT__)
#define LOADDS _loadds
#define FAR
void PUSH_DISABLE(void);
#pragma aux PUSH_DISABLE =  \
  "pushfd",    		    \
  "cli";
void POP_FLAGS(void);
#pragma aux POP_FLAGS =     \
  "popfd";
#elif defined(__BORLANDC__)
#define LOADDS _loadds
#define FAR far
#define PUSH_DISABLE		\
        asm {                   \
            pushf;              \
            cli;                \
        }
#define POP_FLAGS		\
        asm {                   \
            popf;               \
        }
#elif _MSC_VER == 600
#define LOADDS _loadds
#define FAR far
#elif _MSC_VER > 600
#define LOADDS __loadds
#define FAR __far
#endif

#define DEFAULT_BUFFER_NUMBER    3
#define DEFAULT_DATA_BUFFER_SIZE 49152UL
#define DEFAULT_DMA_BUFFER_SIZE  8192UL

static struct riff_header {
  char RIFF[4];
  unsigned long file_size;
  char WAVE[4];
  char fmt[4];
  unsigned long fmt_size;
} riff_header;

static struct format_header {
  unsigned short wFormatTag;
  unsigned short nChannels;
  unsigned long nSamplesPerSec;
  unsigned long nAvgBytesPerSec;
  unsigned short nBlockAllign;
  unsigned short nBitsPerSample;
} format_header;

static struct data_header {
  unsigned char DATA[4];
  unsigned long size;
} data_header;

typedef enum {CLEAR, FULL, LAST} bufstat;

typedef struct data_buffer_s {
  bufstat stat;
  char *vptr;
  unsigned long size;
} data_buffer_t;

static data_buffer_t *data_buffer = 0;
static unsigned long iw_buf = 0;
static struct iw_dma_buff dma_buff = {0,0,0};
static FILE *wave_fp = 0;

/* Global sound parameters */
static unsigned short DataBufferSize   = DEFAULT_DATA_BUFFER_SIZE;
static unsigned short DmaBufferSize    = DEFAULT_DMA_BUFFER_SIZE;
static int DataBufferNumber = DEFAULT_BUFFER_NUMBER;
static unsigned short Frequency        = 8000U;
static short Volume           = 127;
static short Pan = 63;
static unsigned char Stereo           = 0;
static unsigned char Sixteen          = 0;
static int read_index = 0, play_index = -1, played_index = -1;
static int voice = -1;
static int WaveVoice = -1;
static int dig_paused = 0;
static int stop=0;
static int start=0;
extern int wav_use_codec;

static unsigned long get_dma_buffer(unsigned long size, struct iw_dma_buff *dma)
{
  unsigned long s_page, e_page;
  int good = 0;
  char *old_mem;
  unsigned char far *vptr;
  unsigned long paddr;
  int i;
  unsigned long rc;

#ifdef __WATCOMC__
  union REGS regs;
  short selector, old_selector;

  regs.w.ax = 0x0100;
  regs.w.bx = (size+ 15) >> 4;
  int386(0x31, (const union REGS *)&regs, (union REGS *)&regs);
  if (!regs.x.cflag) {
    dma->vptr = (unsigned char RFAR *)((regs.x.eax & 0xffff) << 4);
    dma->paddr = (unsigned long)dma->vptr;
    dma->size = size;
    rc = size;
    selector = (short)(regs.w.dx);
    vptr = dma->vptr;
    for (i=0; (i<6 && !good && (vptr)); i++) {
      paddr = (unsigned long)vptr;
      //paddr = (((unsigned long)FP_SEG(vptr)<<4) + (unsigned long)FP_OFF(vptr));

      s_page = paddr >> 16;
      e_page = (paddr + size) >> 16;

      if (s_page == e_page)
      	good = 1;
      else {
	     old_selector = selector;
        regs.w.ax = 0x0100;
        regs.w.bx = (size+ 15) >> 4;
        int386(0x31, (const union REGS *)&regs, (union REGS *)&regs);
        if (!regs.x.cflag) {
            dma->vptr = (unsigned char RFAR *)((regs.x.eax & 0xffff) << 4);
            dma->paddr = (unsigned long)dma->vptr;
            dma->size = size;
            rc = size;
            selector = (short)(regs.w.dx);
            vptr = dma->vptr;
        }
        else {
          dma->size = 0;
          rc = 0;
        }
        //free the memory
        regs.w.ax = 0x0101;
        regs.w.dx = old_selector;
        int386 (0x31, &regs, &regs);
      }
    }
    if (!good) {
	   old_selector = selector;
      //free the memory
      regs.w.ax = 0x0101;
      regs.w.dx = selector;
      int386 (0x31, &regs, &regs);
      dma->size = 0;
      rc = 0;
    }
    else {
      //dma->vptr = (unsigned char RFAR *)vptr;
      //dma->paddr = paddr;
      //dma->size = size;
      //rc = size;
    }
  }
  else {
    dma->size = 0;
    rc =0;
  }
#else
  /* Get a buffer to hold the data */
  vptr = (unsigned char *)malloc((size_t)size);
  if (!vptr) {
    dma->size = 0;
    rc = 0;
  }
  else {
    for (i=0; (i<6 && !good && (vptr)); i++) {
      paddr = (((unsigned long)FP_SEG(vptr)<<4) + (unsigned long)FP_OFF(vptr));

      s_page = paddr >> 16;
      e_page = (paddr + size) >> 16;

      if (s_page == e_page)
	good = 1;
      else {
	old_mem = vptr;
	vptr = malloc((size_t)size);
	free(old_mem);
      }
    }
    if (!good) {
      free(vptr);
      dma->size = 0;
      rc = 0;
    }
    else {
      dma->vptr = (unsigned char RFAR *)vptr;
      dma->paddr = paddr;
      dma->size = size;
      rc = size;
    }
  }
#endif
  return(rc);
}

static void free_dma_buffer(struct iw_dma_buff *dma)
{
#ifdef __WATCOMC__
#else
  if (dma->vptr) {
    free(dma->vptr);
    dma->vptr = 0;
  }
#endif
}

static int my_open(FILE **fp, char *Filename)
{
  int bits = 0;

  *fp = fopen(Filename,"rb");
  if (*fp == NULL) return(WAV_API_FILE_NOT_FOUND);
/* Check if we have a WAV file */
  fread(&riff_header, 1, sizeof(riff_header), *fp);
  if (strncmp(riff_header.RIFF,"RIFF",4) == 0 &&
      strncmp(riff_header.WAVE,"WAVE",4) == 0 &&
      strncmp(riff_header.fmt, "fmt ",4) == 0) {
    fread(&format_header, 1, (unsigned int)riff_header.fmt_size, *fp);
    if (format_header.wFormatTag == 1) {
      Stereo = format_header.nChannels == 2;
      bits = format_header.nBitsPerSample;
      if (bits != 8 && bits != 16) {
	fclose(*fp);
	return(WAV_API_INVALID_SAMPLE_SIZE);
      } else {
	Sixteen = (bits == 16);
	Frequency = format_header.nSamplesPerSec;
	while (fread(&data_header, sizeof(data_header), 1, *fp)) {
	    if (strncmp(data_header.DATA, "data", 4) == 0) break;
	    fseek(*fp, data_header.size, SEEK_CUR);
	}
	if (strncmp(data_header.DATA, "data", 4)) {
	  fclose(*fp);
	  return(WAV_API_NO_DATA);
	}
	else fread(&data_header.size, 1, 4, *fp);
      }
    } else {
      fclose(*fp);
      return(WAV_API_NOT_WAVE);
    }
  } else {
      fclose(*fp);
      return(WAV_API_NOT_WAVE);
  }
  return(0);
}

static int RFAR LOADDS digCB(int reason,
	       int cb_voice,
	       unsigned char RFAR * RFAR *buf,
	       unsigned long RFAR *size
	      )
{
  int rc = IW_DIG_BUFFER_DONE;

  if (cb_voice == voice)
    switch (reason) {
      case IW_DIG_MORE_DATA:
	if (stop) {
	  rc = IW_DIG_DONE;
	} else {
	  play_index = (play_index+1) % DataBufferNumber;
	  if (data_buffer[play_index].stat != CLEAR) {
	    *buf = (unsigned char RFAR *)data_buffer[play_index].vptr;
	    *size = data_buffer[play_index].size;
	    if (data_buffer[play_index].stat == LAST) stop = 1;
	    rc = IW_DIG_MORE_DATA;
	  }
	  else rc = IW_DIG_DONE;
	}
	break;
      case IW_DIG_BUFFER_DONE:
	played_index = (played_index+1) % DataBufferNumber;
	data_buffer[played_index].stat = CLEAR;
	rc = IW_DIG_BUFFER_DONE;
	break;
      case IW_DIG_DONE:
	voice = -1;
	rc = IW_DIG_DONE;
	break;
    }
    return(rc);
}

static int wav_check_buffers(void)
{
  unsigned short amount_read;
  int i, npi, tookit;
  char RFAR *buf;
  unsigned long size;

  if (voice != -1) {
    if (!stop) {
      while (data_buffer[read_index].stat == CLEAR) {
	amount_read = fread(data_buffer[read_index].vptr, 1, DataBufferSize, wave_fp);
	if (amount_read) {
	  data_buffer[read_index].size = amount_read;
	  if (amount_read < DataBufferSize) {
	    data_buffer[read_index].stat = LAST;
	    break;
	  } else {
	    data_buffer[read_index].stat = FULL;
	  }
	}
	read_index = (read_index + 1) % DataBufferNumber;
      }
      if (start) {
	PUSH_DISABLE;
	npi = (play_index+1) % DataBufferNumber;
	if (data_buffer[npi].stat != CLEAR) {
	  buf = (unsigned char RFAR *)data_buffer[npi].vptr;
	  size = data_buffer[npi].size;
	  if (wav_use_codec) {
	    tookit = iw_codec_play_next_buffer(voice, buf, size);
	  } else {
	    tookit = iw_synth_play_next_buffer(voice, buf, size);
	  }
	  if (tookit == IW_OK) {
	    if (data_buffer[npi].stat == LAST) stop = 1;
	    play_index = npi;
	  }
	}
	POP_FLAGS;
      }
    }
    return(0);
  } else {
    if (wave_fp) {
      fclose(wave_fp);
      wave_fp = 0;
      WaveVoice = -1;
    }
  }
  return(1);
}

static int play_data(void)
{
  int i;
  unsigned char type = 0;
  char RFAR *buf;
  unsigned long size;

  if (Stereo) type |= IW_TYPE_STEREO;
  if (!Sixteen) type |= IW_TYPE_8BIT|IW_TYPE_INVERT_MSB;
  type |= IW_TYPE_PRELOAD;

  stop = 0;
  start = 0;
  read_index = 0;
  play_index = -1;
  played_index = -1;
  for (i=0; i<DataBufferNumber; i++) {
    data_buffer[i].stat = CLEAR;
    data_buffer[i].size = 0L;
  }
  /* Call play digital to get a voice */
  if (wav_use_codec) {
    voice = iw_codec_play_digital(NULL, 0UL,
	   Volume, Pan, Frequency, type, &dma_buff, digCB);
  } else {
    voice = iw_synth_play_digital(0, NULL, 0UL,
	   iw_buf, Volume, Pan, Frequency, type, &dma_buff, digCB);
  }
  if (voice >= 0) {
    wav_check_buffers();
    digCB(IW_DIG_MORE_DATA, voice,
	  (unsigned char RFAR * RFAR *)&buf, 
	  (unsigned long RFAR *)&size);
    if (wav_use_codec) {
      iw_codec_play_next_buffer(voice, buf, size);
      iw_codec_start_digital(voice);
    } else {
      iw_synth_play_next_buffer(voice, buf, size);
      iw_synth_start_digital(voice);
    }
    start = 1;
  } else {
      return(WAV_API_HARDWARE_UNAVAIL);
  }
  return(voice);
}

static int wav_play(char *fname)
{
  int ret;

  /* Open File and read wave header if there is one */
  ret = my_open(&wave_fp, fname);
  if (ret != 0) return(ret);

  /* Time to actually play the data */
  return(play_data());
}
/*** PUBLIC FUNCTIONS ****************************************************/

void PauseWave(void)
{
  if (WaveVoice != -1) {
    if (!dig_paused) {
      dig_paused ^= 1;
      if (wav_use_codec) {
	iw_codec_pause_digital(voice);
      } else {
	iw_synth_pause_digital(voice);
      }
    }
  }
}
void UnPauseWave(void)
{
  if (WaveVoice != -1) {
    if (dig_paused) {
      dig_paused ^= 1;
      if (wav_use_codec) {
	iw_codec_restart_digital(voice);
      } else {
	iw_synth_restart_digital(voice);
      }
    }
  }
}
void StopWaveFile(void)
{
  if (WaveVoice != -1) {
    stop = 1;
    if (wav_use_codec) iw_codec_stop_digital(voice);
    else iw_synth_stop_digital(voice);
    wav_check_buffers();
  }
}
extern struct load_kernel_cfg public_cfg;
int PlayWaveFile(char *fname)
{
    int ret;

    StopWaveFile();
    ret = WaveVoice = wav_play(fname);
    if (WaveVoice < 0) {
	WaveVoice = -1;
	return(ret);
    }
    return(0);
}
int PollWave(void)
{
    return(!wav_check_buffers());
}
void SetWaveBufferSizes(unsigned short dbs, unsigned short dmabs, int nb)
{
    DataBufferSize   = dbs;
    DmaBufferSize    = dmabs;
    DataBufferNumber = nb;
}
int InitWave(void)
{
    int i;

    if (get_dma_buffer(DmaBufferSize, &dma_buff) == 0) {
	return(WAV_API_NO_MEMORY);
    }
    if (!wav_use_codec) {
      iw_buf = iw_malloc(DmaBufferSize);
      if (iw_buf == 0) return(WAV_API_HARDWARE_UNAVAIL);
    }

    /* Get the disk buffers */
    data_buffer = (data_buffer_t *)malloc((size_t)(sizeof(struct data_buffer_s) * DataBufferNumber));
    if (!data_buffer) return(WAV_API_NO_MEMORY);
    memset(data_buffer, 0, (sizeof(struct data_buffer_s) * DataBufferNumber));
    for (i=0; i<DataBufferNumber; i++) {
      data_buffer[i].vptr = malloc((size_t)DataBufferSize);
      if (!data_buffer[i].vptr) return(WAV_API_NO_MEMORY);
      data_buffer[i].stat = CLEAR;
      data_buffer[i].size = 0L;
    }

    /* Clean up alocated memory */
    WaveVoice = -1;
    dig_paused = 0;
    return(0);
}
void DeInitWave(void)
{
    int i;

    StopWaveFile();
    if (data_buffer) {
      for (i=0; i<DataBufferNumber; i++) {
	if (data_buffer[i].vptr) free(data_buffer[i].vptr);
      }
      free(data_buffer);
      data_buffer = 0;
    }
    free_dma_buffer(&dma_buff);
    if (iw_buf) {
      iw_free(iw_buf);
      iw_buf = 0;
    }
}
