/*
 * Gnophone: A client for the Asterisk PBX
 *
 * Copyright (C) 2000, Linux Support Services, Inc.
 *
 * Written by Mark Spencer
 *
 * Linux/UNIX version distributed under the terms of
 * the GNU General Public License
 *
 * audio.c: Generic Audio/Telephony Interface
 *
 */

#include <stdio.h>
#include <malloc.h>
#include <string.h>

#include "audio.h"

#include "phonecore.h"
#include <iax/iax-client.h>
#include <errno.h>

#define MAX_AUDIO_DEVS		16

#ifdef SNOM_HACK
	#include "snom_memset.h"
#endif
static int audioc = -1;
static struct audio_channel *acs[MAX_AUDIO_DEVS];

static int switchtime = DEFAULT_SWITCHTIME;



/***********************************************************
 * Extracted from zaptel goertzel.[ch]
 ***********************************************************/

#define PC_AMP_BITS  9		/* bits in amplitude -- can't be 16,
				   since we'd get integer overflows */
int phonecore_fullmulaw[256];
//int phonecore_mulaw[256];

void phonecore_init_tables(void){
	int i;
	for(i=0;i<256;i++)
	{
	   short mu,e,f,y;
	   short etab[]={0,132,396,924,1980,4092,8316,16764};

	   mu=255-i;
	   e=(mu&0x70)/16;
	   f=mu&0x0f;
	   y=f*(1<<(e+3));

	   y=etab[e]+y;
	   if(mu&0x80)
	      y=-y;

	     phonecore_fullmulaw[i] = y;
	     //phonecore_mulaw[i]= (y >> (15 - PC_AMP_BITS));
	}
}
/***********************************************************
 * (END) Extracted from zaptel goertzel.[ch]
 ***********************************************************/

/***********************************************************
 * Extracted from audio-zaptel 
 ***********************************************************/
#define CLIP 32635 /* These are used for the mulaw/linear16 stuff */
#define BIAS 0x84
unsigned char linear2ulaw(short sample) 
{

	static int exp_lut[256] = {0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3,
				   4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
				   5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
				   5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
				   6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
				   6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
				   6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
				   6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
   		 		   7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
				   7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
				   7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
				   7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
				   7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
				   7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
				   7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
				   7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7};

	int sign, exponent, mantissa;
	unsigned char ulawbyte;

	/* Get the sample into sign-magnitude. */
	sign = (sample >> 8) & 0x80;          /* set aside the sign */
	if (sign != 0) sample = -sample;              /* get magnitude */
	if (sample > CLIP) sample = CLIP;             /* clip the magnitude */

	/* Convert from 16 bit linear to ulaw. */
	sample = sample + BIAS;
	exponent = exp_lut[(sample >> 7) & 0xFF];
	mantissa = (sample >> (exponent + 3)) & 0x0F;
	ulawbyte = ~(sign | (exponent << 4) | mantissa);
#ifdef ZEROTRAP
	if (ulawbyte == 0) ulawbyte = 0x02;   /* optional CCITT trap */
#endif

	return(ulawbyte);
}
/***********************************************************
 * (END) Extracted from audio-zaptel 
 ***********************************************************/

struct audio_channel *audio_new(void)
{
	struct audio_channel *ac;
	ac = malloc(sizeof(struct audio_channel));
	if (ac) {
		memset(ac, 0, sizeof(struct audio_channel));
		ac->id = -1;
		ac->dpid = -1;
	}
	return ac;
}

int audio_current(void)
{
	return audioc;
}

int audio_register_channel(struct audio_channel *c)
{
	int x;
	for (x=0;x<MAX_AUDIO_DEVS;x++)
		if (!acs[x])
			break;

	if (x >= MAX_AUDIO_DEVS) {
		fprintf(stderr, "Can't take any more devices\n");
		return -1;
	}
	printf("Registering %s to channel %d\n", c->name,x);

	acs[x] = c;

	return 0;
}

int send_digit(char digit)
{
	pc_event e;
	e.event = PC_EVENT_AUDIO_DIGIT;
	e.e.dtmf.dtmf = digit;
	return pc_write_event(SOURCE_GUI, &e);
}

int echotest(void){
	struct audio_channel *c;
	printf("Starting Echotest\n");
 	c = acs[0/*audioc*/];
	if(c==NULL){
		fprintf(stderr,"Error retrieving channel\n");
		return -1;
	}
	if (c->open(c)){
		fprintf(stderr,"Error opening audio device %s line:%d %s\n",__FILE__,__LINE__,__PRETTY_FUNCTION__);
		return -1;
	}
	while(1){ 
		fd_set readfd;
		int h=-1,rv;
		FD_ZERO(&readfd);
		FD_SET(c->fd, &readfd);
		if(c->fd> h)
			h = c->fd;
		if ( (rv=select(h+1, &readfd, NULL,NULL,NULL)) >= 0) {
			if(FD_ISSET(c->fd, &readfd)) {
				char buffer[65536]; 
				int bufferlen=160*2;
				if(c->readaudio(c,-1,buffer,&bufferlen)) 
					fprintf(stderr,"Error reading audio %s line:%d %s\n",
							__FILE__,__LINE__,__PRETTY_FUNCTION__);
				else{
					if(0>c->sendaudio(c,-1,buffer,bufferlen/2))
						fprintf(stderr,"Error writing audio:%s %s line:%d %s\n",
								strerror(errno),__FILE__,__LINE__,__PRETTY_FUNCTION__);
				}
			}
		}
	}
	return 0;
}
static int pc_send_sound_cb(void *data)
{
	struct audio_channel *c;
	int ms;
	struct timeval tv;
	int lentosend;
	if (audioc < 0)
			return 0;
			
 	c = acs[audioc];
again:
	lentosend = c->totallen - c->lensofar;
	if (lentosend > CHUNKLEN)
		lentosend = CHUNKLEN;
	audio_send(AST_FORMAT_SLINEAR, c->sound + c->lensofar, lentosend * 2, 1, 0);
	c->lensofar += lentosend;
	if (c->lensofar == c->totallen) {
		if (c->repeat) {
			c->lensofar = 0;
		} else {
			c->id = -1;
#if 0
			if (c->flush)
				c->flush(c);
#endif				
#if 0
			/* Put it in reading mode */
			if (!c->duplex && c->simduplex)
				c->simduplex(c, 0);
#endif
			return 0;
		}
	}

	gettimeofday(&tv, NULL);
	/* Here's how far along we really are... */
	ms = (tv.tv_sec - c->st.tv_sec) * 1000 + (tv.tv_usec - c->st.tv_usec)/1000;
	
	/* Our position increases by samples/8 ms */
	c->pos += lentosend / 8;
	if (ms > c->pos) {
#if 0
		fprintf(stderr, "We must really be slow...\n");
#endif
		/* We already have to send another frame */
		goto again;
	}
	ms=c->pos-ms;
	if(!ms)
		ms=-1;
	c->id = pc_sched_add(ms, pc_send_sound_cb, c);
	return 0;
}

int pc_send_sound(short *a, int len, int repeat)
{
	struct audio_channel *c;
	if (audioc < 0)
			return 0;
	c = acs[audioc];
	/* Put it in writing mode immediately */
	if (c->id > -1) 
		pc_sched_del(c->id);
	if (c->flush)
		c->flush(c);
	c->totallen = len;
	c->lensofar = 0;
	c->sound = a;
	c->repeat = repeat;
	c->pos = 0;
	gettimeofday(&c->st, NULL);
	/* Send the first couple of packets immediately to get things going */
	pc_send_sound_cb(c); 
#if 1
	if (c->id > -1) {
		c->pos = 0;
		pc_sched_del(c->id);
		pc_send_sound_cb(c);
	}
	if (c->id > -1) {
		c->pos = 0;
		pc_sched_del(c->id);
		pc_send_sound_cb(c);
	}
#endif
	return 0;
}


static int unset_duplex(void *av)
{
	struct audio_channel *ac = av;
	/* Turn it back to read-mode */
	printf("Unsetting duplex!\n");
	ac->simduplex(ac, 0);
	ac->dpid = -1;
	return 0;
}


int audio_send(int format, void *data, int datalen, int loud, int stop)
{
	struct audio_channel *c;
	
	if (audioc < 0)
		return 0;
	
	c = acs[audioc];

	if (!c)
		return 0;
		
	if (stop) {
		if (c->id > -1) 
			pc_sched_del(c->id);
		c->id = -1;
	}
	if (!c->duplex) {
		/* If it's not loud, and we're still reading, keep reading.. */
		if (!loud && !c->writemode)
			return 0;
		/* Make sure we switch back to read mode if necessary */
		if (loud) {
			/* If this was "loud" then we reset the timer */
			if (c->dpid > -1) 
				pc_sched_del(c->dpid);
			c->dpid = pc_sched_add(switchtime, unset_duplex, c);
		} else if (c->dpid < 0) /* Be sure there is a timer if quiet, but don't reset it */
			c->dpid = pc_sched_add(switchtime, unset_duplex, c);

		if (!c->writemode)
			c->simduplex(c, 1);

	}
	return c->sendaudio(c, format, data, datalen);
}

int audio_get_fd(void)
{
	struct audio_channel *c;
	if (audioc < 0)
		return -1;
	c = acs[audioc];
	
	if (c) {
		return c->fd;
	}
	return -1;
}

int audio_get_exception(void)
{
	struct audio_channel *c;
	if (audioc < 0)
		return 0;
	c = acs[audioc];
	if (c) {
		if (c->exception)
			return 1;
	}
	return 0;
}

int audio_select(int chan)
{
	struct audio_channel *c = acs[chan];
	if (c->id > -1)
		pc_sched_del(c->id);
	if (c->dpid > -1)
		pc_sched_del(c->dpid);
	c->dpid = -1;
	c->id = -1;
	if ((audioc > -1) && acs[audioc])
		acs[audioc]->close(acs[audioc]);
	audioc = -1;
	if (!c->open(c)) {
		audioc = chan;
		return 0;
	}
	return -1;

}

int pc_send_digit(char digit)
{
	struct audio_channel *c;
	if (audioc < 0)
		return 0;
	c = acs[audioc];
	if (c)
		return c->play_digit(c, digit);
	return 0;
}


int audio_ring(void)
{
	struct audio_channel *c;
	if (audioc < 0)
		return 0;
	c = acs[audioc];
	if (!c)
		return 0;
	if (c->ring)
		return c->ring(c);
	return 0;
}

int audio_ringing()
{
	struct audio_channel *c;
	if (audioc < 0)
		return 0;
	c = acs[audioc];
	if (!c)
		return 0;
	if (c->ringing)
		return c->ringing(c);
	return 0;
}

int audio_shutup(void)
{
	struct audio_channel *c;
	if (audioc < 0)
		return 0;
	c = acs[audioc];
	if (!c)
		return 0;
	if (c->id > -1) 
		pc_sched_del(c->id);
	c->id = -1;
	if (c->flush)
		c->flush(c);
	// For snom select default output
	if(c->configure)
		c->configure(c);
#if 0
	/* Put it in reading mode */
	if (!c->duplex && c->simduplex)
		c->simduplex(c, 0);
#endif
	return 0;
}

int audio_dialtone(void){
	struct audio_channel *c;
	if (audioc < 0)
		return 0;
	c = acs[audioc];
	if (!c)
		return 0;
	if (c->dialtone)
		return c->dialtone(c);
	return 0;
}
int audio_busy(void){
	struct audio_channel *c;
	if (audioc < 0)
		return 0;
	c = acs[audioc];
	if (!c)
		return 0;
	if (c->busy)
		return c->busy(c);
	return 0;
}

int audio_check_exception(void)
{
	struct audio_channel *c;
	if (audioc < 0)
		return 0;
	c = acs[audioc];
	if (c && c->exception)
		c->exception(c);
	else
		fprintf(stderr, "Exception and no handler for %s\n", c->name);
	return 0;
}

int audio_read(int format, char *buffer, int *len)
{
	struct audio_channel *c;
	if (audioc < 0)
		return 0;
	c = acs[audioc];
	if (c && c->readaudio) {
		return c->readaudio(c, format, buffer, len);
	}
	return 0;
}

int audio_deactivate(void)
{
	struct audio_channel *c;
	if (audioc < 0)
		return 0;
	c = acs[audioc];
	if (!c)
		return 0;
	if (c->id > -1)
		pc_sched_del(c->id);
	c->id = -1;
	if (c->deactivate)
		c->deactivate(c);
	return 0;
}

char *audio_getname(int chan)
{
	if ((chan < 0) || (chan >= MAX_AUDIO_DEVS))
		return NULL;
	if (acs[chan])
		return acs[chan]->name;
	return NULL;
}

int audio_findbest(void)
{
	int ac;
	int best;
	int bac;
	if ((audioc < 0) && acs[0]) {
		ac = 0;
		bac = 0;
		best = acs[0]->priority;
		while(acs[ac]) {
			if (acs[ac]->priority > best) {
				bac = ac;
				best = acs[ac]->priority;
			}
			ac++;
		}
		audio_select(bac);
	}
	
	return 0;
}

