/*
 * @(#)sound.c
 *
 * Taken from xlock, many authors...
 *
 * All rights reserved.
 *
 * Permission to use, copy, modify, and distribute this software and
 * its documentation for any purpose and without fee is hereby granted,
 * provided that the above copyright notice appear in all copies and
 * that both that copyright notice and this permission notice appear in
 * supporting documentation, and that the name of the author not be
 * used in advertising or publicity pertaining to distribution of the
 * software without specific, written prior permission.
 *
 * This program 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.
 */

#if defined(USE_RPLAY) || defined(USE_NAS) || defined(USE_VMSPLAY) || defined(USE_ESOUND) || defined (WINVER) || defined(DEF_PLAY)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "file.h"
#include "sound.h"

#ifdef WINVER
#define STRICT

#include <windows.h>
#include <mmsystem.h>

static void
play_sound(const char *fileName)
{
	(void) PlaySound(fileName, NULL, SND_ASYNC | SND_FILENAME);
}
#endif

#ifdef DEF_PLAY
static void
play_sound(const char *fileName)
{
	char *progrun = NULL;
	unsigned int callLength = strlen(DEF_PLAY) + strlen(fileName) + 11;

	if ((progrun = (char *) malloc(callLength)) != NULL) {
#ifdef HAVE_SNPRINTF
		(void) snprintf(progrun, callLength,
			"( %s %s ) 2>&1", DEF_PLAY, fileName);
#else
		(void) sprintf(progrun,
			"( %s %s ) 2>&1", DEF_PLAY, fileName);
#endif
#if 0
		(void) printf("%s\n", progrun);
#endif
		if (system(progrun))
			(void) printf("audio error\n");
		free(progrun);
	}
}

#endif

#ifdef USE_RPLAY
#include <rplay.h>

static void
play_sound(const char *fileName)
{
	int rplay_fd = rplay_open_default();

	if (rplay_fd >= 0) {
		rplay_sound(rplay_fd, (char *) fileName);
		rplay_close(rplay_fd);
	}
}
#endif

#ifdef USE_NAS
/* Gives me lots of errors when I compile nas-1.2p5  -- xlock maintainer */

/*-
 * Connect each time, because it might be that the server was not running
 * when xlock first started, but is when next nas_play is called
 */

#include <audio/audio.h>
#include <audio/audiolib.h>
#include <audio/soundlib.h>
#include "xwin.h"

extern Display *dsp;

static void
play_sound(const char *fileName)
{
	char *auservername = DisplayString(dsp);
	AuServer *aud; /* audio server connection */

	if (!(aud = AuOpenServer(auservername, 0, NULL, 0, NULL, NULL)))
		return;		/*cannot connect - no server? */
	/*
	 * now play the file at recorded volume (3rd arg is a percentage),
	 * synchronously
	 */
	AuSoundPlaySynchronousFromFile(aud, fileName, 100);
	AuCloseServer(aud);
}
#endif

#ifdef USE_XAUDIO
/* Anybody ever get this working? XAudio.007 */
#endif

#ifdef HAS_MMOV
#include <prvdef.h>
#include <ssdef.h>

extern void PLAY_SOUND_MMOV(const char* fileName);
#endif

#ifdef USE_VMSPLAY
/*-
 * Jouk Jansen <joukj AT hrem.nano.tudelft.nl> contributed this
 * which he found at http://axp616.gsi.de:8080/www/vms/mzsw.html
 *
 * quick hack for sounds in xlockmore on VMS
 * with a the above AUDIO package slightly modified
 */
#include <file.h>
#include <unixio.h>
#include <iodef.h>
#include "vms_amd.h"

static int
play_sound_so(const char *fileName)
{
	int i, j, status;
	char buffer[2048];
	int volume = 65; /* volume is default to 65% */

	/* use the internal speaker(s) */
	int speaker = SO_INTERNAL /*SO_EXTERNAL */ ;
	int fp;

	status = AmdInitialize("SO:", volume);	/* Initialize access to AMD */
	if (status != 0) return status;

	AmdSelect(speaker);	/* Select which speaker */
	fp = open(fileName, O_RDONLY, 0777);	/* Open the file */
	if (!(fp == -1)) {
		/* Read through it */
		i = read(fp, buffer, 2048);
		while (i) {
			status = AmdWrite(buffer, i);
			if (!(status & 1))
				exit(status);
			i = read(fp, buffer, 1024);
		}
	}
	(void) close(fp);
	return 0;
}

#endif

#if defined(HAS_MMOV) || defined(USE_VMSPLAY)
static void
play_sound(const char* fileName)
{
#ifdef USE_VMSPLAY
	if (play_sound_so(fileName) != 0)
#endif
	{
#ifdef HAS_MMOV
		int pr_status , privs[2] , SYS$SETPRV();

		privs[1] = 0;
		privs[0] = PRV$M_SYSNAM;
		pr_status = SYS$SETPRV (1, privs, 0, 0);

		if (pr_status == SS$_NORMAL)
			PLAY_SOUND_MMOV(fileName);
#endif
	}
}
#endif

#ifdef USE_ESOUND

#ifdef HAVE_LIBESD

#include <sys/stat.h>
#include <esd.h>

#else

#error Sorry, but you cannot use ESD without ESD !!!?

#endif

typedef struct _esd_sample
{
	char *file;
	int rate;
	int format;
	int samples;
	unsigned char *data;
	int id;
	struct _esd_sample *next;
} EsdSample_t;

typedef EsdSample_t *EsdSample_ptr;

static EsdSample_ptr EsdSamplesList =(EsdSample_ptr)NULL;
static int sound_fd = -1;

static EsdSample_ptr sound_esd_load_sample(const char *file);
static void sound_esd_play(EsdSample_ptr s);
static void sound_esd_destroy_sample(EsdSample_ptr s);
static int sound_esd_init(void);
static void sound_esd_shutdown(void);


/*
 * Public ESOUND sound functions
 * =============================
 */

int init_sound(void)
{
	return (sound_esd_init());
}

void shutdown_sound(void)
{
	sound_esd_shutdown();
}

static void
play_sound(const char *fileName)
{
#ifdef DEBUG
	(void) fprintf(stderr, "play_sound %s\n", fileName);
#endif
	if (fileName && *fileName)
		sound_esd_play(sound_esd_load_sample(fileName));
}

/* Found this snippet from modes/glx/xpm-image.c by way of
   Jamie Zawinski <jwz AT jwz.org> Copyright 1998 */
static int
bigendian (void)
{
	union { int i; char c[sizeof(int)]; } u;
	u.i = 1;
	return !u.c[0];
}


/*
 * Private ESOUND sound functions
 * ==============================
 */
#ifndef MAXPATHLEN
#define MAXPATHLEN 1024
#endif
#ifdef __VMS
#include <unixlib.h>
#else
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#endif

static EsdSample_ptr
sound_esd_load_sample(const char *file)
{
	AFfilehandle in_file;
	struct stat stbuf;
	EsdSample_ptr s;
	int in_format, in_width, in_channels, frame_count;
	int bytes_per_frame;
	double in_rate;
	char *origfile;
	char fullfile[MAXPATHLEN];

	s = EsdSamplesList;
#ifdef DEBUG
	(void) fprintf(stderr, "sound_esd_load_sample: %s\n", s->file);
#endif
	while (s && strcmp(file, s->file))
		s = s->next;
	if (s && !strcmp(file, s->file))
		return s;

	origfile = strdup(file ? file : "" );
#ifdef DEBUG
	(void) fprintf(stderr,
		"sound_esd_load_sample: sample not loaded: loading %s\n",
		origfile);
#endif
#ifdef __VMS
	if (file[0] != '[')
#else
	if (file[0] != '/')
#endif
	{
#ifdef HAVE_SNPRINTF
		(void) snprintf(fullfile, MAXPATHLEN,
			"%s%s%s", SOUNDPATH, FINALDELIM, file);
#else
		(void) sprintf(fullfile,
			"%s%s%s", SOUNDPATH, FINALDELIM, file);
#endif
		file = fullfile;
	}
	if (stat(file, &stbuf) < 0) {
		(void) fprintf(stderr,
			"Cannot find the sound file %s\n", file);
		free(origfile);
		return NULL;
	}
	if (!(in_file = afOpenFile(file, "r", NULL))) {
		(void) fprintf(stderr,
			"Cannot open sound sample, bad format?\n");
		free(origfile);
		return(NULL);
	}
	s = EsdSamplesList;
	if (s) {
		while (s->next)
			s = s->next;
		s->next = (EsdSample_t *) malloc(sizeof(EsdSample_t));
		if (!s->next) {
			(void) fprintf(stderr,
				"Cannot allocate sample data.\n");
			(void) afCloseFile(in_file);
			free(origfile);
			return NULL;
		}
		s = s->next;
		s->next = NULL;
	} else {
		s = (EsdSample_t *) malloc(sizeof(EsdSample_t));
		if (!s) {
			(void) fprintf(stderr,
				"Cannot allocate sample data.\n");
			(void) afCloseFile(in_file);
			free(origfile);
			return NULL;
		}
		EsdSamplesList = s;
		s->next = NULL;
	}
	frame_count = afGetFrameCount(in_file, AF_DEFAULT_TRACK);
	in_channels = afGetChannels(in_file, AF_DEFAULT_TRACK);
	in_rate = afGetRate(in_file, AF_DEFAULT_TRACK);
	afGetSampleFormat(in_file, AF_DEFAULT_TRACK, &in_format, &in_width);
	(void) afSetVirtualByteOrder(in_file, AF_DEFAULT_TRACK, (bigendian()) ?
		AF_BYTEORDER_BIGENDIAN : AF_BYTEORDER_LITTLEENDIAN);
	s->file = origfile;
	s->rate = 44100;
	s->format = ESD_STREAM | ESD_PLAY;
	s->samples = 0;
	s->data = NULL;
	s->id = 0;
	if (in_width == 8)
		s->format |= ESD_BITS8;
	else if (in_width == 16)
		s->format |= ESD_BITS16;
	bytes_per_frame = (in_width * in_channels) / 8;
	if (in_channels == 1)
		s->format |= ESD_MONO;
	else if (in_channels == 2)
		s->format |= ESD_STEREO;
	s->rate = (int)in_rate;
	s->samples = frame_count * bytes_per_frame;
	s->data = (unsigned char *) malloc(frame_count * bytes_per_frame);
	if (!s->data) {
		(void) fprintf(stderr,
			"Cannot allocate memory for sample.\n");
		(void) afCloseFile(in_file);
		return NULL;
	}
	(void) afReadFrames(in_file, AF_DEFAULT_TRACK, s->data,
		frame_count);
	(void) afCloseFile(in_file);
	return s;
}

static
void
sound_esd_play(EsdSample_ptr s)
{
	int size, confirm = 0;

#ifdef DEBUG
	(void) fprintf(stderr, "sound_esd_play\n");
#endif

	if ((sound_fd < 0) || (!s))
		return;
	if (s->id == 0) {
		if (sound_fd >= 0) {
			if (s->data) {
				size = s->samples;
				s->id = esd_sample_getid(sound_fd, s->file);
				if (s->id < 0) {
					s->id = esd_sample_cache(sound_fd,
						s->format, s->rate,
						size, s->file);
					(void) write(sound_fd, s->data, size);
					confirm = esd_confirm_sample_cache(
						sound_fd);
					if (confirm != s->id)
						s->id = 0;
				}
				free(s->data);
				s->data = NULL;
			}
		}
	}
	if (s->id > 0)
		(void) esd_sample_play(sound_fd, s->id);
	return;
}

static void
sound_esd_destroy_sample(EsdSample_ptr s)
{
#ifdef DEBUG
	(void) fprintf(stderr, "sound_esd_destroy_sample\n");
#endif

	if ((s->id) && (sound_fd >= 0))
		(void) esd_sample_free(sound_fd, s->id);
	if (s->data)
		free(s->data);
	if (s->file)
		free(s->file);
}

static int
sound_esd_init(void)
{
	int fd;

#ifdef DEBUG
	(void) fprintf(stderr, "sound_esd_init\n");
#endif
	if (sound_fd != -1)
		return 0;
	fd = esd_open_sound(NULL);
	if (fd >= 0)
		sound_fd = fd;
	else {
#ifdef DEBUG
		/* Do not want this annoying message if does not work. */
		(void) fprintf(stderr, "Error initializing sound (%d).\n", fd);
#endif
		return -1;
	}
	return 0;
}

static void
sound_esd_shutdown(void)
{
#ifdef DEBUG
	(void) fprintf(stderr, "sound_esd_shutdown\n");
#endif
	if (sound_fd >= 0) {
		EsdSample_ptr s = EsdSamplesList;

		while (s) {
			EsdSamplesList = s->next;
			sound_esd_destroy_sample(s);
			free(s);
			s = EsdSamplesList;
		}
		(void) close(sound_fd);
		sound_fd = -1;
	}
}

#endif

void
playSound(const char *fileName)
{
	char *temp, *file;
	size_t callLength = strlen(SOUNDPATH) + strlen(FINALDELIM) +
		strlen(fileName) + strlen(SOUNDEXT) + 1;

	if ((file = (char *) malloc(callLength)) != NULL) {
#ifdef HAVE_SNPRINTF
		(void) snprintf(file, callLength,
			"%s%s%s%s", SOUNDPATH, FINALDELIM, fileName, SOUNDEXT);
#else
		(void) sprintf(file,
			"%s%s%s%s", SOUNDPATH, FINALDELIM, fileName, SOUNDEXT);
#endif
		temp = findFile(file);
#if 0
	(void) printf("sound file %s|%s\n", file, temp);
#endif
		if (temp && *temp) {
			play_sound(temp);
		}
	}
}
#endif
