#ifdef __KERNEL__
#include <linux/string.h>
#include <linux/cuecat_lib.h>
#include <linux/cuecat_scancode2ascii.h>
#else
#include <string.h>
#include "cuecat_lib.h"
#include "cuecat_scancode2ascii.h"
#endif



/* Definitions */
#define KEY_RELEASE    0x80
#define CHARACTER_DECODING_ERROR  -1
#define GOT_CHARACTER             -2
#define GOT_ALT_F10               -3



/* Global variables */
/* Scancodes to ascii table */
unsigned char scancode2ascii[128]=SCANCODE2ASCII_VALS;

/* States and var for the barcode decoder */
char global_barcode_decoder_state=NO_BARCODE_DECODING_IN_PROGRESS;
char nb_dots_read=0;
int nb_codes_read=0;
char triplet_encoded_char_counter=0;
char current_coded_triplet_chars;
char coded_triplet_chars[4];
char ALT_F10_early_notice;

/* States and vars for the scancode decoder */
char got_shift_press=0;
char got_alt_press=0;
char got_letter_press=0;
char got_letter_release=0;
char temp_read_char;
char prev_read_char;
char read_char;



/* Function prototypes */
char cuecat_decode_scancode(unsigned char scancode);
char cuecat_decode_triplet(char *coded_triplet,char *decoded_triplet);
void cuecat_reset_barcode_decoder(void);
void cuecat_reset_scancode_decoder(void);
void cuecat_reset_triplet_char_decoder(void);
void do_barcode_special_cases(char *cuecat_id,
                              char *barcode_type,
                              char *barcode);



/* Functions */
/* Read a scancode, update the decoder's state machine and determine the barcode
   values, completion state ... */
char cuecat_decode_barcode(unsigned char scancode,
                           char *cuecat_id,
                           char *barcode_type,
                           char *barcode)
{
  int i;
  char decoded_triplet[3];

  switch(cuecat_decode_scancode(scancode))	/* Feed scancode to decoder */
  {
  case 0:
    if(ALT_F10_early_notice)			/* Special ALT-F10 half-baked
						   scancode sequence makes us
						   lie to the calling function*/
      return(POSSIBLE_BARCODE_DECODING_IN_PROGRESS);
    return(global_barcode_decoder_state);	/* The scancode decoder reported
						   nothing special yet. Exit
						   exit quietly. */
    break;

  case CHARACTER_DECODING_ERROR:		/* That scancode can't belong to
						   a CueCat character stream */
    cuecat_reset_barcode_decoder();
    return(global_barcode_decoder_state);
    break;

  case GOT_CHARACTER:				/* Decoder got a valid char */
    switch(read_char)
    {
    case '.':					/* We got a dot */
      if(global_barcode_decoder_state==NO_BARCODE_DECODING_IN_PROGRESS)
      {
        cuecat_reset_barcode_decoder();		/* We haven't received */
        return(global_barcode_decoder_state);	/* ALT-F10 yet */
      }

      switch(nb_dots_read)			/* How many dots have we got */
      {						/* already ? */
      case 0:					/* No dot read yet */
        if(nb_codes_read!=0)
        {
          cuecat_reset_barcode_decoder();
          return(global_barcode_decoder_state);	/* We're not supposed to have
						   got codes yet */
        }

        nb_dots_read++;
        global_barcode_decoder_state=POSSIBLE_BARCODE_DECODING_IN_PROGRESS;
        return(global_barcode_decoder_state);
        break;

      case 1:					/* We have 1 dot already, so
						   this one should end the
						   CueCat device ID */
        if(nb_codes_read!=CUECAT_DEVICE_ID_LEN)
        {
          cuecat_reset_barcode_decoder();
          return(global_barcode_decoder_state);
        }

        /* We got the CueCat device ID okay and the dot is at the right place */
	cuecat_id[CUECAT_DEVICE_ID_LEN]=0;	/* Terminate the string */

        nb_codes_read=0;
        nb_dots_read++;
        return(global_barcode_decoder_state);
        break;

      case 2:					/* We have 2 dots already, so
						   this one should end the
						   barcode type code */
        if(nb_codes_read!=CUECAT_BARCODE_TYPE_LEN)
        {
          cuecat_reset_barcode_decoder();
          return(global_barcode_decoder_state);
        }

        /* We got the barcode type code okay and the dot is at the right place*/
	barcode_type[CUECAT_BARCODE_TYPE_LEN]=0;/* Terminate the string */

        nb_codes_read=0;
        nb_dots_read++;
        return(global_barcode_decoder_state);
        break;

      case 3:					/* We have 3 dots already, so
						   this one should end the
						   barcode data */
        if(!nb_codes_read)			/* A barcode's length cannot be
						   null */
        {
          cuecat_reset_barcode_decoder();
          return(global_barcode_decoder_state);
        }

        /* Convert any remaining coded characters */
        if(current_coded_triplet_chars)
        {
          /* Pad the unfinished coded triplet sequence with zeros */
          for(i=current_coded_triplet_chars;i<4;i++)
            coded_triplet_chars[i]='a';		/* 'a'==0 */

          /* Convert this last coded triplet */
          if(cuecat_decode_triplet(coded_triplet_chars,decoded_triplet)==
             CHARACTER_DECODING_ERROR)
          {
            cuecat_reset_barcode_decoder();
            return(global_barcode_decoder_state);/* This is not a CueCat
  						    triplet */
          }

          /* Store this last decoded triplet */
          for(i=0;i<3;i++)
            barcode[nb_codes_read+i]=decoded_triplet[i];
          nb_codes_read+=3;
        }

        /* We got the barcode */
	  /* Terminate the string */
        switch(current_coded_triplet_chars)
        {
        case 0:					/* Last code is complete */
        case 1:					/* Last code only has 1 char :
						   this case shouldn't exist.
						   If it happens, it's wrong! */
	  barcode[nb_codes_read]=0;
          break;
        case 2:					/* Last code only has 2 chars */
	  barcode[nb_codes_read-2]=0;
          break;
        case 3:					/* Last code only has 3 chars */
	  barcode[nb_codes_read-1]=0;
          break;
        default:				/* We don't know about this :
						   this one cannot come from
						   a CueCat */
          cuecat_reset_barcode_decoder();
          return(global_barcode_decoder_state);
          break;
        }

        nb_codes_read=0;
        nb_dots_read++;
        return(global_barcode_decoder_state);
        break;

      case 4:					/* We have 4 dots already, so
						   this one cannot come from
						   a CueCat */
        cuecat_reset_barcode_decoder();
        return(global_barcode_decoder_state);
        break;

      default:					/* This state is not possible,
						   this is abnormal (?) */
        cuecat_reset_barcode_decoder();
        return(BARCODE_DECODER_ERROR);
        break;
      }
      break;

    case '\r':					/* We got a carriage-return */
      if(global_barcode_decoder_state==POSSIBLE_BARCODE_DECODING_IN_PROGRESS &&
         nb_dots_read==4 &&
         nb_codes_read==0 &&
         prev_read_char=='.')
      {
        /* Do special treatment on some barcodes */
        do_barcode_special_cases(cuecat_id,barcode_type,barcode);

        cuecat_reset_barcode_decoder();
        return(BARCODE_DECODED);
      }
      else
      {
        cuecat_reset_barcode_decoder();
        return(global_barcode_decoder_state);
      }
      break;
  
    default:					/* Any other valid character */
      if(global_barcode_decoder_state!=POSSIBLE_BARCODE_DECODING_IN_PROGRESS ||
         nb_dots_read==0 ||
         nb_dots_read==4 ||
         (nb_dots_read==1 && nb_codes_read>=CUECAT_DEVICE_ID_LEN) ||
         (nb_dots_read==2 && nb_codes_read>=1))
      {
        cuecat_reset_barcode_decoder();
        return(global_barcode_decoder_state);	/* We aren't decoding anything
  						   yet, or we shouldn't be
  						   decoding anything, or we
  						   shouldn't decode anymore */
      }
  
      /* Read and store encoded triplet chars */
      coded_triplet_chars[(int)current_coded_triplet_chars]=read_char;
      current_coded_triplet_chars=(current_coded_triplet_chars+1) % 4;
      if(!current_coded_triplet_chars)
      {
        /* Try to decode the triplet */
        if(cuecat_decode_triplet(coded_triplet_chars,decoded_triplet)==
           CHARACTER_DECODING_ERROR)
        {
          cuecat_reset_barcode_decoder();
          return(global_barcode_decoder_state);/* This is not a CueCat
  						    triplet */
        }

        switch(nb_dots_read)
        {
        case 1:					/* We're decoding the cuecat
						   device ID */
          if(nb_codes_read>CUECAT_DEVICE_ID_LEN-3)
          {
            cuecat_reset_barcode_decoder();
            return(global_barcode_decoder_state);/* Too many triplets to come
  						    from the CueCat */
          }

          /* Store the decoded triplet */
          for(i=0;i<3;i++)
            cuecat_id[nb_codes_read+i]=decoded_triplet[i];
          nb_codes_read+=3;
          break;

        case 2:					/* We're decoding the barcode
						   type */
          if(nb_codes_read>CUECAT_BARCODE_TYPE_LEN-3)
          {
            cuecat_reset_barcode_decoder();
            return(global_barcode_decoder_state);/* Too many triplets to come
  						    from the CueCat */
          }

          /* Store the decoded triplet */
          for(i=0;i<3;i++)
            barcode_type[nb_codes_read+i]=decoded_triplet[i];
          nb_codes_read+=3;
          break;

        case 3:					/* We're decoding the barcode */
          /* Store the decoded triplet */
          for(i=0;i<3;i++)
            barcode[nb_codes_read+i]=decoded_triplet[i];
          nb_codes_read+=3;
          break;
        }
      }
      return(global_barcode_decoder_state);
      break;
    }
    break;
  
  case GOT_ALT_F10:				/* We got ALT-F10 */
    if(global_barcode_decoder_state!=NO_BARCODE_DECODING_IN_PROGRESS)
      cuecat_reset_barcode_decoder();
    else
    {
      cuecat_reset_barcode_decoder();
      global_barcode_decoder_state=POSSIBLE_BARCODE_DECODING_IN_PROGRESS;
    }
  
    return(global_barcode_decoder_state);
    break;

  default:					/* This state is not possible,
						   this is abnormal (?) */
    cuecat_reset_barcode_decoder();
    return(BARCODE_DECODER_ERROR);
    break;
  }
}



/* Decode a 4-character encoded triplet. A million thanks to Colin Cross for the
   the original algorith, and to Michael for the once-only table initialization
   that I had missed */
char cuecat_decode_triplet(char *coded_triplet,char *decoded_triplet)
{
  unsigned long int n=0;
  int i;
  int c;

  /* First convert all 4 chars in coded_triplet into their index in
     following sequence (c), and then use the first 6 bits of each as a 24
     bit number (n)
     abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+- */
  for (i=0;i<4;i++) 
  {
    if (coded_triplet[i] >= 'a' && coded_triplet[i] <= 'z')
      c = coded_triplet[i] - 'a';
    else if (coded_triplet[i] >= 'A' && coded_triplet[i] <= 'Z')
        c = coded_triplet[i] - 'A' + 26;
    else if (coded_triplet[i] >= '0' && coded_triplet[i] <= '9')
      c = coded_triplet[i] - '0' + 52;
    else if (coded_triplet[i] == '+')
      c = 62;
    else if (coded_triplet[i] == '-')
      c = 63;
    else
      return(CHARACTER_DECODING_ERROR);
    n = n << 6 | c;
  }

  /* Then shift and XOR to get the correct ASCII code */
  decoded_triplet[0] = (n >> 16) ^ 67;
  decoded_triplet[1] = (n >> 8 & 255) ^ 67;
  decoded_triplet[2] = (n & 255) ^ 67;

  return(0);
}



/* Read a scancode and try to get a character out of it.
   NOTE : as soon as we get ALT-press + F10-press, we warn the calling function
          so that it has the earliest chance of filtering garbage out */
char cuecat_decode_scancode(unsigned char scancode)
{
  unsigned char key;
  char keypress;

  keypress=!(scancode & KEY_RELEASE);
  key=scancode2ascii[scancode & ~KEY_RELEASE];

  switch(key)
  {
  case 0:					/* Unknown key ? */
    cuecat_reset_scancode_decoder();
    return(CHARACTER_DECODING_ERROR);		/* The CueCat only sends a
						   limited number of keys */
    break;
    
  case KEY_ALT:					/* Key is ALT ? */
    if(keypress)				/* The key was depressed */
    {
      if(got_shift_press ||
         got_alt_press ||
         got_letter_press ||
         got_letter_release)
      {
        cuecat_reset_scancode_decoder();
        return(CHARACTER_DECODING_ERROR);	/* ALT press has to be first */
      }
      got_alt_press=1;
      return(0);				/* Nothing special to report */
    }
    else					/* The key was released */
    {
      if(got_shift_press ||
         !got_alt_press ||
         !got_letter_press ||
         !got_letter_release)
      {
        cuecat_reset_scancode_decoder();
        return(CHARACTER_DECODING_ERROR);	/* ALT release has to be last*/
      }
      cuecat_reset_scancode_decoder();
      if(temp_read_char==KEY_F10)
        return(GOT_ALT_F10);			/* We have ALT-F10 */
      else
        return(CHARACTER_DECODING_ERROR);	/* The CueCat doesn't send
						   ALT-anything-else-but-F10 */
    }
    break;

  case KEY_SHIFT:				/* Key is SHIFT ? */
    if(keypress)				/* The key was depressed */
    {
      if(got_shift_press ||
         got_alt_press ||
         got_letter_press ||
         got_letter_release)
      {
        cuecat_reset_scancode_decoder();
        return(CHARACTER_DECODING_ERROR);	/* SHIFT press has to be first*/
      }
      got_shift_press=1;
      return(0);				/* Nothing special to report */
    }
    else					/* The key was released */
    {
      if(got_alt_press ||
         !got_shift_press ||
         !got_letter_press ||
         !got_letter_release)
      {
        cuecat_reset_scancode_decoder();
        return(CHARACTER_DECODING_ERROR);	/*SHIFT release has to be last*/
      }
      cuecat_reset_scancode_decoder();
      if(temp_read_char>='a' && temp_read_char<='z')
      {
	prev_read_char=read_char;
        read_char=temp_read_char-'a'+'A';	/* Turn read char uppercase */
        return(GOT_CHARACTER);			/* We have a valid character */
      }
      else
        return(CHARACTER_DECODING_ERROR);	/* The CueCat doesn't send
						   SHIFT-anything-else-but-a-
						   letter */
    }
    break;

  case '.':					/* Key is a dot */
  case '\r':					/* or a carriage-return */
    if((key=='.' || key=='\r') &&
       got_shift_press)				/* shifted '.' or CR ? */
    {
      cuecat_reset_scancode_decoder();
      return(CHARACTER_DECODING_ERROR);		/* The CueCat doesn't send
                                          	   shifted '.'s or CRs */
    }
  default:					/* Any other key */
    if((got_alt_press && key!=KEY_F10) ||
       (!got_alt_press && key==KEY_F10))
    {
      cuecat_reset_scancode_decoder();
      return(CHARACTER_DECODING_ERROR);		/* The CueCat doesn't send
                                          	   ALTed-anything-but-F10 and
						   doesn't send F10 alone */
    }

    if((keypress && got_letter_press) ||
       (!keypress && got_letter_release))
    {
      cuecat_reset_scancode_decoder();
      return(CHARACTER_DECODING_ERROR);		/* The CueCat doesn't press a
                                          	   key without releasing the
                                                   previous one and doesn't
                                                   release a key without
                                                   pressing one first */
    }

    if(keypress)				/* The key was depressed */
    {
      temp_read_char=key;			/* Store the key for later */
      got_letter_press=1;

      if(key==KEY_F10 &&
         got_alt_press &&
         !got_shift_press)
        ALT_F10_early_notice=1;			/* Special ALT-F10 half-baked
						   scancode sequence */
        return(0);				/* Nothing special to report */
    }
    else					/* The key was released */
    {
      if(key!=temp_read_char)
      {
        cuecat_reset_scancode_decoder();
        return(CHARACTER_DECODING_ERROR);	/* The CueCat doesn't release
                                          	   a key it hasn't pressed
                                                   right before */
      }

      if(got_shift_press || got_alt_press)
      {
        got_letter_release=1;
        return(0);				/* Nothing special to report */
      }
      else
      {
        cuecat_reset_scancode_decoder();
	prev_read_char=read_char;
        read_char=temp_read_char;
        return(GOT_CHARACTER);			/* We have a valid character */
      }
    }

    break;
  }
}



/* Reset the barcode decoder */
void cuecat_reset_barcode_decoder(void)
{
  ALT_F10_early_notice=nb_dots_read=nb_codes_read=current_coded_triplet_chars=0;
  global_barcode_decoder_state=NO_BARCODE_DECODING_IN_PROGRESS;
  cuecat_reset_scancode_decoder();
}



/* Reset the scancode decoder */
void cuecat_reset_scancode_decoder(void)
{
  got_shift_press=got_alt_press=got_letter_press=got_letter_release=0;
}



/* Do special treatment on some barcodes */
void do_barcode_special_cases(char *cuecat_id,
                              char *barcode_type,
                              char *barcode)
{
  /* If the barcode is a UPC-E, it seems that the CueCat has a bug and doesn't
     finish the barcode by the check digit */
  if(!strcmp(barcode_type,"UPE") && strlen(barcode)==7)
  {
    /* Reconstruct the check digit and append it to the barcode to simulate
       a proper operation from the CueCat */
    char result[16];
    int esum = 0, osum = 0, i;
    int even=1;					/* last char is even */

    /* Generate a UPC-A out of the UPC-E */
    strcpy(result, "00000000000"); /* 11 0's */

    switch(barcode[5])				/* last char */
    {
    case '0': case '1': case '2':
      memcpy(result+1,barcode,2);
      result[3]=barcode[5];			/* Manufacturer */
      memcpy(result+8,barcode+2,3);		/* Product */
    case '3':
      memcpy(result+1,barcode,3);		/* Manufacturer */
      memcpy(result+9,barcode+3,2);		/* Product */
    case '4':
      memcpy(result+1,barcode,4);		/* Manufacturer */
      memcpy(result+10,barcode+4,1);		/* Product */
    default:
      memcpy(result+1,barcode,5);		/* Manufacturer */
      memcpy(result+10,barcode+5,1);		/* Product */
    }
    
    /* Calculate the check digit */
    i=strlen(result);				/* end of all */
    while (i--)
    {
      if (even)
        esum+=result[i]-'0';
      else
        osum+=result[i]-'0';
      even=!even;
    }
    i=(3*esum+osum)%10;
    barcode[7]='0'+((10-i)%10);				/* complement to 10 */
    barcode[8]=0;
  }
}
