/**
 * libypri.cpp
 * This file is part of the YATE Project http://YATE.null.ro
 *
 * Common C++ base classes for PRI cards telephony drivers
 *
 * Yet Another Telephony Engine - a fully featured software PBX and IVR
 * Copyright (C) 2004-2006 Null Team
 *
 * 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.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include <modules/libypri.h>

/* Individual bit groups in number presentation - Q931 octet 3a bits 7,6 */
#define PRESENTATION_BIT_MASK        0x60
#define PRESENTATION_ALLOWED         0x00
#define PRESENTATION_RESTRICTED      0x20
#define PRESENTATION_UNAVAILABLE     0x40

/* Individual bit groups in number screening - Q931 octet 3a bits 2,1 */
#define SCREENING_BIT_MASK           0x03
#define SCREENING_USER_NOT_SCREENED  0x00
#define SCREENING_USER_PASSED        0x01
#define SCREENING_USER_FAILED        0x02
#define SCREENING_NETWORK_PROVIDED   0x03

extern "C" {
extern int q931_setup(struct pri *pri, q931_call *c, struct pri_sr *req);
};

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>


using namespace TelEngine;

// default buffer length: 20 ms
static int s_buflen = 160;


#ifdef PRI_NEW_SET_API

#ifdef BRI_NETWORK_PTMP

// bristuff patch changed the error reporting for no good reason

static void pri_err_cb(char *s, int span)
{
    Debug("libpri",DebugWarn,"Span %d: %s",span,s);
}

static void pri_msg_cb(char *s, int span)
{
    Debug("libpri",DebugInfo,"Span %d: %s",span,s);
}

#else

static void pri_err_cb(struct pri *pri, char *s)
{
    PriSpan* span = pri ? (PriSpan*)::pri_get_userdata(pri) : 0;
    if (span)
	Debug(span->driver(),DebugWarn,"Span %d: %s",span->span(),s);
    else
	Debug("libpri",DebugWarn,"%s",s);
}

static void pri_msg_cb(struct pri *pri, char *s)
{
    PriSpan* span = pri ? (PriSpan*)::pri_get_userdata(pri) : 0;
    if (span)
	Debug(span->driver(),DebugInfo,"Span %d: %s",span->span(),s);
    else
	Debug("libpri",DebugInfo,"%s",s);
}

#endif // BRI_NETWORK_PTMP

#else

static void pri_err_cb(char *s)
{
    Debug("libpri",DebugWarn,"%s",s);
}

static void pri_msg_cb(char *s)
{
    Debug("libpri",DebugInfo,"%s",s);
}

#endif // PRI_NEW_SET_API

/* Switch types */
static TokenDict dict_str2switch[] = {
    { "unknown", PRI_SWITCH_UNKNOWN },
    { "ni2", PRI_SWITCH_NI2 },
    { "dms100", PRI_SWITCH_DMS100 },
    { "lucent5e", PRI_SWITCH_LUCENT5E },
    { "at&t4ess", PRI_SWITCH_ATT4ESS },
    { "euroisdn_e1", PRI_SWITCH_EUROISDN_E1 },
    { "euroisdn_t1", PRI_SWITCH_EUROISDN_T1 },
    { "ni1", PRI_SWITCH_NI1 },
    { 0, -1 }
};

static TokenDict dict_str2type[] = {
    { "pri_net", PRI_NETWORK },
    { "pri_cpe", PRI_CPE },
#ifdef BRI_NETWORK_PTMP
    { "bri_net_ptmp", BRI_NETWORK_PTMP },
    { "bri_cpe_ptmp", BRI_CPE_PTMP },
    { "bri_net", BRI_NETWORK },
    { "bri_cpe", BRI_CPE },
#endif
    { 0, -1 }
};

#if 0
/* Numbering plan identifier */
static TokenDict dict_str2nplan[] = {
    { "unknown", PRI_NPI_UNKNOWN },
    { "e164", PRI_NPI_E163_E164 },
    { "x121", PRI_NPI_X121 },
    { "f69", PRI_NPI_F69 },
    { "national", PRI_NPI_NATIONAL },
    { "private", PRI_NPI_PRIVATE },
    { "reserved", PRI_NPI_RESERVED },
    { 0, -1 }
};

/* Type of number */
static TokenDict dict_str2ntype[] = {
    { "unknown", PRI_TON_UNKNOWN },
    { "international", PRI_TON_INTERNATIONAL },
    { "national", PRI_TON_NATIONAL },
    { "net_specific", PRI_TON_NET_SPECIFIC },
    { "subscriber", PRI_TON_SUBSCRIBER },
    { "abbreviated", PRI_TON_ABBREVIATED },
    { "reserved", PRI_TON_RESERVED },
    { 0, -1 }
};
#endif

/* Dialing plan */
static TokenDict dict_str2dplan[] = {
    { "unknown", PRI_UNKNOWN },
    { "international", PRI_INTERNATIONAL_ISDN },
    { "national", PRI_NATIONAL_ISDN },
    { "local", PRI_LOCAL_ISDN },
    { "private", PRI_PRIVATE },
    { 0, -1 }
};

/* Presentation and screening */
static TokenDict dict_str2pres[] = {
    { "allow_user_not_screened", PRES_ALLOWED_USER_NUMBER_NOT_SCREENED },
    { "allow_user_passed", PRES_ALLOWED_USER_NUMBER_PASSED_SCREEN },
    { "allow_user_failed", PRES_ALLOWED_USER_NUMBER_FAILED_SCREEN },
    { "allow_network", PRES_ALLOWED_NETWORK_NUMBER },
    { "prohibit_user_not_screened", PRES_PROHIB_USER_NUMBER_NOT_SCREENED },
    { "prohibit_user_passed", PRES_PROHIB_USER_NUMBER_PASSED_SCREEN },
    { "prohibit_user_failed", PRES_PROHIB_USER_NUMBER_FAILED_SCREEN },
    { "prohibit_network", PRES_PROHIB_NETWORK_NUMBER },
    { "not_available", PRES_NUMBER_NOT_AVAILABLE },
    { 0, -1 }
};


#ifdef PRI_NSF_NONE
#define YATE_NSF_DEFAULT PRI_NSF_NONE
#else
#define YATE_NSF_DEFAULT -1
#endif
/* Network Specific Facilities (AT&T) */
static TokenDict dict_str2nsf[] = {
#ifdef PRI_NSF_NONE
    { "none", PRI_NSF_NONE },
    { "sid_preferred", PRI_NSF_SID_PREFERRED },
    { "ani_preferred", PRI_NSF_ANI_PREFERRED },
    { "sid_only", PRI_NSF_SID_ONLY },
    { "ani_only", PRI_NSF_ANI_ONLY },
    { "call_assoc_tsc", PRI_NSF_CALL_ASSOC_TSC },
    { "notif_catsc_clearing", PRI_NSF_NOTIF_CATSC_CLEARING },
    { "operator", PRI_NSF_OPERATOR },
    { "pcco", PRI_NSF_PCCO },
    { "sdn", PRI_NSF_SDN },
    { "toll_free_megacom", PRI_NSF_TOLL_FREE_MEGACOM },
    { "megacom", PRI_NSF_MEGACOM },
    { "accunet", PRI_NSF_ACCUNET },
    { "long_distance", PRI_NSF_LONG_DISTANCE_SERVICE },
    { "international_toll_free", PRI_NSF_INTERNATIONAL_TOLL_FREE },
    { "at&t_multiquest", PRI_NSF_ATT_MULTIQUEST },
    { "call_redirection", PRI_NSF_CALL_REDIRECTION_SERVICE },
#endif
    { 0, -1 }
};

static TokenDict dict_str2cause[] = {
    { "noroute", PRI_CAUSE_NO_ROUTE_DESTINATION },
    { "noconn", PRI_CAUSE_REQUESTED_CHAN_UNAVAIL },
    { "busy", PRI_CAUSE_USER_BUSY },
    { "noanswer", PRI_CAUSE_NO_USER_RESPONSE },
    { "rejected", PRI_CAUSE_CALL_REJECTED },
    { "forbidden", PRI_CAUSE_OUTGOING_CALL_BARRED },
    { "forbidden", PRI_CAUSE_INCOMING_CALL_BARRED },
    { "offline", PRI_CAUSE_DESTINATION_OUT_OF_ORDER },
    { "unallocated", PRI_CAUSE_UNALLOCATED },
    { "moved", PRI_CAUSE_NUMBER_CHANGED },
    { "congestion", PRI_CAUSE_NORMAL_CIRCUIT_CONGESTION },
    { "congestion", PRI_CAUSE_SWITCH_CONGESTION },
    { "failure", PRI_CAUSE_DESTINATION_OUT_OF_ORDER },
    { 0, -1 }
};

/* Layer 1 formats */
static TokenDict dict_str2law[] = {
    { "mulaw", PRI_LAYER_1_ULAW },
    { "alaw", PRI_LAYER_1_ALAW },
    { "g721", PRI_LAYER_1_G721 },
    { 0, -1 }
};

/* Echo canceller taps */
static TokenDict dict_numtaps[] = {
    { "on", 1 },
    { "yes", 1 },
    { "true", 1 },
    { "enable", 1 },
    { 0, 0 }
};

class ChanGroup : public String
{
public:
    enum {
	FirstAvail = 0,
	RoundRobin = 1,
	RandomChan = 2
    };
    ChanGroup(const String& name, const NamedList* sect, int last);
    virtual ~ChanGroup()
	{ }
    inline void getRange(int& first, int& last, int& used) const
	{ first = m_first; last = m_last; used = m_used; }
    void setUsed(int used);
private:
    int m_mode;
    int m_first;
    int m_last;
    int m_used;
};

ChanGroup::ChanGroup(const String& name, const NamedList* sect, int last)
    : String(name)
{
    static TokenDict dict_groupmode[] = {
	{ "first", FirstAvail },
	{ "firstavail", FirstAvail },
	{ "rotate", RoundRobin },
	{ "roundrobin", RoundRobin },
	{ "random", RandomChan },
	{ 0, 0 }
    };
    m_mode = sect->getIntValue("mode",dict_groupmode,RoundRobin);
    m_first = sect->getIntValue("first",1);
    m_last = sect->getIntValue("last",last);
    setUsed(m_last);
}

void ChanGroup::setUsed(int used)
{
    switch (m_mode) {
	case FirstAvail:
	    m_used = m_last;
	    break;
	case RandomChan:
	    m_used = m_first + (::random() % (m_last - m_first + 1));
	    break;
	default:
	    m_used = used;
    }
}

Fifo::Fifo(int buflen)
    : m_buflen(buflen), m_head(0), m_tail(1)
{
    if (!m_buflen)
	m_buflen = s_buflen;
    m_buffer = (unsigned char*)::malloc(m_buflen);
}

Fifo::~Fifo()
{
    if (m_buffer)
	::free(m_buffer);
}

// make the fifo empty
void Fifo::clear()
{
    m_head = 0;
    m_tail = 1;
}

// put a byte in fifo, overwrite last byte if full
bool Fifo::put(unsigned char value)
{
    m_buffer[m_tail] = value;
    bool full = (m_head == m_tail);
    m_tail++;
    if (m_tail >= m_buflen)
	m_tail = 0;
    if (full)
	m_head = m_tail;
    return full;
}

unsigned int Fifo::put(const unsigned char* buf, unsigned int length)
{
    unsigned int errors = 0;
    while (length--)
	if (put(*buf++))
	    errors++;
    return errors;
}

// get a byte from fifo, return last read if empty
unsigned char Fifo::get()
{
    unsigned char tmp = m_buffer[m_head];
    int nh = m_head+1;
    if (nh >= m_buflen)
	nh = 0;
    if (nh != m_tail)
	m_head = nh;
    return tmp;
}

PriSpan::PriSpan(struct pri *_pri, PriDriver* driver, int span, int first, int chans, int dchan, Configuration& cfg, const String& sect)
    : Mutex(true),
      m_driver(driver), m_span(span), m_offs(first), m_nchans(chans), m_bchans(0),
      m_pri(_pri), m_restart(0), m_chans(0), m_ok(false)
{
    Debug(m_driver,DebugAll,"PriSpan::PriSpan() [%p]",this);
    int buflength = cfg.getIntValue(sect,"buflen", s_buflen);

    m_inband = cfg.getBoolValue(sect,"dtmfinband",cfg.getBoolValue("general","dtmfinband"));
    m_detect = cfg.getBoolValue(sect,"dtmfdetect",cfg.getBoolValue("general","dtmfdetect",m_inband));
    m_layer1 = cfg.getIntValue(sect,"format",dict_str2law,(chans == 24) ? PRI_LAYER_1_ULAW : PRI_LAYER_1_ALAW);
    m_dplan = cfg.getIntValue(sect,"dialplan",dict_str2dplan,PRI_UNKNOWN);
    m_pres = cfg.getIntValue(sect,"presentation",dict_str2pres,PRES_ALLOWED_USER_NUMBER_NOT_SCREENED);
    m_restartPeriod = cfg.getIntValue(sect,"restart",cfg.getIntValue("general","restart")) * (u_int64_t)1000000;
    m_dumpEvents = cfg.getBoolValue(sect,"dumpevents",cfg.getBoolValue("general","dumpevents"));
    m_overlapped = cfg.getIntValue(sect,"overlapdial",cfg.getIntValue("general","overlapdial"));
    if (m_overlapped < 0)
	m_overlapped = 0;
#ifdef PRI_SET_OVERLAPDIAL
    ::pri_set_overlapdial(m_pri, (m_overlapped > 0));
#endif
#ifdef PRI_NSF_NONE
    ::pri_set_nsf(m_pri,cfg.getIntValue(sect,"facilities",dict_str2nsf,YATE_NSF_DEFAULT));
#endif
    ::pri_set_debug(m_pri,cfg.getIntValue(sect,"debug"));
    ::pri_set_userdata(m_pri, this);

    PriChan **ch = new PriChan* [chans];
    for (int i = 1; i <= chans; i++) {
	if (i != dchan) {
	    ch[i-1] = m_driver->createChan(this,i,buflength);
	    m_bchans++;
	}
	else
	    ch[i-1] = 0;
    }

    m_chans = ch;
    m_restart = Time::now() + m_restartPeriod;
    m_driver->m_spans.append(this);
}

PriSpan::~PriSpan()
{
    Debug(m_driver,DebugAll,"PriSpan::~PriSpan() [%p]",this);
    m_driver->m_spans.remove(this,false);
    m_ok = false;
    for (int i = 0; i < m_nchans; i++) {
	PriChan *c = m_chans[i];
	m_chans[i] = 0;
	if (c) {
	    c->hangup(PRI_CAUSE_NORMAL_UNSPECIFIED);
	    c->destruct();
	}
    }
    delete[] m_chans;
}

void PriSpan::runEvent(bool idleRun)
{
    pri_event *ev = 0;
    lock();
    if (idleRun) {
	ev = ::pri_schedule_run(m_pri);
	idle();
    }
    else
	ev = ::pri_check_event(m_pri);
    if (ev) {
	if (m_dumpEvents && debugAt(DebugAll))
	    ::pri_dump_event(m_pri, ev);
	handleEvent(*ev);
    }
    unlock();
}

void PriSpan::idle()
{
    if (!m_chans)
	return;
    if (m_restartPeriod && (Time::now() > m_restart)) {
	m_restart = Time::now() + m_restartPeriod;
	Debug(m_driver,DebugInfo,"Restarting idle channels on span %d",m_span);
	for (int i=0; i<m_nchans; i++)
	    if (m_chans[i])
		restartChan(i+1,true);
    }
    for (int i=0; i<m_nchans; i++)
	if (m_chans[i])
	    m_chans[i]->idle();
}

void PriSpan::handleEvent(pri_event &ev)
{
    switch (ev.e) {
	case PRI_EVENT_DCHAN_UP:
	    Debug(DebugMild,"D-channel up on span %d",m_span);
	    m_ok = true;
	    m_restart = Time::now() + 1000000;
	    {
		for (int i=0; i<m_nchans; i++)
		    if (m_chans[i])
			m_chans[i]->goneUp();
	    }
	    break;
	case PRI_EVENT_DCHAN_DOWN:
	    Debug(DebugWarn,"D-channel down on span %d",m_span);
	    m_ok = false;
	    {
		for (int i=0; i<m_nchans; i++)
		    if (m_chans[i])
			m_chans[i]->hangup(PRI_CAUSE_NETWORK_OUT_OF_ORDER);
	    }
	    break;
	case PRI_EVENT_RESTART:
	    restartChan(ev.restart.channel,false,true);
	    break;
	case PRI_EVENT_CONFIG_ERR:
	    Debug(DebugWarn,"Error on span %d: %s",m_span,ev.err.err);
	    break;
	case PRI_EVENT_RING:
	    ringChan(ev.ring.channel,ev.ring);
	    break;
	case PRI_EVENT_INFO_RECEIVED:
	    infoChan(ev.ring.channel,ev.ring);
	    break;
	case PRI_EVENT_RINGING:
	    Debug(m_driver,DebugInfo,"Ringing our call on channel %d on span %d",ev.ringing.channel,m_span);
	    ringingChan(ev.proceeding.channel);
	    break;
	case PRI_EVENT_HANGUP:
	    Debug(m_driver,DebugInfo,"Hangup detected on channel %d on span %d",ev.hangup.channel,m_span);
	    hangupChan(ev.hangup.channel,ev.hangup);
	    break;
	case PRI_EVENT_ANSWER:
	    Debug(m_driver,DebugInfo,"Answered channel %d on span %d",ev.answer.channel,m_span);
	    answerChan(ev.setup_ack.channel);
	    break;
	case PRI_EVENT_HANGUP_ACK:
	    Debug(m_driver,DebugInfo,"Hangup ACK on channel %d on span %d",ev.hangup.channel,m_span);
	    break;
	case PRI_EVENT_RESTART_ACK:
	    Debug(m_driver,DebugInfo,"Restart ACK on channel %d on span %d",ev.restartack.channel,m_span);
	    break;
	case PRI_EVENT_SETUP_ACK:
	    Debug(m_driver,DebugInfo,"Setup ACK on channel %d on span %d",ev.setup_ack.channel,m_span);
	    ackChan(ev.setup_ack.channel);
	    break;
	case PRI_EVENT_HANGUP_REQ:
	    Debug(m_driver,DebugInfo,"Hangup REQ on channel %d on span %d",ev.hangup.channel,m_span);
	    hangupChan(ev.hangup.channel,ev.hangup);
	    break;
	case PRI_EVENT_PROCEEDING:
	    Debug(m_driver,DebugInfo,"Call proceeding on channel %d on span %d",ev.proceeding.channel,m_span);
	    proceedingChan(ev.proceeding.channel);
	    break;
#ifdef PRI_EVENT_PROGRESS
	case PRI_EVENT_PROGRESS:
	    Debug(m_driver,DebugInfo,"Call progressing on channel %d on span %d",ev.proceeding.channel,m_span);
	    proceedingChan(ev.proceeding.channel);
	    break;
#endif
#ifdef PRI_EVENT_KEYPAD_DIGIT
	case PRI_EVENT_KEYPAD_DIGIT:
	    digitsChan(ev.digit.channel,ev.digit.digits);
	    break;
#endif
	default:
	    Debug(m_driver,DebugInfo,"Unhandled PRI event %d",ev.e);
    }
}

bool PriSpan::validChan(int chan) const
{
    return (chan > 0) && (chan <= m_nchans) && m_chans && m_chans[chan-1];
}

int PriSpan::findEmptyChan(int first, int last) const
{
    if (!m_ok)
	return -1;
    first -= m_offs;
    last -= m_offs;
    if (first < 0)
	first = 0;
    if (last > m_nchans-1)
	last = m_nchans-1;
    for (int i=first; i<=last; i++)
	if (m_chans[i] && !m_chans[i]->inUse())
	    return i+1;
    return -1;
}

PriChan *PriSpan::getChan(int chan) const
{
    return validChan(chan) ? m_chans[chan-1] : 0;
}

void PriSpan::restartChan(int chan, bool outgoing, bool force)
{
    if (chan < 0) {
	Debug(DebugInfo,"Restart request on entire span %d",m_span);
	return;
    }
    if (!validChan(chan)) {
	Debug(DebugInfo,"Restart request on invalid channel %d on span %d",chan,m_span);
	return;
    }
    if (force || !getChan(chan)->inUse()) {
	Debug(m_driver,DebugAll,"Restarting B-channel %d on span %d",chan,m_span);
	getChan(chan)->restart(outgoing);
    }
}

void PriSpan::ringChan(int chan, pri_event_ring &ev)
{
    if (chan == -1)
	chan = findEmptyChan();
    if (!validChan(chan)) {
	Debug(DebugInfo,"Ring on invalid channel %d on span %d",chan,m_span);
	::pri_hangup(pri(),ev.call,PRI_CAUSE_CHANNEL_UNACCEPTABLE);
	::pri_destroycall(pri(),ev.call);
	return;
    }
    Debug(m_driver,DebugInfo,"Ring on channel %d on span %d",chan,m_span);
    Debug(m_driver,DebugInfo,"caller='%s' callerno='%s' callingplan=%d",
	ev.callingname,ev.callingnum,ev.callingplan);
    Debug(m_driver,DebugInfo,"callednum='%s' redirectnum='%s' calledplan=%d",
	ev.callednum,ev.redirectingnum,ev.calledplan);
    Debug(m_driver,DebugInfo,"type=%d complete=%d format='%s'",
	ev.ctype,ev.complete,lookup(ev.layer1,dict_str2law,"unknown"));
    PriChan* c = getChan(chan);
    if (c)
	c->ring(ev);
}

void PriSpan::infoChan(int chan, pri_event_ring &ev)
{
    if (!validChan(chan)) {
	Debug(DebugInfo,"Info on invalid channel %d on span %d",chan,m_span);
	return;
    }
    Debug(m_driver,DebugInfo,"info on channel %d on span %d",chan,m_span);
    Debug(m_driver,DebugInfo,"caller='%s' callerno='%s' callingplan=%d",
	ev.callingname,ev.callingnum,ev.callingplan);
    Debug(m_driver,DebugInfo,"callednum='%s' redirectnum='%s' calledplan=%d",
	ev.callednum,ev.redirectingnum,ev.calledplan);
    getChan(chan)->gotDigits(ev.callednum,true);
}

void PriSpan::digitsChan(int chan, const char* digits)
{
    if (!validChan(chan)) {
	Debug(DebugInfo,"Digits on invalid channel %d on span %d",chan,m_span);
	return;
    }
    getChan(chan)->gotDigits(digits,false);
}

void PriSpan::hangupChan(int chan,pri_event_hangup &ev)
{
    if (!validChan(chan)) {
	Debug(DebugInfo,"Hangup on invalid channel %d on span %d",chan,m_span);
	return;
    }
    Debug(m_driver,DebugInfo,"Hanging up channel %d on span %d",chan,m_span);
    getChan(chan)->hangup(ev.cause);
}

void PriSpan::ackChan(int chan)
{
    if (!validChan(chan)) {
	Debug(DebugInfo,"ACK on invalid channel %d on span %d",chan,m_span);
	return;
    }
    Debug(m_driver,DebugInfo,"ACKnowledging channel %d on span %d",chan,m_span);
    getChan(chan)->setTimeout(0);
}

void PriSpan::answerChan(int chan)
{
    if (!validChan(chan)) {
	Debug(DebugInfo,"ANSWER on invalid channel %d on span %d",chan,m_span);
	return;
    }
    Debug(m_driver,DebugInfo,"ANSWERing channel %d on span %d",chan,m_span);
    getChan(chan)->answered();
}

void PriSpan::proceedingChan(int chan)
{
    if (!validChan(chan)) {
	Debug(DebugInfo,"Proceeding on invalid channel %d on span %d",chan,m_span);
	return;
    }
    Debug(m_driver,DebugInfo,"Extending timeout on channel %d on span %d",chan,m_span);
    getChan(chan)->extTimeout(120000000);
    Engine::enqueue(getChan(chan)->message("call.progress"));
}

void PriSpan::ringingChan(int chan)
{
    if (!validChan(chan)) {
	Debug(DebugInfo,"Ringing on invalid channel %d on span %d",chan,m_span);
	return;
    }
    Debug(m_driver,DebugInfo,"Extending timeout on channel %d on span %d",chan,m_span);
    getChan(chan)->extTimeout(120000000);
    Engine::enqueue(getChan(chan)->message("call.ringing"));
}

PriSource::PriSource(PriChan *owner, const char* format, unsigned int bufsize)
    : DataSource(format),
      m_owner(owner), m_buffer(0,bufsize)
{
    Debug(m_owner,DebugAll,"PriSource::PriSource(%p,'%s',%u) [%p]",owner,format,bufsize,this);
}

PriSource::~PriSource()
{
    Debug(m_owner,DebugAll,"PriSource::~PriSource() [%p]",this);
}

PriConsumer::PriConsumer(PriChan *owner, const char* format, unsigned int bufsize)
    : DataConsumer(format),
      m_owner(owner), m_buffer(0,bufsize)
{
    Debug(m_owner,DebugAll,"PriConsumer::PriConsumer(%p,'%s',%u) [%p]",owner,format,bufsize,this);
}

PriConsumer::~PriConsumer()
{
    Debug(m_owner,DebugAll,"PriConsumer::~PriConsumer() [%p]",this);
}

PriChan::PriChan(const PriSpan *parent, int chan, unsigned int bufsize)
    : Channel(parent->driver()),
      m_span(const_cast<PriSpan*>(parent)), m_chan(chan), m_ring(false),
      m_timeout(0), m_call(0), m_bufsize(bufsize)
{
    Debug(this,DebugAll,"PriChan::PriChan(%p,%d,%u) [%p]",parent,chan,bufsize,this);
    // I hate counting from one...
    m_abschan = m_chan+m_span->chan1()-1;
    m_isdn = true;
    m_address << m_span->span() << "/" << m_chan << "/" << m_abschan;
    status(chanStatus());
}

PriChan::~PriChan()
{
    Debug(this,DebugAll,"PriChan::~PriChan() [%p] %d",this,m_chan);
    hangup(PRI_CAUSE_NORMAL_UNSPECIFIED);
}

void PriChan::disconnected(bool final, const char *reason)
{
    Debugger debug("PriChan::disconnected()", " '%s' [%p]",reason,this);
    if (!final) {
	Message* m = message("chan.disconnected");
	m_targetid.clear();
	m->addParam("span",String(m_span->span()));
	m->addParam("channel",String(m_chan));
	m->addParam("reason",reason);
	Engine::enqueue(m);
    }
    m_span->lock();
    hangup(PRI_CAUSE_NORMAL_CLEARING);
    m_span->unlock();
}

bool PriChan::nativeConnect(DataEndpoint *peer)
{
    return false;
}

const char *PriChan::chanStatus() const
{
    if (m_ring)
	return "ringing";
    if (m_call)
	return m_timeout ? "calling" : "connected";
    return m_span->outOfOrder() ? "alarm" : "idle";
}

void PriChan::idle()
{
    if (m_timeout && (Time::now() > m_timeout)) {
	Debug(this,DebugWarn,"Timeout %s channel %s (%s)",
	    chanStatus(),id().c_str(),address().c_str());
	m_timeout = 0;
	hangup(PRI_CAUSE_RECOVERY_ON_TIMER_EXPIRE);
    }
}

void PriChan::restart(bool outgoing)
{
    disconnect("restart");
    closeData();
    if (outgoing)
	::pri_reset(m_span->pri(),m_chan);
}

void PriChan::closeData()
{
    m_span->lock();
    // remove all types of data nodes
    clearEndpoint();
    m_span->unlock();
}

bool PriChan::progress()
{
    if (!m_call) {
	Debug(this,DebugWarn,"Progress request on %s channel %s (%s)",
	    chanStatus(),id().c_str(),address().c_str());
	return false;
    }
    Debug(this,DebugInfo,"Progressing on %s (%s)",id().c_str(),address().c_str());
#ifdef PRI_PROGRESS
    ::pri_progress(m_span->pri(),m_call,m_chan,1);
#endif
    return true;
}

bool PriChan::ringing()
{
    if (!m_call) {
	Debug(this,DebugWarn,"Ringing request on %s channel %s (%s)",
	    chanStatus(),id().c_str(),address().c_str());
	return false;
    }
    Debug(this,DebugInfo,"Answering on %s (%s)",id().c_str(),address().c_str());
    ::pri_acknowledge(m_span->pri(),m_call,m_chan,1);
    return true;
}

bool PriChan::answer()
{
    if (!m_ring) {
	Debug(this,DebugWarn,"Answer request on %s channel %s (%s)",
	    chanStatus(),id().c_str(),address().c_str());
	return false;
    }
    m_ring = false;
    m_timeout = 0;
    status(chanStatus());
    Debug(this,DebugInfo,"Answering on %s (%s)",id().c_str(),address().c_str());
    ::pri_answer(m_span->pri(),m_call,m_chan,!m_isdn);
    return true;
}

void PriChan::goneUp()
{
    status(chanStatus());
}

void PriChan::hangup(int cause)
{
    if (!cause)
	cause = PRI_CAUSE_INVALID_MSG_UNSPECIFIED;
    const char *reason = pri_cause2str(cause);
    if (inUse())
	Debug(this,DebugInfo,"Hanging up %s (%s) in state %s: %s (%d)",
	    id().c_str(),address().c_str(),chanStatus(),reason,cause);
    timeout(0);
    maxcall(0);
    m_timeout = 0;
    m_targetid.clear();
    disconnect(lookup(cause,dict_str2cause,reason));
    closeData();
    m_ring = false;
    if (m_call) {
	::pri_hangup(m_span->pri(),m_call,cause);
	::pri_destroycall(m_span->pri(),m_call);
	m_call = 0;
	Message *m = message("chan.hangup");
	m->addParam("span",String(m_span->span()));
	m->addParam("channel",String(m_chan));
	m->addParam("reason",pri_cause2str(cause));
	Engine::enqueue(m);
    }
    m_answered = false;
    m_billid.clear();
    status(chanStatus());
}

void PriChan::answered()
{
    if (!m_call) {
	Debug(this,DebugWarn,"Answer detected on %s channel %s (%s)",
	    chanStatus(),id().c_str(),address().c_str());
	return;
    }
    m_timeout = 0;
    m_answered = true;
    status(chanStatus());
    Debug(this,DebugInfo,"Remote answered on %s (%s)",id().c_str(),address().c_str());
    maxcall(0);
    dataChanged();
    Message *m = message("call.answered");
    m->addParam("span",String(m_span->span()));
    m->addParam("channel",String(m_chan));
    Engine::enqueue(m);
}

void PriChan::gotDigits(const char *digits, bool overlapped)
{
    if (null(digits)) {
	Debug(this,DebugMild,"Received empty digits string in mode %s channel %s (%s)",
	    (overlapped ? "overlapped" : "keypad"),id().c_str(),address().c_str());
	return;
    }
    Message *m = message("chan.dtmf");
    m->addParam("span",String(m_span->span()));
    m->addParam("channel",String(m_chan));
    m->addParam("text",digits);
    if (overlapped)
	m->addParam("overlapped","yes");
    Engine::enqueue(m);
}

void PriChan::sendDigit(char digit)
{
    if (!m_call)
	return;
#ifdef PRI_KEYPAD_FACILITY_TX
    if (isAnswered()) {
	char buf[2];
	buf[0] = digit;
	buf[1] = '\0';
	::pri_keypad_facility(m_span->pri(),m_call,buf);
	return;
    }
#endif
    ::pri_information(m_span->pri(),m_call,digit);
}

bool PriChan::call(Message &msg, const char *called)
{
    if (m_span->outOfOrder()) {
	Debug(this,DebugMild,"Span %d is out of order, failing call",m_span->span());
	msg.setParam("error","offline");
	return false;
    }
    if (!called)
	called = msg.getValue("called");
    Debug(this,DebugInfo,"Calling '%s' on %s (%s)",
	called,id().c_str(),address().c_str());
    int complete = 1;
    if (m_span->overlapped()) {
	// if asked explicitely or number is too short mark it as incomplete
	if (msg.getBoolValue("overlapped",!called || (m_span->overlapped() < ::strlen(called))))
	    complete = 0;
    }
    int layer1 = msg.getIntValue("format",dict_str2law,m_span->layer1());
    hangup(PRI_CAUSE_PRE_EMPTED);
    setOutgoing(true);
    CallEndpoint *ch = static_cast<CallEndpoint*>(msg.userData());
    if (ch) {
	openData(lookup(layer1,dict_str2law),msg.getIntValue("cancelecho",dict_numtaps));
	if (connect(ch,msg.getValue("reason"))) {
	    msg.setParam("peerid",id());
	    callConnect(msg);
	}
	m_targetid = msg.getValue("id");
	msg.setParam("targetid",id());
    }
    else
	msg.userData(this);
    m_inband = msg.getBoolValue("dtmfinband",m_span->inband());
    m_detect = msg.getBoolValue("dtmfdetect",m_span->detect());
    char *caller = (char *)msg.getValue("caller");
    int callerplan = msg.getIntValue("callerplan",dict_str2dplan,m_span->dplan());
    char *callername = (char *)msg.getValue("callername");
    int callerpres = m_span->pres();
    String tmp = msg.getValue("screened");
    if (tmp.isBoolean())
	callerpres = (callerpres & ~SCREENING_BIT_MASK) |
	    (tmp.toBoolean() ? SCREENING_USER_PASSED : SCREENING_USER_NOT_SCREENED);
    tmp = msg.getValue("privacy");
    if (tmp && tmp.toBoolean(true))
	callerpres = (callerpres & ~PRESENTATION_BIT_MASK) | PRESENTATION_RESTRICTED;
    callerpres = msg.getIntValue("presentation",dict_str2pres,callerpres);
    int calledplan = msg.getIntValue("calledplan",dict_str2dplan,m_span->dplan());
    Debug(this,DebugAll,"Caller='%s' name='%s' plan=%s pres=%s, Called plan=%s",
	caller,callername,lookup(callerplan,dict_str2dplan),
	lookup(callerpres,dict_str2pres),lookup(calledplan,dict_str2dplan));
    m_call =::pri_new_call(span()->pri());
#ifdef PRI_DUMP_INFO
    struct pri_sr *req = ::pri_sr_new();
    ::pri_sr_set_bearer(req,0/*transmode*/,layer1);
    ::pri_sr_set_channel(req,m_chan,1/*exclusive*/,!m_isdn);
    ::pri_sr_set_caller(req,caller,callername,callerplan,callerpres);
    ::pri_sr_set_called(req,(char *)called,calledplan,complete);
    ::q931_setup(span()->pri(),m_call,req);
#else
    ::pri_call(m_span->pri(),m_call,0/*transmode*/,m_chan,1/*exclusive*/,!m_isdn,
	caller,callerplan,callername,callerpres,(char *)called,calledplan,layer1
    );
#endif
    setTimeout(30000000);
    status(chanStatus());
    setMaxcall(msg);
    Message* m = message("chan.startup",msg);
    m->setParam("caller",msg.getValue("caller"));
    m->setParam("called",msg.getValue("called"));
    m->setParam("billid",msg.getValue("billid"));
    m->addParam("span",String(m_span->span()));
    m->addParam("channel",String(m_chan));
    m->addParam("direction","outgoing");
    Engine::enqueue(m);
    return true;
}

void PriChan::ring(pri_event_ring &ev)
{
    q931_call *call = ev.call;
    if (!call) {
	hangup(PRI_CAUSE_WRONG_CALL_STATE);
	return;
    }

    setTimeout(180000000);
    setOutgoing(false);
    m_billid.clear();
    m_billid << Engine::runId() << "-" << allocId();
    m_call = call;
    m_ring = true;
    status(chanStatus());
#ifdef PRI_PROCEEDING_FULL
    ::pri_proceeding(m_span->pri(),m_call,m_chan,1);
#else
    // we signal ringing without media if the library doesn't know any better
    ::pri_acknowledge(m_span->pri(),m_call,m_chan,0);
#endif
    Message* m = message("chan.startup");
    m->addParam("span",String(m_span->span()));
    m->addParam("channel",String(m_chan));
    m->addParam("direction","incoming");
    Engine::enqueue(m);

    m_inband = m_span->inband();
    m_detect = m_span->detect();
    openData(lookup(ev.layer1,dict_str2law),0);

    m = message("call.preroute");
    m->addParam("span",String(m_span->span()));
    m->addParam("channel",String(m_chan));
    if (m_span->overlapped() && !ev.complete && (::strlen(ev.callednum) < m_span->overlapped())) {
	::pri_need_more_info(m_span->pri(),m_call,m_chan,!isISDN());
	m->addParam("overlapped","yes");
    }
    if (ev.callingnum[0])
	m->addParam("caller",ev.callingnum);
    if (ev.callednum[0])
	m->addParam("called",ev.callednum);
    switch (ev.callingpres & PRESENTATION_BIT_MASK) {
	case PRESENTATION_RESTRICTED:
	    m->addParam("privacy",String::boolText(true));
	    break;
	case PRESENTATION_ALLOWED:
	    m->addParam("privacy",String::boolText(false));
	    break;
    }
    switch (ev.callingpres & SCREENING_BIT_MASK) {
	case SCREENING_USER_PASSED:
	    m->addParam("screened","yes");
	    break;
	case SCREENING_USER_NOT_SCREENED:
	    m->addParam("screened","no");
	    break;
    }
    const char* dataLaw = "slin";
    switch (ev.layer1) {
	case PRI_LAYER_1_ALAW:
	    dataLaw = "alaw";
	    break;
	case PRI_LAYER_1_ULAW:
	    dataLaw = "mulaw";
	    break;
    }
    m->addParam("format",dataLaw);
    if (!startRouter(m))
	hangup(PRI_CAUSE_SWITCH_CONGESTION);
}

void PriChan::dataChanged()
{
}

void PriChan::callAccept(Message& msg)
{
    Debug(this,DebugAll,"PriChan::callAccept() [%p]",this);
    extTimeout(180000000);
    Channel::callAccept(msg);
}

void PriChan::callRejected(const char* error, const char* reason, const Message* msg)
{
    if (m_span->overlapped() && error && msg && msg->getBoolValue("overlapped")) {
	// call was using overlapped dialing, check for incomplete numbers
	if (!::strcmp(error,"incomplete")) {
	    ::pri_need_more_info(m_span->pri(),m_call,m_chan,!isISDN());
	    return;
	}
    }
    int cause = lookup(error,dict_str2cause,PRI_CAUSE_NETWORK_OUT_OF_ORDER);
    Channel::callRejected(error,reason,msg);
    hangup(cause);
}

bool PriChan::msgProgress(Message& msg)
{
    if (!progress())
	return true;
    return Channel::msgProgress(msg);
}

bool PriChan::msgRinging(Message& msg)
{
    if (!ringing())
	return true;
    return Channel::msgRinging(msg);
}

bool PriChan::msgAnswered(Message& msg)
{
    if (!answer())
	return true;
    dataChanged();
    return Channel::msgAnswered(msg);
}

bool PriChan::msgTone(Message& msg, const char* tone)
{
    if (null(tone))
	return false;
    if (m_inband && dtmfInband(tone))
	return true;
    // if we failed try to send as signalling anyway
    while (*tone)
	sendDigit(*tone++);
    return true;
}

bool PriChan::msgText(Message& msg, const char* text)
{
    return false;
}

bool PriChan::msgDrop(Message& msg, const char* reason)
{
    if (inUse()) {
	hangup(PRI_CAUSE_INTERWORKING);
	return true;
    }
    return false;
}


bool PriDriver::msgExecute(Message& msg, String& dest)
{
    Regexp r("^\\([^/]*\\)/\\?\\(.*\\)$");
    if (!dest.matches(r))
	return false;
    if (!msg.userData()) {
	Debug(DebugWarn,"Pri call found but no data channel!");
	return false;
    }
    String chan = dest.matchString(1);
    String num = dest.matchString(2);
    DDebug(this,DebugInfo,"Found call to pri chan='%s' name='%s'",
	chan.c_str(),num.c_str());
    PriChan *c = 0;

    r = "^\\([0-9]\\+\\)-\\([0-9]*\\)$";
    Lock lock(this);
    if (chan.matches(r))
	c = findFree(chan.matchString(1).toInteger(),
	    chan.matchString(2).toInteger(65535));
    else if ((chan[0] < '0') || (chan[0] > '9'))
	c = findFree(chan);
    else
	c = findFree(chan.toInteger(-1));

    if (c) {
	Debug(this,DebugInfo,"Will call '%s' on chan %s (%s)",
	    num.c_str(),c->id().c_str(),c->address().c_str());
	return c->call(msg,num);
    }
    else {
	Debug(this,DebugMild,"Found no free channel '%s'",chan.c_str());
	msg.setParam("error","congestion");
    }
    return false;
}

void PriDriver::dropAll()
{
    Debug(this,DebugInfo,"Dropping all %s calls",name().c_str());
    lock();
    const ObjList *l = &m_spans;
    for (; l; l=l->next()) {
	PriSpan *s = static_cast<PriSpan *>(l->get());
	if (s) {
	    for (int n=1; n<=s->chans(); n++) {
		PriChan *c = s->getChan(n);
		if (c)
		    c->hangup(PRI_CAUSE_INTERWORKING);
	    }
	}
    }
    unlock();
}

u_int8_t PriDriver::s_bitswap[256];

bool PriDriver::s_init = true;


PriDriver::PriDriver(const char* name)
    : Driver(name,"fixchans")
{
    varchan(false);
    if (s_init) {
	s_init = false;
	for (unsigned int c = 0; c <= 255; c++) {
	    u_int8_t v = 0;
	    for (int b = 0; b <= 7; b++)
		if (c & (1 << b))
		    v |= (0x80 >> b);
	    s_bitswap[c] = v;
	}
	::pri_set_error(pri_err_cb);
	::pri_set_message(pri_msg_cb);
    }
}

PriDriver::~PriDriver()
{
}

PriSpan* PriDriver::findSpan(int chan)
{
    const ObjList *l = &m_spans;
    for (; l; l=l->next()) {
	PriSpan *s = static_cast<PriSpan *>(l->get());
	if (s && s->belongs(chan))
	    return s;
    }
    return 0;
}

PriChan* PriDriver::findFree(int first, int last)
{
    DDebug(this,DebugAll,"PriDriver::findFree(%d,%d)",first,last);
    // see first if we have an exact request
    if (first > 0 && last < 0) {
	PriSpan *s = findSpan(first);
	return s ? s->getChan(first - s->chan1() + 1) : 0;
    }
    if (last < 0)
	last = 65535;
    const ObjList *l = &m_spans;
    for (; l; l=l->next()) {
	PriSpan *s = static_cast<PriSpan *>(l->get());
	if (s) {
	    Debug(this,DebugAll,"Searching for free chan in span %d [%p]",
		s->span(),s);
	    int c = s->findEmptyChan(first,last);
	    if (c > 0)
		return s->getChan(c);
	    if (s->belongs(last))
		break;
	}
    }
    return 0;
}

PriChan* PriDriver::findFree(const String& group)
{
    ObjList* lst = m_groups.find(group);
    if (!lst)
	return 0;
    ChanGroup* grp = static_cast<ChanGroup*>(lst->get());
    if (!grp)
	return 0;
    int first = 0, last = 0, used = 0;
    grp->getRange(first,last,used);
    PriChan* c = (used < last) ? findFree(used+1,last) : 0;
    if (!c)
	c = (first <= used) ? findFree(first,used) : 0;
    if (!c)
	return 0;
    grp->setUsed(c->absChan());
    return c;
}

bool PriDriver::isBusy() const
{
    const ObjList *l = &m_spans;
    for (; l; l=l->next()) {
	PriSpan *s = static_cast<PriSpan *>(l->get());
	if (s) {
	    for (int n=1; n<=s->chans(); n++) {
		PriChan *c = s->getChan(n);
		if (c && c->inUse())
		    return true;
	    }
	}
    }
    return false;
}

void PriDriver::statusModule(String& str)
{
    Driver::statusModule(str);
    String sp;
    const ObjList *l = &m_spans;
    for (; l; l=l->next()) {
	PriSpan *s = static_cast<PriSpan *>(l->get());
	if (s)
	    sp.append(String(s->chans()),"|");
    }
    str.append("spans=",",") << m_spans.count();
    if (sp)
	str.append("spanlen=",",") << sp;
    str.append("groups=",",") << m_groups.count();
}

void PriDriver::statusParams(String& str)
{
    Driver::statusParams(str);
    int i = 0;
    int u = 0;
    const ObjList *l = &m_spans;
    for (; l; l=l->next()) {
	PriSpan *s = static_cast<PriSpan *>(l->get());
	if (s && !s->outOfOrder()) {
	    for (int n=1; n<=s->chans(); n++) {
		PriChan *c = s->getChan(n);
		if (c) {
		    if (c->inUse())
			u++;
		    else
			i++;
		}
	    }
	}
    }
    str.append("idle=",",") << i;
    str.append("used=",",") << u;
}

void PriDriver::netParams(Configuration& cfg, const String& sect, int chans, int* netType, int* swType, int* dChan)
{
    if (netType)
	*netType = cfg.getIntValue(sect,"type",dict_str2type,PRI_NETWORK);
    if (swType)
	*swType = cfg.getIntValue(sect,"swtype",dict_str2switch,PRI_SWITCH_UNKNOWN);
    if (dChan) {
	int dchan = -1;
	// guess where we may have a D channel
	switch (chans) {
	    case 3:  // BRI ISDN
		dchan = 3;
		break;
	    case 24: // T1 with CCS
		dchan = 24;
		break;
	    case 31: // EuroISDN
		dchan = 16;
		break;
	}
	*dChan = cfg.getIntValue(sect,"dchan", dchan);
    }
}

void PriDriver::init(const char* configName)
{
    Configuration cfg(Engine::configFile(configName));
    s_buflen = cfg.getIntValue("general","buflen",160);
    if (!m_spans.count()) {
	int chan1 = 1;
	for (int span = 1;;span++) {
	    String sect("span ");
	    sect << span;
	    int num = cfg.getIntValue(sect,"chans",-1);
	    if (num < 0)
		break;
	    if (num) {
		chan1 = cfg.getIntValue(sect,"first",chan1);
		if (cfg.getBoolValue(sect,"enabled",true))
		    createSpan(this,span,chan1,num,cfg,sect);
		chan1 += num;
	    }
	}
	if (m_spans.count()) {
	    Debug(DebugInfo,"Created %d spans",m_spans.count());
	    unsigned int n = cfg.sections();
	    for (unsigned int i = 0; i < n; i++) {
		const NamedList* sect = cfg.getSection(i);
		if (!sect)
		    continue;
		String s(*sect);
		if (s.startSkip("group") && sect->getBoolValue("enabled",true))
		    m_groups.append(new ChanGroup(s,sect,chan1-1));
	    }
	    if (m_groups.count())
		Debug(DebugInfo,"Created %d groups",m_groups.count());
	    setup();
	}
	else
	    Debug(DebugWarn,"No spans created, module not activated");
    }
}

/* vi: set ts=8 sw=4 sts=4 noet: */
