import java.lang.*;

/** The "CatDecoder" class provides a means of decoding the :Cue:C.A.T. encrypted
    string read as though typed on the keyboard by the :Cue:C.A.T. barcode scanner.
    <P>
    These scanners (and their software) are provided &quot;free&quot; by
    Digital Convergence:.Com (initially through Radio Shack stores).&nbsp; Each
    scanner has its own built-in serial number which it reports as part of each
    scan.&nbsp; The output of the scanner is (slightly) encrypted using a modified
    MIME-style base64 pattern with either of 2 exclusive-OR masks (one for the
    special :Cue:C.A.T. &quot;cues&quot;, another for all other bar codes).
    <P>

@author David Schnee (dave@schnee.com)
@version JDK 1.1 (or later)
    */

public class CatDecoder
{

/*  Class variables  */

static  private  Character   C   = new Character( '$' );

static  private  String      base64c = new String(
						"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" +
						"0123456789+-" );

static  private  String      ASCII   = new String(
						"                                 " + // 000 - 032
						"!\"#$%&'()*+,-./0123456789 ;<=>?@" + // 033 - 064
						"ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`" + // 065 - 096
						"abcdefghijklmnopqrstuvwxyz{|}~  "  + // 097 - 128
						"üéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥Pƒá"  + // 129 - 160
						"íóúñÑªº¿¬¬½¦¡«»¦¦¦¦¦¦¦++¦¦++++++"  + // 161 - 192
						"--+-+¦¦++--¦-+----++++++++¦_¦¦¯a"  + // 193 - 224
						"ßGpSsµttTOd8fen=±==()÷˜°··vn²¦ " );  // 225 - 255

/*  Instance variables  */

private  String      SerialNumber;
private  String      BarcodeType;
private  String      ScanData;
private  String      ToDecode;
private  int         InputLength;
private  boolean     IsCueCat;


//  Constructor   ----------------------------------------------------

/**  Instantiate a CatDecoder object (no arguments).  */

public CatDecoder() {

					}  //  constructor with no arguments

//  Method    ---------------------------------------------------------

/**  Accepts the scanned string, decodes it and returns an indication of
     whether or not the input is valid (true for &quot;yes&quot;, false
     for &quot;no&quot;).<P> */

public boolean CatDecode( String input ) {

String tinput = "";

tinput = input.trim();

SerialNumber = "";
BarcodeType  = "";
ScanData     = "";
InputLength  = tinput.length();

// Validate format as ".SerialString.Type.Scanvalue."
if ( InputLength < 34 )                        return false;
if ( tinput.charAt( 0) != '.' )                return false;
if ( tinput.charAt(25) != '.' )                return false;
if ( tinput.charAt(30) != '.' )                return false;
if ( tinput.charAt( InputLength - 1 ) != '.' ) return false;

// if input string starts with lowercase letter, Caps Lock is probably ON -- reverse the case
if ( C.isLowerCase( tinput.charAt(1) ) )
	ToDecode = ReverseCase( tinput );
else
	ToDecode = tinput;

IsCueCat     = false;
SerialNumber = Doit( ToDecode.substring(  1, 25              ) );
BarcodeType  = Doit( ToDecode.substring( 26, 30              ) );

if ( BarcodeType.length() > 2 )
{
	if ( BarcodeType.substring( 0, 2 ).compareTo( "CC" ) == 0 ) IsCueCat = true;
}

ScanData     = Doit( ToDecode.substring( 31, InputLength - 1 ) );

return true;
                                      }  // CatDecode()

// Private method to actually decode an element of the input string

private String Doit( String inp )
{
	String Out;
	int    dclen;
	int    done;
	int    LI;
	int    LT;
	int    bytesout;
	String append;

	dclen    = inp.length();
	done     = 0;
	Out      = "";

	// If this is a :Cue:Cat :Cue, emit the prologue
	if ( IsCueCat )
		{
			Out = Out.concat( "C " );
			LI = ASCII.indexOf( BarcodeType.substring( 2, 3 ) ) - 32;
			append = ( new Integer( LI ) ).toString();
			if ( append.length() < 2 ) Out = Out.concat( "0" );
			Out = Out.concat( append );
		}

	while ( done < dclen ) // for each group of up-to-4 input characters
	{

		// First, convert 4 input characters (6-bits each) to a 24 bit number
		LT       = 0;
		bytesout = 0;

		if ( ( dclen - done ) >= 2 )
		{
			bytesout++;
			LI      = base64c.indexOf( inp.substring( done, ++done ) );
			LT      = LT + ( LI * 64 * 64 * 64 );
			LI      = base64c.indexOf( inp.substring( done, ++done ) );
			LT      = LT + ( LI * 64 * 64 );
		}

		if ( ( dclen - done ) >= 1 )
		{
			bytesout++;
			LI      = base64c.indexOf( inp.substring( done, ++done ) );
			LT      = LT + ( LI * 64 );
		}

		if ( ( dclen - done ) >= 1 )
		{
			bytesout++;
			LI      = base64c.indexOf( inp.substring( done, ++done ) );
			LT      = LT + ( LI );
		}

		// If this is a :Cue:Cat :Cue, XOR with 99, convert to decimal
		if ( IsCueCat )
		{
			if ( bytesout-- > 0 )
			{
				Out = Out.concat( " " );
				LI = ( LT / 65536 ) ^ 99;
				while ( LI > 95 ) LI = LI - 64;
				append = ( new Integer( LI ) ).toString();
				if ( append.length() < 2 ) Out = Out.concat( "0" );
				Out = Out.concat( append );
			}

			if ( bytesout-- > 0 )
			{
				Out = Out.concat( " " );
				LI = ( ( LT / 256 ) & 255 ) ^ 99;
				while ( LI > 95 ) LI = LI - 64;
				append = ( new Integer( LI ) ).toString();
				if ( append.length() < 2 ) Out = Out.concat( "0" );
				Out = Out.concat( append );
			}

			if ( bytesout-- > 0 )
			{
				Out = Out.concat( " " );
				LI = ( LT & 255 ) ^ 99;
				while ( LI > 95 ) LI = LI - 64;
				append = ( new Integer( LI ) ).toString();
				if ( append.length() < 2 ) Out = Out.concat( "0" );
				Out = Out.concat( append );
			}
		}
		else  // All barcodes EXCEPT a :Cue:Cat :Cue, XOR with 67, treat as ASCII
		{
			if ( bytesout-- > 0 )
			{
				LI = ( LT / 65536 ) ^ 67;
				Out = Out.concat( new Character( ASCII.charAt( LI ) ).toString() );
			}

			if ( bytesout-- > 0 )
			{
				LI = ( ( LT / 256 ) & 255 ) ^ 67;
				Out = Out.concat( new Character( ASCII.charAt( LI ) ).toString() );
			}

			if ( bytesout-- > 0 )
			{
				LI = ( LT & 255 ) ^ 67;
				Out = Out.concat( new Character( ASCII.charAt( LI ) ).toString() );
			}
		}
	} // end while

	return Out;
}

// Private method to invert the case of an alphanumeric string

private String ReverseCase( String inp ) {

String      Out = new String();
char        ch;
int         i;

for ( i = 0; i < inp.length(); i++ )
   {
	ch = inp.charAt( i );
	if ( C.isLetter( ch ) )
	   {
			if ( C.isLowerCase( ch ) )
			Out = Out.concat( ( new Character( C.toUpperCase( ch ) ) ).toString() );
		else
			Out = Out.concat( ( new Character( C.toLowerCase( ch ) ) ).toString() );
	   }
	else    Out = Out.concat( ( new Character( ch ) ).toString() );
   }  // for ( ; ; )
return Out;
                                         } // ReverseCase()

//  Method    ---------------------------------------------------------

/**  Returns the decoded serial number (or a null string if the input
     was not valid).<P> */

public String getSerialNumber(  )

{ return SerialNumber; }

//  Method    ---------------------------------------------------------

/**  Returns the decoded barcode type (or a null string if the input
     was not valid).<P> */

public String getBarcodeType(  )

{ return BarcodeType; }

//  Method    ---------------------------------------------------------

/**  Returns the decoded scanned data (or a null string if the input
     was not valid).<P> */

public String getScanData(  )

{ return ScanData; }

}  // end of CatDecoder class