#include "config.h"

#ifdef USE_AUDIO
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <ctype.h>
#include <sys/types.h>

#ifdef USE_NLLIBC
#include <nllibc.h>
#endif

#include "nlllib.h"
#include "memory.h"
#include "sound.h"

struct chunk_header {
#define CHUNK_HEADER_ID_RIFF 0x46464952 /* RIFF */
#define CHUNK_HEADER_ID_FMT  0x20746d66 /* fmt */
#define CHUNK_HEADER_ID_DATA 0x61746164 /* data */
  uint32_t id;
  uint32_t size;
};
#define CHUNK_HEADER_SIZE sizeof(struct chunk_header)

struct riff_chunk_header {
  struct chunk_header header;
#define RIFF_CHUNK_HEADER_FORMAT_WAVE 0x45564157 /* WAVE */
  uint32_t format;
};
#define RIFF_CHUNK_HEADER_SIZE sizeof(struct riff_chunk_header)

struct fmt_chunk_header {
  struct chunk_header header;
#define FMT_CHUNK_HEADER_FORMAT_TYPE_PCM 0x0001
  uint16_t format_type;
  uint16_t channels;
  uint32_t samples_per_sec;
  uint32_t bytes_per_sec;
  uint16_t block_size;
  uint16_t bits_per_sample;
};
#define FMT_CHUNK_HEADER_SIZE sizeof(struct fmt_chunk_header)

struct data_chunk_header {
  struct chunk_header header;
};
#define DATA_CHUNK_HEADER_SIZE sizeof(struct data_chunk_header)

static sound_t sounds[AUDIO_SOUND_MAXNUM];
static int sound_num = 0;

int sound_init(void)
{
  sound_num = 0;
  memset(sounds, 0, sizeof(sounds));
  return 0;
}

int sound_done(void)
{
  int index;
  sound_t sound;

  for (index = 0; index < AUDIO_SOUND_MAXNUM; index++) {
    sound = sounds[index];
    if (sound) {
      sound_destroy(sound);
    }
  }

  return 0;
}

int sound_check(void)
{
  int index;

  if (sound_num)
    return NLL_ERRCODE_MEMORY_NOT_EMPTY;

  for (index = 0; index < AUDIO_SOUND_MAXNUM; index++) {
    if (sounds[index])
      return NLL_ERRCODE_MEMORY_NOT_EMPTY;
  }

  return 0;
}

static sound_t _alloc(void)
{
  sound_t sound;
  if ((sound = memory_alloc(sizeof(*sound))) == NULL)
    return NULL;
  return sound;
}

static int _free(sound_t sound)
{
  memory_free(sound);
  return 0;
}

union evalue {
  uint8_t  b[4];
  uint16_t h[2];
  uint32_t w[1];
};

static uint16_t read_le16(uint16_t val)
{
  union evalue e;
  e.h[0] = val;
  return (e.b[1] << 8) | e.b[0];
}

static uint32_t read_le32(uint32_t val)
{
  union evalue e;
  e.w[0] = val;
  return (e.b[3] << 24) | (e.b[2] << 16) | (e.b[1] << 8) | e.b[0];
}

static uint16_t write_le16(uint16_t val)
{
  union evalue e;
  e.b[0] =  val        & 0xff;
  e.b[1] = (val >>  8) & 0xff;
  return e.h[0];
}

static uint32_t write_le32(uint32_t val)
{
  union evalue e;
  e.b[0] =  val        & 0xff;
  e.b[1] = (val >>  8) & 0xff;
  e.b[2] = (val >> 16) & 0xff;
  e.b[3] = (val >> 24) & 0xff;
  return e.w[0];
}

static int sound_null_ropen(sound_t sound, const char *filename)
{
  return -1;
}

static int sound_null_wopen(sound_t sound, const char *filename,
			    int samplefreq, int channels, int bits, int samplenum)
{
  return -1;
}

static int sound_null_read(sound_t sound, short *frames, int num)
{
  return -1;
}

static int sound_null_write(sound_t sound, short *frames, int num)
{
  return -1;
}

static int sound_null_rclose(sound_t sound)
{
  return -1;
}

static int sound_null_wclose(sound_t sound)
{
  return -1;
}

static int sound_wav_ropen(sound_t sound, const char *filename)
{
  struct riff_chunk_header rhdr;
  struct fmt_chunk_header fhdr;
  struct data_chunk_header dhdr;
  int riffsize, fmtsize, datasize, i, ch, n = 0, size;
  const char *mode;
  unsigned char dummy;
  union evalue e;

#ifndef WIN32
  mode = "r";
#else
  mode = "rb";
#endif

  sound->fp = fopen(filename, mode);
  if (!sound->fp)
    goto err;

  memset(&rhdr, 0, sizeof(rhdr));
  memset(&fhdr, 0, sizeof(fhdr));
  memset(&dhdr, 0, sizeof(dhdr));

  if (fread(&rhdr, 1, RIFF_CHUNK_HEADER_SIZE, sound->fp) != RIFF_CHUNK_HEADER_SIZE)
    goto err;

  rhdr.header.id         = read_le32(rhdr.header.id);
  rhdr.header.size       = read_le32(rhdr.header.size);
  rhdr.format            = read_le32(rhdr.format);

  if ((rhdr.header.id != CHUNK_HEADER_ID_RIFF) ||
      (rhdr.format    != RIFF_CHUNK_HEADER_FORMAT_WAVE))
    goto err;

  riffsize = rhdr.header.size + CHUNK_HEADER_SIZE;

  if (fread(&fhdr, 1, FMT_CHUNK_HEADER_SIZE, sound->fp) != FMT_CHUNK_HEADER_SIZE)
    goto err;

  fhdr.header.id         = read_le32(fhdr.header.id);
  fhdr.header.size       = read_le32(fhdr.header.size);
  fhdr.format_type       = read_le16(fhdr.format_type);
  fhdr.channels          = read_le16(fhdr.channels);
  fhdr.samples_per_sec   = read_le32(fhdr.samples_per_sec);
  fhdr.bytes_per_sec     = read_le32(fhdr.bytes_per_sec);
  fhdr.block_size        = read_le16(fhdr.block_size);
  fhdr.bits_per_sample   = read_le16(fhdr.bits_per_sample);

  if ((fhdr.header.id   != CHUNK_HEADER_ID_FMT) ||
      (fhdr.format_type != FMT_CHUNK_HEADER_FORMAT_TYPE_PCM))
    goto err;

  fmtsize = fhdr.header.size + CHUNK_HEADER_SIZE;

  if (fmtsize < FMT_CHUNK_HEADER_SIZE)
    goto err;

  sound->channels   = fhdr.channels;
  sound->samplefreq = fhdr.samples_per_sec;
  sound->bits       = fhdr.bits_per_sample;

  if (sound->samplefreq <= 0)
    goto err;

  if ((sound->channels != 1) && (sound->channels != 2))
    goto err;

  if ((sound->bits != 8) && (sound->bits != 16) && (sound->bits != 32))
    goto err;

  if (fmtsize > FMT_CHUNK_HEADER_SIZE) {
    for (i = 0; i < fmtsize - FMT_CHUNK_HEADER_SIZE; i++) {
      if (fread(&dummy, 1, 1, sound->fp) != 1)
	goto err;
    }
  }

  if (fread(&dhdr, 1, DATA_CHUNK_HEADER_SIZE, sound->fp) != DATA_CHUNK_HEADER_SIZE)
    goto err;

  dhdr.header.id         = read_le32(dhdr.header.id);
  dhdr.header.size       = read_le32(dhdr.header.size);

  if (dhdr.header.id != CHUNK_HEADER_ID_DATA)
    goto err;

  datasize = dhdr.header.size + CHUNK_HEADER_SIZE;

  if (riffsize < RIFF_CHUNK_HEADER_SIZE + fmtsize + datasize)
    goto err;

  sound->samplenum = (datasize - DATA_CHUNK_HEADER_SIZE) / sound->channels / (sound->bits / 8);

  if (sound->samplenum < 0)
    goto err;

  size = sound->channels * (sound->bits / 8) * sound->samplenum;
  sound->frames.p = memory_alloc(size);
  if (!sound->frames.p)
    goto err;
  memset(sound->frames.p, 0, size);

  sound->offset = 0;

  for (i = 0; i < sound->samplenum; i++) {
    for (ch = 0; ch < sound->channels; ch++) {
      memset(&e, 0, sizeof(e));
      if (fread(&e, 1, sound->bits / 8, sound->fp) != (sound->bits / 8))
	goto err;
      switch (sound->bits) {
      case  8: sound->frames.b[n] =           e.b[0] ; break;
      case 16: sound->frames.h[n] = read_le16(e.h[0]); break;
      case 32: sound->frames.w[n] = read_le32(e.w[0]); break;
      default: break;
      }
      n++;
    }
    sound->offset++;
  }

  sound->offset = 0;

  fclose(sound->fp);
  sound->fp = NULL;

  return 0;

err:
  if (sound->frames.p) {
    memory_free(sound->frames.p);
    sound->frames.p = NULL;
  }
  if (sound->fp) {
    fclose(sound->fp);
    sound->fp = NULL;
  }
  return -1;
}

static int sound_wav_wopen(sound_t sound, const char *filename,
			   int samplefreq, int channels, int bits, int samplenum)
{
  struct riff_chunk_header rhdr;
  struct fmt_chunk_header fhdr;
  struct data_chunk_header dhdr;
  int riffsize, fmtsize, datasize;
  const char *mode;

  if ((samplenum < 0) || (samplefreq <= 0))
    goto err;

  if ((channels != 1) && (channels != 2))
    goto err;

  if ((bits != 8) && (bits != 16) && (bits != 32))
    goto err;

  sound->samplefreq = samplefreq;
  sound->channels   = channels;
  sound->bits       = bits;
  sound->samplenum  = samplenum;

#ifndef WIN32
  mode = "w";
#else
  mode = "wb";
#endif

  sound->fp = fopen(filename, mode);
  if (!sound->fp)
    goto err;

  datasize = DATA_CHUNK_HEADER_SIZE + (sound->channels * (sound->bits / 8) * sound->samplenum);
  fmtsize  = FMT_CHUNK_HEADER_SIZE;
  riffsize = RIFF_CHUNK_HEADER_SIZE + fmtsize + datasize;

  memset(&rhdr, 0, sizeof(rhdr));
  memset(&fhdr, 0, sizeof(fhdr));
  memset(&dhdr, 0, sizeof(dhdr));

  rhdr.header.id         = write_le32(CHUNK_HEADER_ID_RIFF);
  rhdr.header.size       = write_le32(riffsize - CHUNK_HEADER_SIZE);
  rhdr.format            = write_le32(RIFF_CHUNK_HEADER_FORMAT_WAVE);

  fhdr.header.id         = write_le32(CHUNK_HEADER_ID_FMT);
  fhdr.header.size       = write_le32(fmtsize - CHUNK_HEADER_SIZE);
  fhdr.format_type       = write_le16(FMT_CHUNK_HEADER_FORMAT_TYPE_PCM);
  fhdr.channels          = write_le16(sound->channels);
  fhdr.samples_per_sec   = write_le32(sound->samplefreq);
  fhdr.bytes_per_sec     = write_le32((sound->bits / 8) * sound->channels * sound->samplefreq);
  fhdr.block_size        = write_le16((sound->bits / 8) * sound->channels);
  fhdr.bits_per_sample   = write_le16(sound->bits);

  dhdr.header.id         = write_le32(CHUNK_HEADER_ID_DATA);
  dhdr.header.size       = write_le32(datasize - CHUNK_HEADER_SIZE);

  if (fwrite(&rhdr, 1, RIFF_CHUNK_HEADER_SIZE, sound->fp) != RIFF_CHUNK_HEADER_SIZE)
    goto err;

  if (fwrite(&fhdr, 1, FMT_CHUNK_HEADER_SIZE, sound->fp) != FMT_CHUNK_HEADER_SIZE)
    goto err;

  if (fwrite(&dhdr, 1, DATA_CHUNK_HEADER_SIZE, sound->fp) != DATA_CHUNK_HEADER_SIZE)
    goto err;

  return 0;

err:
  if (sound->fp) {
    fclose(sound->fp);
    sound->fp = NULL;
  }
  return -1;
}

static int _read(sound_t sound, short *frames, int num)
{
  int i, ch, n = 0, m;

  m = sound->offset * sound->channels;

  for (i = 0; i < num; i++) {
    for (ch = 0; ch < sound->channels; ch++) {
      if (sound->offset < sound->samplenum) {
	switch (sound->bits) {
	case  8: frames[n] = sound->frames.b[m] <<  8; break;
	case 16: frames[n] = sound->frames.h[m]      ; break;
	case 32: frames[n] = sound->frames.w[m] >> 16; break;
	default: break;
	}
      } else {
	frames[n] = 0;
      }
      n++;
      m++;
    }
    sound->offset++;
  }

  return 0;
}

static int sound_wav_read(sound_t sound, short *frames, int num)
{
  return _read(sound, frames, num);
}

static int sound_wav_write(sound_t sound, short *frames, int num)
{
  int i, ch, n = 0;
  union evalue e;

  if (!sound->fp)
    return -1;

  for (i = 0; i < num; i++) {
    for (ch = 0; ch < sound->channels; ch++) {
      memset(&e, 0, sizeof(e));
      if (sound->offset < sound->samplenum) {
	switch (sound->bits) {
	case  8: e.b[0] =           (frames[n] >>  8); break;
	case 16: e.h[0] = write_le16(frames[n]      ); break;
	case 32: e.w[0] = write_le32(frames[n] << 16); break;
	default: break;
	}
      }
      fwrite(&e, 1, sound->bits / 8, sound->fp);
      n++;
    }
    sound->offset++;
  }

  return 0;
}

static int _close(sound_t sound)
{
  if (!sound->fp)
    return -1;

  fclose(sound->fp);
  sound->fp = NULL;

  return 0;
}

static int sound_wav_rclose(sound_t sound)
{
  return _close(sound);
}

static int sound_wav_wclose(sound_t sound)
{
  int align_num, ch;
  union evalue e;

  if (!sound->fp)
    return -1;

  align_num = sound->samplenum;
  if ((sound->channels == 1) && (sound->bits == 8) && ((sound->samplenum % 2) != 0))
    align_num++;

  memset(&e, 0, sizeof(e));

  while (sound->offset < align_num) {
    for (ch = 0; ch < sound->channels; ch++) {
      fwrite(&e, 1, sound->bits / 8, sound->fp);
    }
    sound->offset++;
  }

  fclose(sound->fp);
  sound->fp = NULL;

  return 0;
}

static int skipspace(char **p)
{
  while (**p && isspace(**p))
    (*p)++;
  if (!**p)
    return -1;
  return 0;
}

static int skipnspace(char **p)
{
  while (**p && !isspace(**p))
    (*p)++;
  if (!**p)
    return -1;
  return 0;
}

static int getparam(char *p, char *keyword)
{
  char *k;
  int param;

  if (*p != '#')
    return -1;
  p++;

  if (skipspace(&p) < 0)
    return -1;

  k = p;

  if (skipnspace(&p) < 0)
    return -1;

  *(p++) = '\0';
  if (strcmp(k, keyword))
    return -1;

  param = strtol(p, NULL, 0);
  if (param < 0)
    return 0;

  return param;
}

static int sound_text_ropen(sound_t sound, const char *filename)
{
  const char *mode;
  char line[256], *p;
  int i, ch, n = 0, size;

  mode = "r";

  sound->fp = fopen(filename, mode);
  if (!sound->fp)
    goto err;

  if (fgets(line, sizeof(line), sound->fp) == NULL) /* filename */
    goto err;

  if (fgets(line, sizeof(line), sound->fp) == NULL)
    goto err;
  sound->samplefreq = getparam(line, "Freq:");

  if (fgets(line, sizeof(line), sound->fp) == NULL)
    goto err;
  sound->channels = getparam(line, "Channels:");

  if (fgets(line, sizeof(line), sound->fp) == NULL)
    goto err;
  sound->bits = getparam(line, "Bits:");

  if (fgets(line, sizeof(line), sound->fp) == NULL)
    goto err;
  sound->samplenum = getparam(line, "Length:");

  if ((sound->samplenum < 0) || (sound->samplefreq <= 0))
    goto err;

  if ((sound->bits != 8) && (sound->bits != 16) && (sound->bits != 32))
    goto err;

  size = sound->channels * (sound->bits / 8) * sound->samplenum;
  sound->frames.p = memory_alloc(size);
  if (!sound->frames.p)
    goto err;
  memset(sound->frames.p, 0, size);

  sound->offset = 0;

  for (i = 0; i < sound->samplenum; i++) {
    if (fgets(line, sizeof(line), sound->fp) == NULL)
      goto err;
    p = line;
    for (ch = 0; ch < sound->channels; ch++) {
      if (skipnspace(&p) < 0)
	goto err;
      if (skipspace(&p) < 0)
	goto err;
      switch (sound->bits) {
      case  8: sound->frames.b[n] = strtol(p, NULL, 0); break;
      case 16: sound->frames.h[n] = strtol(p, NULL, 0); break;
      case 32: sound->frames.w[n] = strtol(p, NULL, 0); break;
      default: break;
      }
      n++;
    }
    sound->offset++;
  }

  sound->offset = 0;

  fclose(sound->fp);
  sound->fp = NULL;

  return 0;

err:
  if (sound->frames.p) {
    memory_free(sound->frames.p);
    sound->frames.p = NULL;
  }
  if (sound->fp) {
    fclose(sound->fp);
    sound->fp = NULL;
  }
  return -1;
}

static int sound_text_wopen(sound_t sound, const char *filename,
			    int samplefreq, int channels, int bits, int samplenum)
{
  const char *mode;

  if ((samplenum < 0) || (samplefreq <= 0))
    goto err;

  if ((bits != 8) && (bits != 16) && (bits != 32))
    goto err;

  sound->samplefreq = samplefreq;
  sound->channels   = channels;
  sound->bits       = bits;
  sound->samplenum  = samplenum;

  mode = "w";

  sound->fp = fopen(filename, mode);
  if (!sound->fp)
    goto err;

  fprintf(sound->fp, "# %s\n", filename);
  fprintf(sound->fp, "# Freq: %d\n", sound->samplefreq);
  fprintf(sound->fp, "# Channels: %d\n", sound->channels);
  fprintf(sound->fp, "# Bits: %d\n", sound->bits);
  fprintf(sound->fp, "# Length: %d\n", sound->samplenum);

  return 0;

err:
  if (sound->fp) {
    fclose(sound->fp);
    sound->fp = NULL;
  }
  return -1;
}

static int sound_text_read(sound_t sound, short *frames, int num)
{
  return _read(sound, frames, num);
}

static int sound_text_write(sound_t sound, short *frames, int num)
{
  int i, ch, n = 0, val;

  if (!sound->fp)
    return -1;

  for (i = 0; i < num; i++) {
    fprintf(sound->fp, "%d", sound->offset);
    for (ch = 0; ch < sound->channels; ch++) {
      val = 0;
      if (sound->offset < sound->samplenum)
	val = frames[n];
      fprintf(sound->fp, " %d", val);
      n++;
    }
    fprintf(sound->fp, "\n");
    sound->offset++;
  }

  return 0;
}

static int sound_text_rclose(sound_t sound)
{
  return _close(sound);
}

static int sound_text_wclose(sound_t sound)
{
  return _close(sound);
}

static struct {
  int (*ropen)(sound_t sound, const char *filename);
  int (*wopen)(sound_t sound, const char *filename,
	       int samplefreq, int channels, int bits, int samplenum);
  int (*read)(sound_t sound, short *frames, int num);
  int (*write)(sound_t sound, short *frames, int num);
  int (*rclose)(sound_t sound);
  int (*wclose)(sound_t sound);
} functions[] = {
  { sound_null_ropen, sound_null_wopen, sound_null_read, sound_null_write, sound_null_rclose, sound_null_wclose },
  { sound_wav_ropen,  sound_wav_wopen,  sound_wav_read,  sound_wav_write,  sound_wav_rclose,  sound_wav_wclose  },
  { sound_text_ropen, sound_text_wopen, sound_text_read, sound_text_write, sound_text_rclose, sound_text_wclose },

  { NULL, NULL, NULL }
};

int sound_get_index(sound_t sound)
{
  if (!sound)
    return -1;
  return sound->index;
}

sound_t sound_get_sound(int index)
{
  if ((index < 0) || (index >= AUDIO_SOUND_MAXNUM))
    return NULL;
  return sounds[index];
}

int sound_destroy(sound_t sound)
{
  int r;

  sounds[sound->index] = NULL;

  if (sound->frames.p) memory_free(sound->frames.p);
  if (sound->fp) fclose(sound->fp);

  memset(sound, 0, sizeof(*sound));

  sound_num--;
  if ((r = _free(sound)) < 0)
    return r;

  return 0;
}

sound_t sound_create(sound_type_t type, unsigned int flags)
{
  sound_t sound = NULL;
  int index;

  if (type >= SOUND_TYPE_NUM)
    goto err;

  for (index = 0; index < AUDIO_SOUND_MAXNUM; index++) {
    sound = sounds[index];
    if (!sound)
      break;
  }
  if (index == AUDIO_SOUND_MAXNUM)
    goto err;

  if ((sound = _alloc()) == NULL)
    goto err;
  sound_num++;

  memset(sound, 0, sizeof(*sound));
  sound->index = index;

  sounds[index] = sound;

  sound->type       = type;
  sound->samplefreq = 0;
  sound->channels   = 0;
  sound->bits       = 0;
  sound->samplenum  = 0;

  sound->flags = flags;

  sound->fp = NULL;
  sound->offset = 0;

  sound->frames.p = NULL;

  return sound;

err:
  if (sound)
    sound_destroy(sound);
  return NULL;
}

int sound_ropen(sound_t sound, const char *filename)
{
  if (!sound)
    return -1;
  return functions[sound->type].ropen(sound, filename);
}

int sound_wopen(sound_t sound, const char *filename,
		int samplefreq, int channels, int bits, int samplenum)
{
  if (!sound)
    return -1;
  return functions[sound->type].wopen(sound, filename, samplefreq, channels, bits, samplenum);
}

int sound_read(sound_t sound, short *frames, int num)
{
  if (!sound)
    return -1;
  return functions[sound->type].read(sound, frames, num);
}

int sound_write(sound_t sound, short *frames, int num)
{
  if (!sound)
    return -1;
  return functions[sound->type].write(sound, frames, num);
}

int sound_rclose(sound_t sound)
{
  if (!sound)
    return -1;
  return functions[sound->type].rclose(sound);
}

int sound_wclose(sound_t sound)
{
  if (!sound)
    return -1;
  return functions[sound->type].wclose(sound);
}
#endif
