/*
 * Copyright (c) 1991-1993 Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the Computer Systems
 *	Engineering Group at Lawrence Berkeley Laboratory.
 * 4. Neither the name of the University nor of the Laboratory may be used
 *    to endorse or promote products derived from this software without
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */
static const char rcsid[] =
    "@(#) $Header: audio-bsd.cc,v 1.16 96/03/16 13:12:46 van Exp $ (LBL)";

#include "audio-sun.h"
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#ifdef sun
#include <sbusdev/bsd_audioio.h>
#else
#include <machine/audioio.h>
#endif

class BSDAudio : public SUNAudio {
    public:
	inline BSDAudio(const char* device) : SUNAudio(device) {}
	virtual int FrameReady();
	virtual u_char* Read();
	virtual void Obtain();
	virtual void Catchup(int);
	virtual int RDrops();
	virtual int AdjustTime(u_int);
	virtual void Flush();
	virtual int CanFilter() const { return 1; }
	virtual int outputs() const;
    protected:
	virtual int setinfo(struct audio_info*);
	virtual int getinfo(struct audio_info*);
};

static class BSDAudioMatcher : public Matcher {
public:
	BSDAudioMatcher() : Matcher("audio") {}
	TclObject* match(const char* id) {
		if (strcasecmp(id, "bsd") == 0) {
			Tcl& tcl = Tcl::instance();
			const char* device = tcl.attr("audioFileName");
			int i = -1;;
			if (strcmp(device, "/dev/audio") == 0 &&
			    (i = open("/dev/audioctl", O_RDONLY, 0)) < 0)
				return (new BSDAudio(device));
			(void)close(i);
		}
		return (0);
	}
} bsdaudio_matcher;

void BSDAudio::Obtain()
{
	if (HaveAudio())
		abort();

	fd = open(device_, O_RDWR|O_NDELAY);
	if (fd >= 0) {
		int on = 1;
		ioctl(fd, FIONBIO, (char*)&on);
		audio_info_t* i = (audio_info_t*)state;
		AUDIO_INITINFO((audio_info_t*)state);
		i->blocksize = blksize;
		i->record.gain = rgain;
		i->play.gain = pgain;
		i->play.port = oport? AUDIO_HEADPHONE :
		AUDIO_SPEAKER;
		setinfo(i);
		/* flush input to get rid of any data fragments */
		Flush();
		Audio::Obtain();
	}
}

u_char* BSDAudio::Read()
{
	u_char* cp = buf;
	register int len = blksize;
	int cc = read(fd, (char *)cp, len);
	if ((len -= cc) != 0) {
		do {
			if (cc < 0) {
				switch (errno) {
				case EINVAL:
					/* probably wrapped file pos. */
					lseek(fd, 0, SEEK_SET);
					break;

				case EPERM:
					/* probably lost audio */
					break;

				default:
					perror("audio read");
					break;
				}
				break;
			}
			cp += cc;
			cc = read(fd, (char *)cp, len);
			len -= cc;
		} while (len > 0);
	}
	return (buf);
}

int BSDAudio::FrameReady()
{
	return (1);
}

void BSDAudio::Catchup(int)
{
	/* XXX This should happen only with a very busy system. */
	printf("trying to catch up!\n");
	exit(1);
}

int BSDAudio::RDrops()
{
	int n;
	if (fd < 0 || ioctl(fd, AUDIO_RERROR, (char *)&n) < 0)
		n = -1;
	return (n);
}

int BSDAudio::getinfo(audio_info_t* info)
{
	int sts;
	if (fd < 0)
		sts = 0;
	else
		sts = ioctl(fd, AUDIO_GETINFO, (char*)info);
	return (sts);
}

int BSDAudio::setinfo(audio_info_t* info)
{
	int sts;
	if (fd < 0)
		sts = 0;
	else
		sts = ioctl(fd, AUDIO_SETINFO, (char*)info);
	return (sts);
}

/*
 * The time difference betwee now and when the audio last write
 * actually happens (i.e., when the last write actually starts
 * being output by the kernel).
 */
int BSDAudio::AdjustTime(u_int now)
{
	u_long stamp;
	if (ioctl(fd, AUDIO_WSEEK, (char *)&stamp) < 0) {
		perror("AUDIO_WSEEK");
		return (0);
	}
	return (stamp - now);
}

void BSDAudio::Flush()
{
	if (ioctl(fd, AUDIO_FLUSH, (char *)0) < 0) {
		perror("AUDIO_FLUSH");
		exit(1);
	}
}

int BSDAudio::outputs() const
{
	return (2);
}
