#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>

void showstats();
uint8_t getbit();
void checkbit(uint8_t bit);
void printhelp();
void corrupt_stream();
void corrupt_stream2();
void corrupt_bit(uint8_t bit);

uint16_t scrambleLFSR=0; //Shorter is better: The shorter the LSFR, the quicker it can recover from errors.
uint32_t errcount=0,bitcount=0;
uint32_t recbytes=0;
float corrupt_prob=0.01; //Used in test2 mode.
uint8_t msbfirst=0;

int main(int argc, char *argv[]){
  uint8_t transmit=0; //If false, assume receive mode.
  uint32_t n=0,showevery=256;
 
  for(n=0;n<argc;n++){
    if(!strcmp("-t", argv[n]))
      transmit=1;
    if(!strcmp("-msb", argv[n]))
      msbfirst=1;
    if(!strcmp("-h", argv[n]))
      printhelp();
    if(!strcmp("-n", argv[n]) && n<argc)
      showevery=atoi(argv[n+1]);
    if(!strcmp("-test1", argv[n]))
      {corrupt_stream();return(0);}
    if(!strcmp("-test2", argv[n]) && n<argc){
      srand((unsigned int)time(NULL));
      corrupt_prob=atof(argv[n+1]);
      corrupt_stream2();return(0);
    }
  }
  if(transmit){
    getbit();getbit();getbit(); //This is to aid in testing the program itsself by forcing it to start unsyncronised.
    if(msbfirst)
      do{
        uint8_t  out=(getbit()<<7 )|
                     (getbit()<<6)|
                     (getbit()<<5)|
                     (getbit()<<4)|
                     (getbit()<<3)|
                     (getbit()<<2)|
                     (getbit()<<1)|
                     (getbit()   );
        putchar(out);
      }while(1);
    else do{
      uint8_t  out=(getbit()   )|
                   (getbit()<<1)|
                   (getbit()<<2)|
                   (getbit()<<3)|
                   (getbit()<<4)|
                   (getbit()<<5)|
                   (getbit()<<6)|
                   (getbit()<<7);
      putchar(out);
    }while(1);
    return(0);
  }

  //Must be in receive. This mode is a little more complicated.
  if(msbfirst)
    do{
      uint8_t in=getchar();
      if(feof(stdin))
        break;
      checkbit((in&0x80)>>7);
      checkbit((in&0x40)>>6);
      checkbit((in&0x20)>>5);
      checkbit((in&0x10)>>4);
      checkbit((in&0x08)>>3);
      checkbit((in&0x04)>>2);
      checkbit((in&0x02)>>1);
      checkbit((in&0x01)   );
      recbytes++;
      if(recbytes==showevery)
        showstats();
    }while(1);
  else do{
    uint8_t in=getchar();
    if(feof(stdin))
      break;
    checkbit((in&0x01)   );
    checkbit((in&0x02)>>1);
    checkbit((in&0x04)>>2);
    checkbit((in&0x08)>>3);
    checkbit((in&0x10)>>4);
    checkbit((in&0x20)>>5);
    checkbit((in&0x40)>>6);
    checkbit((in&0x80)>>7);
    recbytes++;
    if(recbytes==showevery)
      showstats();
  }while(1);
}

void corrupt_stream(){
  if(msbfirst){
    fprintf(stderr,"Berty: Error, -msb specified but test mode does not support -msb.\n");
    return;
  }
  uint16_t counter=0;
  do{
    uint8_t abyte=getchar();
    if(feof(stdin))
      break;
    if(!(counter++%512))
      abyte=abyte^0x01;
    putchar(abyte);
  }while(1);
}

void corrupt_stream2(){
  if(msbfirst){
    fprintf(stderr,"Berty: Error, -msb specified but test mode does not support -msb.\n");
    return;
  }
  do{
    uint8_t in=getchar();
    if(feof(stdin))
      break;
    corrupt_bit((in&0x01)<<7);
    corrupt_bit((in&0x02)<<6);
    corrupt_bit((in&0x04)<<5);
    corrupt_bit((in&0x08)<<4);
    corrupt_bit((in&0x10)<<3);
    corrupt_bit((in&0x20)<<2);
    corrupt_bit((in&0x40)<<1);
    corrupt_bit((in&0x80)   );
  }while(1);
}

uint8_t output_bit_counter=0;
uint8_t shiftregister;
void corrupt_bit(uint8_t bit){
  float p=((float)rand())/RAND_MAX; //0 to 1
  if(corrupt_prob>p){
    p=((float)rand())/RAND_MAX;
    if(p<0.90)
      bit=!bit; //Corrupt this bit.
    if(p<0.95)
      return; //Drop this bit.
    else
      corrupt_bit(bit); //Duplicate this bit.
  }

  shiftregister=(shiftregister>>1)|bit;
  output_bit_counter++;
  if(output_bit_counter==8){
    output_bit_counter=0;
    putchar(shiftregister);
  }

}

void showstats(){
  printf("Got %u bits, %u errs. BER %f\n", bitcount, errcount, errcount/(float)bitcount);
  recbytes=0;errcount=0;bitcount=0;
  fflush(stdout);
}

uint8_t getbit(){
/* Relic of testing using the V34 scrambler. Turned out a much shorter one is better.
 uint32_t bits=scrambleLFSR&0x00420000;
 scrambleLFSR=scrambleLFSR<<1;
 uint8_t newbit=1;
 if(bits==0x00400000 || bits==0x00020000)
   newbit=0;
 else
   scrambleLFSR=scrambleLFSR|1;

*/
 uint16_t bits=scrambleLFSR&0x0042;
 scrambleLFSR=scrambleLFSR<<1;
 uint8_t newbit=1;
 if(bits==0x0040 || bits==0x0002)
   newbit=0;
 else
   scrambleLFSR=scrambleLFSR|1;
 return(newbit);
}

uint8_t skip=0;
void checkbit(uint8_t bit){
  uint16_t bits=scrambleLFSR&0x0042;
  scrambleLFSR=(scrambleLFSR<<1)|bit;
  if(bits==0x0040 || bits==0x0002)
    bit=!bit;
  if(skip){
    skip--;
  }else{
    bitcount++;
    if(!bit){
      errcount++;
      skip=7; //Prevents error-multiplication effect of the scrambler and propagation of bit dup/skip errors.
    }
  }
}

void printhelp(){
  printf("Berty is a very simple utility for conducting Bit Error Ratio tests. It does this by using a linear feedback shift register to form a self-syncronising scrambler fed using an all-ones sequence. The resulting pseudorandom sequence can then be sent over a communication channel under test. At the receiving end another instance of berty then receives the stream, carries out the other half of the self-syncronising scrambler, and counts the 'zero' symbols - each of which indicates a single bit error. The BER is then calculated from these. It's not perfectly accurate - it has to ignore 24 bits following any error to allow for resyncronisation - but it's probably the best you'll get without out-of-channel syncronisation.\n  In addition to measuring bit error rate, it can act as a simulated noisy channel in order to test error detection and correction protocols. This function will operate only in LSB-first mode.\n");
  printf("Berty was designed as testing tool for use in the design and optimisation of audio-frequency modems for use in amateur radio, but may be applied to any bit-orientated communications channel.\n\n");
  printf("    -t         Transmit mode - default is receive.\n    -msb       Send/receive bytes msb-first - default is lsb-first)\n    -n <int>   Show statistics every 8*n received bits. Default is 256, or 2Kib.\n    -test1     Test mode - input to output, but corrupting every 300th bit.\n    -test2 <p> Another test mode, a bit more aggressive - it'll randomly flip, drop or duplicate bits, with flips being ten times more common than duplications and drops combined - a simplistic but reasonable model of a noisy channel. The p parameter, 0-1, sets the probability of an error - 0.1 is a very poor channel.\n\nTo test berty, simply pipe one instance into another via the data-corrupting test mode: \"berty -t|berty -test1|berty\" - the simulated channel should read a single error in every other block.\n\nBerty is a bit error ratio tester, not a byte tester - if you are using byte-orientated communication channel, you will receive more accurate results if you disable byte-boundry syncronisation features when possible. On minimodem, this is done with \"--startbits 0 --stopbits 0.0\"\n");

  exit(1);
}
