/* ****************************************************************
This file has the main encrypt and decrypt engines.  A 3rd function
is used to demo my special file compression routine.
**************************************************************** */

function Decrypt (strn, seed1, seed2) {  // decrypt according to seeds
var i,j,t,la=0;
var mk   = String.fromCharCode(128);     // marker byte - norm comp
var wd   = new Array ();                 // compression word array
var wk   = new Array ();                 // working array
var ary1 = new Array ();                 // output array
var s1   = new Array ();                 // seed arrays
var s2   = new Array ();
var s256 = new Array ();
var sg   = new Array ();
var sx   = new Array ();
var sy   = new Array ();
                        /* recover the data */
  ary1 = strn.split(" ");                // clear spaces...
  strn = ary1.join("");
                       /* read base-64 stuff */
  for (i=0; i<strn.length; i+=4) {
    t=0;
    for (j=i; j<(i+4); j++) {
      t<<=6;
      t+=B64.indexOf(strn[j]);
    }
    for (j=2; j>=0; j--)
      ary1[la++]=(t>>(8*j))&255;
  }
                        /* decrypt the data */
  s1 = FixUTF (seed1+12);                // expand UTF codes
  s2 = FixUTF (seed2+34);
  sg = FixUTF (SubGenKey+78);
                         /* build working keys */
  s256 = Hash2(sg.concat(s1,s2),256,0);  // special for SubGen
  sx   = Hash2(sg.concat(s2,s1),0,0);
  sy   = Hash2(sx,521,0);
  s1   = Hash2(s1,521,0);                // all keys are hashed
  s2   = Hash2(s2,521,0);

  ary1 = UnBitShuffle(ary1, sy, 1);      // unshuffle crypto block
                          /* begin decryption */
  j=Rand15(sx)^Math.floor(Rand1(s2)*la); // set RNGs, and J index
  for (i=la-1; i>=0; i--) {              // decrypt all values
    ary1[j++ % la]^=Rand15();            // forward from Jth loc
    if (Rand7() < FR)                    // some confusion
      ary1[Math.floor(Rand1()*la)]^=Rand15(); // (random loc)
    ary1[i]^=Math.floor(Rand1()*256);    // backward from end
    if (Rand3() > FR)                    // some more confusion
      ary1[Math.floor(Rand7()*la)]^=Rand15(); // (random loc)
  }
                           /* restore message */
  ary1 = UnBitShuffle(ary1, s1, 1);      // undo 1st shuffle
  ary1 = UnSubGen(ary1, s256, 1);        // remove substitution chars

  while(ary1[la-1] == 15) la--;          // clear the padding
  t = ary1[la-1];                        // remember last char
  ary1.length = --la;                    // zap last character
  strn = RstUTF (ary1);                  // restore any UTF codes
                /* undo compression, if any was done */
  if (t == 128) {                        // undo partial compress
    wk = strn.split(mk+mk);              // build working arrays
    strn = wk[0];                        // the data to expand
    wd = wk[1].split(mk);                // build the dictionary
    for (i=wd.length-1; i>=0; i--) {     // run the wd array
      t = String.fromCharCode(i+129);    // the compression code
      wk = strn.split(t);                // divide on the code
      strn = wk.join(wd[i]+" ");         // insert word back in
    }
  }
  return strn;                           // return decoded string
}

function Encrypt (strn, seed1, seed2) {  // encrypt according to seeds
var i,j=128,k,s,t,c=0;
var mk   = String.fromCharCode(128);     // compression marker byte
var wd   = new Array ();                 // compression word array
var wk   = new Array ();                 // working array
var ary1 = new Array ();                 // numeric data array
var s1   = new Array ();                 // numeric seed arrays
var s2   = new Array ();
var s256 = new Array ();
var sg   = new Array ();
var sx   = new Array ();
var sy   = new Array ();
                     /* see if we compress the data */
/* ******************************************************************
There are 2 different things we can do with the data.
Data is terminated by a byte indicating what was done to it.
(That byte is thrown away on the decrypt end.)
  048 - (ASCII zero char) No compression done.
  128 - My special word-replacement compression.
The object is to hide the message length, but there are better
compression algorithms available in languages that can address the
true byte nature of data - JavaScript cannot do that easily.
****************************************************************** */
  for (i=0; i<t; i++) {                  // check all characters
    k = strn.charCodeAt(i);              //  to test numeric values
    if (k > 127)                         // these are problems
      j = Math.min(j,k-129);             // don't use toxic markers
  }
  if (j > 0) {                           // things are OK, compress
    wd = Words(strn);                    // get words for compression
    j  = Math.min(j,wd.length);          // number of words to use
    for (i=0; i<j; i++) {                // replace wd's with markers
      wk = strn.split(wd[i]+" ");        // break text apart on wd
      strn=wk.join(String.fromCharCode(i+129)); // replace wd w/marker
    }
    wd.length = j;                       // dictionary length
    if (j == 0) strn += 0;               // nothing here
    else strn += mk+mk+wd.join(mk)+mk;   // add dictionary to end
  }
  else strn += 0;                        // no comp - mark with zero
                   /* go about normal encryption stuff */
  s1 = FixUTF (seed1+12);                // fix any UTF codes
  s2 = FixUTF (seed2+34);                //  UTF stuff > 255
  sg = FixUTF (SubGenKey+78);            //  (convert to numerics)
  ary1 = FixUTF (strn);                  // fix UTF in the data
var la = ary1.length;                    // current length of msg
                        /* prepare for encryption */
  while(la%180 != 0) ary1[la++]=15;      // pad out to block size
                         /* build working seeds */
  s256 = Hash2(sg.concat(s1,s2),256,0);  // special for SubGen
  sx   = Hash2(sg.concat(s2,s1),0,0);    // orig length key
  sy   = Hash2(sx,521,0);                // 521 is special for Rand7
  s1   = Hash2(s1,521,0);
  s2   = Hash2(s2,521,0);
                         /* mess up the message */
  ary1 = SubGen(ary1, s256, 1);          // apply substitution chars
  ary1 = BitShuffle(ary1, s1, 1);        // mix - 2st shuffle
                          /* begin encryption */
  j=Rand15(sx)^Math.floor(Rand1(s2)*la); // set RNGs, and J index
  for (i=la-1; i>=0; i--) {              // encrypt all values
    ary1[j++ % la]^=Rand15();            // forward from Jth loc
    if (Rand7() < FR)                    // some confusion
      ary1[Math.floor(Rand1()*la)]^=Rand15(); // (random loc)
    ary1[i]^=Math.floor(Rand1()*256);    // backward from end
    if (Rand3() > FR)                    // some more confusion
      ary1[Math.floor(Rand7()*la)]^=Rand15(); // (random loc)
  }
                      /* mess up encrypted data */
  ary1 = BitShuffle(ary1, sy, 1);        // mix up, 2nd shuffle
                    /*  form output to HTML page  */
  wk.length = 0;                         // clear working array
  j = 0;                                 // where to put blocks
                      /* base-64 output notation */
  for (i=0; i<la; i+=3) {
    t=(ary1[i]<<16)|(ary1[i+1]<<8)|(ary1[i+2]);
    s="";
    for (k=0; k<4; k++) {
      s=B64.charAt(t&63) + s;
      t>>=6;
    }
    if ((i+3)%9 == 0) s+=" ";
    wk[j++]=s;
  }

  return wk.join("");                    // return coded string
}

/* ***************************************************************
This is unique to JavaScript.  It counts how often words occur in
a text so we can apply compression to the data.  Works for any UTF
data (even Arabic) - it replaces often-used words with markers,
and attaches a compression dictionary to the end of the data.
The Encrypt function uses the output of this function to produce a
compressed output file like this:

Uncompressed file  -      T the the the  and and and x.
Compressed file    -      T 111 000x.~~and~the~

Often-used words are replaced by a 1-byte marker, and the
dictionary is position-sensitive (note how the first word in the
dictionary is and, and has the 0 marker - but values other than
these are really used.)  The dictionary may have 127 entries.

This function just produces an array of the top 127 words to be
used in compression.  Encrypt and Decrypt functions work with
the results.  Average space compression is about 25%.

Compression would be a useful stragegy for any data because it
hides the true length of that data.  This simple example is
included just to show what compression does.
*************************************************************** */
function Words (strn) {  // most used words
var i,j,k=0,c,t,la=0;
var wk = new Array ();          // working array
var wd = new Array ();          // isolated words
var ct = new Array ();          // count & value for a word
                     /* get word list */
  wk = strn.split(" ");         // break out the words
  for (i=0; i<wk.length; i++)   // find words long enough
    if (wk[i].length > 0) {     // good word?
      wd[la] = wk[i];           // record it
      ct[la++] = 1;             // count one instance
    }
  wk = strn.split("    ");      // don't forget spaces
  wd[la] = "   ";               // (trick - the "word" is 3-sp)
  ct[la++] = wk.length - 1;     // count of 4-space groups
              /* count instances of the words */
  for (i=la-2; i>0; i--) {      // run backwards for totals
    c = wd[i];                  // remember the word
    for (j=0; j<i; j++)         // look for dup word
      if (c == wd[j]) {         // same word - total 'em
        ct[j]++;                //  (another instance)
        break;                  // move on to next word
      }
  }
                      /* get rid of junk */
  for (i=0; i<la; i++)          // run the array
    if (ct[i] > 1) {            // 2 wds before we can save
      wd[k] = wd[i];
      t=FixUTF(wd[i]).length;   // get 8-bit bytes for wd
      ct[k++]=t*ct[i]-(t+1);    // how much it really saves   
    }
  la = k;                       // correct the length
   /* final key-sort of words according to their savings */
var inc = la >> 1;              // initial increment
  while (inc > 0) {             // decreasing inc loop
    for (i=inc; i<la; i++) {    // one pass thru the data
      j=i; k=i-inc;
      while ((j>=inc) && (ct[k]<ct[j])) { // order pairs
        t=ct[j]; ct[j]=ct[k]; ct[k]=t;    // propagate back
        c=wd[j]; wd[j]=wd[k]; wd[k]=c;
        j-=inc; k-=inc;         // do we go back more?
      }
    }
    inc = Prime(inc >> 1);      // decrease the increment
  }
                 /* get ready for output */
  while ((la>0) && (ct[la-1]<2)) la--; // just a catch-all
  wd.length=Math.min(127,la);   // words that save space
  return wd;
}

