/* Faxanadu password generator / decoder
 * Copyright (C) 1992,2004 Joel Yliluoma (http://iki.fi/bisqwit/)
 * Written for http://tasvideos.org/PasswordGenerators.html
 * Permission to copy and modify is granted under the following terms:
 *   The copyright notice is kept unmodified
 *   No attempts are made to prevent anyone downloading the source code
 *
 * Reverse engineering by TNSe
 */
function faxinput(name)
{
  this.gel = function(id)   { return document.getElementById(name+id) }
  this.id  = function(id)   { return this.gel(id).selectedIndex }
  this.ids = function(id,v) { this.gel(id).selectedIndex=v }
  this.ch  = function(id)   { return this.gel(id).checked?1:0 }
  this.chs = function(id,v) { this.gel(id).checked = v>0 }
  this.tx  = function(id)   { return this.gel(id).value }
  this.txs = function(id,v) { this.gel(id).value = v }
  
  /* 24 * 8-bit values {1,2,3,4,5,6,7,8}{A,B,C} */
  this.bytes = [0,0,0, 0,0,0, 0,0,0, 0,0,0,
                0,0,0, 0,0,0, 0,0,0, 0,0,0]

  /* 32 * 6-bit encoded values */
  this.chars = [0,0,0,0, 0,0,0,0,  0,0,0,0, 0,0,0,0,
                0,0,0,0, 0,0,0,0,  0,0,0,0, 0,0,0,0]
  
  /* Length: 64 characters                                    */
  /*           0123456789ABCDEF   0123456789ABCDEF   0123456789ABCDEF   0123456789ABCDEF */
  this.cset = 'ABCDEFGHIJKLMNOP'+'QRSTUVWXYZabcdef'+'ghijklmnopqrstuv'+'wxyz0123456789,?'
  
  this.inputs =
  [
    /* selectinputs */
    3, 'town',
    4, 'rank',
    /* bools */
    1, 'ui7', 1, 'ui6', 1, 'ui5', 1, 'ui4', 1, 'ui3', 1, 'ui2', 1, 'ui1', 1, 'ui0',
    1, 'qu7', 1, 'qu6', 1, 'qu5', 1, 'qu4', 1, 'qu3', 1, 'qu2', 1, 'qu1', 1, 'qu0',
    /* bool, 2-bit value. */
    -2, 'we',
    -2, 'ar',
    -2, 'sh',
    -3, 'ma',
    -5, 'it',
    
    /* inventories: index bits, item bits, n, ids */
    0, 3,2, 4, 'we0','we1','we2','we3',
    0, 3,2, 4, 'ar0','ar1','ar2','ar3',
    0, 3,2, 4, 'sh0','sh1','sh2','sh3',
    0, 3,3, 4, 'ma0','ma1','ma2','ma3',
    0, 4,5, 8, 'it0','it1','it2','it3','it4','it5','it6','it7'
  ]
  
  this.convert_bits = function(input, ib, ob, wanted_length)
  {
    cache=0; cachelen=0
    result = []
    nm = input.length
    obm = (1 << ob) - 1
    for(n=0; n<nm; ++n)
    {
      /* populate low bits */
      cache = (cache << ib) + input[n]
      for(cachelen += ib; cachelen >= ob; )
      {
        cachelen -= ob
        /* eat high bits */ result.push((cache >> cachelen) & obm)
        /* keep low bits */ cache &= (1 << cachelen)-1;
      }
    }
    while(result.length < wanted_length) result.push(0);
    if(result.length > wanted_length)
    {
      result.splice(wanted_length, result.length - wanted_length)
    }
    return result
  }
  this.showpass = function()
  {
    s = ''
    t = this.cset
    l = this.chars.length
    for(n=0; n<l; ++n)
    {
      i = this.chars[n]
      ch = t.charAt(i)
      s += ch
    }
    this.txs('l', s)
  }
  this.decode = function()
  {
    this.do_decode(1)
  }
  this.do_decode = function(act)
  {
    error   = 0
    message = ''

    /* Get user input (32 6-bit characters) */
    l = this.tx('l')
    t = this.cset
    this.chars = []
    for(n=0; n<l.length; ++n)
    {
      ch = t.indexOf(l.charAt(n)); if(ch >= 0) this.chars.push(ch & 63)
    }
    
    /* Add a 0-char just in case the division isn't even */
    this.chars.push(0)

    /* convert 6-bit to 8-bit */
    this.bytes = this.convert_bits(this.chars, 6, 8, 24)
    
    this.bitpos = 0
    
    checksum = this.getbits(8) /* ignored, not needed for validation */
    length   = this.getbits(5)
    
    b = this.inputs.length
    for(a=0; a<b; )
    {
      type = this.inputs[a++]
      switch(type)
      {
        case 1: /* checkbox */
        {
          value = this.getbits(1)
          fname = this.inputs[a++]
          if(act) this.chs(fname, value)
          break;
        }
        case 0: /* inventories */
        {
          indexbits = this.inputs[a++]
          itembits  = this.inputs[a++]
          maxn      = this.inputs[a++]
          n = this.getbits(indexbits)
          if(n > maxn)
          {
            error = 1
            message += '(limit '+n+','+maxn+')'
          }
          for(i=0; i<maxn; ++i)
          {
            if(i < n)
              value = this.getbits(itembits) + 1;
            else
              value = 0;
            if(act)this.ids(this.inputs[a+i], value)
          }
          a += maxn
          break;
        }
        default: /* value */
        {
          fname = this.inputs[a++]
          
          if(type > 0)
            value = this.getbits(type);
          else if(this.getbits(1))
            value = this.getbits(-type) + 1;
          else
            value = 0;
          if(act) this.ids(fname, value)
          break;
        }
      }
    }
    
    if(this.calc_checksum() != 0)
    {
      error=1
      message += '(cksum)'
    }
    
    /* -1 because we added 1 char */
    if(length != this.chars.length-1)
    {
      error=1
      message += '(length)'
    }
    
    wanted_length = Math.ceil(this.bitpos / 6)
    
    if(!error)
    {
      if(length != wanted_length)
      {
        message += '(unorthodox)'
      }
    }
    
    crashwarning = 0
    if(this.id('ar') == 0)
    {
      crashwarning += 1
    }
    for(n=0; n<8; ++n)
    {
      value = this.id('it'+n)
      if(value > 0x15) crashwarning += 1
    }
    if(this.id('ma') > 0x05) crashwarning += 1
    for(n=0; n<4; ++n)
    {
      value = this.id('ma'+n)
      if(value > 0x5) crashwarning += 1
    }
    if(crashwarning)
    {
      message += '(invalid)'
    }

    /*
    if(((this.id('we') == 3) + (this.id('ar') == 3) + (this.id('sh') == 3)) != 3)
    {
    }
    */
    
    if(error) message = '(error)' + message
    
    this.txs('msg', message)
  }
  this.calc_checksum = function()
  {
    a=0;
    for(n=0; n<24; ++n) a += this.bytes[n];
    return (256 - (a&255)) & 255
  }
  
  this.putbits = function(value, nbits)
  {
    while(nbits > 0)
    {
      shift = this.bitpos & 7   /* how many bits are there in this byte (lsb) */
      byten = this.bitpos >> 3  /* the byte must be filled from lsb */
      
      /* how many bits is there room before eating */
      room = 8-shift
      
      /* eat as much as we can */
      eat = room
      if(eat > nbits) eat = nbits
      
      /* how many bits is there room after eating */
      room -= eat
      
      mask = (1 << eat)-1
      nbits -= eat
      
      shifted = value >> nbits
      this.bytes[byten] |= (shifted & mask) << room

      this.bitpos += eat
    }
  }
  this.getbits = function(nbits)
  {
    result = 0
    while(nbits > 0)
    {
      shift = this.bitpos & 7   /* how many bits are eaten from this byte (lsb) */
      byten = this.bitpos >> 3  /* the byte must be eaten from msb */
      
      /* how many bits are left before eating */
      left = 8-shift
      
      /* eat as much as we can */
      eat = left
      if(eat > nbits) eat = nbits
      
      /* how many bits are left after eating */
      left -= eat
      
      mask = (1 << eat)-1
      nbits -= eat
      
      shifted = (this.bytes[byten] >> left)
      result |= (shifted & mask) << nbits /* populate lsb */

      this.bitpos += eat
    }
    return result
  }
  
  this.setup = function()
  {
    for(n=0; n<24; ++n) this.bytes[n] = 0
    
    this.bitpos = 8+5  /* fill checksum and length later */
    
    b = this.inputs.length
    for(a=0; a<b; )
    {
      type = this.inputs[a++]

      switch(type)
      {
        case 1: /* checkbox */
        {
          this.putbits(this.ch(this.inputs[a++]), 1)
          break;
        }
        case 0: /* inventories */
        {
          indexbits = this.inputs[a++]
          itembits  = this.inputs[a++]
          maxn      = this.inputs[a++]

          n = 0
          for(i=0; i<maxn; ++i)
          {
            value = this.id(this.inputs[a+i])
            if(value > 0) ++n;
          }
          this.putbits(n, indexbits)
          for(i=0; i<maxn; ++i)
          {
            value = this.id(this.inputs[a+i])
            if(value > 0) this.putbits(value-1, itembits);
          }
          a += maxn;
          break;
        }
        default: /* value */
        {
          fname = this.inputs[a++]
          value = this.id(fname)

          if(type < 0)
          {
            /* bool, value */
            if(value == 0) { this.putbits(0, 1); break; }
            type = -type
            this.putbits(1, 1)
            --value;
          }
          this.putbits(value, type)
          break;
        }
      }
    }
    
    /* Find the last byte that contains something */
    wanted_bits = this.bitpos
/**/
    wanted_bits = 24*8
    for(n=24; --n>0; wanted_bits -= 8) if(this.bytes[n] != 0) break;
    last_byte = this.bytes[n]
    for(i=0; i<8; ++i)
    {
      if(last_byte & (1 << i)) break;
      --wanted_bits
    }
    
    minimum_length = 6*4;
    if(wanted_bits < minimum_length)
      wanted_bits = minimum_length;
/**/
    wanted_length = Math.ceil(wanted_bits/6)
    
    this.bytes[1] |= wanted_length << 3

    this.putbits(0, 8)

    this.bytes[0] = this.calc_checksum()
    
    /* convert 8-bit to 6-bit */
    this.chars = this.convert_bits(this.bytes, 8, 6, wanted_length)

    this.showpass()
    this.do_decode(0)
  }
}

