/*****************************************************************************
 * libhpsangoma.c:  Sangoma High Performance TDM API - Span Based Library
 *
 * Author(s):	Nenad Corbic <ncorbic@sangoma.com>
 *
 * Copyright:	(c) 2008 Nenad Corbic <ncorbic@sangoma.com>
 *
 *		This program is free software; you can redistribute it and/or
 *		modify it under the terms of the GNU General Public License
 *		as published by the Free Software Foundation; either version
 *		2 of the License, or (at your option) any later version.
 * ============================================================================
 *
 */

#include "libhpsangoma.h"
#include "libhpsangoma_priv.h"

/*---------------------------------------------------------
  PRIVATE STRUCTURES
 ----------------------------------------------------------*/

void (*lib_log)(int level, FILE *fp, char *file, const char *func, int line, char *fmt, ...)=NULL;


/*!
  \brief Channel Method: User tx a chunk into a channel
  \param chan channel object
  \param data pointer to user voice chunk
  \param len size of user voice chunk
  \return -1 = packet too large, -2 = channel closed, 1 = busy, 0 = tx ok
*/

static int sangoma_hp_tdm_chan_push(struct sangoma_hptdm_chan *chan, char *data, int len)
{
	hp_tmd_chunk_t *tx_chunk;
	int free_slots;

	if (!chan->init) {
		return -2;
	}

	if (len >= SMG_HP_MAX_CHAN_DATA) {
		/* Data Too Big */
		lib_printf(0,NULL,"chan_push c%i tx chunk len %i too big\n",
				chan->span_no+1,chan->chan_no+1,chan->tx_idx_in,len);
		return -1;
	}

	/* Lock */
	if (chan->tx_idx_in >= chan->tx_idx_out) {
		free_slots=SMG_HP_TDM_CHUNK_IDX_SZ-(chan->tx_idx_in-chan->tx_idx_out);
	} else {
		free_slots=chan->tx_idx_out-chan->tx_idx_in;
	}
	/* Un Lock */

	if (!free_slots) {
		/* We have just overruned the tx buffer */
		lib_printf(0,NULL,"chan_push c%i failed no free slots in %i out %i\n",
				chan->span_no+1,chan->chan_no+1, chan->tx_idx_in,chan->tx_idx_out);
		return 1;
	}

	tx_chunk = &chan->tx_idx[chan->tx_idx_in];
	if (tx_chunk->init) {
		/* This should NEVER happen the chunk should be free */
		lib_printf(15,NULL,"chan_push s%ic%i tx chunk overrun in %i \n",
				chan->span_no+1,chan->chan_no+1,chan->tx_idx_in);
		return 1;
	}

	memset(tx_chunk,0,sizeof(hp_tmd_chunk_t));
	memcpy(&tx_chunk->data,data,len);
	tx_chunk->len=len;
	tx_chunk->init=1;

	lib_printf(15,NULL,"chan_push s%ic%i tx chunk in %i \n",
				chan->span_no+1,chan->chan_no+1,chan->tx_idx_in);

	chan->tx_idx_in++;
	if (chan->tx_idx_in >= SMG_HP_TDM_CHUNK_IDX_SZ) {
		chan->tx_idx_in=0;
	}

	return 0;
}

/*-------------------------------------------------
  Internal Span Methods
 -------------------------------------------------*/


/*!
  \brief Span Method: Open a channel inside of span
  \param span span object
  \param cfg channel registration structure
  \param chan_no channel number
  \param chan_ptr pass user the channel pointer reference
  \return 0 = pass, non zero fail
*/

static int sangoma_hp_tdm_open_chan(sangoma_hptdm_span_t *span,
			      sangoma_hptdm_chan_reg_t *cfg,
			      unsigned int chan_no,
			      sangoma_hptdm_chan_t **chan_ptr)
{
	sangoma_hptdm_chan_t *chan;

	if (!span->init) {
		return -1;
	}

	if (chan_no >= SMG_HP_TDM_MAX_CHANS) {
		lib_printf(0,NULL,"open_chan failed chan_no %i >= max chans %i\n",
				chan_no, SMG_HP_TDM_MAX_CHANS);
		return -1;
	}

	if (!cfg->rx_data || !cfg->p) {
		return -1;
	}

	if (span->chan_idx[chan_no].chan_no_hw < 0) {
		lib_printf(0,NULL,"open_chan failed chan_no s%ic%i is not mapped to hardware\n",
				span->span_no+1,chan_no+1);
		return -1;
	}

	chan = &span->chan_idx[chan_no].chan;
	if (chan->init) {
		/* Chan Busy */
		lib_printf(0,NULL,"open_chan failed chan_no s%ic%i is busy\n",
				span->span_no+1,chan_no+1);
		return 1;
	}

	memset(chan,0,sizeof(sangoma_hptdm_chan_t));

	chan->chan_no = chan_no;
	chan->span = span;
	memcpy(&chan->chan_reg, cfg, sizeof(sangoma_hptdm_chan_reg_t));

	chan->push = sangoma_hp_tdm_chan_push;

	chan->init=1;
	lib_printf(15,NULL,"open_chan chan_no s%ic%i ok\n",
				span->span_no+1,chan_no+1);

	*chan_ptr = chan;

	return 0;

}


/*!
  \brief Span Method: Close channel
  \param chan channel object
  \return 0 = pass, non zero fail
*/

static int sangoma_hp_tdm_close_chan(sangoma_hptdm_chan_t *chan)
{
	chan->init=0;
	chan->chan_reg.p=NULL;
	lib_printf(15,NULL,"close_chan chan_no s%ic%i ok\n",
				chan->span_no+1,chan->chan_no+1);
	return 0;
}


/*!
  \brief Span Method: Test if channel is closed
  \param chan channel object
  \return 0 = channel is NOT closed,  non zero channel IS closed
*/
static int sangoma_hp_tdm_is_chan_closed (sangoma_hptdm_chan_t *chan)
{
	return (chan->init == 0) ? 1:0;
}

/*!
  \brief Span Method: Close span
  \param span span object
  \return 0 = pass, non zero fail
*/
static int sangoma_hp_tdm_close_span(sangoma_hptdm_span_t *span)
{
	int i;
	sangoma_hptdm_chan_t *chan=NULL;

	for (i=0;i<SMG_HP_TDM_MAX_CHANS;i++) {
		chan = &span->chan_idx[i].chan;
		if (chan->init) {
			chan->init=0;
		}
	}

	close(span->sock);
	span->sock=-1;

	return 0;
}

/*!
  \brief Span Method: User passes cmd to the span
  \param span span object
  \return 0 = pass, non zero fail
*/
static int sangoma_hp_tdm_event_ctrl_span(sangoma_hptdm_span_t *span, hp_tdmapi_tx_event_t *event)
{
	int err;

	if (!span->init || span->sock < 0) {
		return -1;
	}

	err = ioctl(span->sock,SIOC_WANPIPE_API,event);
	if (err < 0){
		lib_printf(0,NULL,"Error: SPAN %i Failed to execute event!\n",
				span->span_no+1);
		return -1;
	}

	return 0;
}

/*!
  \brief Span Method: User requests full span configuration
  \param span span object
  \return 0 = pass, non zero fail
*/
static int sangoma_hp_tdm_event_get_cfg(sangoma_hptdm_span_t *span, wan_if_cfg_t *if_cfg)
{
	memcpy(if_cfg,&span->span_cfg,sizeof(if_cfg));
	return 0;
}

/*!
  \brief Span Method: handle the span
  \param span span object
  \return 0 = pass, non zero fail
*/
static int sangoma_hp_tdm_run_span(sangoma_hptdm_span_t *span)
{
	int err=0;
	fd_set 	sock_read,sock_write,sock_oob;

	if (!span->init) {
		lib_printf(0, NULL, "Span %i not initialized %i\n",span->span_no+1);
		return -1;
	}

	lib_printf(15, NULL, "Starting RUN SPAN %i Sock=%i\n",span->span_no+1, span->sock);

	if (span->sock < 0) {
		err=sangoma_hptdm_span_open(span);
		if (err) {
			usleep(500000);
			err=-2;
			goto sangoma_hp_tdm_run_span_exit;
		}
	}

	/* Initialize all select() descriptors */
	FD_ZERO(&sock_read);
	FD_ZERO(&sock_write);
	FD_ZERO(&sock_oob);

	FD_SET(span->sock,&sock_oob);
	FD_SET(span->sock,&sock_read);
	FD_SET(span->sock,&sock_write);

	err=select(span->sock + 1,&sock_read, NULL, &sock_oob, NULL);

	if (err > 0) {

		if (FD_ISSET(span->sock,&sock_oob)){
			err=sangoma_hp_tdm_handle_oob_event(span);
			if (err) {
				lib_printf(0, NULL, "RUN SPAN: %i oob err %i\n",
					span->span_no+1, err);
				err=-3;
				goto sangoma_hp_tdm_run_span_exit;
			}
		}
		if (FD_ISSET(span->sock,&sock_read)){
			err=sangoma_hp_tdm_handle_read_event(span);
			if (err) {
				lib_printf(0, NULL, "RUN SPAN: %i read err %i\n",
					span->span_no+1, err);
				err=-4;
				goto sangoma_hp_tdm_run_span_exit;
			}

			err=sangoma_hp_tdm_handle_write_event(span);
			if (err) {
				lib_printf(0, NULL, "RUN SPAN: %i write err %i\n",
					span->span_no+1, err);
				err=-5;
				goto sangoma_hp_tdm_run_span_exit;
			}
		}

	} else if (err==0) {
		/* Timeout continue */
		return 0;

	} else {
		/* Error */
		if (errno == EAGAIN) {
			goto sangoma_hp_tdm_run_span_exit;
		}

		err=-6;
	}

sangoma_hp_tdm_run_span_exit:

	if (err < 0) {
		if (span->sock) {
			close(span->sock);
			span->sock=-1;
		}
	}

	return err;
}



/*---------------------------------------------------------
  PUBLIC STRUCTURES
 ----------------------------------------------------------*/


/*
  \brief Initialize and Configure Span - private functions not to be used directly!
  \param span_no span number - integer
  \param cfg span registration struct
  \param version library version number added by the macro
  \return NULL: fail,  Span Object: pass
 *
 * The __sangoma_hptdm_api_span_init() function must NOT be called directly!
 *  One MUST use defined sangoma_hptdm_api_span_init() macro instead
 */

sangoma_hptdm_span_t * __sangoma_hptdm_api_span_init(int span_no, sangoma_hptdm_span_reg_t *cfg, int version)
{
	int err,i,ch=0;
	sangoma_hptdm_span_t *span;

	span = malloc(sizeof(sangoma_hptdm_span_t));
	if (!span) {
		return NULL;
	}

	memset(span,0,sizeof(sangoma_hptdm_span_t));

	span->span_no=span_no;
	sprintf(span->if_name,"w%ig1",span_no+1);

	if (cfg) {
		memcpy(&span->span_reg,cfg,sizeof(sangoma_hptdm_span_reg_t));
		if (!lib_log) {
			lib_log=cfg->log;
		}
	}

	err=sangoma_hptdm_span_open(span);
	if (err) {
		free(span);
		return NULL;
	}

	if (span->span_cfg.media == WAN_MEDIA_E1) {
		span->span_cfg.active_ch = span->span_cfg.active_ch >> 1;
	}

	lib_printf(0,NULL,"Span %i Configuration\n",span->span_no+1);
	lib_printf(0,NULL,"Used By\t:%i\n",span->span_cfg.usedby);
	lib_printf(0,NULL,"Media\t:%i\n",span->span_cfg.media);
	lib_printf(0,NULL,"Active Ch\t:0x%08X\n",span->span_cfg.active_ch);
	lib_printf(0,NULL,"Chunk Sz\t:%i\n",span->span_cfg.chunk_sz);
	lib_printf(0,NULL,"HW Coding\t:%i\n",span->span_cfg.hw_coding);
	lib_printf(0,NULL,"If Number\t:%i\n",span->span_cfg.interface_number);


	/* Map all channels to the actually configued on hardware */
	for (i=0;i<SMG_HP_TDM_MAX_CHANS;i++) {
		span->chan_idx[i].chan_no_hw=-1;
		if (span->span_cfg.active_ch & (1<<i)) {
			span->chan_idx[i].chan_no_hw=ch;
			lib_printf(0,NULL,"Chan %i Mapped to %i",i,ch);
			ch++;
			span->max_chans++;
		} else {
			lib_printf(0,NULL,"Chan %i Not Mapped",i);
		}
	}

	lib_printf(0,NULL,"Total Chans\t:%i\n",span->max_chans);

	/* Must be configurable */
	span->chunk_sz=span->span_cfg.chunk_sz;
	span->tx_size=span->max_chans*span->chunk_sz;

	span->init=1;
	span->idle=0xFF;

	span->open_chan = sangoma_hp_tdm_open_chan;
	span->close_chan = sangoma_hp_tdm_close_chan;
	span->is_chan_closed = sangoma_hp_tdm_is_chan_closed;
	span->run_span =sangoma_hp_tdm_run_span;
	span->close_span = sangoma_hp_tdm_close_span;
	span->event_ctrl = sangoma_hp_tdm_event_ctrl_span;
	span->get_cfg = sangoma_hp_tdm_event_get_cfg;

	lib_printf(5, NULL, "Span %i Initialized\n",span->span_no+1);

	return span;
}

/*
  \brief Free, Un-Initialize Span
  \param span_no span object
  \return 0 = pass, non zero fail
 */

int sangoma_hptdm_api_span_free(sangoma_hptdm_span_t *span)
{
	if (span->sock >= 0) {
		span->close_span(span);
	}

	free(span);
	span=NULL;

	return 0;
}

