/*
 * sliptest - A simple test program to investigate slips and dropouts in Zaptel channels.
 *
 * sliptest.c
 *
 * Written by Steve Underwood <steveu@coppice.org>
 *
 * Copyright (C) 2005 Steve Underwood
 *
 * All rights reserved.
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id$
 */

#define _ISOC9X_SOURCE  1
#define _ISOC99_SOURCE  1

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <limits.h>
#include <stdio.h>
#include <stdarg.h>
#include <math.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/ioctl.h>

#include <linux/zaptel.h>

#include <tiffio.h>
#include <spandsp.h>

/* A simple test program to investigate frame slips and drops in Zaptel channels.

   The principle is to look for a peak in the cross-correlation of a  tranmitted
   signal and its received echo. For an analogue circuit the echo can simply be
   the natural echo of the hybrid on the card under test. That is, don't connect
   anything to the port, and let the energy spilled back from the DAC to the ADC
   by the hybrid on the card correlate with the transmitted signal.
   
   Using something like a sine wave creates ambiguity about the real lag - you don't
   know exactly how many cycles long the pipe is. We use AWGN, so the sequence does not
   repeat. We track the peak correlation over 320ms (16 x 20ms blocks), so we should
   definitely find the one true lag. The pipe through the buffers, and the audio delay
   should never approach that.
   
   The measured lag should be very stable. If it is a pretty clear sign the pipe length
   is changing. This would be a bad thing!
*/

#define FALSE 0
#define TRUE (!FALSE)

#define BLOCK_LEN   160
#define MAX_BLOCK   16


int16_t in_buf[MAX_BLOCK*BLOCK_LEN];
int16_t out_buf[MAX_BLOCK*BLOCK_LEN];

int in_step = 0;
int out_step = 0;

awgn_state_t noise_source;

static int cross_correlate(int16_t in[], int16_t out[], int pos)
{
    int i;
    int j;
    int k;
    float temp;
    float scale;
    float max;
    int maxpos;
    int start;

    start = pos - BLOCK_LEN;
    if (start < 0)
        start += MAX_BLOCK*BLOCK_LEN;
    max = -9999999999999999.0;
    maxpos = pos - BLOCK_LEN;
    for (k = start;  k != pos;  k = (k > 0)  ?  k - 1  :  MAX_BLOCK*BLOCK_LEN - 1)
    {
        temp = 0.0;
        for (i = 0;  i < BLOCK_LEN;  i++)
        {
            j = i + k;
            if (j >= MAX_BLOCK*BLOCK_LEN)
                j -= MAX_BLOCK*BLOCK_LEN;
            temp += out[j]*in[i];
        }
        if (temp > max)
        {
            max = temp;
            maxpos = k;
        }
    }
    pos -= maxpos;
    if (pos < 0)
        pos += MAX_BLOCK*BLOCK_LEN;
    return pos;
}
/*- End of function --------------------------------------------------------*/

static int channel_open(char *dev)
{
    struct zt_bufferinfo bi;
    struct zt_gains g;
    int x;
    int fd;
    int i;
    int chan;
    int linear;

    if ((chan = atoi(dev)) > 0)
    {
        if ((fd = open("/dev/zap/channel", O_RDWR | O_NONBLOCK)) < 0)
            return -1;
        /*endif*/
        if (ioctl(fd, ZT_SPECIFY, &chan))
        {
            x = errno;
            close(fd);
            errno = x;
            return -1;
        }
        /*endif*/
    }
    else
    {
        if ((fd = open(dev, O_RDWR | O_NONBLOCK)) < 0)
            return -1;
        /*endif*/
    }
    /*endif*/
    if (ioctl(fd, ZT_GET_BUFINFO, &bi) < 0)
    {
        x = errno;
        close(fd);
        errno = x;
        return -1;
    }
    /*endif*/
    bi.txbufpolicy = ZT_POLICY_IMMEDIATE;
    bi.rxbufpolicy = ZT_POLICY_IMMEDIATE;
    bi.numbufs = 4;
    bi.bufsize = BLOCK_LEN;
    if (ioctl(fd, ZT_SET_BUFINFO, &bi) < 0)
    {
        x = errno;
        close(fd);
        errno = x;
        return -1;
    }
    /*endif*/
    if (ioctl(fd, ZT_CHANNO, &chan))
    {
        x = errno;
        close(fd);
        errno = x;
        return -1;
    }
    /*endif*/
    /* Set default gains */
    g.chan = 0;
    for (i = 0;  i < 256;  i++)
    {
        g.rxgain[i] = i;
        g.txgain[i] = i;
    }
    if (ioctl(fd, ZT_SETGAINS, &g) < 0)
    {
        x = errno;
        close(fd);
        errno = x;
        return -1;
    }
    /*endif*/
    linear = 1;
    if (ioctl(fd, ZT_SETLINEAR, &linear))
        return -1;
    /*endif*/
    return fd;
}
/*- End of function --------------------------------------------------------*/

int main(int argc, char *argv[])
{
    int res;
    int len;
    int xlen;
    int maxlen;
    int i;
    int x;
    int ch;
    int best;
    ZT_SPANINFO zi;
    int fd;
    
    if (argc < 2)
    {
        fprintf(stderr, "No channel specified\n");
        exit(2);
    }
    if ((fd = channel_open(argv[1])) < 0)
    {
        fprintf(stderr, "Error opening channel '%s'\n", argv[1]);
        exit(2);
    }
    awgn_init_dbm0(&noise_source, 1234567, -6);

    for (;;)
    {
        //x = ZT_IOMUX_SIGEVENT | ZT_IOMUX_READ | ZT_IOMUX_WRITE | ZT_IOMUX_WRITEEMPTY | ZT_IOMUX_NOWAIT;
        x = ZT_IOMUX_SIGEVENT | ZT_IOMUX_READ | ZT_IOMUX_WRITE | ZT_IOMUX_NOWAIT;
        if ((res = ioctl(fd, ZT_IOMUX, &x)))
        {
            fprintf(stderr, "Error at ZT_IOMUX\n");
            exit(2);
        }
        /*endif*/
        if (x == 0)
            continue;
        /*endif*/
        if ((x & ZT_IOMUX_SIGEVENT))
        {
            res = ioctl(fd, ZT_GETEVENT, &x);
            if (res == 0  &&  x != 0)
            {
                switch (x)
                {
                case ZT_EVENT_ALARM:
                case ZT_EVENT_NOALARM:
                    memset(&zi, 0, sizeof(zi));
                    zi.spanno = 0;
                    if ((res = ioctl(fd, ZT_SPANSTAT, &zi)) < 0)
                    {
                        fprintf(stderr, "Error at ZT_SPANSTAT\n");
                        break;
                    }
                    /*endif*/
                    fprintf(stderr, "Alarms - 0x%X\n", zi.alarms);
                    break;
                default:
                    break;
                }
                /*endswitch*/
            }
            /*endif*/
            continue;
        }
        /*endif*/
        if ((x & ZT_IOMUX_READ))
        {
            if (in_step >= MAX_BLOCK - 1)
                in_step = 0;
            else
                in_step++;
            if ((res = read(fd, in_buf + in_step*BLOCK_LEN, sizeof(int16_t)*BLOCK_LEN)) < 0)
            {
                if (errno == ELAST)
                {
                    /* This means there is some out of band stuff to deal with - alarms,
                       CAS bits signaling changes, etc. */
                }
                else if (errno != EAGAIN)
                {
                    fprintf(stderr, "Error at read\n");
                    exit(2);
                }
                /*endif*/
                continue;
            }
            /*endif*/
            if (res != sizeof(int16_t)*BLOCK_LEN)
            {
                fprintf(stderr, "Error at read %d bytes\n", res);
                exit(2);
            }
            /* Cross-correlate the audio */
            best = cross_correlate(in_buf + in_step*BLOCK_LEN, out_buf, out_step*BLOCK_LEN);
            printf("%d\n", best);
            continue;
        }
        /*endif*/
        if ((x & ZT_IOMUX_WRITE))
        {
            /* Cook up some audio */
            if (out_step >= MAX_BLOCK - 1)
                out_step = 0;
            else
                out_step++;
            for (i = out_step*BLOCK_LEN;  i < (out_step + 1)*BLOCK_LEN;  i++)
                out_buf[i] = awgn(&noise_source);
            xlen = write(fd, out_buf + out_step*BLOCK_LEN, sizeof(int16_t)*BLOCK_LEN);
            if (xlen != sizeof(int16_t)*BLOCK_LEN)
            {
                fprintf(stderr, "Error at write\n");
                exit(2);
            }
            /*endif*/
            continue;
        }
        /*endif*/
        if ((x & ZT_IOMUX_WRITEEMPTY))
        {
            continue;
        }
        /*endif*/
    }
    /*endfor*/
}
/*- End of function --------------------------------------------------------*/
/*- End of file ------------------------------------------------------------*/
