/*
 * Phonecore: A client framework 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
 *
 * phonecore.c: Core telephony thread
 *
 */

#include <iax/iax-client.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <gsm/gsm.h>
#include <sys/socket.h>
#include <netdb.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <sys/wait.h>
#include <sys/signal.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "phonecore.h"
#include "io.h"
#include "asched.h"
#include "audio.h"
#include "ctype.h"
#include "../sounds/zero.h"
#include "../sounds/one.h"
#include "../sounds/two.h"
#include "../sounds/three.h"
#include "../sounds/four.h"
#include "../sounds/five.h"
#include "../sounds/six.h"
#include "../sounds/seven.h"
#include "../sounds/eight.h"
#include "../sounds/nine.h"
#include "../sounds/star.h"
#include "../sounds/pound.h"

#include "../sounds/dialtone.h"
#include "../sounds/busy.h"
#include "../sounds/ringt.h"
#include "../sounds/ring.h"
#include "ringtones.h"
#include "audio.h"

#define USE_FORK


#ifndef USE_FORK
#include <pthread.h>
#endif

#ifdef SNOM_HACK
#include "snom_memset.h"
#endif

/* Keep three levels of conference buffer */
#define CNF_BUF 4

#define SEND(a) pc_send_sound(a, sizeof(a)/2, 0 )
#define SEND2(a) pc_send_sound(a, sizeof(a)/2, 1 )

#define LAG_TIMEOUT 15000

static struct io_context *io;
static struct sched_context *sched;

static char defaultuser[80];
static char defaultpassword[80];
static char defaultcontext[80];
static char defaultserver[256];

struct registration {
	int active;
	struct iax_session *session;
	char hostname[256];
	char secret[80];
	char peer[80];
	int refresh;
	int regid;
} regs[PC_MAX_REGS];

/* Sched ID of timeout for audio */
static int *id;

/* Desired read format */
static int format = AST_FORMAT_SLINEAR;
/* Max chunk to ask the driver to read at once.  Deliver
   only exactly this much data to in a frame passed
   to the top level. */
static int chunk = 320;

/* This is whether we must have a full chunk or not */
static int enforcechunk = 1;

static int ready = 3;

/* Whether to debug or not */
static int pcdebug = 0;

#ifdef USE_FORK
static pid_t pcpid;
#endif

/* Kinda handy */
void *unused;

static int __pc_hangup(int src, int callno, char *why);

#define CNF_INC(z) do { \
	z = (z + 1) % CNF_BUF; \
} while(0)

static int cnfcount = 0;

enum callstate {OUTGOING_INCOMPLETE=0,OUTGOING,INCOMING_INCOMPLETE,INCOMING};
struct peer {
	gsm gsmin;
	gsm gsmout;
	int mute;
	int created_session;
	struct iax_session *session;
	int cnfin;
	int cnfout;
	int inconf;
	int lagid;
	short cnfbuf[CNF_BUF * CHUNKLEN];
	enum callstate cstate; 
} *peers[PC_MAX_CALLS];

static int current;

static int socks[4];

int *pc_io_add(int fd, ast_io_cb callback, short events, void *data)
{
	return ast_io_add(io, fd, callback, events, data);
}

int pc_io_remove(int *id)
{
	return ast_io_remove(io, id);
}

#ifdef USE_FORK
void child_handler(int sig)
{
	pid_t pid;
	int status;
	pid = waitpid(-1, &status, WNOHANG);
	if (pid == pcpid) {
		fprintf(stderr, "!!! Phone Core Died !!!\n");
		fprintf(stderr,"!!! Killing Parent !!!\n");
		exit(-1);
	}
}
#endif

struct peer *new_peer(struct iax_session *session)
{
	struct peer *p;
	p = malloc(sizeof(struct peer));
	if (p) {
		bzero(p, sizeof(struct peer));
		if(session!=NULL){
			p->session=session;
			p->created_session=0;
			p->cstate = INCOMING_INCOMPLETE;
		} else {
			p->session=iax_session_new();
			p->created_session=1;
			p->cstate = OUTGOING_INCOMPLETE;
		}
		p->mute = 1;
		p->cnfin = 1;
		p->cnfout = 0;
		if (!p->session) {
			free(p);
			p = NULL;
		}
	}
	return p;
};


int pc_read_event(int src, pc_event *e)
{
	int res;
	res = read(socks[src], e, sizeof(pc_event));
	if (res != sizeof(pc_event)) {
		fprintf(stderr, "Only read %d of %d bytes on %d: %s\n", res, sizeof(pc_event), src, strerror(errno));
		return -1;
	}
	if ((e->callno >= PC_MAX_CALLS) || (e->callno < -1)) {
		fprintf(stderr, "!!! Call number %d invalid (event %d len %d)!!!\n", e->callno, e->event, e->len);
		return -1;
	}
	return 0;
}

pc_event *pc_get_event(void)
{
	static pc_event e;
	if (!pc_read_event(SOURCE_GUI, &e))
		return &e;
	return NULL;
}

static int __lag_send(void *data)
{
	int callid = (int)data;
	/* Make sure that we have a valid session before sending the request */
	if ((callid >= 0) && peers[callid])
		if (iax_lag_request(peers[callid]->session))
			fprintf(stderr, "IAX failed to send a lag request: %s\n", strerror(errno));
	peers[callid]->lagid = ast_sched_add(sched, LAG_TIMEOUT, __lag_send, data);
	return 0;
}

static int lag_send(int chan)
{
	peers[chan]->lagid = ast_sched_add(sched, LAG_TIMEOUT, __lag_send, (void *)chan);
	return 0;
}

char *pc_event2str(int event)
{
	static char str[80];
	switch (event) {
	case PC_EVENT_IMAGE:
		return "Image";
	case PC_EVENT_AUDIO:
		return "Audio";
	case PC_EVENT_LOADCOMPLETE:
		return "Load Complete";
	case PC_EVENT_DTMF:
		return "DTMF";
	case PC_EVENT_HANGUP:
		return "Hangup";
	case PC_EVENT_ANSWER:
		return "Answer";
	case PC_EVENT_REJECT:
		return "Reject";
	case PC_EVENT_ACCEPT:
		return "Accept";
	case PC_EVENT_AUTHRQ:
		return "Authentication Request";
	case PC_EVENT_AUTHRP:
		return "Authentication Reply";
	case PC_EVENT_URL:
		return "URL";
	case PC_EVENT_RINGA:
		return "Ring Announce";
	case PC_EVENT_REGREP:
		return "Registration Reply";
	case PC_EVENT_CONNECT:
		return "Connect Request";
	case PC_EVENT_SELECT:
		return "Select active call";
	case PC_EVENT_NEW:
		return "New Call";
	case PC_EVENT_NEW_REPLY:
		return "New Call Reply";
	case PC_EVENT_SELAUDIO:
		return "Select Audio";
	case PC_EVENT_AUDIO_REPLY:
		return "Audio Reply";
	case PC_EVENT_AUDIO_READY:	
		return "Audio Ready";
	case PC_EVENT_ADDR:
		return "Address";
	case PC_EVENT_AUDIO_SHUTUP:
		return "Audio Shutup";
	case PC_EVENT_AUDIO_BUSY:
		return "Audio Busy";
	case PC_EVENT_AUDIO_DIALTONE:
		return "Audio Dialtone";
	case PC_EVENT_OPTION_SET:
		return "Option Set";
	case PC_EVENT_AUDIO_RING:
		return "Audio Ring";
	case PC_EVENT_AUDIO_RINGING:
		return "Audio Ringing";
	case PC_EVENT_LAGREP:
		return "Lag Reply";
	default:
		snprintf(str, sizeof(str), "Unknown event %d", event);
		return str;
	}
		
}

void pc_dump_event(pc_event *e)
{
	printf("Event for call %d, type: %d (%s)\n", e->callno, e->event, pc_event2str(e->event));
}

int pc_write_event(int src, pc_event *e)
{
	int res;
#if 0
	printf("Writing even %d on %d\n", e->event, src);
#endif	
	res = write(socks[src], e, sizeof(pc_event));
	if (res != sizeof(pc_event)) {
		fprintf(stderr, "Only wrote %d of %d bytes: %s\n", res, sizeof(*e), strerror(errno));
		exit(1);
		return -1;
	}
	return 0;
}


#define INIT_PE(ev) do { \
	pe.len = sizeof(pe); \
	pe.callno = get_callno(e->session); \
	pe.event = ev; \
} while (0)

static int get_level(short *data, int samples);

static inline int get_callno(struct iax_session *s)
{
	int x;
	for (x=0;x<PC_MAX_CALLS;x++)
		if (peers[x] && (peers[x]->session == s))
			return x;
	return -1;
}

static void handle_voice(struct iax_event *e)
{
	int id;
	short fr[160];
	int len;
	pc_event pe;
	struct peer *p;
	id = get_callno(e->session);
	if (id > -1) {
		p = peers[id];
		if (!p->gsmin)
			p->gsmin = gsm_create();
			
		len = 0;
		if (e->event.voice.datalen % 33) {
			fprintf(stderr, "Error: audio not a multiple of 33 bytes\n");
			return;
		}
		while(len < e->event.voice.datalen) {
			gsm_decode(p->gsmin, e->event.voice.data + len, fr);
			if ((id == current))
				audio_send(AST_FORMAT_SLINEAR, fr, sizeof(fr), 0, 1);
			memcpy(p->cnfbuf + p->cnfin * CHUNKLEN, fr, sizeof(fr));
			CNF_INC(p->cnfin);
			/* If we've overrun our buffer, increment the output conference since we've overwritten
			   its (now outdated) data anyway */
			if (p->cnfin == p->cnfout) {
#if 0
				printf("Out of storage space, forcing inc\n");
#endif				
				CNF_INC(p->cnfout);
			}
			if (ready && !len) {
				pe.len = sizeof(pe);
				pe.callno = id;
				pe.event = PC_EVENT_AUDIO;
				pe.e.audio.level = get_level(fr, 160);
				pc_write_event(SOURCE_PC, &pe);
				ready--;
			}
			len += 33;
		}
	} else
		fprintf(stderr, "Voice on non-existant session %p\n", e->session);
}

static void free_peer(int id)
{
	if(id>-1){
		if (peers[id]) {
			if (peers[id]->inconf)
				cnfcount--;
			if (peers[id]->gsmin)
				gsm_destroy(peers[id]->gsmin);
			if (peers[id]->gsmout)
				gsm_destroy(peers[id]->gsmout);
			if (peers[id]->lagid)
				ast_sched_del(sched, peers[id]->lagid);
			if (peers[id]->created_session)
				iax_hangup(peers[id]->session,"Peer is being freed");	
			free(peers[id]);
			peers[id] = NULL;
		}
	}
}

static int new_peer_id(struct iax_session *session);

static int handle_iax_event(struct iax_event *e)
{
	pc_event pe;
	int cn;
	int x;

	switch(e->etype) {
	case IAX_EVENT_CONNECT:
		pe.len = sizeof(pe);
		pe.event = PC_EVENT_CONNECT;
		pe.callno = new_peer_id(e->session);
		if(pe.callno>-1){
			if (e->event.connect.callerid)
				strncpy(pe.e.connect.callerid, e->event.connect.callerid,
						sizeof(pe.e.connect.callerid)-1);
			else
				strcpy(pe.e.connect.callerid, "");

			if (e->event.connect.dnid)
				strncpy(pe.e.connect.dnid, e->event.connect.dnid,
						sizeof(pe.e.connect.dnid)-1);
			else
				strcpy(pe.e.connect.dnid, "");

			pe.e.connect.addr = iax_get_peer_addr(e->session);
			lag_send(pe.callno);
			if (pc_write_event(SOURCE_PC, &pe))
				fprintf(stderr, "Unable to deliver connect event\n");
			return 0;
		} else {
			return -1;
		}
	case IAX_EVENT_HANGUP:
		cn = get_callno(e->session);
		if(cn>-1){
			free_peer(cn);
			return __pc_hangup(SOURCE_PC, cn, e->event.hangup.byemsg);
		}
		return -1;
	case IAX_EVENT_REJECT:
		cn = get_callno(e->session);
		free_peer(cn);
		return __pc_reject(SOURCE_PC, cn, e->event.reject.reason);
	case IAX_EVENT_ACCEPT:
		return __pc_accept(SOURCE_PC,get_callno(e->session));
	case IAX_EVENT_ANSWER:
		cn=get_callno(e->session);
		if(cn>-1){
			peers[cn]->mute = 0;
			if(peers[cn]->cstate != OUTGOING_INCOMPLETE)
				printf("Hmmm ... non outoing_not_answered call answered?\n");
			peers[cn]->cstate = OUTGOING;
			return __pc_answer(SOURCE_PC, get_callno(e->session));
		}
		return -1;
	case IAX_EVENT_AUTHRQ:
		INIT_PE(PC_EVENT_AUTHRQ);
		pe.e.authrequest.authmethods = e->event.authrequest.authmethods;
		if (e->event.authrequest.challenge)
			strncpy(pe.e.authrequest.challenge, e->event.authrequest.challenge,
					sizeof(pe.e.authrequest.challenge)-1);
		else
			strcpy(pe.e.authrequest.challenge, "");
		if (e->event.authrequest.username)
			strncpy(pe.e.authrequest.username, e->event.authrequest.username,
					sizeof(pe.e.authrequest.username)-1);
		else
			strcpy(pe.e.authrequest.username, "");
		if (pc_write_event(SOURCE_PC, &pe))
			fprintf(stderr, "Unable to report auth request\n");
		return 0;
	case IAX_EVENT_IMAGE:
		INIT_PE(PC_EVENT_IMAGE);
		if (e->event.image.datalen > sizeof(pe.e.image.data)) {
			fprintf(stderr, "!! Image too large to copy (%d bytes) !!\n",
				e->event.image.datalen);
			return 0;
		}
		memcpy(pe.e.image.data, e->event.image.data, e->event.image.datalen);
		pe.e.image.datalen = e->event.image.datalen;
		if (pc_write_event(SOURCE_PC, &pe))
			fprintf(stderr, "Unable to return image\n");
		return 0;
	case IAX_EVENT_LAGRP:
		INIT_PE(PC_EVENT_LAGREP);

		pe.e.lag.lag = e->event.lag.lag;
		pe.e.lag.jitter = e->event.lag.jitter;

		if (pc_write_event(SOURCE_PC, &pe))
			fprintf(stderr, "Unable to send LAGREP\n");
		
		break;

	case IAX_EVENT_UNLINK:
		__pc_send_unlink(SOURCE_PC, get_callno(e->session));

		break;

	case IAX_EVENT_LINKREJECT:
		__pc_send_link_reject(SOURCE_PC, get_callno(e->session));

		break;

	case IAX_EVENT_TEXT:
		INIT_PE(PC_EVENT_TEXT);
		if (e->event.text.text)
			strncpy(pe.e.text.text, e->event.text.text, 
					sizeof(pe.e.text.text)-1);

		if (pc_write_event(SOURCE_PC, &pe))
				fprintf(stderr, "Unable to return text\n");
		
		return 0;

	case IAX_EVENT_URL:
		INIT_PE(PC_EVENT_URL);
		if (e->event.url.url)
			strncpy(pe.e.url.url, e->event.url.url,
					sizeof(pe.e.url.url)-1);
		else
			strcpy(pe.e.url.url, "");
		
		pe.e.url.link = e->event.url.link;
		
		if (pc_write_event(SOURCE_PC, &pe))
			fprintf(stderr, "Unable to return URL\n");
		return 0;
	case IAX_EVENT_RINGA:
		return __pc_ring_announce(SOURCE_PC,get_callno(e->session));
	case IAX_EVENT_REGREP:
		/* Make a note tha the session is gone now */
		pe.e.regreply.them[0] = '\0';
		for (x=0;x<PC_MAX_REGS;x++) {
			if (regs[x].active && regs[x].session == e->session) {
				/* Reset session */
				/* See iax.c:
				 * 	static struct iax_event *handle_event(struct iax_event *event)
				 * 	case IAX_EVENT_REGREP:
				 */
				regs[x].session = NULL;
				strncpy(pe.e.regreply.them, regs[x].hostname, sizeof(pe.e.regreply.them) - 1);
			}
		}
		INIT_PE(PC_EVENT_REGREP);
		pe.callno = -1;
		if (e->event.regreply.ourip)
			strncpy(pe.e.regreply.ourip, e->event.regreply.ourip, 
					sizeof(pe.e.regreply.ourip)-1);
		else
			strcpy(pe.e.regreply.ourip, "");
		
		pe.e.regreply.ourport = e->event.regreply.ourport;
		pe.e.regreply.refresh = e->event.regreply.refresh;
		pe.e.regreply.status = e->event.regreply.status;

		if (e->event.regreply.callerid)
			strncpy(pe.e.regreply.callerid, e->event.regreply.callerid, sizeof(pe.e.regreply.callerid)-1);
		else
			strcpy(pe.e.regreply.callerid, "");

		if (pc_write_event(SOURCE_PC, &pe))
			fprintf(stderr, "Unable to report registration reply\n");
		return 0;
	case IAX_EVENT_VOICE:
		cn=get_callno(e->session);
		if(cn>-1){
			if( peers[cn]->cstate != INCOMING_INCOMPLETE )
				handle_voice(e);
			return 0;
		} 
		return -1;
	case IAX_EVENT_TRANSFER:
		printf("Transferred to %s:%d\n", e->event.transfer.newip, e->event.transfer.newport);
		pc_transfer(SOURCE_PC, get_callno(e->session), e->event.transfer.newip, e->event.transfer.newport);
		return 0;
	case IAX_EVENT_DPREP:

		if (!e->event.dprep.canexist)
		{

			INIT_PE(PC_EVENT_DPREP);

			pe.callno = -1;
			pe.e.dprep.exists = e->event.dprep.exists;
			pe.e.dprep.nonexistant = e->event.dprep.nonexistant;
 			pe.e.dprep.canexist = e->event.dprep.canexist;
			pe.e.dprep.ignorepat = e->event.dprep.ignorepat;

			strcpy(pe.e.dprep.number, e->event.dprep.number);

			if (pc_write_event(SOURCE_PC, &pe))
				fprintf(stderr, "Unable to report dialplan reply.\n");
		}

		return 0;
	default:
		fprintf(stderr, "Don't know what to do with IAX event %d\n", e->etype);
	}
	return -1;
}

static int handle_iax(int *id, int fd, short events, void *cbdata)
{
	struct iax_event *e;
	while((e = iax_get_event(0))) {
		handle_iax_event(e);
		iax_event_free(e);
	}
	return 1;
}

int ring_select(struct audio_channel *c)
{
	if(c!=NULL){
		if(c->ringtone==DEFAULT_RINGTONE)
			std_ring(c);
	}
	return 0;
}
int std_ring(struct audio_channel *c)
{
	SEND2(ring);
	return 0;	
}

int std_ringing(struct audio_channel *c)
{
	SEND2(ringt);
	return 0;	
}


int std_busy(struct audio_channel *c)
{
	SEND2(busy);
	return 0;	
}
int std_dialtone(struct audio_channel *c)
{
	SEND2(dialtone);
	return 0;	
}

int std_fastbusy(struct audio_channel *c)
{
	SEND2(busy);
	return 0;	
}

int std_play_digit(struct audio_channel *c, char digit)
{
	/* Nothing to do if they don't have audio */
	if (!c->sendaudio)
		return 0;
	switch(digit) {
	case '0':
		SEND(zero);
		break;
	case '1':
		SEND(one);
		break;
	case '2':
		SEND(two);
		break;
	case '3':
		SEND(three);
		break;
	case '4':
		SEND(four);
		break;
	case '5':
		SEND(five);
		break;
	case '6':
		SEND(six);
		break;
	case '7':
		SEND(seven);
		break;
	case '8':
		SEND(eight);
		break;
	case '9':
		SEND(nine);
		break;
	case '*':
		SEND(star);
		break;
	case '#':
		SEND(pound);
		break;
	default:
		fprintf(stderr, "Unknown digit: %c\n", digit);
	}
	return 0;
}

int pc_send_dpreq(int callno, char *digits)
{
	pc_event e;
	e.len = sizeof(pc_event);
	e.callno = callno;
	e.event = PC_EVENT_DPREQ;
	snprintf(e.e.dpreq.dest, sizeof(e.e.dpreq.dest), "%s", digits);
	return pc_write_event(SOURCE_GUI, &e);
}

int pc_send_linked_url(int callno, char *url)
{
	pc_event e;
	e.len = sizeof(pc_event);
	e.callno = callno;
	e.event = PC_EVENT_URL;
	e.e.url.link = 1;
	snprintf(e.e.url.url, sizeof(e.e.url.url), "%s", url);
	return pc_write_event(SOURCE_GUI, &e);
}

int pc_transfer(int src, int callno, char *newip, int newport)
{
	pc_event e;
	e.len = sizeof(pc_event);
	e.callno = callno;
	e.event = PC_EVENT_TRANSFER;
	strncpy(e.e.transfer.newip, newip, sizeof(e.e.transfer.newip));
	e.e.transfer.newport = newport;
	return pc_write_event(src, &e);
}

static int new_peer_id(struct iax_session *session)
{
	int x;
	for (x=0;x<PC_MAX_CALLS;x++)
		if (!peers[x])
			break;
	if (x < PC_MAX_CALLS) {
		peers[x] = new_peer(session);
		if (peers[x])
			return x;
	}
	return -1;
}

int pc_peer_info(struct peer_info *info)
{
	pc_event e;
	int res;
	
	if(info==NULL)
		return -1;
	memset(&e, 0, sizeof(e));
	e.len = sizeof(pc_event);
	e.callno = -1;
	e.event = PC_EVENT_PEER_INFO_REQ;
	res =  pc_write_event(SOURCE_GUI, &e);
	if (res < 0)
		return -1;
	/* Read back result */
	res = pc_read_event(SOURCE_PRI_OUT, &e);
	if (res < 0)
		return -1;
	if (e.event != PC_EVENT_PEER_INFO) {
		fprintf(stderr, "Did not rx peer info reply\n");
		return -1;
	}
	memcpy(info,&e.e.peer_info,sizeof(e.e.peer_info));
	return 0;	
}

static int get_peer_info(int array[],int num_entries){
	if(num_entries>=PC_MAX_CALLS){
		int x,idx;
		for(idx=0,x=0;x<PC_MAX_CALLS;x++){
			if(peers[x]!=NULL){
				array[idx++]=x;
			}
		}
		return idx;
	}
	return -1;
}

static int get_option(char *name, char *value, int *valuelen)
{
	int x;
	char *s;
	if (!strcasecmp(name, "audio-current")) {
		snprintf(value, *valuelen, "%d", audio_current());
		*valuelen = strlen(value);
	} else if (!strncasecmp(name, "audio-",strlen("audio-"))) {
		if (sscanf(name, "audio-%d", &x) != 1) {
			fprintf(stderr, "Option '%s' invalid format\n", name);
			return -1;
		}
		s = audio_getname(x);
		if (s) {
			printf("s is '%s'\n", s);
			strncpy(value, s, *valuelen);
			*valuelen = strlen(value);
		} else {
			strcpy(value, "");
			*valuelen = 0;
		}
	} else {
		fprintf(stderr, "Get unknown option: %s\n", name);
		return -1;
	}
	return 0;
}


static int registration_run(void *vreg)
{
	struct registration *reg = vreg;

	if (reg->session) {
		fprintf(stderr,"Iax_hangup on session %p\n",reg->session);
		iax_hangup(reg->session, "You're being replaced");
	}
	reg->session = iax_session_new();
	if (!reg->session) {
		fprintf(stderr, "Unable to create session\n");
		return -1;
	}
	if (pcdebug)
		printf("Attempting to register '%s' at '%s' with secret '%s'\n", 
			reg->peer, reg->hostname, reg->secret);
	if(!reg->hostname || !reg->peer || !reg->secret){
		fprintf(stderr,"Invalid hostname peer or secret\n");
		iax_hangup(reg->session,"Invalid hostname");
		return -1;
	} 
	if ((iax_register(reg->session, reg->hostname, reg->peer, reg->secret, 60))<0) {
		fprintf(stderr, "Unable to register session\n");
		iax_hangup(reg->session,"failed to register");
		return -1;
	}
	/* Schedule repeat (repeat 10 seconds earlier) */
	reg->regid = ast_sched_add(sched, 50000, registration_run, reg);
	return 0;
}

static int set_option(char *name, char *value, int valuelen)
{
	int x;
	char *user, *password, *host;
	if (!strncasecmp(name, "registration-", strlen("registration-"))) {
		if (sscanf(name, "registration-%d", &x) != 1) {
			fprintf(stderr, "Option '%s' invalid format\n", name);
			return -1;
		}
		if ((x < 0) || (x >= PC_MAX_REGS)) {
			fprintf(stderr, "Registration %d is out of bounds (0-%d)\n", x, PC_MAX_REGS-1);
			return -1;
		}
		if (regs[x].regid) {
			ast_sched_del(sched, regs[x].regid);
			regs[x].regid = 0;
		}
		if (!strlen(value)) {
			regs[x].active = 0;
			return 0;
		}
		user = strtok(value, "@");
		host = strtok(NULL, "@");
		if (!host) {
			fprintf(stderr, "Registration '%s' missing a hostname\n", user);
			return -1;
		}
		strncpy(regs[x].hostname, host, sizeof(regs[x].hostname) - 1);
		strtok(user, ":");
		password = strtok(NULL, ":");
		strncpy(regs[x].peer, user, sizeof(regs[x].peer) - 1);
		if (password)
			strncpy(regs[x].secret, password, sizeof(regs[x].secret) - 1);
		else
			regs[x].secret[0]='\0';
		regs[x].active = 1;
		/* Start the process */
		registration_run(&regs[x]);
	} else if (!strcasecmp(name, "defaultuser")) {
		strncpy(defaultuser, value, sizeof(defaultuser) - 1);
	} else if (!strcasecmp(name, "defaultpassword")) {
		strncpy(defaultpassword, value, sizeof(defaultpassword) - 1);
	} else if (!strcasecmp(name, "defaultserver")) {
		/*
		struct hostent *h=gethostbyname2(value,AF_INET);
		sprintf(defaultserver,"%u.%u.%u.%u",h->h_addr_list[0][0]&0xff,h->h_addr_list[0][1]&0xff,h->h_addr_list[0][2]&0xff,h->h_addr_list[0][3]&0xff);
		*/
		strncpy(defaultserver, value, sizeof(defaultserver) -1 );
	} else if (!strcasecmp(name, "defaultcontext")) {
		strncpy(defaultcontext, value, sizeof(defaultcontext) -1 );
	} else {
		fprintf(stderr, "Set unknown option: %s\n", name);
		return -1;
	}
	return 0;
}

int pc_set_option(char *name, char *value, int len)
{
	pc_event e;
	int res;
	
	if (len > sizeof(e.e.option.data) - 1) {
		fprintf(stderr, "Option length %d is longer than max %d\n", len, sizeof(e.e.option.data));
		return -1;
	}
	memset(&e, 0, sizeof(e));
	e.len = sizeof(pc_event);
	e.callno = -1;
	e.event = PC_EVENT_OPTION_SET;
	strncpy(e.e.option.name, name, sizeof(e.e.option.name) - 1);
	memcpy(e.e.option.data, value, len);
	e.e.option.datalen = len;
	res =  pc_write_event(SOURCE_GUI, &e);
	if (res < 0)
		return -1;
	/* Read back result */
	res = pc_read_event(SOURCE_PRI_OUT, &e);
	if (res < 0)
		return -1;
	if (e.event != PC_EVENT_OPTION_VAL) {
		fprintf(stderr, "Huh?  Didn't get option value back...\n");
		return -1;
	}
	return e.e.option.result;	
}

int pc_get_option(char *name, char *value, int *len)
{
	pc_event e;
	int res;
	
	if (*len > sizeof(e.e.option.data) - 1) {
		fprintf(stderr, "Option length %d is longer than max %d\n", *len, sizeof(e.e.option.data));
		return -1;
	}
	memset(&e, 0, sizeof(e));
	e.len = sizeof(pc_event);
	e.callno = -1;
	e.event = PC_EVENT_OPTION_GET;
	strncpy(e.e.option.name, name, sizeof(e.e.option.name) - 1);
	memcpy(e.e.option.data, value, *len);
	e.e.option.datalen = *len;
	res =  pc_write_event(SOURCE_GUI, &e);
	if (res < 0)
		return -1;
	/* Read back result */
	res = pc_read_event(SOURCE_PRI_OUT, &e);
	if (res < 0)
		return -1;
	if (e.event != PC_EVENT_OPTION_VAL) {
		fprintf(stderr, "Huh?  Didn't get option value back...\n");
		return -1;
	}
	*len = e.e.option.datalen;
	memcpy(value, e.e.option.data, *len);
	return e.e.option.result;	
}

static int read_audio(int *id, int fd, short events, void *data);

static int handle_socks(int *sid, int fd, short events, void *cbdata)
{
	pc_event e;
	int res;
	char dest[256];
	
	res = pc_read_event(SOURCE_PC, &e);
	if (res < 0) 
		return 1;
	switch(e.event) {
	case PC_EVENT_SELAUDIO:
		e.event = PC_EVENT_AUDIO_REPLY;
		/* Remove ID */
		if (id)
			ast_io_remove(io, id);
		id = NULL;
		e.e.asel.response = audio_select(e.e.asel.chan);
		if (audio_get_fd() > -1) {
			if (audio_get_exception()) {
				id = ast_io_add(io, audio_get_fd(), read_audio, AST_IO_IN | AST_IO_PRI, NULL);
			} else {
				id = ast_io_add(io, audio_get_fd(), read_audio, AST_IO_IN, NULL);
			}
		}
		if (pc_write_event(SOURCE_PRI_IN, &e))
			fprintf(stderr, "Unable to send audio result\n");
		break;
	case PC_EVENT_NEW:
		e.event = PC_EVENT_NEW_REPLY;
		e.e.newreply.id = new_peer_id(NULL);
		if (pc_write_event(SOURCE_PRI_IN, &e))
			fprintf(stderr, "Unable to send new reply\n");
		break;
	case PC_EVENT_AUDIO_READY:
		/* Send another audio update whenever */
		ready++;
		break;
	case PC_EVENT_CONNECT:
		if (isdigit(e.e.connect.dest[0])) {
			/* Assume this is a phone number and work with it */
			if (strlen(defaultserver)) {
				/* Gotta have at least this much */
				if (strlen(defaultuser)) {
					/* Got a username */
					if (strlen(defaultpassword)) {
						/* Got a password */
							snprintf(dest, sizeof(dest), "%s:%s@%s/%s", defaultuser, defaultpassword,defaultserver, e.e.connect.dest);
					} else {
						/* No pasword, just username */
						snprintf(dest, sizeof(dest), "%s@%s/%s", defaultuser, defaultserver,e.e.connect.dest);
					}
				} else {
					/* hostname only */
					snprintf(dest, sizeof(dest), "%s/%s", defaultserver,e.e.connect.dest);
				}
				/* Add default context if specified */
				if (strlen(defaultcontext)) {
					strncat(dest, "@", sizeof(dest) - strlen(dest) - 1);
					strncat(dest, defaultcontext, sizeof(dest) - strlen(dest) - 1);
				}
				printf("New destination: %s\n", dest);
			} else {
				fprintf(stderr, "Asked to dial phone number but no default server\n");
				break;
			}
		} else
			strncpy(dest, e.e.connect.dest, sizeof(dest) - 1);
		fprintf(stderr,"Destination string %s\n",dest);
		if(peers[e.callno]!=NULL){
			if (iax_call(peers[e.callno]->session, e.e.connect.callerid, dest, e.e.connect.language, 0)) {
				fprintf(stderr, "IAX Call failed: (session: %p, callerid: %s, dest: %s, language: %s): %s\n", peers[e.callno]->session, e.e.connect.callerid, e.e.connect.dest, e.e.connect.language, strerror(errno));
			}
			pc_addr(SOURCE_PC, e.callno, iax_get_peer_addr(peers[e.callno]->session));
			/* Start lag requests */
			lag_send(e.callno);
		} else 
			fprintf(stderr,"Unable to call on %d : Inactive Callno Try \"new\".\n",e.callno);
		break;

	case PC_EVENT_COMPLETE:
		printf("DEBUG: e.callno = %d\n", e.callno);
		printf("DEBUG: session  = %p\n", peers[e.callno]->session);
		printf("DEBUG: dest     = %s\n", e.e.connect.dest);
		if (iax_dial(peers[e.callno]->session, e.e.connect.dest)) {
			fprintf(stderr, "IAX Dial failed: %s\n", strerror(errno));
		}
		pc_addr(SOURCE_PC, e.callno, iax_get_peer_addr(peers[e.callno]->session));
		break;

	case PC_EVENT_HANGUP:
		if ((e.callno >= 0 && e.callno<PC_MAX_CALLS)&&peers[e.callno]){
			if(iax_hangup(peers[e.callno]->session, e.e.hangup.why))
				fprintf(stderr, "IAX hangup failed: %s\n", strerror(errno));
			free_peer(e.callno);
		}
		break;
	case PC_EVENT_AUDIO_DIGIT:
		pc_send_digit(e.e.dtmf.dtmf);
		break;
	case PC_EVENT_AUDIO_RING:
		audio_ring();
		break;
	case PC_EVENT_AUDIO_RINGING:
	 	printf("Audio Current %d\n",audio_current());
		audio_ringing();
		break;
	case PC_EVENT_AUDIO_SHUTUP:
		audio_shutup();
		break;
	case PC_EVENT_AUDIO_BUSY:
		audio_busy();
		break;
	case PC_EVENT_AUDIO_DIALTONE:
		audio_dialtone();
		break;
	case PC_EVENT_AUDIO_DEACTIVATE:
		audio_deactivate();
		break;
	case PC_EVENT_SELECT:
		printf("Selected Callno %d\n",e.callno);
		current = e.callno;
		break;
	case PC_EVENT_OPTION_SET:
		e.event = PC_EVENT_OPTION_VAL;
		e.e.option.result = set_option(e.e.option.name, e.e.option.data, e.e.option.datalen);
		if (pc_write_event(SOURCE_PRI_IN, &e))
			fprintf(stderr, "Unable to send option val result\n");
		break;
	case PC_EVENT_OPTION_GET:
		/* Modify in place */
		res = get_option(e.e.option.name, e.e.option.data, &e.e.option.datalen);
		e.event = PC_EVENT_OPTION_VAL;
		if (pc_write_event(SOURCE_PRI_IN, &e))
			fprintf(stderr, "Unable to send option value\n");
		break;
	case PC_EVENT_PEER_INFO_REQ:
		res = get_peer_info(e.e.peer_info.callnos,PC_MAX_CALLS);
		if(res>-1){
			e.event = PC_EVENT_PEER_INFO;
			e.e.peer_info.calls=res;
			if(pc_write_event(SOURCE_PRI_IN,&e))
				fprintf(stderr,"Failed to send peer info response\n");
		} else 
			fprintf(stderr,"Unable to fullfill peer info request\n");
		break;
	case PC_EVENT_DTMF:
		if ((e.callno >= 0) && peers[e.callno] && iax_send_dtmf(peers[e.callno]->session, e.e.dtmf.dtmf))
			fprintf(stderr, "IAX dtmf failed: %s\n", strerror(errno));
		break;
	case PC_EVENT_RINGA:
		if ((e.callno >= 0) && peers[e.callno] && iax_ring_announce(peers[e.callno]->session))
			fprintf(stderr, "IAX ring announce failed: %s\n", strerror(errno));
		break;
	case PC_EVENT_ACCEPT:
		if((e.callno >= 0) && peers[e.callno]){
			printf("Accepting call on session %p\n", peers[e.callno]->session);
			if (iax_accept(peers[e.callno]->session))
				fprintf(stderr, "IAX accept failed: %s\n", strerror(errno));
		}
		break;
	case PC_EVENT_ANSWER:
		if (!iax_answer(peers[e.callno]->session)){
			peers[e.callno]->mute = 0;
			if (!iax_answer(peers[e.callno]->session)){
				printf("INCOMING_INCOMPLETE has been answered\n");
				if(peers[e.callno]->cstate!=INCOMING_INCOMPLETE)
					printf("Hmmm... non incoming_not_answered answered?\n");
				peers[e.callno]->cstate=INCOMING;
			} else fprintf(stderr, "IAX answer failed: %s\n", strerror(errno));
		}
		break;
	case PC_EVENT_LOADCOMPLETE:
		if ((e.callno >= 0) && peers[e.callno] && iax_load_complete(peers[e.callno]->session))
			fprintf(stderr, "IAX load complete failed: %s\n", strerror(errno));
		break;
	case PC_EVENT_AUTHRP:
		if (iax_auth_reply(peers[e.callno]->session, e.e.authreply.password,
				e.e.authreply.challenge, e.e.authreply.methods))
			fprintf(stderr, "IAX hangup failed: %s\n", strerror(errno));
		break;
	case PC_EVENT_CONFERENCE:
		if ((e.callno >= 0) && peers[e.callno] && !peers[e.callno]->inconf) {
			peers[e.callno]->inconf=1;
			/* If it's our current talker, put us in the conference now too */
			if (e.callno == current)
				current = -1;
			cnfcount++;
		}
		break;
	case PC_EVENT_UNCONFERENCE:
		if ((e.callno >= 0) && peers[e.callno] && peers[e.callno]->inconf) {
			peers[e.callno]->inconf=0;
			cnfcount--;
		}
		break;
	case PC_EVENT_TEXT:
		printf("I would be sending the following text: %s to pcid(%d)\n", e.e.text.text, e.callno);

		if ((e.callno >= 0) && peers[e.callno])
			if (iax_send_text(peers[e.callno]->session, e.e.text.text))
				fprintf(stderr, "IAX failed to send text: %s\n", strerror(errno));

		break;		
	case PC_EVENT_IMAGE:
		if ((e.callno >= 0) && peers[e.callno])
			if (iax_send_image(peers[e.callno]->session, AST_FORMAT_JPEG, e.e.image.data, e.e.image.datalen))
				fprintf(stderr, "IAX failed to send image: %s\n", strerror(errno));

		break;

	case PC_EVENT_URL:
		if ((e.callno >= 0) && peers[e.callno])
			if (iax_send_url(peers[e.callno]->session, e.e.url.url, e.e.url.link))
				fprintf(stderr, "IAX failed to send url: %s\n", strerror(errno));

		break;
		
	case PC_EVENT_DPREQ:

		if (iax_dialplan_request(peers[e.callno]->session, e.e.dpreq.dest))
			fprintf(stderr, "IAX failed to send a dialplan request: %s\n", strerror(errno));

		break;

	case PC_EVENT_UNLINK:

		if ((e.callno >= 0) && peers[e.callno])
			if (iax_send_unlink(peers[e.callno]->session))
				fprintf(stderr, "IAX failed to send an unlink request: %s\n", strerror(errno));
		break;
	case PC_EVENT_LINKREJECT:

		if ((e.callno >= 0) && peers[e.callno])
			if (iax_send_link_reject(peers[e.callno]->session))
				fprintf(stderr, "IAX failed to send a link reject request: %s\n", strerror(errno));
		break;

	default:
		fprintf(stderr, "Don't know what to do with sockie event %d\n", e.event);
	}
	return 1;
}
static int enable_echotest=0;
int enable_pcinit_echotest(void){
	enable_echotest=1;
	return 0;
}
int pc_init(char *modules)
{
	int port;
	int fd;
	int tos = IPTOS_LOWDELAY;
	int x;
	
#ifdef USE_FORK
	/* Return if already going */
	if (pcpid)
		return 0;
	
	signal(SIGCHLD, child_handler);
#endif
	
	if ((port = iax_init(0)) < 0) {
		fprintf(stderr, "Failed to initialize IAX subsystem\n");
		return -1;
	}
	fd = iax_get_fd();
	if (setsockopt(fd, SOL_IP, IP_TOS, &tos, sizeof(tos)))
		fprintf(stderr, "Warning: Unable to set IP TOS bits\n");
	fprintf(stderr, "Listening on port %d\n", port);
	if (socketpair(AF_UNIX, SOCK_DGRAM, 0, socks)) {
		fprintf(stderr, "Failed to create socket pair\n");
		return -1;
	}
	if (socketpair(AF_UNIX, SOCK_DGRAM, 0, socks + 2)) {
		fprintf(stderr, "Failed to create priority socket pair\n");
		return -1;
	}
	iax_set_formats(AST_FORMAT_GSM);
	fprintf(stderr, "Initialized phone core\n");
#ifdef USE_FORK
	if ((pcpid = fork()) < 0) {
		fprintf(stderr, "Fork failed...\n");
		return -1;
	}
	if (!pcpid) {
#endif
		//iax_enable_debug();
		io = io_context_create();
		sched = sched_context_create();
		ast_io_add(io, iax_get_fd(), handle_iax, AST_IO_IN, NULL);
		ast_io_add(io, socks[SOURCE_PC], handle_socks, AST_IO_IN, NULL);
		if(audio_load_modules(modules)){
			if(modules!=NULL){
				printf("Failed to load modules %s\n",modules);
				return -1;
			} else {
				printf("Failed to load modules NULL\n");
				return -1;
			}
		}
		printf("Successfully loaded %s\n",modules);
		
		if(enable_echotest) {
			extern int echotest(void);
			echotest();
		}
#ifdef USE_FORK
		for (x=0;x<256;x++) {
			/* Close all file descriptors we don't need */
			if ((x != STDIN_FILENO) &&
				(x != STDOUT_FILENO) &&
				(x != STDERR_FILENO) &&
				(x != socks[SOURCE_PC]) &&
				(x != socks[SOURCE_PRI_IN]) &&
				(x != iax_get_fd()))
					close(x);
		}
#endif
		
		audio_findbest();
#ifdef USE_FORK
		phonecore_thread(NULL);
#else
		if(1){
			pthread_t thread;
			pthread_attr_t attr;
			pthread_attr_init(&attr);
			pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
			pthread_create(&thread,&attr,phonecore_thread,NULL);
		}
#endif
#ifdef USE_FORK
		exit(0);
	}
#endif
	return port;
}

int pc_deny_link(int callno)
{
	pc_event e;

	e.event = PC_EVENT_UNLINK;
	e.len = sizeof(pc_event);
	e.callno = callno;

	return pc_write_event(SOURCE_GUI, &e);
}

int pc_audio_ack(int callno)
{
	pc_event e;

	e.event = PC_EVENT_AUDIO_READY;
	e.len = sizeof(pc_event);
	e.callno = callno;
	return pc_write_event(SOURCE_GUI, &e);
}

int pc_send_image(int callno, char *filename)
{
	pc_event e;
	int fd;
	char data[16384];
	int size = 0;
	struct stat s;

	/* Check to see if the file exists */
	if (stat(filename, &s))
		return -1;

	fd = open(filename, O_RDONLY);
	if (fd < 0)
		return fd;

	size = read(fd, data, sizeof(e.e.image.data));

	close(fd);

	if (size < 0) 
		return size;

	e.event = PC_EVENT_IMAGE;
	e.len = sizeof(pc_event);
	e.callno = callno;
	e.e.image.datalen = size;

	if (size > sizeof(e.e.image.data))
		size = sizeof(e.e.image.data);

	memcpy(e.e.image.data, data, size);

	return pc_write_event(SOURCE_GUI, &e);
}

void pc_cleanup(void)
{
	int x;
#ifdef USE_FORK
	if (pcpid) {
		kill(pcpid, SIGTERM);
#ifdef SNOM_HACK
		kill(pcpid, SIGKILL);
#endif
		pcpid = 0;
	}
#endif
	for (x=0;x<4;x++) {
		close(socks[x]);
		socks[x] = -1;
	}
		
}

int pc_send_dtmf(int callno, char digit)
{
	pc_event e;
	e.len = sizeof(pc_event);
	e.callno = callno;
	e.event = PC_EVENT_DTMF;
	e.e.dtmf.dtmf = digit;
	return pc_write_event(SOURCE_GUI, &e);
}

static int __pc_hangup(int src, int callno, char *why)
{
	pc_event e;
	e.len = sizeof(pc_event);
	e.callno = callno;
	e.event = PC_EVENT_HANGUP;
	if (why)
		strncpy(e.e.hangup.why, why, sizeof(e.e.hangup.why)-1);
	else
		strcpy(e.e.hangup.why, "");
	return pc_write_event(src, &e);
}

int pc_hangup(int callno, char *why)
{
	return __pc_hangup(SOURCE_GUI, callno, why);
}

int __pc_reject(int src, int callno, char *why)
{
	pc_event e;
	e.len = sizeof(pc_event);
	e.callno = callno;
	e.event = PC_EVENT_REJECT;
	if (why)
		strncpy(e.e.reject.why, why, sizeof(e.e.reject.why)-1);
	else
		strcpy(e.e.reject.why, "");
	return pc_write_event(src, &e);
}

int pc_reject(int callno, char *why)
{
	return __pc_reject(SOURCE_GUI, callno, why);
}

int pc_dial(int src, char digit)
{
	pc_event e;
	
	e.len = sizeof(pc_event);
	e.callno = -1;
	e.event = PC_EVENT_DIAL;
	e.e.dial.digit = digit;

	return pc_write_event(src, &e);
}

int pc_send_text(int callno, char *text)
{
	pc_event e;
	e.len = sizeof(pc_event);
	e.callno = callno;
	e.event = PC_EVENT_TEXT;
	snprintf(e.e.text.text, sizeof(e.e.text.text), "%s", text);
	return pc_write_event(SOURCE_GUI, &e);
}

int pc_addr(int src, int callno, struct sockaddr_in sin)
{
	pc_event e;
	e.len = sizeof(pc_event);
	e.callno = callno;
	e.event = PC_EVENT_ADDR;
	e.e.addr.addr = sin;
	return pc_write_event(src, &e);
}

int pc_auth_reply(int callno, char *passwd, char *challenge, int authmethods)
{
	pc_event e;
	e.len = sizeof(pc_event);
	e.callno = callno;
	e.event = PC_EVENT_AUTHRP;
	strncpy(e.e.authreply.password, passwd, sizeof(e.e.authreply.password)-1);
	if (challenge)
		strncpy(e.e.authreply.challenge, challenge,sizeof(e.e.authreply.challenge)-1);
	else
		strcpy(e.e.authreply.challenge, "");
	e.e.authreply.methods = authmethods;
	return pc_write_event(SOURCE_GUI, &e);
}

int pc_call(int callno, char *callerid, char *dest, char *language)
{
	pc_event e;
	e.len = sizeof(pc_event);
	e.callno = callno;
	e.event = PC_EVENT_CONNECT;
	if (callerid)
		strncpy(e.e.connect.callerid, callerid, sizeof(e.e.connect.callerid)-1);
	else
		strcpy(e.e.connect.callerid, "");
	if (dest)
		strncpy(e.e.connect.dest, dest, sizeof(e.e.connect.dest)-1);
	else
		strcpy(e.e.connect.dest, "");
	if (language)
		strncpy(e.e.connect.language, language, sizeof(e.e.connect.language)-1);
	else
		strcpy(e.e.connect.language, "");
	return pc_write_event(SOURCE_GUI, &e);
}
int pc_audio_ringing(void)
{
	return pc_simple_event(SOURCE_GUI,-1,PC_EVENT_AUDIO_RINGING);
}
int pc_audio_ring(void)
{
	return pc_simple_event(SOURCE_GUI,-1,PC_EVENT_AUDIO_RING);
}
int pc_audio_shutup(void)
{
	return pc_simple_event(SOURCE_GUI,-1,PC_EVENT_AUDIO_SHUTUP);
}
int pc_audio_busy(void)
{
	return pc_simple_event(SOURCE_GUI,-1,PC_EVENT_AUDIO_BUSY);
}
int pc_audio_dialtone(void){
	return pc_simple_event(SOURCE_GUI,-1,PC_EVENT_AUDIO_DIALTONE);
}
int pc_complete(int callno, char *dest)
{
	pc_event e;
	e.len = sizeof(pc_event);
	e.callno = callno;
	e.event = PC_EVENT_COMPLETE;

	if (dest)
		strncpy(e.e.connect.dest, dest, sizeof(e.e.connect.dest)-1);
	else
		strcpy(e.e.connect.dest, "");

	return pc_write_event(SOURCE_GUI, &e);
}

int pc_select_audio(int chan)
{
	pc_event e;
	e.event = PC_EVENT_SELAUDIO;
	e.len = sizeof(pc_event);
	e.callno = 0;
	e.e.asel.chan = chan;
	pc_write_event(SOURCE_GUI, &e);
	if (pc_read_event(SOURCE_PRI_OUT, &e))
		return -1;
	if (e.event != PC_EVENT_AUDIO_REPLY) {
		fprintf(stderr, "Huh?  Got '%d' instead of audio reply\n", e.event);
		return -1;
	}
	return e.e.asel.response;
}

int pc_get_fd(void)
{
	return socks[SOURCE_GUI];
}

int pc_session_new(void)
{
	pc_event e;
	e.event = PC_EVENT_NEW;
	e.len = sizeof(pc_event);
	e.callno = 0;
	pc_write_event(SOURCE_GUI, &e);
	if (pc_read_event(SOURCE_PRI_OUT, &e))
		return -1;
	if (e.event != PC_EVENT_NEW_REPLY) {
		fprintf(stderr, "Huh?  Got '%d' instead of new reply\n", e.event);
		return -1;
	}
	return e.e.newreply.id;
}

static int get_level(short *data, int samples)
{
	int sum=0;
	int x;
	for (x=0;x<samples;x++)
		sum += abs(data[x]);
	sum /= samples;
	return sum;
}

void calc_conf(struct peer *peer, short *data)
{
	int x;
	int y;
	int a;
	int s;
	short sum[CHUNKLEN] = { 0, };
	short *out;
	char fr[33];
	/* If we're in a direct talking situation, get out of it */
	if (!peer && (current > -1))
		return;
	/* Start by adding up anyone but us */
	for (x=0;x<PC_MAX_CALLS;x++) {
		if (peers[x] && (peers[x] != peer) && (peers[x]->inconf)) {
			a = peers[x]->cnfout;
			CNF_INC(a);
			if (a != peers[x]->cnfin) {
				/* If we have some data that we can conference, add it in */
				out = peers[x]->cnfbuf + CHUNKLEN * peers[x]->cnfout;
				for (y=0;y<CHUNKLEN;y++) {
					s = sum[y] + out[y];
					if (s > 32767)
						s = 32767;
					else if (s < -32768)
						s = -32768;
					sum[y] = s;
				}
			}
#if 0
			 else
				printf("Having to add in silence\n");
#endif				
		}
	}
	if (peer) {
		if (current < 0) {
			/* We have a conference up and are talking to it, add in local speaker */
			for (y=0;y<CHUNKLEN;y++) {
				s = sum[y] + data[y];
				if (s > 32767)
					s = 32767;
				else if (s < -32768)
					s = -32768;
				sum[y] = s;
			}
		}
		/* Encode and transmit */
		if (!peer->gsmout)
			peer->gsmout = gsm_create();
		gsm_encode(peer->gsmout, sum, fr);
		iax_send_voice(peer->session, AST_FORMAT_GSM, fr, sizeof(fr));
	} else
		audio_send(AST_FORMAT_SLINEAR, sum, sizeof(sum), 0, 1);

}

int deliver_sound(int format, void *data, int len)
{
	pc_event e;
	int x;
	char fr[33];
	int a;
	if (ready) {
		e.event = PC_EVENT_AUDIO;
		e.len = sizeof(pc_event);
		e.callno = -1;
		e.e.audio.level = get_level((short *)data, len/2);
		if (pc_write_event(SOURCE_PC, &e))
			fprintf(stderr, "Unable to notify about sound event!\n");
		ready--;
	}
	if (cnfcount) {
		/* Gotta calculate conference stuff for everyone */
		for (x=0;x<PC_MAX_CALLS;x++) {
			if (peers[x] && peers[x]->inconf)
				calc_conf(peers[x], (short *)data);
		}
		calc_conf(NULL, NULL);
		for (x=0;x<PC_MAX_CALLS;x++) {
			if (peers[x] && peers[x]->inconf) {
				/* Increment the input if possible */
				a = peers[x]->cnfout;
				CNF_INC(a);
				if (a != peers[x]->cnfin) {
					CNF_INC(peers[x]->cnfout);
				}
			}
		}
	} else {
		if ((current > -1) && peers[current] && !peers[current]->mute) {
			if (!peers[current]->gsmout)
				peers[current]->gsmout = gsm_create();
			gsm_encode(peers[current]->gsmout, data, fr);
			iax_send_voice(peers[current]->session, AST_FORMAT_GSM, fr, sizeof(fr));
		}
	}
	return 0;
}

static int read_audio(int *id, int fd, short events, void *data)
{
	static char buffer[65536];
	static int sofar = 0;
	int bufferlen = chunk - sofar;
	int res;
	if (events & AST_IO_PRI) {
		audio_check_exception();
	}
	
	if (events & AST_IO_IN) {
		res = audio_read(format, buffer + sofar, &bufferlen);
		/* res returns error result*/
		if (res) {
			fprintf(stderr, "Error reading voice data: %s\n",strerror(errno));
			return 1;
		}
		/* bufferlen is modified from function call audio_read*/
		sofar += bufferlen;
		if (sofar > chunk) {
			fprintf(stderr, "Huh?  I have too much data\n");
			sofar = 0;
			return 1;
		}
		if ((sofar == chunk) || !enforcechunk) {
			deliver_sound(format, buffer, sofar);
			sofar = 0;
		}
	}
	return 1;
}

static int toid = -1;

static int do_iax_timeout(void *data)
{
	struct iax_event *e;
	while((e = iax_get_event(0))) {
		handle_iax_event(e);
		iax_event_free(e);
	}
	if (toid > -1){
		/*Scheduler complains if I leave this in ast_sched_del(sched, toid);*/
		toid = -1;
	}
	return 0;
}


void *phonecore_thread(void *unused)
{
	int ms=0;
	int ms2=0;
#ifdef MEASURE_TME
	int tme;
	int maxtme = 0;
	struct timeval last = { 0,0};
	struct timeval tv;
#endif	
	for (;;) {
		if (toid > -1){
			ast_sched_del(sched, toid);
			toid = -1;
		}
		do {
			ms2 = iax_time_to_next_event();
			if (!ms2)
				do_iax_timeout(NULL);
		} while (!ms2);
		if (ms2 > -1) 
			toid = ast_sched_add(sched, ms2, do_iax_timeout, NULL);
		ms = ast_sched_wait(sched);
#ifdef MEASURE_TME
		if (last.tv_sec || last.tv_usec) {
			gettimeofday(&tv, NULL);
			tme = (tv.tv_sec - last.tv_sec) * 1000 +
					(tv.tv_usec - last.tv_usec) / 1000;
		}
		if (tme > maxtme)
			maxtme = tme;
#endif			
		ast_io_wait(io, ms);
#ifdef MEASURE_TME
		gettimeofday(&last, NULL);
#endif		
		ast_sched_runq(sched);
	}
	/* Never reached */
	return NULL;
}

int pc_sched_add(int when, ast_sched_cb callback, void *data)
{
	return ast_sched_add(sched, when, callback, data);
}

int pc_sched_del(int id)
{
	return ast_sched_del(sched, id);
}

void audio_digit_ready(int digit)
{
	printf("Got a digit: '%c' from the audio\n", digit);
}
