/*
 * ux_audio_oss.c - Sound support using the OSS drivers
 *
 * This code is mostly verbatim from the file x_sample.c in Daniel
 * Schepler's xfrotz-2.32.1.
 *
 * This file is part of Frotz.
 *
 * Frotz is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Frotz is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#define __UNIX_PORT_FILE

#include <signal.h>
/* #include <bits/sigaction.h> */


#ifdef USE_NCURSES_H
#include <ncurses.h>
#else
#include <curses.h>
#endif

#include "ux_frotz.h"

#ifdef OSS_SOUND	/* don't compile this if not using OSS */

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/wait.h>

#include "../soundcard.h"

extern void end_of_sound(void);

/* Buffer used to store sample data */
static char *sound_buffer = NULL;
static int sound_length;
static int sample_rate;
static int current_num;

/* Implementation of the separate process which plays the sounds.
   The signals used to communicate with the process are:
     SIGINT - complete current repeat of the sound, then quit
     SIGTERM - stop sound immediately
*/

static pid_t child_pid;

/* Number of repeats left */
static int num_repeats;

/* File handles for mixer and PCM devices */
static int mixer_fd, dsp_fd;

static int old_volume;

static void sigterm_handler(int signal) {
  ioctl(dsp_fd, SNDCTL_DSP_RESET, 0);
  if (mixer_fd >= 0)
    ioctl(mixer_fd, SOUND_MIXER_WRITE_VOLUME, &old_volume);
  _exit(0);
}

static void oss_sigint_handler(int signal) {
  num_repeats = 1;
}

static void play_sound(int volume, int repeats) {
  struct sigaction sa;
  int format = AFMT_U8;
  int channels = 1;

  dsp_fd = open(SOUND_DEV, O_WRONLY);
  if (dsp_fd < 0) {
    perror(SOUND_DEV);
    _exit(1);
  }


    /* This section of code fixes the nasty problem of samples
     * being played with pops and scratches when used with a
     * non-Linux system implementing OSS sound.
     */
  if (ioctl(dsp_fd, SNDCTL_DSP_SETFMT, &format) == -1) {
    perror(SOUND_DEV);
    exit(1);
  }
  if (format != AFMT_U8) {
    fprintf(stderr, "bad sound format\n");
    exit(1);
  }
  if (ioctl(dsp_fd, SNDCTL_DSP_CHANNELS, &channels) == -1) {
    perror(SOUND_DEV);
    exit(1);
  }
  if (channels != 1) {
    fprintf(stderr, "bad channels\n");
    exit(1);
  }
  /* End sound fix from Torbjorn Andersson (no dot thingies) */

  ioctl(dsp_fd, SNDCTL_DSP_SPEED, &sample_rate);

  if (volume != 255) {
    mixer_fd = open("/dev/mixer", O_RDWR);
    if (mixer_fd < 0)
      perror("/dev/mixer");
    else {
      int new_vol;
      ioctl(mixer_fd, SOUND_MIXER_READ_VOLUME, &old_volume);
      new_vol = volume * 100 / 8;
      new_vol = (new_vol << 8) | new_vol;
      ioctl(mixer_fd, SOUND_MIXER_WRITE_VOLUME, &new_vol);
    }
  }
  else
    mixer_fd = -1;

  sa.sa_handler = sigterm_handler;
  sigemptyset(&sa.sa_mask);
  sigaddset(&sa.sa_mask, SIGINT);
  sigaddset(&sa.sa_mask, SIGTERM);
  sa.sa_flags = 0;
  sigaction(SIGTERM, &sa, NULL);
  sa.sa_handler = oss_sigint_handler;
  sigaction(SIGINT, &sa, NULL);

  for (num_repeats = repeats; num_repeats > 0;
       num_repeats < 255 ? num_repeats-- : 0) {
    char *curr_pos = sound_buffer;
    int len_left = sound_length;
    int write_result;

    while (len_left > 0) {
      write_result = write(dsp_fd, curr_pos, len_left);
      if (write_result <= 0) {
        perror(SOUND_DEV);
        goto finish;
      }
      curr_pos += write_result;
      len_left -= write_result;
    }
  }

 finish:
  ioctl(dsp_fd, SNDCTL_DSP_SYNC, 0);
  if (mixer_fd >= 0)
    ioctl(mixer_fd, SOUND_MIXER_WRITE_VOLUME, &old_volume);
  _exit(0);
}


/*
 * os_beep
 *
 * Play a beep sound. Ideally, the sound should be high- (number == 1)
 * or low-pitched (number == 2).
 *
 */

void os_beep (int number)
{

    /* This should later be expanded to support high and low beeps.
     */

    beep();

}/* os_beep */


/*
 * os_prepare_sample
 *
 * Load the sample from the disk.
 *
 */

void os_prepare_sample (int number)
{
  FILE *samples;
  char *filename;
  const char *basename, *dotpos;
  int namelen;
  int read_length;

  if (sound_buffer != NULL && current_num == number)
    return;

  free(sound_buffer);
  sound_buffer = NULL;

  filename = malloc(strlen(f_setup.story_name) + 10);

  if (! filename)
    return;

  basename = strrchr(f_setup.story_name, '/');
  if (basename) basename++; else basename = f_setup.story_name;
  dotpos = strrchr(basename, '.');
  namelen = (dotpos ? dotpos - basename : strlen(basename));
  if (namelen > 6) namelen = 6;
  sprintf(filename, "%.*ssound/%.*s%02d.snd",
          basename - f_setup.story_name, f_setup.story_name,
          namelen, basename, number);


  samples = fopen(filename, "r");
  if (samples == NULL) {
    perror(filename);
    return;
  }

  fgetc(samples); fgetc(samples); fgetc(samples); fgetc(samples);
  sample_rate = fgetc(samples) << 8;
  sample_rate |= fgetc(samples);
  fgetc(samples); fgetc(samples);
  sound_length = fgetc(samples) << 8;
  sound_length |= fgetc(samples);
  sound_buffer = NULL;

  if (sound_length > 0) {
    sound_buffer = malloc(sound_length);
    if (!sound_buffer) {
	perror("malloc");
	return;
    }
    read_length = fread(sound_buffer, 1, sound_length, samples);
    if (read_length < sound_length) {
      if (feof(samples)) {
	/*
	 * One of the Sherlock samples trigger this for me, so let's make it
	 * a non-fatal error.
	 */
	sound_buffer = realloc(sound_buffer, read_length);
	if (! sound_buffer) {
	  perror("realloc");
	  return;
	}
	sound_length = read_length;
      } else {
	errno = ferror(samples);
	perror(filename);
	free(sound_buffer);
	sound_buffer = NULL;
      }
    }
  }
}/* os_prepare_sample */

static void sigchld_handler(int signal) {
  int status;
  struct sigaction sa;

  waitpid(child_pid, &status, WNOHANG);
  child_pid = 0;
  sa.sa_handler = SIG_IGN;
  sigemptyset(&sa.sa_mask);
  sa.sa_flags = 0;
  sigaction(SIGCHLD, &sa, NULL);
  end_of_sound();
}

/*
 * os_start_sample
 *
 * Play the given sample at the given volume (ranging from 1 to 8 and
 * 255 meaning a default volume). The sound is played once or several
 * times in the background (255 meaning forever). In Z-code 3 the
 * repeats value is always 0 and the number of repeats is taken from
 * the sound file itself. The end_of_sound function is called as soon
 * as the sound finishes.
 *
 */

void os_start_sample (int number, int volume, int repeats, zword eos)
{
  /* INCOMPLETE */

  sigset_t sigchld_mask;
  struct sigaction sa;

  os_prepare_sample(number);
  if (! sound_buffer)
    return;
  os_stop_sample(0);

  sigemptyset(&sigchld_mask);
  sigaddset(&sigchld_mask, SIGCHLD);
  sigprocmask(SIG_BLOCK, &sigchld_mask, NULL);

  child_pid = fork();

  if (child_pid < 0) {          /* error in fork */
    perror("fork");
    return;
  }
  else if (child_pid == 0) {    /* child */
    sigprocmask(SIG_UNBLOCK, &sigchld_mask, NULL);
    play_sound(volume, repeats);
  }
  else {                        /* parent */
    sa.sa_handler = sigchld_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    sigaction(SIGCHLD, &sa, NULL);

    sigprocmask(SIG_UNBLOCK, &sigchld_mask, NULL);
  }
}/* os_start_sample */

/* Send the specified signal to the player program, then wait for
   it to exit. */

static void stop_player(int signal) {
  sigset_t sigchld_mask;
  struct sigaction sa;
  int status;

  sigemptyset(&sigchld_mask);
  sigaddset(&sigchld_mask, SIGCHLD);
  sigprocmask(SIG_BLOCK, &sigchld_mask, NULL);

  if (child_pid == 0) {
    sigprocmask(SIG_UNBLOCK, &sigchld_mask, NULL);
    return;
  }
  kill(child_pid, signal);
  waitpid(child_pid, &status, 0);
  child_pid = 0;

  sa.sa_handler = SIG_IGN;
  sigemptyset(&sa.sa_mask);
  sa.sa_flags = 0;
  sigaction(SIGCHLD, &sa, NULL);

  sigprocmask(SIG_UNBLOCK, &sigchld_mask, NULL);
}

/*
 * os_stop_sample
 *
 * Turn off the current sample.
 *
 */

void os_stop_sample (int number)
{
  /* INCOMPLETE */

  stop_player(SIGTERM);
}/* os_stop_sample */

/*
 * os_finish_with_sample
 *
 * Remove the current sample from memory (if any).
 *
 */

void os_finish_with_sample (int number)
{
  /* INCOMPLETE */

  free(sound_buffer);
  sound_buffer = NULL;
}/* os_finish_with_sample */

/*
 * os_wait_sample
 *
 * Stop repeating the current sample and wait until it finishes.
 *
 */

void os_wait_sample (void)
{
  stop_player(SIGINT);
}/* os_wait_sample */

#endif /* OSS_SOUND */
