/* 
 * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
 * Copyright (C) 2005/2006, Anthony Minessale II <anthmct@yahoo.com>
 *
 * Version: MPL 1.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
 *
 * The Initial Developer of the Original Code is
 * Anthony Minessale II <anthmct@yahoo.com>
 * Portions created by the Initial Developer are Copyright (C)
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 * 
 * Anthony Minessale II <anthmct@yahoo.com>
 *
 *
 * mod_wanpipe.c -- WANPIPE PRI Channel Module
 *
 */

#include <switch.h>
#include <libsangoma.h>
#include <sangoma_pri.h>
#include <libteletone.h>
//#define DOTRACE
static const char modname[] = "mod_wanpipe";
#define STRLEN 15

static switch_memory_pool_t *module_pool = NULL;

typedef enum {
	PFLAG_ANSWER = (1 << 0),
	PFLAG_HANGUP = (1 << 1),
} PFLAGS;


typedef enum {
	PPFLAG_RING = (1 << 0),
} PPFLAGS;

typedef enum {
	TFLAG_MEDIA = (1 << 0),
	TFLAG_INBOUND = (1 << 1),
	TFLAG_OUTBOUND = (1 << 2),
	TFLAG_INCOMING = (1 << 3),
	TFLAG_PARSE_INCOMING = (1 << 4),
	TFLAG_ACTIVATE = (1 << 5),
	TFLAG_DTMF = (1 << 6),
	TFLAG_DESTROY = (1 << 7),
	TFLAG_ABORT = (1 << 8),
	TFLAG_SWITCH = (1 << 9),
	TFLAG_NOSIG = (1 << 10),
	TFLAG_BYE = (1 << 11)
} TFLAGS;


#define DEFAULT_MTU 160

static struct {
	int debug;
	int panic;
	int mtu;
	int dtmf_on;
	int dtmf_off;
	int supress_dtmf_tone;
	int configured_spans;
	char *dialplan;
	switch_hash_t *call_hash;
	switch_mutex_t *hash_mutex;
} globals;

struct wanpipe_pri_span {
	int span;
	int dchan;
	unsigned int bchans;
	int node;
	int mtu;
	int pswitch;
	char *dialplan;
	unsigned int l1;
	unsigned int dp;
	struct sangoma_pri spri;
};

struct wpsock {
	int fd;
	char *name;
	struct private_object *tech_pvt;
};

typedef struct wpsock wpsock_t;


#define MAX_SPANS 128
static struct wanpipe_pri_span *SPANS[MAX_SPANS];


struct private_object {
	unsigned int flags;			/* FLAGS */
	switch_frame_t read_frame;	/* Frame for Writing */
	switch_core_session_t *session;
	switch_codec_t read_codec;
	switch_codec_t write_codec;
	unsigned char databuf[SWITCH_RECCOMMENDED_BUFFER_SIZE];
	struct sangoma_pri *spri;
	sangoma_api_hdr_t hdrframe;
	switch_caller_profile_t *caller_profile;
	wpsock_t *wpsock;
	int callno;
	int span;
	int cause;
	q931_call *call;
	teletone_dtmf_detect_state_t dtmf_detect;
	teletone_generation_session_t tone_session;
	switch_buffer_t *dtmf_buffer;
	unsigned int skip_read_frames;
	unsigned int skip_write_frames;
	switch_mutex_t *flag_mutex;
#ifdef DOTRACE
	int fd;
	int fd2;
#endif
};

struct channel_map {
	switch_core_session_t *map[36];
};

static int wp_close(struct private_object *tech_pvt)
{
	int ret = 0;

	switch_mutex_lock(globals.hash_mutex);
	if (tech_pvt->wpsock && tech_pvt->wpsock->tech_pvt == tech_pvt) {
		tech_pvt->wpsock->tech_pvt = NULL;
		ret = 1;
	}
	switch_mutex_unlock(globals.hash_mutex);
	
	return ret;
}

static int wp_open(struct private_object *tech_pvt, int span, int chan)
{
	int fd;
	wpsock_t *sock;
	char name[25];
	
	snprintf(name, sizeof(name), "s%dc%d", span, chan);

	switch_mutex_lock(globals.hash_mutex);
	if ((sock = switch_core_hash_find(globals.call_hash, name))) {
		fd = sock->fd;
	} else {
		if ((fd = sangoma_open_tdmapi_span_chan(span, chan)) > -1) {
			if ((sock = malloc(sizeof(*sock)))) {
				memset(sock, 0, sizeof(*sock));
				sock->fd = fd;
				sock->name = strdup(name);
				switch_core_hash_insert(globals.call_hash, sock->name, sock);
			}
		}
	}

	if (sock) {
		if (sock->tech_pvt) {
			if (tech_pvt->session) {
				switch_channel_t *channel = switch_core_session_get_channel(tech_pvt->session);
				switch_channel_hangup(channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER);
			}
		}
		sock->tech_pvt = tech_pvt;
		tech_pvt->wpsock = sock;
	}

	switch_mutex_unlock(globals.hash_mutex);

	return sock ? 1 : 0;
}

static int wp_restart(int span, int chan)
{
	wpsock_t *sock;
	int fd;
	char name[25];
	
	snprintf(name, sizeof(name), "s%dc%d", span, chan);

	switch_mutex_lock(globals.hash_mutex);
	if ((sock = switch_core_hash_find(globals.call_hash, name))) {
		switch_core_hash_delete(globals.call_hash, sock->name);
		fd = sock->fd;
	} else {
		fd = sangoma_open_tdmapi_span_chan(span, chan);
	}
	switch_mutex_unlock(globals.hash_mutex);

	if (fd > -1) {
		close(fd);
		return 0;
	}
	
	return -1;
}


static void set_global_dialplan(char *dialplan)
{
	if (globals.dialplan) {
		free(globals.dialplan);
		globals.dialplan = NULL;
	}

	globals.dialplan = strdup(dialplan);
}


static int str2node(char *node)
{
	if (!strcasecmp(node, "cpe"))
		return PRI_CPE;
	if (!strcasecmp(node, "network"))
		return PRI_NETWORK;
	return -1;
}

static int str2switch(char *swtype)
{
	if (!strcasecmp(swtype, "ni2"))
		return PRI_SWITCH_NI2;
	if (!strcasecmp(swtype, "dms100"))
		return PRI_SWITCH_DMS100;
	if (!strcasecmp(swtype, "lucent5e"))
		return PRI_SWITCH_LUCENT5E;
	if (!strcasecmp(swtype, "att4ess"))
		return PRI_SWITCH_ATT4ESS;
	if (!strcasecmp(swtype, "euroisdn"))
		return PRI_SWITCH_EUROISDN_E1;
	if (!strcasecmp(swtype, "gr303eoc"))
		return PRI_SWITCH_GR303_EOC;
	if (!strcasecmp(swtype, "gr303tmc"))
		return PRI_SWITCH_GR303_TMC;
	return -1;
}


static int str2l1(char *l1)
{
	if (!strcasecmp(l1, "alaw"))
		return PRI_LAYER_1_ALAW;

	return PRI_LAYER_1_ULAW;
}

static int str2dp(char *dp)
{
	if (!strcasecmp(dp, "international"))
		return PRI_INTERNATIONAL_ISDN;
	if (!strcasecmp(dp, "national"))
		return PRI_NATIONAL_ISDN;
	if (!strcasecmp(dp, "local"))
		return PRI_LOCAL_ISDN;
	if (!strcasecmp(dp, "private"))
		return PRI_PRIVATE;		
	if (!strcasecmp(dp, "unknown"))
		return PRI_UNKNOWN;

	return PRI_UNKNOWN;
}



static void set_global_dialplan(char *dialplan);
static int str2node(char *node);
static int str2switch(char *swtype);
static switch_status_t wanpipe_on_init(switch_core_session_t *session);
static switch_status_t wanpipe_on_hangup(switch_core_session_t *session);
static switch_status_t wanpipe_on_loopback(switch_core_session_t *session);
static switch_status_t wanpipe_on_transmit(switch_core_session_t *session);
static switch_status_t wanpipe_outgoing_channel(switch_core_session_t *session, switch_caller_profile_t *outbound_profile,
											  switch_core_session_t **new_session, switch_memory_pool_t *pool);
static switch_status_t wanpipe_read_frame(switch_core_session_t *session, switch_frame_t **frame, int timeout,
										switch_io_flag_t flags, int stream_id);
static switch_status_t wanpipe_write_frame(switch_core_session_t *session, switch_frame_t *frame, int timeout,
										 switch_io_flag_t flags, int stream_id);
static int on_info(struct sangoma_pri *spri, sangoma_pri_event_t event_type, pri_event *event);
static int on_hangup(struct sangoma_pri *spri, sangoma_pri_event_t event_type, pri_event *event);
static int on_ring(struct sangoma_pri *spri, sangoma_pri_event_t event_type, pri_event *event);
static int check_flags(struct sangoma_pri *spri);
static int on_restart(struct sangoma_pri *spri, sangoma_pri_event_t event_type, pri_event *event);
static int on_anything(struct sangoma_pri *spri, sangoma_pri_event_t event_type, pri_event *event);
static void *pri_thread_run(switch_thread_t *thread, void *obj);
static switch_status_t config_wanpipe(int reload);


/* 
   State methods they get called when the state changes to the specific state 
   returning SWITCH_STATUS_SUCCESS tells the core to execute the standard state method next
   so if you fully implement the state you can return SWITCH_STATUS_FALSE to skip it.
*/
static switch_status_t wanpipe_on_init(switch_core_session_t *session)
{
	struct private_object *tech_pvt;
	switch_channel_t *channel = NULL;
	wanpipe_tdm_api_t tdm_api = {{0}};
	int err = 0;
	int mtu_mru;
	unsigned int rate = 8000;
	int new_mtu = ((globals.mtu / 8) / 2);

	channel = switch_core_session_get_channel(session);
	assert(channel != NULL);

	tech_pvt = switch_core_session_get_private(session);
	assert(tech_pvt != NULL);

	tech_pvt->read_frame.data = tech_pvt->databuf;

	err = sangoma_tdm_set_codec(tech_pvt->wpsock->fd, &tdm_api, WP_SLINEAR);
	
	mtu_mru = sangoma_tdm_get_usr_mtu_mru(tech_pvt->wpsock->fd, &tdm_api);	
	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "WANPIPE INIT MTU is %d\n", mtu_mru);

	if (mtu_mru != globals.mtu) {
		sangoma_tdm_set_usr_period(tech_pvt->wpsock->fd, &tdm_api, 40);
		err = sangoma_tdm_set_usr_period(tech_pvt->wpsock->fd, &tdm_api, new_mtu);
		mtu_mru = sangoma_tdm_get_usr_mtu_mru(tech_pvt->wpsock->fd, &tdm_api);
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "ADJUSTED MTU AFTER SETTING IT TO %d is %d %d [%s]\n", new_mtu, mtu_mru, err, strerror(err));
		if (mtu_mru != globals.mtu) {
			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failure to adjust MTU\n");
			switch_channel_hangup(channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER);
			return SWITCH_STATUS_FALSE;
		}
	}

	if (switch_core_codec_init
		(&tech_pvt->read_codec, "L16", rate, 20, 1, SWITCH_CODEC_FLAG_ENCODE | SWITCH_CODEC_FLAG_DECODE, NULL,
		 switch_core_session_get_pool(session)) != SWITCH_STATUS_SUCCESS) {
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "%s Cannot set read codec\n", switch_channel_get_name(channel));
		switch_channel_hangup(channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER);
		return SWITCH_STATUS_FALSE;
	}

	if (switch_core_codec_init
		(&tech_pvt->write_codec, "L16", rate, 20, 1, SWITCH_CODEC_FLAG_ENCODE | SWITCH_CODEC_FLAG_DECODE, NULL,
		 switch_core_session_get_pool(session)) != SWITCH_STATUS_SUCCESS) {
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "%s Cannot set read codec\n", switch_channel_get_name(channel));
		switch_channel_hangup(channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER);
		return SWITCH_STATUS_FALSE;
	}
	tech_pvt->read_frame.rate = rate;
	tech_pvt->read_frame.codec = &tech_pvt->read_codec;
	switch_core_session_set_read_codec(session, &tech_pvt->read_codec);
	switch_core_session_set_write_codec(session, &tech_pvt->write_codec);

#ifdef DOTRACE	
						tech_pvt->fd = open("/tmp/wp-in.raw", O_WRONLY | O_TRUNC | O_CREAT);
						tech_pvt->fd2 = open("/tmp/wp-out.raw", O_WRONLY | O_TRUNC | O_CREAT);
#endif

	/* Setup artificial DTMF stuff */
	memset(&tech_pvt->tone_session, 0, sizeof(tech_pvt->tone_session));
	teletone_init_session(&tech_pvt->tone_session, 1024, NULL, NULL);
	
	if (globals.debug) {
		tech_pvt->tone_session.debug = globals.debug;
		tech_pvt->tone_session.debug_stream = stdout;
	}
	
	tech_pvt->tone_session.rate = rate;
	tech_pvt->tone_session.duration = globals.dtmf_on * (tech_pvt->tone_session.rate / 1000);
	tech_pvt->tone_session.wait = globals.dtmf_off * (tech_pvt->tone_session.rate / 1000);
	
	teletone_dtmf_detect_init (&tech_pvt->dtmf_detect, rate);

	if (switch_test_flag(tech_pvt, TFLAG_NOSIG)) {
		switch_channel_answer(channel);
	}


	/* Move Channel's State Machine to RING */
	switch_channel_set_state(channel, CS_RING);
	return SWITCH_STATUS_SUCCESS;
}

static switch_status_t wanpipe_on_ring(switch_core_session_t *session)
{
	switch_channel_t *channel = NULL;
	struct private_object *tech_pvt = NULL;

	channel = switch_core_session_get_channel(session);
	assert(channel != NULL);

	tech_pvt = switch_core_session_get_private(session);
	assert(tech_pvt != NULL);

	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "WANPIPE RING\n");

	

	return SWITCH_STATUS_SUCCESS;
}

static switch_status_t wanpipe_on_hangup(switch_core_session_t *session)
{
	struct private_object *tech_pvt;
	switch_channel_t *channel = NULL;
	struct channel_map *chanmap = NULL;

	
	channel = switch_core_session_get_channel(session);
	assert(channel != NULL);

	tech_pvt = switch_core_session_get_private(session);
	assert(tech_pvt != NULL);

	switch_set_flag_locked(tech_pvt, TFLAG_BYE);

	if (!switch_test_flag(tech_pvt, TFLAG_NOSIG)) {
		chanmap = tech_pvt->spri->private_info;
	}

	//sangoma_socket_close(&tech_pvt->wpsock->fd);
	wp_close(tech_pvt);

	switch_core_codec_destroy(&tech_pvt->read_codec);
	switch_core_codec_destroy(&tech_pvt->write_codec);


	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "WANPIPE HANGUP\n");

	if (!switch_test_flag(tech_pvt, TFLAG_NOSIG)) {
		pri_hangup(tech_pvt->spri->pri, tech_pvt->call, tech_pvt->cause);
		pri_destroycall(tech_pvt->spri->pri, tech_pvt->call);

		if (chanmap->map[tech_pvt->callno]) {
			chanmap->map[tech_pvt->callno] = NULL;
		}
		/*
		  pri_hangup(tech_pvt->spri->pri,
		  tech_pvt->hangup_event.hangup.call ? tech_pvt->hangup_event.hangup.call : tech_pvt->ring_event.ring.call,
		  tech_pvt->cause);
		  pri_destroycall(tech_pvt->spri->pri,
		  tech_pvt->hangup_event.hangup.call ? tech_pvt->hangup_event.hangup.call : tech_pvt->ring_event.ring.call);
		*/
	}

	teletone_destroy_session(&tech_pvt->tone_session);

	switch_buffer_destroy(&tech_pvt->dtmf_buffer);
	
	return SWITCH_STATUS_SUCCESS;
}

static switch_status_t wanpipe_on_loopback(switch_core_session_t *session)
{
	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "WANPIPE LOOPBACK\n");
	return SWITCH_STATUS_SUCCESS;
}

static switch_status_t wanpipe_on_transmit(switch_core_session_t *session)
{
	struct private_object *tech_pvt;
	switch_channel_t *channel;

	channel = switch_core_session_get_channel(session);
	assert(channel != NULL);

	tech_pvt = switch_core_session_get_private(session);
	assert(tech_pvt != NULL);



	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "WANPIPE TRANSMIT\n");
	return SWITCH_STATUS_SUCCESS;
}


static switch_status_t wanpipe_answer_channel(switch_core_session_t *session)
{
	struct private_object *tech_pvt;
	switch_channel_t *channel = NULL;

	channel = switch_core_session_get_channel(session);
	assert(channel != NULL);

	tech_pvt = switch_core_session_get_private(session);
	assert(tech_pvt != NULL);

	if (switch_test_flag(tech_pvt, TFLAG_INBOUND) && !switch_test_flag(tech_pvt, TFLAG_NOSIG)) {
		pri_answer(tech_pvt->spri->pri, tech_pvt->call, 0, 1);
	}
	return SWITCH_STATUS_SUCCESS;
}



static switch_status_t wanpipe_read_frame(switch_core_session_t *session, switch_frame_t **frame, int timeout,
										switch_io_flag_t flags, int stream_id)
{
	struct private_object *tech_pvt;
	switch_channel_t *channel = NULL;
	uint8_t *bp;
	int bytes = 0, res = 0;
	char digit_str[80];

	channel = switch_core_session_get_channel(session);
	assert(channel != NULL);

	tech_pvt = switch_core_session_get_private(session);
	assert(tech_pvt != NULL);


	if (switch_test_flag(tech_pvt, TFLAG_BYE) || tech_pvt->wpsock->fd < 0) {
		return SWITCH_STATUS_GENERR;
	}

	bp = tech_pvt->databuf;

	*frame = NULL;
	memset(tech_pvt->databuf, 0, sizeof(tech_pvt->databuf));
	while (bytes < globals.mtu) {

		if (switch_test_flag(tech_pvt, TFLAG_BYE) || tech_pvt->wpsock->fd < 0) {
			return SWITCH_STATUS_GENERR;
		}


		if (sangoma_socket_waitfor(tech_pvt->wpsock->fd, 1000, POLLIN | POLLERR | POLLHUP) <= 0) {
			return SWITCH_STATUS_GENERR;
		}

		if ((res = sangoma_readmsg_socket(tech_pvt->wpsock->fd,
										  &tech_pvt->hdrframe,
										  sizeof(tech_pvt->hdrframe), bp, sizeof(tech_pvt->databuf) - bytes, 0)) < 0) {
			if (errno == EBUSY) {
				continue;
			} else {
				return SWITCH_STATUS_GENERR;
			}

		}
		bytes += res;
		bp += bytes;
	}

	if (!bytes || switch_test_flag(tech_pvt, TFLAG_BYE) || tech_pvt->wpsock->fd < 0) {
		return SWITCH_STATUS_GENERR;
	}

	tech_pvt->read_frame.datalen = bytes;
	tech_pvt->read_frame.samples = bytes / 2;

	res = teletone_dtmf_detect (&tech_pvt->dtmf_detect, tech_pvt->read_frame.data, tech_pvt->read_frame.samples);
	res = teletone_dtmf_get(&tech_pvt->dtmf_detect, digit_str, sizeof(digit_str));

	if(digit_str[0]) {
		switch_channel_queue_dtmf(channel, digit_str);
		if (globals.debug) {
			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "DTMF DETECTED: [%s]\n", digit_str);
		}
		if (globals.supress_dtmf_tone) {
			memset(tech_pvt->read_frame.data, 0, tech_pvt->read_frame.datalen);
		}
	}

	if (tech_pvt->skip_read_frames > 0) {
		memset(tech_pvt->read_frame.data, 0, tech_pvt->read_frame.datalen);
		tech_pvt->skip_read_frames--;
	}

#ifdef DOTRACE	
	write(tech_pvt->fd2, tech_pvt->read_frame.data, (int) tech_pvt->read_frame.datalen);
#endif
	//printf("read %d\n", tech_pvt->read_frame.datalen);
	*frame = &tech_pvt->read_frame;
	return SWITCH_STATUS_SUCCESS;
}

static switch_status_t wanpipe_write_frame(switch_core_session_t *session, switch_frame_t *frame, int timeout,
										 switch_io_flag_t flags, int stream_id)
{
	struct private_object *tech_pvt;
	switch_channel_t *channel = NULL;
	int result = 0;
	int bytes = frame->datalen;
	uint8_t *bp = frame->data;
	unsigned char dtmf[1024];
	int bread, bwrote = 0;
	switch_status_t status = SWITCH_STATUS_SUCCESS;

	channel = switch_core_session_get_channel(session);
	assert(channel != NULL);

	tech_pvt = switch_core_session_get_private(session);
	assert(tech_pvt != NULL);


	while (tech_pvt->dtmf_buffer && bwrote < frame->datalen && bytes > 0 && switch_buffer_inuse(tech_pvt->dtmf_buffer) > 0) {
		if (switch_test_flag(tech_pvt, TFLAG_BYE) || tech_pvt->wpsock->fd < 0) {
			return SWITCH_STATUS_GENERR;
		}

		if ((bread = switch_buffer_read(tech_pvt->dtmf_buffer, dtmf, globals.mtu)) < globals.mtu) {
			while (bread < globals.mtu) {
				dtmf[bread++] = 0;
			}
		}

		if (sangoma_socket_waitfor(tech_pvt->wpsock->fd, 1000, POLLOUT | POLLERR | POLLHUP) <= 0) {
			return SWITCH_STATUS_GENERR;
		}

#ifdef DOTRACE	
		write(tech_pvt->fd, dtmf, (int) bread);
#endif
		result = sangoma_sendmsg_socket(tech_pvt->wpsock->fd,
									 &tech_pvt->hdrframe, sizeof(tech_pvt->hdrframe), dtmf, bread, 0);
		if (result < 0) {
			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR,
								  "Bad Write %d bytes returned %d (%s)!\n", bread,
								  result, strerror(errno));
			if (errno == EBUSY) {
				continue;
			}
			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Write Failed!\n");
			status = SWITCH_STATUS_GENERR;
			break;
		} else {
			bytes -= result;
			bwrote += result;
			bp += result;
			result = 0;
		}
	}

	if (switch_test_flag(tech_pvt, TFLAG_BYE) || tech_pvt->wpsock->fd < 0) {
		return SWITCH_STATUS_GENERR;
	}

	if (tech_pvt->skip_write_frames) {
		tech_pvt->skip_write_frames--;
		return SWITCH_STATUS_SUCCESS;
	}

	while (bytes > 0) {
		unsigned int towrite;

#if 1
		if (sangoma_socket_waitfor(tech_pvt->wpsock->fd, 1000, POLLOUT | POLLERR | POLLHUP) <= 0) {
			return SWITCH_STATUS_GENERR;
		}
#endif

#ifdef DOTRACE	
		write(tech_pvt->fd, bp, (int) globals.mtu);
#endif
		towrite = bytes >= globals.mtu ? globals.mtu : bytes;

		if (towrite < globals.mtu) {
			int diff = globals.mtu - towrite;
			memset(bp + towrite, 0, diff);
			towrite = globals.mtu;
		}

		result = sangoma_sendmsg_socket(tech_pvt->wpsock->fd,
									 &tech_pvt->hdrframe, sizeof(tech_pvt->hdrframe), bp, towrite, 0);
		if (result < 0) {
			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR,
								  "Bad Write frame len %u write %d bytes returned %d (%s)!\n", towrite,
								  globals.mtu, result, strerror(errno));
			if (errno == EBUSY) {
				continue;
			}
			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Write Failed!\n");
			status = SWITCH_STATUS_GENERR;
			break;
		} else {
			bytes -= result;
			bp += result;
			result = 0;
		}
	}
	
	//printf("write %d %d\n", frame->datalen, status);	
	return status;
}

static switch_status_t wanpipe_send_dtmf(switch_core_session_t *session, char *digits)
{
	struct private_object *tech_pvt;
	switch_channel_t *channel = NULL;
	switch_status_t status = SWITCH_STATUS_SUCCESS;
	int wrote = 0;
	char *cur = NULL;

	channel = switch_core_session_get_channel(session);
	assert(channel != NULL);

	tech_pvt = switch_core_session_get_private(session);
	assert(tech_pvt != NULL);

	if (!tech_pvt->dtmf_buffer) {
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Allocate DTMF Buffer....");
		if (switch_buffer_create_dynamic(&tech_pvt->dtmf_buffer, 1024, 3192, 0) != SWITCH_STATUS_SUCCESS) {
			switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_ERROR, "Failed to allocate DTMF Buffer!\n");
			return SWITCH_STATUS_FALSE;
		} else {
			switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_DEBUG, "SUCCESS!\n");
		}
	}
	for (cur = digits; *cur; cur++) {
		if ((wrote = teletone_mux_tones(&tech_pvt->tone_session, &tech_pvt->tone_session.TONES[(int)*cur]))) {
			switch_buffer_write(tech_pvt->dtmf_buffer, tech_pvt->tone_session.buffer, wrote * 2);
		}
	}

	tech_pvt->skip_read_frames = 200;
	
	return status;
}

static switch_status_t wanpipe_receive_message(switch_core_session_t *session, switch_core_session_message_t *msg)
{
	return SWITCH_STATUS_FALSE;
}

static switch_status_t wanpipe_kill_channel(switch_core_session_t *session, int sig)
{
	struct private_object *tech_pvt;
	switch_channel_t *channel = NULL;

	channel = switch_core_session_get_channel(session);
	assert(channel != NULL);

	tech_pvt = switch_core_session_get_private(session);
	assert(tech_pvt != NULL);

	switch_mutex_lock(tech_pvt->flag_mutex);
	switch_set_flag(tech_pvt, TFLAG_BYE);
	switch_clear_flag(tech_pvt, TFLAG_MEDIA);
	switch_mutex_unlock(tech_pvt->flag_mutex);

	//sangoma_socket_close(&tech_pvt->wpsock->fd);
	//wp_close(tech_pvt);

	return SWITCH_STATUS_SUCCESS;

}


static const switch_io_routines_t wanpipe_io_routines = {
	/*.outgoing_channel */ wanpipe_outgoing_channel,
	/*.answer_channel */ wanpipe_answer_channel,
	/*.read_frame */ wanpipe_read_frame,
	/*.write_frame */ wanpipe_write_frame,
	/*.kill_channel */ wanpipe_kill_channel,
	/*.waitfor_read */ NULL,
	/*.waitfor_read */ NULL,
	/*.send_dtmf*/ wanpipe_send_dtmf,
	/*.receive_message*/ wanpipe_receive_message
};

static const switch_state_handler_table_t wanpipe_state_handlers = {
	/*.on_init */ wanpipe_on_init,
	/*.on_ring */ wanpipe_on_ring,
	/*.on_execute */ NULL,
	/*.on_hangup */ wanpipe_on_hangup,
	/*.on_loopback */ wanpipe_on_loopback,
	/*.on_transmit */ wanpipe_on_transmit
};

static const switch_endpoint_interface_t wanpipe_endpoint_interface = {
	/*.interface_name */ "wanpipe",
	/*.io_routines */ &wanpipe_io_routines,
	/*.state_handlers */ &wanpipe_state_handlers,
	/*.private */ NULL,
	/*.next */ NULL
};

static const switch_loadable_module_interface_t wanpipe_module_interface = {
	/*.module_name */ modname,
	/*.endpoint_interface */ &wanpipe_endpoint_interface,
	/*.timer_interface */ NULL,
	/*.dialplan_interface */ NULL,
	/*.codec_interface */ NULL,
	/*.application_interface */ NULL
};


static switch_status_t wanpipe_outgoing_channel(switch_core_session_t *session, switch_caller_profile_t *outbound_profile,
											  switch_core_session_t **new_session, switch_memory_pool_t *pool)
{
	char *bchan = NULL;
	char name[128] = "";
		

	if (outbound_profile && outbound_profile->destination_number) {
		bchan = strchr(outbound_profile->destination_number, '=');
	} else {
		return SWITCH_STATUS_FALSE;
	}

	if (bchan) {
		bchan++;
		if (!bchan) {
			return SWITCH_STATUS_FALSE;
		}
		outbound_profile->destination_number++;
	} else if (!globals.configured_spans) {
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error No Spans Configured.\n");
		return SWITCH_STATUS_FALSE;
	}


	if ((*new_session = switch_core_session_request(&wanpipe_endpoint_interface, pool))) {
		struct private_object *tech_pvt;
		switch_channel_t *channel;

		switch_core_session_add_stream(*new_session, NULL);
		if ((tech_pvt = (struct private_object *) switch_core_session_alloc(*new_session, sizeof(struct private_object)))) {
			memset(tech_pvt, 0, sizeof(*tech_pvt));
			switch_mutex_init(&tech_pvt->flag_mutex, SWITCH_MUTEX_NESTED, switch_core_session_get_pool(*new_session));
			channel = switch_core_session_get_channel(*new_session);
			switch_core_session_set_private(*new_session, tech_pvt);
			tech_pvt->session = *new_session;
		} else {
			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Hey where is my memory pool?\n");
			switch_core_session_destroy(new_session);
			return SWITCH_STATUS_GENERR;
		}

		
		if (outbound_profile) {
			switch_caller_profile_t *caller_profile;
			struct sangoma_pri *spri;
			int span = 0, autospan = 0, autochan = 0;
			char *num, *p;
			int channo = 0;
			struct channel_map *chanmap = NULL;

			caller_profile = switch_caller_profile_clone(*new_session, outbound_profile);
			if (!bchan) {
				num = caller_profile->destination_number;
				if ((p = strchr(num, '/'))) {
					*p++ = '\0';
					if (*num != 'a') {
						if (num && *num > 47 && *num < 58) {
							span = atoi(num);
						} else {
							switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Invlid Syntax\n");
							switch_core_session_destroy(new_session);
							return SWITCH_STATUS_GENERR;
						}
					} else {
						span = 0;
						autospan = 1;
					}
					num = p;
					if ((p = strchr(num, '/'))) {
						*p++ = '\0';
						if (*num == 'a') {
							autochan = 1;
						} else if (*num == 'A') {
							autochan = -1;
						} else if (num && *num > 47 && *num < 58) {
							channo = atoi(num);
						} else {
							switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Invlid Syntax\n");
                            switch_core_session_destroy(new_session);
                            return SWITCH_STATUS_GENERR;
						}
						caller_profile->destination_number = p;
					} else {
						switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Invlid Syntax\n");
						switch_core_session_destroy(new_session);
						return SWITCH_STATUS_GENERR;
					}
				}
			}

			tech_pvt->caller_profile = caller_profile;

			if (bchan) {
				int chan, span;

				if (sangoma_span_chan_fromif(bchan, &span, &chan)) {
					if (!wp_open(tech_pvt, span, chan)) {
						switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Can't open fd for s%dc%d! [%s]\n", span, chan, strerror(errno));
						switch_core_session_destroy(new_session);
						return SWITCH_STATUS_GENERR;
					}
					switch_set_flag_locked(tech_pvt, TFLAG_NOSIG);
					snprintf(name, sizeof(name), "WanPipe/%s/nosig-%04x", bchan, rand() & 0xffff);
					switch_channel_set_name(channel, name);			
					switch_channel_set_caller_profile(channel, caller_profile);
				} else {
					switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Invalid address\n");
					switch_core_session_destroy(new_session);
					return SWITCH_STATUS_GENERR;
				}
			} else {
				do {
					if (autospan) {
						span++;
					}
					if ((spri = &SPANS[span]->spri) && switch_test_flag(spri, SANGOMA_PRI_READY)) {
						chanmap = spri->private_info;
						if (channo == 0) {
							if (autochan > 0) {
								for(channo = 1; channo < SANGOMA_MAX_CHAN_PER_SPAN; channo++) {
									if ((SPANS[span]->bchans & (1 << channo)) && !chanmap->map[channo]) {
										switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Choosing channel %d\n", channo);
										break;
									}
								}
							} else if (autochan < 0) {
								for(channo = SANGOMA_MAX_CHAN_PER_SPAN; channo > 0; channo--) {
									if ((SPANS[span]->bchans & (1 << channo)) && !chanmap->map[channo]) {
										switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Choosing channel %d\n", channo);
										break;
									}
								}
							}

							if (channo <= 0 || channo == (SANGOMA_MAX_CHAN_PER_SPAN)) {
								switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "No Free Channels!\n");
								channo = 0;
							}
						}
						if (channo) {
							break;
						}
					}
				} while(autospan && span < MAX_SPANS && !spri && !channo);

				if (!spri || channo == 0 || channo == (SANGOMA_MAX_CHAN_PER_SPAN)) {
					switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "No Free Channels!\n");
					switch_core_session_destroy(new_session);
					return SWITCH_STATUS_GENERR;
				}
			
				if (spri && (tech_pvt->call = pri_new_call(spri->pri))) {
					struct pri_sr *sr;
					
					snprintf(name, sizeof(name), "WanPipe/s%dc%d/%s-%04x", spri->span, channo, caller_profile->destination_number, rand() & 0xffff);
					switch_channel_set_name(channel, name);			
					switch_channel_set_caller_profile(channel, caller_profile);
					sr = pri_sr_new();
					pri_sr_set_channel(sr, channo, 0, 0);
					pri_sr_set_bearer(sr, 0, SPANS[span]->l1);
					pri_sr_set_called(sr, caller_profile->destination_number, SPANS[span]->dp, 1);
					pri_sr_set_caller(sr,
									  caller_profile->caller_id_number,
									  caller_profile->caller_id_name,
									  SPANS[span]->dp,
									  PRES_ALLOWED_USER_NUMBER_PASSED_SCREEN);
					pri_sr_set_redirecting(sr,
										   caller_profile->caller_id_number,
										   SPANS[span]->dp,
										   PRES_ALLOWED_USER_NUMBER_PASSED_SCREEN,
										   PRI_REDIR_UNCONDITIONAL);
				
					if (pri_setup(spri->pri, tech_pvt->call , sr)) {
						switch_core_session_destroy(new_session);
						pri_sr_free(sr);
						return SWITCH_STATUS_GENERR;
					}

					if (!wp_open(tech_pvt, spri->span, channo)) {
						switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Can't open fd!\n");
						switch_core_session_destroy(new_session);
						pri_sr_free(sr);
						return SWITCH_STATUS_GENERR;
					}
					pri_sr_free(sr);
					chanmap->map[channo] = *new_session;
					tech_pvt->spri = spri;
				}
			}
		} else {
			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Doh! no caller profile\n");
			switch_core_session_destroy(new_session);
			return SWITCH_STATUS_GENERR;
		}

		switch_channel_set_flag(channel, CF_OUTBOUND);
		switch_set_flag_locked(tech_pvt, TFLAG_OUTBOUND);
		switch_channel_set_state(channel, CS_INIT);
		return SWITCH_STATUS_SUCCESS;
	}

	return SWITCH_STATUS_GENERR;
}



static void s_pri_error(struct pri *pri, char *s)
{
	if (globals.debug) {
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, s);
	}
}

static void s_pri_message(struct pri *pri, char *s)
{
	s_pri_error(pri, s);
}

SWITCH_MOD_DECLARE(switch_status_t) switch_module_load(const switch_loadable_module_interface_t **interface, char *filename)
{
	switch_status_t status = SWITCH_STATUS_SUCCESS;

	memset(SPANS, 0, sizeof(SPANS));

	pri_set_error(s_pri_error);
	pri_set_message(s_pri_message);

	if (switch_core_new_memory_pool(&module_pool) != SWITCH_STATUS_SUCCESS) {
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "OH OH no pool\n");
		return SWITCH_STATUS_TERM;
	}

	memset(&globals, 0, sizeof(globals));
	switch_core_hash_init(&globals.call_hash, module_pool);
	switch_mutex_init(&globals.hash_mutex, SWITCH_MUTEX_NESTED, module_pool);

	/* start the pri's */
	if ((status = config_wanpipe(0)) != SWITCH_STATUS_SUCCESS) {
		return status;
	}

	/* connect my internal structure to the blank pointer passed to me */
	*interface = &wanpipe_module_interface;

	/* indicate that the module should continue to be loaded */
	return status;
}





/*event Handlers */

static int on_info(struct sangoma_pri *spri, sangoma_pri_event_t event_type, pri_event *event)
{
	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "number is: %s\n", event->ring.callednum);
	if (strlen(event->ring.callednum) > 3) {
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "final number is: %s\n", event->ring.callednum);
		pri_answer(spri->pri, event->ring.call, 0, 1);
	}
	return 0;
}

static int on_hangup(struct sangoma_pri *spri, sangoma_pri_event_t event_type, pri_event *event)
{
	struct channel_map *chanmap;
	switch_core_session_t *session;
	struct private_object *tech_pvt;

	chanmap = spri->private_info;
	if ((session = chanmap->map[event->hangup.channel])) {
		switch_channel_t *channel = NULL;

		channel = switch_core_session_get_channel(session);
		assert(channel != NULL);

		tech_pvt = switch_core_session_get_private(session);
		assert(tech_pvt != NULL);

		if (!tech_pvt->call) {
			tech_pvt->call = event->hangup.call;
		}

		tech_pvt->cause = event->hangup.cause;

		switch_channel_hangup(channel, SWITCH_CAUSE_NORMAL_CLEARING);

		chanmap->map[event->hangup.channel] = NULL;
	}

	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "-- Hanging up channel s%dc%d\n", spri->span, event->hangup.channel);
	return 0;
}

static int on_answer(struct sangoma_pri *spri, sangoma_pri_event_t event_type, pri_event *event)
{
	switch_core_session_t *session;
	switch_channel_t *channel;
	struct channel_map *chanmap;


	chanmap = spri->private_info;

	if ((session = chanmap->map[event->answer.channel])) {
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "-- Answer on channel s%dc%d\n", spri->span, event->answer.channel);
		channel = switch_core_session_get_channel(session);
		assert(channel != NULL);
		switch_channel_answer(channel);
	} else {
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "-- Answer on channel s%dc%d but it's not in use?\n", spri->span, event->answer.channel);
	}
	
	return 0;
}


static int on_proceed(struct sangoma_pri *spri, sangoma_pri_event_t event_type, pri_event *event)
{
	switch_core_session_t *session;
	switch_channel_t *channel;
	struct channel_map *chanmap;

	chanmap = spri->private_info;

	if ((session = chanmap->map[event->proceeding.channel])) {
		switch_caller_profile_t *originator;

		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "-- Proceeding on channel s%dc%d\n", spri->span, event->proceeding.channel);
		channel = switch_core_session_get_channel(session);
		assert(channel != NULL);
		
		if ((originator = switch_channel_get_originator_caller_profile(channel))) {
			switch_core_session_message_t msg;

			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "-- Passing progress to Originator %s\n", originator->chan_name);

			msg.message_id = SWITCH_MESSAGE_INDICATE_PROGRESS;
			msg.from = switch_channel_get_name(channel);
			
			switch_core_session_message_send(originator->uuid, &msg);

			switch_channel_set_flag(channel, CF_EARLY_MEDIA);
		}

		//switch_channel_answer(channel);
	} else {
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "-- Proceeding on channel s%dc%d but it's not in use?\n", spri->span, event->proceeding.channel);
	}

	return 0;
}

#if 0
static int on_ringing(struct sangoma_pri *spri, sangoma_pri_event_t event_type, pri_event *event)
{
	switch_core_session_t *session;
	switch_channel_t *channel;
	struct channel_map *chanmap;
	struct private_object *tech_pvt;

	chanmap = spri->private_info;

	if ((session = chanmap->map[event->ringing.channel])) {
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "-- Ringing on channel s%dc%d\n", spri->span, event->ringing.channel);
		channel = switch_core_session_get_channel(session);
		assert(channel != NULL);

		pri_proceeding(spri->pri, event->ringing.call, event->ringing.channel, 0);
		pri_acknowledge(spri->pri, event->ringing.call, event->ringing.channel, 0);

		tech_pvt = switch_core_session_get_private(session);
		if (!tech_pvt->call) {
			tech_pvt->call = event->ringing.call;
		}
		tech_pvt->callno = event->ring.channel;
		tech_pvt->span = spri->span;
	} else {
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "-- Ringing on channel s%dc%d but it's not in use?\n", spri->span, event->ringing.channel);
	}

	return 0;
}
#endif 

static int on_ring(struct sangoma_pri *spri, sangoma_pri_event_t event_type, pri_event *event)
{
	char name[128];
	switch_core_session_t *session;
	switch_channel_t *channel;
	struct channel_map *chanmap;



	chanmap = spri->private_info;
	if (chanmap->map[event->ring.channel]) {
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "--Duplicate Ring on channel s%dc%d (ignored)\n",
							  spri->span, event->ring.channel);
		return 0;
	}

	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "-- Ring on channel s%dc%d (from %s to %s)\n", spri->span, event->ring.channel,
						  event->ring.callingnum, event->ring.callednum);


	pri_proceeding(spri->pri, event->ring.call, event->ring.channel, 0);
	pri_acknowledge(spri->pri, event->ring.call, event->ring.channel, 0);

	if ((session = switch_core_session_request(&wanpipe_endpoint_interface, NULL))) {
		struct private_object *tech_pvt;
		char ani2str[4] = "";
		//wanpipe_tdm_api_t tdm_api;

		switch_core_session_add_stream(session, NULL);
		if ((tech_pvt = (struct private_object *) switch_core_session_alloc(session, sizeof(struct private_object)))) {
			memset(tech_pvt, 0, sizeof(*tech_pvt));
			switch_mutex_init(&tech_pvt->flag_mutex, SWITCH_MUTEX_NESTED, switch_core_session_get_pool(session));
			channel = switch_core_session_get_channel(session);
			switch_core_session_set_private(session, tech_pvt);
			sprintf(name, "s%dc%d", spri->span, event->ring.channel);
			switch_channel_set_name(channel, name);

		} else {
			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Hey where is my memory pool?\n");
			switch_core_session_destroy(&session);
			return 0;
		}

		if (event->ring.ani2 >= 0) {
			snprintf(ani2str, 5, "%.2d", event->ring.ani2);
		}

		if ((tech_pvt->caller_profile = switch_caller_profile_new(switch_core_session_get_pool(session),
																  NULL,
																  globals.dialplan,
																  "FreeSWITCH",
																  event->ring.callingnum,
																  event->ring.callingani,
																  switch_strlen_zero(ani2str) ? NULL : ani2str,
																  NULL,
																  NULL,
																  (char *)modname,
																  NULL,
																  event->ring.callednum))) {
			switch_channel_set_caller_profile(channel, tech_pvt->caller_profile);
		}

		switch_set_flag_locked(tech_pvt, TFLAG_INBOUND);
		tech_pvt->spri = spri;
		tech_pvt->cause = -1;

		if (!tech_pvt->call) {
			tech_pvt->call = event->ring.call;
		}
		
		tech_pvt->callno = event->ring.channel;
		tech_pvt->span = spri->span;

		if (!wp_open(tech_pvt, spri->span, event->ring.channel)) {
			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Can't open fd!\n");
		}
		//sangoma_tdm_set_hw_period(fd, &tdm_api, 480);

		chanmap->map[event->ring.channel] = session;

		switch_channel_set_state(channel, CS_INIT);
		switch_core_session_thread_launch(session);
	} else {
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Cannot Create new Inbound Channel!\n");
	}


	return 0;
}

static int check_flags(struct sangoma_pri *spri)
{

	return 0;
}

static int on_restart(struct sangoma_pri *spri, sangoma_pri_event_t event_type, pri_event *event)
{
	switch_core_session_t *session;
	struct channel_map *chanmap;


	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "-- Restarting s%dc%d\n", spri->span, event->restart.channel);

	if (event->restart.channel < 1) {
		return 0;
	}


	chanmap = spri->private_info;
	
	if ((session = chanmap->map[event->restart.channel])) {
		switch_channel_t *channel;
		channel = switch_core_session_get_channel(session);
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "Hanging Up channel %s\n", switch_channel_get_name(channel));
		switch_channel_hangup(channel, SWITCH_CAUSE_NORMAL_CLEARING);
	}
	
	wp_restart(spri->span, event->restart.channel);

	return 0;
}

static int on_dchan_up(struct sangoma_pri *spri, sangoma_pri_event_t event_type, pri_event *event)
{

	if (!switch_test_flag(spri, SANGOMA_PRI_READY)) {
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Span %d D-Chan UP!\n", spri->span);
		switch_set_flag(spri, SANGOMA_PRI_READY);
	}

	return 0;
}

static int on_dchan_down(struct sangoma_pri *spri, sangoma_pri_event_t event_type, pri_event *event)
{

	if (switch_test_flag(spri, SANGOMA_PRI_READY)) {
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Span %d D-Chan DOWN!\n", spri->span);
		switch_clear_flag(spri, SANGOMA_PRI_READY);
	}
	
	return 0;
}

static int on_anything(struct sangoma_pri *spri, sangoma_pri_event_t event_type, pri_event *event)
{

	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Caught Event span %d %u (%s)\n", spri->span, event_type,
						  sangoma_pri_event_str(event_type));
	return 0;
}


static void *pri_thread_run(switch_thread_t *thread, void *obj)
{
	struct sangoma_pri *spri = obj;
	struct channel_map chanmap;

	switch_event_t *s_event;
	SANGOMA_MAP_PRI_EVENT((*spri), SANGOMA_PRI_EVENT_ANY, on_anything);
	SANGOMA_MAP_PRI_EVENT((*spri), SANGOMA_PRI_EVENT_RING, on_ring);
	//SANGOMA_MAP_PRI_EVENT((*spri), SANGOMA_PRI_EVENT_RINGING, on_ringing);
	//SANGOMA_MAP_PRI_EVENT((*spri), SANGOMA_PRI_EVENT_SETUP_ACK, on_proceed);
	SANGOMA_MAP_PRI_EVENT((*spri), SANGOMA_PRI_EVENT_PROCEEDING, on_proceed);
	SANGOMA_MAP_PRI_EVENT((*spri), SANGOMA_PRI_EVENT_ANSWER, on_answer);
	SANGOMA_MAP_PRI_EVENT((*spri), SANGOMA_PRI_EVENT_DCHAN_UP, on_dchan_up);
	SANGOMA_MAP_PRI_EVENT((*spri), SANGOMA_PRI_EVENT_DCHAN_DOWN, on_dchan_down);
	SANGOMA_MAP_PRI_EVENT((*spri), SANGOMA_PRI_EVENT_HANGUP_REQ, on_hangup);
	SANGOMA_MAP_PRI_EVENT((*spri), SANGOMA_PRI_EVENT_HANGUP, on_hangup);
	SANGOMA_MAP_PRI_EVENT((*spri), SANGOMA_PRI_EVENT_INFO_RECEIVED, on_info);
	SANGOMA_MAP_PRI_EVENT((*spri), SANGOMA_PRI_EVENT_RESTART, on_restart);

	spri->on_loop = check_flags;
	spri->private_info = &chanmap;

	if (switch_event_create(&s_event, SWITCH_EVENT_PUBLISH) == SWITCH_STATUS_SUCCESS) {
		switch_event_add_header(s_event, SWITCH_STACK_BOTTOM, "service", "_pri._tcp");
		switch_event_fire(&s_event);
	}

	sangoma_run_pri(spri);

	free(spri);
	return NULL;
}

static void pri_thread_launch(struct sangoma_pri *spri)
{
	switch_thread_t *thread;
	switch_threadattr_t *thd_attr = NULL;
	
	switch_threadattr_create(&thd_attr, module_pool);
	switch_threadattr_detach_set(thd_attr, 1);
	switch_threadattr_stacksize_set(thd_attr, SWITCH_THREAD_STACKSIZE);
	switch_thread_create(&thread, thd_attr, pri_thread_run, spri, module_pool);

}

static switch_status_t config_wanpipe(int reload)
{
	char *cf = "wanpipe.conf";
	int current_span = 0;
	switch_xml_t cfg, xml, settings, param, span;

	globals.mtu = DEFAULT_MTU;
	globals.dtmf_on = 150;
	globals.dtmf_off = 50;



	if (!(xml = switch_xml_open_cfg(cf, &cfg, NULL))) {
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "open of %s failed\n", cf);
		return SWITCH_STATUS_TERM;
	}


	if ((settings = switch_xml_child(cfg, "settings"))) {
		for (param = switch_xml_child(settings, "param"); param; param = param->next) {
			char *var = (char *) switch_xml_attr_soft(param, "name");
			char *val = (char *) switch_xml_attr_soft(param, "value");

			if (!strcmp(var, "debug")) {
				globals.debug = atoi(val);
			} else if (!strcmp(var, "mtu")) {
				globals.mtu = atoi(val);
			} else if (!strcmp(var, "dtmf-on")) {
				globals.dtmf_on = atoi(val);
			} else if (!strcmp(var, "dtmf-off")) {
				globals.dtmf_off = atoi(val);
			} else if (!strcmp(var, "supress-dtmf-tone")) {
				globals.supress_dtmf_tone = switch_true(val);
			}
		}
	}

	
	for (span = switch_xml_child(cfg, "span"); span; span = span->next) {
		for (param = switch_xml_child(span, "param"); param; param = param->next) {
			char *var = (char *) switch_xml_attr_soft(param, "name");
			char *val = (char *) switch_xml_attr_soft(param, "value");

			if (!strcmp(var, "span")) {
				current_span = atoi(val);
				if (current_span <= 0 || current_span > MAX_SPANS) {
					switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Invalid SPAN!\n");
					current_span = 0;
					continue;
				}
				if (!SPANS[current_span]) {
					if (!(SPANS[current_span] = switch_core_alloc(module_pool, sizeof(*SPANS[current_span])))) {
						switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "MEMORY ERROR\n");
						break;;
					}
					SPANS[current_span]->span = current_span;
				}
				
			} else {
				if (!current_span) {
					switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Invalid option %s when no span defined.\n", var);
					continue;
				}
				
				if (!strcmp(var, "dchan")) {
					SPANS[current_span]->dchan = atoi(val);
				} else if (!strcmp(var, "bchan")) {
					char from[128];
					char *to;
					switch_copy_string(from, val, sizeof(from));
					if ((to = strchr(from, '-'))) {
						int fromi, toi, x = 0;
						*to++ = '\0';
						fromi = atoi(from);
						toi = atoi(to);
						if (fromi > 0 && toi > 0 && fromi < toi && fromi < MAX_SPANS && toi < MAX_SPANS) {
							for(x = fromi; x <= toi; x++) {
								SPANS[current_span]->bchans |= (1 << x);
							}
						} else {
							switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Invalid bchan range!\n");
						}
					} else {
						int i = atoi(val);
						if (i > 0 && i < 31) {
							SPANS[current_span]->bchans |= (1 << i);
						} else {
							switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Invalid bchan!\n");
						}
					}
				} else if (!strcmp(var, "node")) {
					SPANS[current_span]->node = str2node(val);
				} else if (!strcmp(var, "switch")) {
					SPANS[current_span]->pswitch = str2switch(val);
				} else if (!strcmp(var, "dp")) {
					SPANS[current_span]->dp = str2dp(val);
				} else if (!strcmp(var, "l1")) {
					SPANS[current_span]->l1 = str2l1(val);
				} else if (!strcmp(var, "dialplan")) {
					set_global_dialplan(val);
				} else if (!strcmp(var, "mtu")) {
					int mtu = atoi(val);

					if (mtu >= 10 && mtu < 960) {
						SPANS[current_span]->mtu = mtu;
					} else {
						switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Invalid MTU (%s)!\n", val);
					}
				}
			}
		}
	}
	switch_xml_free(xml);

	if (!globals.dialplan) {
		set_global_dialplan("default");
	}


	globals.configured_spans = 0;
	for(current_span = 1; current_span < MAX_SPANS; current_span++) {
		if (SPANS[current_span]) {

			if (!SPANS[current_span]->l1) {
				SPANS[current_span]->l1 = PRI_LAYER_1_ULAW;
			}
			if (sangoma_init_pri(&SPANS[current_span]->spri,
								 current_span,
								 SPANS[current_span]->dchan,
								 SPANS[current_span]->pswitch,
								 SPANS[current_span]->node,
								 globals.debug)) {
				switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Cannot launch span %d\n", current_span);
				continue;
			}
			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Launch span %d\n", current_span);
			pri_thread_launch(&SPANS[current_span]->spri);
			globals.configured_spans++;
		}
	}



	return SWITCH_STATUS_SUCCESS;

}



