%{
#include "lfsr.h"
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>
#include <tcl.h>
#include "utils.h"
#include "tcl.h"

FILE* lfsr_yyin;

static int lfsr_yylex(){
  int i = fgetc(lfsr_yyin);
  while(!feof(lfsr_yyin) && isspace(i))
      i = fgetc(lfsr_yyin);
  return i;
}

#define lfsr_yyerror(x) printf(x)

#define PRIM_POLY_MAX 63
t_POLY prim_polys[PRIM_POLY_MAX];

%}

%union{
  int number;
  t_POLY polynom;
  unsigned long l_number;
}

%type <polynom>  polynom
%type <number>   element    
%type <number>   lastelement
%type <l_number> elements   
%type <number>   digit      
%type <number>   number     
%%


polynoms: polynom
        | polynoms polynom;

polynom: element delimiter elements delimiter lastelement {
  $$ = (1UL << $1) | $3 | $5;
//  $$.width = $1;
  if($1 < PRIM_POLY_MAX){
    memcpy(&prim_polys[$1], &$$, sizeof($$));
  }
};

elements: element {$$ = (1UL << $1);}
        | elements delimiter element {$$ = $1 | (1UL << $3);};

element : 'x''^'number {$$ = $3;}
        | 'x' {$$ = 1;};

lastelement: '1' {$$ = 1UL;};

delimiter: '+';

number: digit {$$ = $1;}
      | number digit {$$ = $1*10 + $2;};

digit : '0' {$$ = 0;}
      | '1' {$$ = 1;}
      | '2' {$$ = 2;}
      | '3' {$$ = 3;}
      | '4' {$$ = 4;}
      | '5' {$$ = 5;}
      | '6' {$$ = 6;}
      | '7' {$$ = 7;}
      | '8' {$$ = 8;}
      | '9' {$$ = 9;};

%%


t_POLY LFSR_Reverse(const t_POLY polynom) {
  t_POLY forward = polynom;
  t_POLY reverse;

  reverse = 0;

  for(int i = 0; i <= LFSR_GetWidth(polynom); i++){
    reverse <<= 1;
    reverse |= ((forward % 2) & 1);
    forward >>= 1;
  }
  
  printf("Reverse LFSR from ");
  LFSR_PrintPoly(polynom);
  printf(" to ");
  LFSR_PrintPoly(reverse);
  putchar('\n');
  return reverse;
}

unsigned long  LFSR_Next(const t_POLY polynom, const unsigned long d) {
  //Here we must calculate Fibonnaci LFSR
  int fb = 0;
  int width = LFSR_GetWidth(polynom);
  for(int i = 0; i < width ; i++){
    fb = (fb ^ ((d >> i) & (polynom >> i))) & 1;
  }
  //Forward
  return (d >> 1) | ((unsigned long)fb << (width - 1));
}

unsigned long  LFSR_Prev(const t_POLY polynom, const unsigned long d) {
  //Here we must calculate Fibonnaci LFSR
  int fb = 0;
  int width = LFSR_GetWidth(polynom);
  for(int i = 0; i < width ; i++){
    fb = (fb ^ ((d >> i) & (polynom >> (i+1)))) & 1;
  }
  return ((d << 1) | (unsigned long)fb ) & ((1UL << width)-1UL);
}

char* LFSR_PrintPoly(const t_POLY polynom) {
  t_POLY p = polynom;

  int p_width = LFSR_GetWidth(p);
  int size = 100;
  char* string = malloc(size * sizeof(char));
  int index = 0;

  p <<= (sizeof(p)*8)-p_width-1;

  for(int i = p_width; i >= 0; i--){
    if(p & POLY_MSB) {
      if(i != p_width) {
        memcpy(string + index, " + ", 3);
        index += 3;
      }
      if(i != 0)
        index += sprintf(string + index, "x^%d", i);
      else {
        string[index] = '1';
        index++;
      }
    }
    p <<= 1;
    if (size - index < 20){
      size += 100;
      string = realloc(string, size);
      C_ERROR((string == NULL), "Can't expand allocated memory");
    }
  }
  return string;
}

void LFSR_ReadPolys(FILE* f){
  lfsr_yyin = f;
  lfsr_yyparse();
}

const t_POLY LFSR_GetPrimPoly(int width){
  assert(width > 1);
  assert(width < PRIM_POLY_MAX);
  return prim_polys[width];
}

const int LFSR_GetWidth(const t_POLY polynom){
  int length = sizeof(t_POLY)*8;
  for(t_POLY p = polynom; 
      (p & POLY_MSB) == 0UL;
      p <<= 1) length--;
  return length - 1;
}

static int LFSR_TclPoly(ClientData clientData, Tcl_Interp* interp, int objc, Tcl_Obj* CONST * objv){
  if(objc < 2){
    Tcl_WrongNumArgs(interp, objc, objv, "usage: poly [readFile, getPrimitive, getReverse, print, getWidth]");
    return TCL_ERROR;
  }

  char* subCommand = Tcl_GetStringFromObj(objv[1], NULL);
  if(strcmp(subCommand, "readFile") == 0){
    FILE* f;
    OPEN(f, Tcl_GetStringFromObj(objv[2], NULL), "r");
    LFSR_ReadPolys(f);
    fclose(f);
    return TCL_OK;
  }
  else if (strcmp(subCommand, "getPrimitive") == 0){
    int length;
    TCL_CATCH(Tcl_GetIntFromObj(interp, objv[2], &length), "Usage: poly getPrimitive [length as integer]");
    const t_POLY polynom = LFSR_GetPrimPoly(length);
 
    Tcl_SetLongResult(interp, polynom);   
    return TCL_OK;
  }
  else if (strcmp(subCommand, "getReverse") == 0){
    t_POLY polynom;
    TCL_CATCH(Tcl_GetLongFromObj(interp, objv[2], (long*)&polynom), "Usage: poly reverse [polynom as long]");
    t_POLY reverse = LFSR_Reverse(polynom);
    Tcl_SetLongResult(interp, reverse);
    return TCL_OK;
  }
  else if (strcmp(subCommand, "print") == 0){
    t_POLY polynom;
    TCL_CATCH(Tcl_GetLongFromObj(interp, objv[2], (long*)&polynom), "Usage: poly reverse [polynom as long]");
    Tcl_SetStringResult(interp, LFSR_PrintPoly(polynom));
    return TCL_OK; 
  }
  else {
    Tcl_SetStringResult(interp, "Unknown subcommand");
    return TCL_ERROR;
  }
  return TCL_ERROR;
}

void LFSR_TclRegister(Tcl_Interp* tcl){
  TCL_FUNCTION("poly", LFSR_TclPoly);  
}
