/*
 * Copyright (c) 1991-1994 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.cc,v 1.34 96/05/03 06:26:49 van Exp $ (LBL)";

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#if defined(sgi)
#include <bstring.h>
#endif
#ifdef WIN32
#include <fcntl.h>
#else
#include <unistd.h>
#include <sys/file.h>
#include <fcntl.h>
#endif

#include "audio.h"
#include "filter.h"
#include "mulaw.h"
#include "Tcl.h"


Audio::Audio() :
	lock_fd(-1),
	blksize(FRAMESIZE),
	fd(-1),
	oport(0),
	iport(0),
	oports(1),
	iports(1),
	rmute(0),
	pmute(0),
	loopback(0),
	sampleclk(0),
	rgain(0),
	pgain(0),
	filter(new Filter(this)),
	handler_(0)
{
	for (u_int i = 0; i < sizeof(omode)/sizeof(omode[0]); ++i)
		omode[i] = mode_mikemutesnet;
}

Audio::~Audio()
{
	close(fd);
	delete filter;
}

void Audio::Release()
{
	if (HaveAudio()) {
		unlink();
		(void)close(fd);
		fd = -1;
		notify();
	}
}

void Audio::Obtain()
{
	link(fd, TK_READABLE);
	notify();
}

void Audio::InputPort(int p)
{
	iport = p;
}

void Audio::OutputPort(int p)
{
	oport = p;
}

void Audio::dispatch(int)
{
	while (FrameReady())
		handler_->audio_handle();
}

/*
 * The following are called by user interface routines to get
 * the input/output ports & port types.
 */
int Audio::inputs() const
{
	return (iports);
}

int Audio::input_type(int i) const
{
	return (i < inputs()? i : 0); /*XXX*/
}

int Audio::outputs() const
{
	return (oports);
}

int Audio::output_type(int i) const
{
	return (i < outputs()? i : 0); /*XXX*/
}

int Audio::StrToMode(const char *s) const
{
	if (strcasecmp("MikeMutesNet", s) == 0 ||
	    strcasecmp("SpeakerPhone", s) == 0)
		return mode_mikemutesnet;
	else if (strcasecmp("NetMutesMike", s) == 0 ||
	         strcasecmp("VTSpeakerPhone", s) == 0)
		return mode_netmutesmike;
	else if (strcasecmp("EchoCancel", s) == 0 || 
		 strcasecmp("ec", s) == 0)
		return mode_ec;
	else
		return mode_none;
}

int Audio::StrToIPort(const char *s) const
{
	if (
		 strcasecmp("mike", s) == 0 ||
		 strcasecmp("microphone", s) == 0 ||
		 strcasecmp("mic", s) == 0 ||
		 strcasecmp("wave", s) == 0 ||
		 strcasecmp("wavein", s) == 0 ||
		 strcasecmp("wave in", s) == 0
		)
		return input_mike;
	else if (
		 strcasecmp("linein", s) == 0 ||
		 strcasecmp("line-in", s) == 0 ||
		 strcasecmp("line in", s) == 0 ||
		 strcasecmp("line", s) == 0 ||
		 strcasecmp("linein1", s) == 0 ||
		 strcasecmp("line-in1", s) == 0 ||
		 strcasecmp("line-in 1", s) == 0 ||
		 strcasecmp("line in 1", s) == 0 
		)
		return input_line;
	else if (
		 strcasecmp("linein2", s) == 0 ||
		 strcasecmp("line-in2", s) == 0 ||
		 strcasecmp("line-in 2", s) == 0 ||
		 strcasecmp("line in 2", s) == 0 ||
		 strcasecmp("line2", s) == 0 ||
		 strcasecmp("aux", s) == 0 ||
		 strcasecmp("cd", s) == 0 ||
		 strcasecmp("cd audio", s) == 0
		)
		return input_line2;
	else if (
		 strcasecmp("linein3", s) == 0 ||
		 strcasecmp("line-in3", s) == 0 ||
		 strcasecmp("line-in 3", s) == 0 ||
		 strcasecmp("line in 3", s) == 0 ||
		 strcasecmp("line3", s) == 0 ||
		 strcasecmp("synth", s) == 0 ||
		 strcasecmp("synthesizer", s) == 0 ||
		 strcasecmp("fm", s) == 0 ||
		 strcasecmp("wt", s) == 0 ||
		 strcasecmp("wavetable", s) == 0
		)
		return input_line3;
	else
		return -1;
}

int Audio::StrToOPort(const char *s) const
{
	if (
		 strcasecmp("speaker", s) == 0 ||
		 strcasecmp("wave", s) == 0 ||
		 strcasecmp("waveout", s) == 0 ||
		 strcasecmp("wave out", s) == 0 
		)
		return output_speaker;
	else if (
		 strcasecmp("jack", s) == 0 ||
		 strcasecmp("phones", s) == 0 ||
		 strcasecmp("headphone", s) == 0 ||
		 strcasecmp("headphones", s) == 0
		)
		return output_phones;
	else if (
		 strcasecmp("lineout", s) == 0 ||
		 strcasecmp("line out", s) == 0 ||
		 strcasecmp("line", s) == 0 ||
		 strcasecmp("lineout1", s) == 0 ||
		 strcasecmp("line out1", s) == 0 ||
		 strcasecmp("line1", s) == 0
		)
		return output_line;
	else if (
		 strcasecmp("lineout2", s) == 0 ||
		 strcasecmp("line out2", s) == 0 ||
		 strcasecmp("line out 2", s) == 0 ||
		 strcasecmp("line2", s) == 0 ||
		 strcasecmp("aux", s) == 0 ||
		 strcasecmp("aux out", s) == 0 
		)
		return output_line2;
	else
		return -1;
}

const char* Audio::IPortToStr(int p) const
{
	switch (p) {
	default:
	case input_mike:	return ("mike");
	case input_line:	return ("linein");
	case input_line2:	return ("linein2");
	case input_line3:	return ("linein3");
	}
}

const char* Audio::OPortToStr(int p) const
{
	switch (p) {
	default:
	case output_speaker:	return ("speaker");
	case output_phones:	return ("jack");
	case output_line:	return ("lineout");
	case output_line2:	return ("lineout2");
	}
}

/* XXX this can be coded better */
/*
 * Output the ulaw sample in x and read the mike response back into y.
 * The two signals are guaranteed to coincide in time.  len must be
 * less than the max output buffer available (currently 4K).
 */
int Audio::PlayRec(u_char *x, u_char *y, int len)
{
	int bufsize = (len / blksize) * blksize;
	int rbufsize = bufsize + 4096;
	u_char* tmpbuf = new u_char[rbufsize];
	Flush();
	int cc = write(fd, (char*)x, bufsize);
	if (cc != bufsize) {
		perror("PlayRec write");
		return (bufsize);
	}
	int offset = AdjustTime(0);
	if (offset < 0 || offset + bufsize > rbufsize) {
		fprintf(stderr, "Playrec offset %d\n", offset);
		return (bufsize);
	}
	char* bp = (char*)tmpbuf;
	for (int rem = offset + bufsize; rem > 0; ) {
		if ((cc = read(fd, bp, rem)) <= 0) {
			perror("PlayRec read");
			return (bufsize);
		}
		bp += cc;
		rem -= cc;
	}
	memcpy((char*)y, (char*)&tmpbuf[offset], bufsize);
	delete tmpbuf;
	return (bufsize);
}

void Audio::RetrainFilter(int len, int maxtaps)
{
	filter->Train(len, maxtaps);
}

void Audio::RMute()
{
	rmute |= 1;
}

void Audio::RUnmute()
{
	rmute &=~ 1;
}

void Audio::SetRGain(int)
{
}

void Audio::SetPGain(int)
{
}

#if defined(__osf__) || defined(sun) || defined(ultrix)
extern "C" {
int flock(int, int);
}
#endif
#if defined(hpux) || defined(__svr4__) || defined(sco)
#include <fcntl.h>

#define LOCK_SH   1    /* shared lock */
#define LOCK_EX   2    /* exclusive lock */
#define LOCK_NB   4    /* don't block when locking */
#define LOCK_UN   8    /* unlock */

int flock(int fd, int op) {
	struct flock f;
	f.l_whence = 0;
	f.l_start = 0;
	f.l_len = 0;
	if (op == LOCK_UN)
		f.l_type = F_UNLCK;
	else
		f.l_type = F_WRLCK;
	return (fcntl(fd, F_SETLK, &f));
}
#endif

/* XXX should make a NOLOCKING define that configure sets */
#ifdef WIN32
void Audio::openlock()	{ printf("Audio:openlock\n"); }
void Audio::unlock()	{ printf("Audio:unlock\n"); }
int Audio::lock()	{ printf("Audio:lock\n"); return (0); }
#else
void Audio::openlock()
{
	char *wrk = new char[sizeof("/tmp/.vat_audio_lock.") + 32];
	sprintf(wrk, "/tmp/.vat_audio_lock.%d", getuid());
	/* open (or create) the lock file */
	lock_fd = open(wrk, O_RDWR|O_CREAT, 0777);
	if (lock_fd < 0) {
		perror(wrk);
		exit(2);
	}
	delete wrk;
}

void Audio::unlock()
{
	if (flock(lock_fd, LOCK_UN))
		perror("vat: sock_audio unlock");
}

int Audio::lock()
{
	return (flock(lock_fd, (LOCK_EX|LOCK_NB)));
}
#endif

int Audio::command(int argc, const char*const* argv)
{
	Tcl& tcl = Tcl::instance();
	if (argc == 4) {
		if (strcmp(argv[1], "mode") == 0) {
			int port = StrToOPort(argv[2]);
			int mode = StrToMode(argv[3]);
			SetMode(port, mode);
			return (TCL_OK);
		}
	}
	if (argc == 2) {
		if (strcmp(argv[1], "halfduplex") == 0) {
			tcl.result(HalfDuplex() ? "1" : "0");
			return (TCL_OK);
		}
		if (strcmp(argv[1], "obtain") == 0) {
			Obtain();
			return (TCL_OK);
		}
		if (strcmp(argv[1], "release") == 0) {
			Release();
			return (TCL_OK);
		}
		if (strcmp(argv[1], "have") == 0) {
			tcl.result(HaveAudio() ? "1" : "0");
			return (TCL_OK);
		}
	} else if (argc == 3) {
		if (strcmp(argv[1], "test") == 0) {
			SetLoopback(atoi(argv[2]));
			return (TCL_OK);
		}
		if (strcmp(argv[1], "input") == 0) {
			if (strcmp(argv[2], "mute") == 0) {
				RMute();
				return (TCL_OK);
			}
			if (strcmp(argv[2], "unmute") == 0) {
				RUnmute();
				tcl.result(HaveAudio()? "0" : "1");
				return (TCL_OK);
			}
			if (strcmp(argv[2], "current") == 0) {
				sprintf(tcl.result(), "%d", InputPort());
				return (TCL_OK);
			}
			if (strcmp(argv[2], "names") == 0) {
				/*
				 * Return a list of the names of the
				 * audio input ports.
				 */
				char* cp = tcl.result();
				int n = inputs();
				for (int i = 0; i < n; ++i) {
					const char* port = IPortToStr(i);
					strcpy(cp, port);
					cp += strlen(port);
					*cp++ = ' ';
				}
				*cp++ = '\0';
				return (TCL_OK);
			}
			if (strcmp(argv[2], "gain") == 0) {
				sprintf(tcl.result(), "%s %d",
					IPortToStr(InputPort()), RGain());
				return (TCL_OK);
			}
		} else if (strcmp(argv[1], "output") == 0) {
			if (strcmp(argv[2], "gain") == 0) {
				sprintf(tcl.result(), "%s %d",
					OPortToStr(OutputPort()), PGain());
				return (TCL_OK);
			}
			if (strcmp(argv[2], "mute") == 0) {
				PMute();
				return (TCL_OK);
			}
			if (strcmp(argv[2], "unmute") == 0) {
				PUnmute();
				return (TCL_OK);
			}
			if (strcmp(argv[2], "current") == 0) {
				sprintf(tcl.result(), "%d", OutputPort());
				return (TCL_OK);
			}
			if (strcmp(argv[2], "names") == 0) {
				/*
				 * Return a list of the names of the
				 * audio output ports.
				 */
				char* cp = tcl.result();
				int n = outputs();
				for (int i = 0; i < n; ++i) {
					const char* port = OPortToStr(i);
					strcpy(cp, port);
					cp += strlen(port);
					*cp++ = ' ';
				}
				*cp++ = '\0';
				return (TCL_OK);
			}
		}
	} else if (argc == 4) {
		if (strcmp(argv[1], "input") == 0) {
			if (strcmp(argv[2], "gain") == 0) {
				SetRGain(atoi(argv[3]));
				return (TCL_OK);
			}
			if (strcmp(argv[2], "set") == 0) {
				InputPort(StrToIPort(argv[3]));
				return (TCL_OK);
			}
			if (strcmp(argv[2], "name") == 0) {
				strcpy(tcl.result(),
				       IPortToStr(StrToIPort(argv[3])));
				return (TCL_OK);
			}
		}
		if (strcmp(argv[1], "output") == 0) {
			if (strcmp(argv[2], "gain") == 0) {
				SetPGain(atoi(argv[3]));
				return (TCL_OK);
			} 
			if (strcmp(argv[2], "set") == 0) {
				OutputPort(StrToOPort(argv[3]));
				return (TCL_OK);
			}
			if (strcmp(argv[2], "name") == 0) {
				strcpy(tcl.result(),
				       OPortToStr(StrToOPort(argv[3])));
				return (TCL_OK);
			}
		}
	}
	return (TclObject::command(argc, argv));
}
