/* stream_test.c
 *
 * Test reading  from FT2232H in synchronous FIFO mode.
 *
 * The FT2232H must supply data due to an appropriate circuit
 *
 * To check for skipped block with appended code, 
 *     a structure as follows is assumed
 * 1* uint32_t num (incremented in 0x4000 steps)
 * 3* uint32_t dont_care
 *
 * After start, data will be read in streaming until the program is aborted
 * Progess information wil be printed out
 * If a filename is given on the command line, the data read will be
 * written to that file
 *
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <getopt.h>
#include <signal.h>
#include <errno.h>
#include <ftdi.h>
void check_outfile(char *);

static FILE *outputFile;

static int check = 1;
static int exitRequested = 0;
/*
 * sigintHandler --
 *
 *    SIGINT handler, so we can gracefully exit when the user hits ctrl-C.
 */

static void
sigintHandler(int signum)
{
   exitRequested = 1;
}

static void
usage(const char *argv0)
{
   fprintf(stderr,
           "Usage: %s [options...] \n"
           "Test streaming read from FT2232H\n"
           "[-P string] only look for product with given string\n"
           "[-n] don't check for special block structure\n"
           "\n"
           "If some filename is given, write data read to that file\n"
           "Progess information is printed each second\n"
           "Abort with ^C\n"
           "\n"
           "Options:\n"
           "\n"
           "Copyright (C) 2009 Micah Dowty <micah@navi.cx>\n"
           "Adapted for use with libftdi (C) 2010 Uwe Bonnes <bon@elektron.ikp.physik.tu-darmstadt.de>\n",
           argv0);
   exit(1);
}

static uint32_t start = 0;
static uint32_t offset = 0;
static uint64_t blocks = 0;
static uint32_t skips = 0;
static uint32_t n_err = 0;
static int
readCallback(uint8_t *buffer, int length, FTDIProgressInfo *progress, void *userdata)
{
   if (length)
   {
       if (check)
       {
           int i,rem;
           uint32_t num;
           for (i= offset; i<length-16; i+=16)
           {
               num = *(uint32_t*) (buffer+i);
               if (start && (num != start +0x4000))
               {
                   uint32_t delta = ((num-start)/0x4000)-1;
                   fprintf(stderr, "Skip %7d blocks from 0x%08x to 0x%08x at blocks %10llu\n",
                           delta, start -0x4000, num, (unsigned long long)blocks);
                   n_err++;
                   skips += delta;
               }
               blocks ++;
               start = num;
           }
           rem = length -i;
           if (rem >3)
           {
               num = *(uint32_t*) (buffer+i);
               if (start && (num != start +0x4000))
               {
                   uint32_t delta = ((num-start)/0x4000)-1;
                   fprintf(stderr, "Skip %7d blocks from 0x%08x to 0x%08x at blocks %10llu\n",
                           delta, start -0x4000, num, (unsigned long long) blocks);
                   n_err++;
                   skips += delta;
               }
               start = num;
           }
           else if (rem)
               start += 0x4000;
           if (rem != 0)
           {
               blocks ++;
               offset = 16-rem;
           }
       }
       if (outputFile)
       {
           if (fwrite(buffer, length, 1, outputFile) != 1)
           {
               perror("Write error");
               return 1;
           }
       }
   }
   if (progress)
   {
       fprintf(stderr, "%10.02fs total time %9.3f MiB captured %7.1f kB/s curr rate %7.1f kB/s totalrate %d dropouts\n",
               progress->totalTime,
               progress->current.totalBytes / (1024.0 * 1024.0),
               progress->currentRate / 1024.0,
               progress->totalRate / 1024.0,
               n_err);
   }
   return exitRequested ? 1 : 0;
}

int main(int argc, char **argv)
{
   struct ftdi_context *ftdi;
   int err, c;
   FILE *of = NULL;
   char const *outfile  = 0;
   outputFile =0;
   exitRequested = 0;
   char *descstring = NULL;
   int option_index;
   static struct option long_options[] = {{NULL},};

   while ((c = getopt_long(argc, argv, "P:n", long_options, &option_index)) !=- 1)
       switch (c) 
       {
       case -1:
           break;
       case 'P':
           descstring = optarg;
           break;
       case 'n':
           check = 0;
           break;
       default:
           usage(argv[0]);
       }
   
   if (optind == argc - 1)
   {
       // Exactly one extra argument- a dump file
       outfile = argv[optind];
   }
   else if (optind < argc)
   {
       // Too many extra args
       usage(argv[0]);
   }
   
   if ((ftdi = ftdi_new()) == 0)
   {
       fprintf(stderr, "ftdi_new failed\n");
       return EXIT_FAILURE;
   }
   
   if (ftdi_set_interface(ftdi, INTERFACE_A) < 0)
   {
       fprintf(stderr, "ftdi_set_interface failed\n");
       ftdi_free(ftdi);
       return EXIT_FAILURE;
   }
   
   if (ftdi_usb_open_desc(ftdi, 0x0403, 0x6010, descstring, NULL) < 0)
   {
       fprintf(stderr,"Can't open ftdi device: %s\n",ftdi_get_error_string(ftdi));
       ftdi_free(ftdi);
       return EXIT_FAILURE;
   }
   
   /* A timeout value of 1 results in may skipped blocks */
   if(ftdi_set_latency_timer(ftdi, 2))
   {
       fprintf(stderr,"Can't set latency, Error %s\n",ftdi_get_error_string(ftdi));
       ftdi_usb_close(ftdi);
       ftdi_free(ftdi);
       return EXIT_FAILURE;
   }
   
/*   if(ftdi_usb_purge_rx_buffer(ftdi) < 0)
   {
       fprintf(stderr,"Can't rx purge\n",ftdi_get_error_string(ftdi));
       return EXIT_FAILURE;
       }*/
   if (outfile)
       if ((of = fopen(outfile,"w+")) == 0)
           fprintf(stderr,"Can't open logfile %s, Error %s\n", outfile, strerror(errno));
   if (of)
       if (setvbuf(of, NULL, _IOFBF , 1<<16) == 0)
           outputFile = of;
   signal(SIGINT, sigintHandler);
   
   err = ftdi_readstream(ftdi, readCallback, NULL, 8, 256);
   if (err < 0 && !exitRequested)
       exit(1);
   
   if (outputFile) {
       fclose(outputFile);
       outputFile = NULL;
   }
   fprintf(stderr, "Capture ended.\n");
   
   if (ftdi_set_bitmode(ftdi,  0xff, BITMODE_RESET) < 0)
   {
       fprintf(stderr,"Can't set synchronous fifo mode, Error %s\n",ftdi_get_error_string(ftdi));
       ftdi_usb_close(ftdi);
       ftdi_free(ftdi);
       return EXIT_FAILURE;
   }
   ftdi_usb_close(ftdi);
   ftdi_free(ftdi);
   signal(SIGINT, SIG_DFL);
   if (check && outfile)
   {
       if ((outputFile = fopen(outfile,"r")) == 0)
       {
           fprintf(stderr,"Can't open logfile %s, Error %s\n", outfile, strerror(errno));
           ftdi_usb_close(ftdi);
           ftdi_free(ftdi);
           return EXIT_FAILURE;
       }
       check_outfile(descstring);
       fclose(outputFile);
   }
   else if (check)
       fprintf(stderr,"%d errors of %llu blocks (%Le), %d (%Le) blocks skipped\n",
               n_err, (unsigned long long) blocks, (long double)n_err/(long double) blocks,
               skips, (long double)skips/(long double) blocks);
   exit (0);
}

void check_outfile(char *descstring)
{
    if(strcmp(descstring,"FT2232HTEST") == 0)
    {
       char buf0[1024];
       char buf1[1024];
       char bufr[1024];
       char *pa, *pb, *pc;
       unsigned int num_lines = 0, line_num = 1;
       int err_count = 0;
       unsigned int num_start, num_end;

       pa = buf0;
       pb = buf1;
       pc = buf0;
       if(fgets(pa, 1023, outputFile) == NULL)
       {
           fprintf(stderr,"Empty output file\n");
           return;
       }
       while(fgets(pb, 1023, outputFile) != NULL)
       {
           num_lines++;
           unsigned int num_save = num_start;
           if( sscanf(pa,"%6u%94s%6u",&num_start, bufr,&num_end) !=3)
           {
               fprintf(stdout,"Format doesn't match at line %8d \"%s",
                       num_lines, pa);
               err_count++;
               line_num = num_save +2;
           }
           else
           {
               if ((num_start+1)%100000 != num_end)
               {
                   if (err_count < 20)
                       fprintf(stdout,"Malformed line %d \"%s\"\n", 
                               num_lines, pa);
                   err_count++;
               }
               else if(num_start != line_num)
               {
                   if (err_count < 20)
                       fprintf(stdout,"Skipping from %d to %d\n", 
                               line_num, num_start);
                   err_count++;
                  
               }
               line_num = num_end;
           }
           pa = pb;
           pb = pc;
           pc = pa;
       }
       if(err_count)
           fprintf(stdout,"\n%d errors of %d data sets %f\n", err_count, num_lines, (double) err_count/(double)num_lines);
       else
           fprintf(stdout,"No errors for %d lines\n",num_lines);
   }
    else if(strcmp(descstring,"LLBBC10") == 0)
    { 
        uint32_t block0[4];
        uint32_t block1[4];
        uint32_t *pa = block0;
        uint32_t *pb = block1;
        uint32_t *pc = block0;
        uint32_t start= 0;
        uint32_t nread = 0;
        int n_shown = 0;
        int n_errors = 0;
        if (fread(pa, sizeof(uint32_t), 4,outputFile) < 4)
        {
            fprintf(stderr,"Empty result file\n");
            return;
        }
        while(fread(pb, sizeof(uint32_t), 4,outputFile) != 0)
        {
            blocks++;
            nread =  pa[0];
            if(start>0 && (nread != start))
            {
                if(n_shown < 30)
                {
                    fprintf(stderr, "Skip %7d blocks from 0x%08x to 0x%08x at blocks %10llu \n",
                            (nread-start)/0x4000, start -0x4000, nread, (unsigned long long) blocks);
                    n_shown ++;
                }
                n_errors++;
            }
            else if (n_shown >0) 
                n_shown--; 
            start = nread + 0x4000;
            pa = pb;
            pb = pc;
            pc = pa;
        }
        if(n_errors)
            fprintf(stderr, "%d blocks wrong from %llu blocks read\n",
                    n_errors, (unsigned long long) blocks);
        else
            fprintf(stderr, "%llu blocks all fine\n", (unsigned long long) blocks);
    }
}
