/**
 * zapchan.cpp
 * This file is part of the YATE Project http://YATE.null.ro
 *
 * Zapata telephony driver
 *
 * 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>

#ifdef _WINDOWS
#error This module is not for Windows
#else

extern "C" {
#ifdef NEW_ZAPTEL_LOCATION
#define __LINUX__
#include <zaptel/zaptel.h>
#else
#include <linux/zaptel.h>
#endif
};

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

#ifndef _WINDOWS
#include <sys/ioctl.h>
#include <fcntl.h>
#endif

#ifndef ZT_EVENT_DTMFDIGIT
#ifdef ZT_EVENT_DTMFDOWN
#define ZT_EVENT_DTMFDIGIT ZT_EVENT_DTMFDOWN
#else
#define ZT_EVENT_DTMFDIGIT 0
#endif
#endif

#ifndef ZT_EVENT_PULSEDIGIT
#define ZT_EVENT_PULSEDIGIT 0
#endif

using namespace TelEngine;
namespace { // anonymous

/* Zaptel formats */
static TokenDict dict_str2ztlaw[] = {
    { "slin", -1 },
    { "default", ZT_LAW_DEFAULT },
    { "mulaw", ZT_LAW_MULAW },
    { "alaw", ZT_LAW_ALAW },
    { 0, -2 }
};

class ZapChan;

class ZapSpan : public PriSpan, public Thread
{
    friend class ZapDriver;
public:
    virtual ~ZapSpan();
    virtual void run();
    inline int train() const
	{ return m_train; }

private:
    ZapSpan(struct pri *_pri, PriDriver* driver, int span, int first, int chans, int dchan, Configuration& cfg, const String& sect, int fd);
    int m_fd;
    int m_train;
};

class ZapSource : public PriSource, public Thread
{
public:
    ZapSource(ZapChan *owner, const char* format, unsigned int bufsize);
    ~ZapSource();
    virtual void run();
private:
    DataBlock m_data;
};

class ZapConsumer : public PriConsumer
{
public:
    ZapConsumer(ZapChan *owner, const char* format, unsigned int bufsize);
    ~ZapConsumer();
    virtual void Consume(const DataBlock &data, unsigned long tStamp);
private:
    unsigned int m_bufsize;
    DataErrors m_overruns;
};

class ZapChan : public PriChan
{
    friend class ZapSource;
    friend class ZapConsumer;
public:
    ZapChan(const PriSpan *parent, int chan, unsigned int bufsize);
    virtual ~ZapChan();
    virtual bool openData(const char* format, int echoTaps);
    virtual void closeData();
    inline int fd() const
	{ return m_fd; }
    inline int law() const
	{ return m_law; }
protected:
    virtual void dataChanged();
private:
    int m_fd;
    int m_law;
    bool m_train;
};

class ZapDriver : public PriDriver
{
    friend class PriSpan;
    friend class ZapHandler;
public:
    ZapDriver();
    virtual ~ZapDriver();
    virtual void initialize();
    virtual PriSpan* createSpan(PriDriver* driver, int span, int first, int chans, Configuration& cfg, const String& sect);
    virtual PriChan* createChan(const PriSpan* span, int chan, unsigned int bufsize);
};

INIT_PLUGIN(ZapDriver);

static int zt_get_event(int fd)
{
    /* Avoid the silly zt_getevent which ignores a bunch of events */
    int j = 0;
    if (::ioctl(fd, ZT_GETEVENT, &j) == -1)
	return -1;
    return j;
}

static int zt_open_dchan(int channo, int bsize = 1024, int nbufs = 16)
{
    DDebug(&__plugin,DebugInfo,"Opening zap d-channel %d with %d x %d buffers",channo,nbufs,bsize);
    int fd = ::open("/dev/zap/channel", O_RDWR, 0600);
    if (fd < 0) {
	Debug(&__plugin,DebugGoOn,"Failed to open device: error %d: %s",errno,::strerror(errno));
	return -1;
    }
    if (::ioctl(fd,ZT_SPECIFY,&channo) == -1) {
	Debug(&__plugin,DebugGoOn,"Failed to specify chan %d: error %d: %s",channo,errno,::strerror(errno));
	::close(fd);
	return -1;
    }
    ZT_PARAMS par;
    if (::ioctl(fd, ZT_GET_PARAMS, &par) == -1) {
	Debug(&__plugin,DebugGoOn,"Failed to get params of chan %d: error %d: %s",channo,errno,::strerror(errno));
	::close(fd);
	return -1;
    }
    if (par.sigtype != ZT_SIG_HDLCFCS) {
	Debug(&__plugin,DebugGoOn,"Channel %d is not in HDLC/FCS mode",channo);
	::close(fd);
	return -1;
    }
    ZT_BUFFERINFO bi;
    bi.txbufpolicy = ZT_POLICY_IMMEDIATE;
    bi.rxbufpolicy = ZT_POLICY_IMMEDIATE;
    bi.numbufs = nbufs;
    bi.bufsize = bsize;
    if (::ioctl(fd, ZT_SET_BUFINFO, &bi) == -1)
	Debug(&__plugin,DebugWarn,"Could not set buffering on %d: error %d: %s",channo,errno,::strerror(errno));
    return fd;
}

static int zt_open_bchan(int channo, bool subchan, unsigned int blksize)
{
    DDebug(&__plugin,DebugInfo,"Opening zap b-channel %d with block size=%d",channo,blksize);
    int fd = ::open(subchan ? "/dev/zap/pseudo" : "/dev/zap/channel",O_RDWR|O_NONBLOCK);
    if (fd < 0) {
	Debug(&__plugin,DebugGoOn,"Failed to open device: error %d: %s",errno,::strerror(errno));
	return -1;
    }
    if (channo) {
	if (::ioctl(fd, subchan ? ZT_CHANNO : ZT_SPECIFY, &channo)) {
	    Debug(&__plugin,DebugGoOn,"Failed to specify chan %d: error %d: %s",channo,errno,::strerror(errno));
	    ::close(fd);
	    return -1;
	}
    }
    if (blksize) {
	if (::ioctl(fd, ZT_SET_BLOCKSIZE, &blksize) == -1) {
	    Debug(&__plugin,DebugGoOn,"Failed to set block size %d: error %d: %s",blksize,errno,::strerror(errno));
	    ::close(fd);
	    return -1;
	}
    }
    return fd;
}

static bool zt_set_law(int fd, int law)
{
    if (law < 0) {
	int linear = 1;
	if (::ioctl(fd, ZT_SETLINEAR, &linear) != -1)
	    return true;
    }
    else
	if (::ioctl(fd, ZT_SETLAW, &law) != -1)
	    return true;
    DDebug(&__plugin,DebugInfo,"Failed to set law %d: error %d: %s",law,errno,::strerror(errno));
    return false;
}

static bool zt_echo_cancel(int fd, int taps)
{
    int tmp = 1;
    if (taps && (::ioctl(fd, ZT_AUDIOMODE, &tmp) < 0)) {
	Debug(&__plugin,DebugNote,"Failed to set audio mode: error %d: %s",errno,::strerror(errno));
	return false;
    }
    if (::ioctl(fd, ZT_ECHOCANCEL, &taps) < 0) {
	Debug(&__plugin,DebugMild,"Failed to set %d echo cancellation taps: error %d: %s",taps,errno,::strerror(errno));
	return false;
    }
    return true;
}

static bool zt_echo_train(int fd, int train = 400)
{
    if ((train > 0) && (::ioctl(fd, ZT_ECHOTRAIN, &train) < 0)) {
	Debug(&__plugin,DebugMild,"Failed to start echo trainig for %d ms: error %d: %s",train,errno,::strerror(errno));
	return false;
    }
    return true;
}

static bool zt_dtmf_detect(int fd, bool detect)
{
#ifdef ZT_TONEDETECT
    int tdetect = detect ? ZT_TONEDETECT_ON | ZT_TONEDETECT_MUTE : 0;
    if (::ioctl(fd, ZT_TONEDETECT, &tdetect) >= 0)
	return true;
#else
    // pretend enabling fails, disabling succeeds
    if (!detect)
	return true;
    errno = ENOSYS;
#endif
    return false;
}

ZapSpan::ZapSpan(struct pri *_pri, PriDriver* driver, int span, int first, int chans, int dchan, Configuration& cfg, const String& sect, int fd)
    : PriSpan(_pri,driver,span,first,chans,dchan,cfg,sect), Thread("ZapSpan"),
      m_fd(fd), m_train(0)
{
    Debug(m_driver,DebugAll,"ZapSpan::ZapSpan() [%p]",this);
    m_train = cfg.getIntValue(sect,"echotrain",cfg.getIntValue("general","echotrain",400));
}

ZapSpan::~ZapSpan()
{
    Debug(m_driver,DebugAll,"ZapSpan::~ZapSpan() [%p]",this);
    m_ok = false;
    ::close(m_fd);
    m_fd = -1;
}

void ZapSpan::run()
{
    Debug(m_driver,DebugAll,"ZapSpan::run() [%p]",this);
    fd_set rdfds;
    fd_set errfds;
    for (;;) {
	FD_ZERO(&rdfds);
	FD_SET(m_fd, &rdfds);
	FD_ZERO(&errfds);
	FD_SET(m_fd, &errfds);
	struct timeval tv;
	tv.tv_sec = 0;
	tv.tv_usec = 100;
	int sel = ::select(m_fd+1, &rdfds, NULL, &errfds, &tv);
	Thread::check();
	if (!sel)
	    runEvent(true);
	else if (sel > 0) {
	    if (FD_ISSET(m_fd, &errfds)) {
		int zev = zt_get_event(m_fd);
		if (zev)
		    Debug(DebugInfo,"Zapata event %d on span %d",zev,span());
	    }
	    if (FD_ISSET(m_fd, &rdfds))
		runEvent(false);
	}
	else if (errno != EINTR)
	    Debug("ZapSpan",DebugGoOn,"select() error %d: %s",
		errno,::strerror(errno));
    }
}

ZapSource::ZapSource(ZapChan *owner, const char* format, unsigned int bufsize)
    : PriSource(owner,format,bufsize), Thread("ZapSource")
{
    Debug(m_owner,DebugAll,"ZapSource::ZapSource(%p) [%p]",owner,this);
}

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

void ZapSource::run()
{
    int rd = 0;
    for (;;) {
	Thread::yield(true);
	int fd = static_cast<ZapChan*>(m_owner)->fd();
	if (fd != -1) {
	    rd = ::read(fd,m_buffer.data(),m_buffer.length());
	    XDebug(m_owner,DebugAll,"ZapSource read %d bytes [%p]",rd,this);
	    if (rd > 0)
		Forward(m_buffer);
	    else if (rd < 0) {
		if ((errno != EAGAIN) && (errno != EINTR)) {
		    int zev = zt_get_event(fd);
		    if (zev) {
			Debug(m_owner,DebugInfo,"ZapSource event %d [%p]",zev,this);
			// driver-decoded digit arrived
			if (zev & (ZT_EVENT_DTMFDIGIT | ZT_EVENT_PULSEDIGIT)) {
			    char buf[2];
			    buf[0] = zev & 0xff;
			    buf[1] = '\0';
			    m_owner->gotDigits(buf);
			}
		    }
		    else
			break;
		}
	    }
	}
	else
	    break;
    }
    Debug(m_owner,DebugWarn,"ZapSource at EOF (read %d) [%p]",rd,this);
    // TODO: find a better way of dealing with this abnormal condition
    for (;;)
	Thread::yield(true);
}

ZapConsumer::ZapConsumer(ZapChan *owner, const char* format, unsigned int bufsize)
    : PriConsumer(owner,format,bufsize), m_bufsize(bufsize)
{
    Debug(m_owner,DebugAll,"ZapConsumer::ZapConsumer(%p) [%p]",owner,this);
}

ZapConsumer::~ZapConsumer()
{
    Debug(m_owner,DebugAll,"ZapConsumer::~ZapConsumer() [%p]",this);
    if (m_overruns.events())
	Debug(m_owner,DebugMild,"Consumer had %u overruns (%lu bytes)",
	    m_overruns.events(),m_overruns.bytes());
}

void ZapConsumer::Consume(const DataBlock &data, unsigned long tStamp)
{
    int fd = static_cast<ZapChan*>(m_owner)->fd();
    XDebug(DebugAll,"ZapConsumer fd=%d datalen=%u",fd,data.length());
    if ((fd != -1) && !data.null()) {
	if (m_buffer.length()+data.length() <= m_bufsize*4)
	    m_buffer += data;
	else {
	    m_overruns.update(data.length());
	    DDebug(m_owner,DebugAll,"ZapConsumer skipped %u bytes, buffer is full",data.length());
	}
	if (m_buffer.null())
	    return;
	if (m_buffer.length() >= m_bufsize) {
	    int wr = ::write(fd,m_buffer.data(),m_bufsize);
	    if (wr < 0) {
		if ((errno != EAGAIN) && (errno != EINTR))
		    Debug(DebugGoOn,"ZapConsumer write error %d: %s",
			errno,::strerror(errno));
	    }
	    else {
		if ((unsigned)wr != m_bufsize)
		    Debug(m_owner,DebugInfo,"ZapConsumer short write, %d of %u bytes",wr,m_bufsize);
		    m_buffer.cut(-wr);
	    }
	}
    }
}

ZapChan::ZapChan(const PriSpan *parent, int chan, unsigned int bufsize)
    : PriChan(parent,chan,bufsize), m_fd(-1), m_law(-1), m_train(false)
{
}

ZapChan::~ZapChan()
{
    closeData();
}

bool ZapChan::openData(const char* format, int echoTaps)
{
    m_fd = zt_open_bchan(m_abschan,false,m_bufsize);
    if (m_fd == -1)
	return false;
    int defLaw = ZT_LAW_ALAW;
    if (m_span->chans() == 24)
	defLaw = ZT_LAW_MULAW;
    defLaw = lookup(format,dict_str2ztlaw,defLaw);
    if (zt_set_law(m_fd,defLaw)) {
	m_law = defLaw;
	format = lookup(m_law,dict_str2ztlaw,"unknown");
	Debug(this,DebugInfo,"Opened Zap channel %d, law is: %s",m_abschan,format);
    }
    if (zt_echo_cancel(m_fd,echoTaps) && echoTaps)
	m_train = true;
    if (!zt_dtmf_detect(m_fd,m_detect)) {
	Debug(this,m_detect ? DebugFail : DebugMild,
	    "Failed to %s DTMF detection: %s (%d)",
	    m_detect ? "enable" : "disable",::strerror(errno),errno);
    }
    ZapSource* src = new ZapSource(this,format,m_bufsize);
    setSource(src);
    src->startup();
    src->deref();
    setConsumer(new ZapConsumer(this,format,m_bufsize));
    getConsumer()->deref();
    return true;
}

void ZapChan::dataChanged()
{
    if (m_train && zt_echo_train(m_fd,static_cast<ZapSpan*>(span())->train()))
	Debug(this,DebugCall,"Started echo canceller training [%p]",this);
}

void ZapChan::closeData()
{
    PriChan::closeData();
    m_train = false;
    if (m_fd != -1) {
	::close(m_fd);
	m_fd = -1;
    }
}

PriSpan* ZapDriver::createSpan(PriDriver* driver, int span, int first, int chans, Configuration& cfg, const String& sect)
{
    Debug(this,DebugAll,"ZapDriver::createSpan(%p,%d,%d,%d) [%p]",driver,span,first,chans,this);
    int netType = -1;
    int swType = -1;
    int dchan = -1;
    netParams(cfg,sect,chans,&netType,&swType,&dchan);
    if (dchan < 0)
	return 0;
    int fd = zt_open_dchan(dchan+first-1);
    if (fd < 0)
	return 0;
#if defined(PRI_NEW_SET_API) && defined(BRI_NETWORK_PTMP)
    // Klaus-Peter Junghanns broke this one too
    pri* p = ::pri_new(fd,netType,swType,span);
#else
    pri* p = ::pri_new(fd,netType,swType);
#endif
    if (!p)
	return 0;
    ZapSpan *zs = new ZapSpan(p,driver,span,first,chans,dchan,cfg,sect,fd);
    zs->startup();
    return zs;
}

PriChan* ZapDriver::createChan(const PriSpan* span, int chan, unsigned int bufsize)
{
    Debug(this,DebugAll,"ZapDriver::createChan(%p,%d,%u) [%p]",span,chan,bufsize,this);
    return new ZapChan(span,chan,bufsize);
}

ZapDriver::ZapDriver()
    : PriDriver("zap")
{
    Debug(DebugAll,"Loaded module ChanZap");
}

ZapDriver::~ZapDriver()
{
    Debug(DebugAll,"Unloading module ChanZap");
}

void ZapDriver::initialize()
{
    Debug(DebugAll,"Initializing module ChanZap");
    init("zapchan");
}

}; // anonymous namespace

#endif /* _WINDOWS */

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