/*
 * 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-snom.c: OSS Audio Driver
 *
 */
 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include "../../snomphone/snom-soundcard.h"
#include <sys/stat.h>
#include <string.h>
#include <errno.h>
#include "audio.h"
#include "phonecore.h"
#include <iax/iax-client.h>
#include "lin2mu.h"
#include <errno.h>

#include "snom_memset.h"
#define MAX_DEVS 2

/* 
 * Fragment size is of the form 2^x.  With 160 byte GSM frames, we can 
 * only use 64-byte fragments, which are very small and create fairly
 * substantial overhead.  at some point we might want to use larger 
 * fragments
 * 
 */

#define FRAG_SIZE  6

/*
 * It takes 5 64-byte buffers per 20 millisecond sample.  80 milliseconds
 * is probably enough protection against underruns in most cases.
 */

#define BUFFERS    40

#define MAX_TX_GAIN	255
#define MAX_RX_GAIN	255

static int snom_open(struct audio_channel *c);
static int snom_close(struct audio_channel *c);
static int snom_activate(struct audio_channel *c);
static int snom_deactivate(struct audio_channel *c);
static int snom_sendaudio(struct audio_channel *c, int format, void *data, int len);
static int snom_readaudio(struct audio_channel *c, int format, void *buffer, int *len);

static char snom_driver[] = "Snomphone/snomaud";

struct snom_pvt {
	/* Filename */
	char fn[80];
};


static int snom_play_digit(struct audio_channel *ac,char digit);
static int snom_ring(struct audio_channel *ac);
static int snom_dialtone(struct audio_channel *ac);
static int snom_busy(struct audio_channel *ac);
static int snom_fastbusy(struct audio_channel *ac);
static int snom_ringing(struct audio_channel *ac);
static int snom_configure(struct audio_channel *ac);
static int snom_flush(struct audio_channel *c);


static struct audio_channel *snom_channel_new(char *filename)
{
	struct audio_channel *ac;
	struct snom_pvt *pvt = (struct snom_pvt *)malloc(sizeof(struct snom_pvt));
	ac = audio_new();

	/* Construct Memory */
	if(ac==NULL)
		return NULL;
	pvt = (struct snom_pvt *)malloc(sizeof(struct snom_pvt));
	if(pvt==NULL){ free(ac); ac=NULL;}

	bzero(pvt, sizeof(struct snom_pvt));

	strncpy(pvt->fn, filename, sizeof(pvt->fn));
	// FIXME
	snprintf(ac->name, sizeof(ac->name), "%s",filename);
	ac->priority = 100;
	ac->driver = snom_driver;
	ac->open = snom_open;
	ac->close = snom_close;
	ac->play_digit = snom_play_digit;
	ac->activate = snom_activate;
	ac->ring = snom_ring;
	ac->busy = snom_busy;
	ac->ringing = snom_ringing;
	ac->dialtone = snom_dialtone;
	ac->fastbusy = snom_fastbusy;
	ac->deactivate = snom_deactivate;
	ac->configure = snom_configure;
	ac->sendaudio = snom_sendaudio;
	ac->readaudio = snom_readaudio;
	ac->flush=snom_flush;
	ac->hz = 8000;
	ac->sformats = AST_FORMAT_SLINEAR;
	ac->cananswer = 1; 
	ac->duplex = 1;
	ac->fd = -1;
	ac->pvt = pvt;
	//ac->echocancelled = 0;
		
	return ac;
}

static unsigned char lin2mu[65536];
static short mulaw[256];
#define ZEROTRAP    /* turn on the trap as per the MIL-STD */
#define BIAS 0x84   /* define the add-in bias for 16 bit samples */
#define CLIP 32635
static unsigned char
linear2ulaw2(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);
}
void ulaw_init(void)
{
	int i;
	/* 
	 *  Set up mu-law conversion table
	 */
	for(i = 0;i < 256;i++)
	   {
		short mu,e,f,y;
		static 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];
		if (mu & 0x80) y = -y;
	        mulaw[i] = y;
	   }
	  /* set up the reverse (mu-law) conversion table */
	for(i = 0; i < 65536; i++)
	   {
		lin2mu[i] = linear2ulaw2(i - 32768);
	   }

}
static int set_amp_gain(struct audio_channel *ac, int amp){
	/* amp = 1-15 */
	int val=-1;
	if(amp<0) amp=0;
	else if(amp>15) amp=15;

	if((val=ioctl(ac->fd,SNDCTL_DSP_SET_AMP_GAIN_LEVEL,&amp)))
		fprintf(stderr,"Failed to Set AMP GAIN\n");
	return val;
}
static int set_gains(struct audio_channel *ac,int txgain, int rxgain)
{
	if(ac->fd<0) 
		return -1;
	if (txgain > MAX_TX_GAIN)
		txgain = MAX_TX_GAIN;
	if (rxgain > MAX_RX_GAIN)
		rxgain = MAX_RX_GAIN;

	if (rxgain > -1){
		if (ioctl(ac->fd, SNDCTL_DSP_SET_RX_GAIN_LEVEL, &rxgain))
			fprintf(stderr, "Failed to set rx gain level: %s\n", strerror(errno));
	} 
	if (txgain > -1){
		if (ioctl(ac->fd, SNDCTL_DSP_SET_TX_GAIN_LEVEL, &txgain))
			fprintf(stderr, "Failed to set tx gain level: %s\n", strerror(errno));
	}
	set_amp_gain(ac,15);
	return 0;
}
static int audio_setup(char *dev)
{
	int fd;	
	int fmt = AFMT_MU_LAW;
	if ( (fd = open(dev, O_RDWR | O_NONBLOCK)) < 0) {
		fprintf(stderr, "Unable to open audio device %s: %s\n", dev, strerror(errno));
		return -1;
	}
	if (ioctl(fd, SNDCTL_DSP_NONBLOCK, 1)) {
		fprintf(stderr, "Unable to set nonblocking mode.\n");
	}
	if (ioctl(fd, SNDCTL_DSP_SETFMT, &fmt)) {
		fprintf(stderr, "Unable to set mulaw format.\n");
	}
	return fd;
}
static int snom_open(struct audio_channel *ac)
{
	struct snom_pvt *pvt = ac->pvt;
	if(0>(ac->fd=audio_setup(pvt->fn)))
		return -1;
	set_gains(ac,MAX_TX_GAIN, MAX_RX_GAIN);
	return 0;
}

static int snom_close(struct audio_channel *c)
{
	close(c->fd);
	c->fd = -1;
	return 0;
}

#define OUTPUT_SPEAKER 0x1
#define OUTPUT_HANDSET 0x2
static int set_output(struct audio_channel *ac,int output)
{
	int x;
	int res;
	static int last_output=-1;/* Memory of output */
	if(output==-1)
		return -1;
	if(last_output==output)
		return 0;
	x=(output&OUTPUT_SPEAKER)?1:0;
	res = ioctl(ac->fd, SNDCTL_DSP_SHUTDOWN_SPEAKER, &x);
	if (res < 0) {
		fprintf(stderr, "Unable to set speaker %d\n",x);
		return -1;
	}
	x=(output&OUTPUT_HANDSET)?1:0;
	res = ioctl(ac->fd, SNDCTL_DSP_SHUTDOWN, &x);
	if (res < 0) {
		fprintf(stderr, "Unable to set handset\n");
		return -1;
	}
	last_output=output;
	return 0;
}

static int snom_flush(struct audio_channel *ac)
{
	//ioctl(c->fd, SNDCTL_DSP_POST, 0);
	return 0;
}
static int snom_play_digit(struct audio_channel *ac,char digit)
{
	set_output(ac,OUTPUT_HANDSET);	
	set_amp_gain(ac,15);
	return std_play_digit(ac,digit);
}
static int snom_ring(struct audio_channel *ac)
{
	int val=-1;
	set_output(ac,OUTPUT_SPEAKER);	
	set_amp_gain(ac,5);
	val=ring_select(ac);
	return val;
}
static int snom_dialtone(struct audio_channel *ac)
{
	set_output(ac,OUTPUT_HANDSET);	
	set_amp_gain(ac,15);
	return std_dialtone(ac);
}
static int snom_busy(struct audio_channel *ac)
{
	set_output(ac,OUTPUT_HANDSET);	
	set_amp_gain(ac,15);
	return std_busy(ac);
}
static int snom_fastbusy(struct audio_channel *ac)
{
	set_output(ac,OUTPUT_HANDSET);	
	set_amp_gain(ac,15);
	return std_fastbusy(ac);
}
static int snom_ringing(struct audio_channel *ac)
{
	set_output(ac,OUTPUT_HANDSET);	
	set_amp_gain(ac,15);
	return std_ringing(ac);
}
static int snom_activate(struct audio_channel *ac)
{
	/* Nothing really necessary to activate us */
	return 0;		
}

static int snom_deactivate(struct audio_channel *ac)
{
	/* Nothing really necessary to deactivate us either */
	return 0;
}

static int snom_configure(struct audio_channel *ac)
{
	set_output(ac,OUTPUT_HANDSET);	
	set_amp_gain(ac,15);
	return 0;
}

/* Can only write mulaw to Snom audio driver */
/* format is the format of input data */
static int snom_write_linear(int fd,short *buf, int len){
	int x;
	int res;
	char tmp[8000];
	len /= 2;
	if (len > sizeof(tmp)) {
		fprintf(stderr, "Asked to write too much: %d\n", len);
		return -1;
	}
	for (x=0;x<len;x++)
		tmp[x] = lin2mu[buf[x]+ 32768];
	res = write(fd, tmp, len);
	return res;
}

static int snom_read_linear(int fd,short *buf,int len){
	int res;
	int x;
	char tmp[8000];
	if(len>sizeof(tmp)){
		len=sizeof(tmp);
	}
	if ((res = read(fd, tmp, len))<0){
		fprintf(stderr,"Failed to read %d bytes from audio [errno(%d)]\n",len,errno);
		return res;
	}
	for (x=0;x<res;x++)
		buf[x] = mulaw[(int)tmp[x]];
	return res;
}

static int snom_write(int fd, void *buf, int len, int format)
{
	return snom_write_linear(fd,(short *)buf,len);
}
/* Can only read mulaw from Snom audio driver */
/* format is the format in which to return data */
static int snom_read(int fd, void *buf,int len,int format)
{
	return snom_read_linear(fd,(short *)buf,len);	
}
/* Receive data buffer (16 bit linear with len in bytes */
static int snom_readaudio(struct audio_channel *ac, int format, void *data, int *len)
{
	if(0>(*len=snom_read(ac->fd,(short *)data,(*len/*in bytes*/)/sizeof(short),0))){
		*len=0;
		return -1;
	}
	*len=(*len)*2;
	return 0;
}

static int snom_sendaudio(struct audio_channel *ac, int format, void *data, int len)
{
	//set_output(ac,OUTPUT_HANDSET);	
	return snom_write(ac->fd,(short *)data,len,0);
}


char *key() 
{
	return KEY;
}

char *name()
{
	return "snom";
}


int init(){
	struct audio_channel *ac;
	//phonecore_init_tables();
	printf("Good morning from audio-snomphone init()\n");
	ulaw_init();
	printf("Got past ulaw_init()\n");
	ac=snom_channel_new("/dev/audio");
	printf("Got past snom_channel_new() %p\n",ac);
	if(ac==NULL){
		fprintf(stderr,"Failed to get a snom_channel\n");
		return -1;
	}
	printf("Audio snomphone got a channel sucessfully\n");
	if(audio_register_channel(ac))
		return -2;
	fprintf(stderr,"Audio snomphone channel registered\n");
	set_output(ac,OUTPUT_HANDSET);	
	return 0;
}
