// Copyright © 2004 - 2012 Interact Computer Services Ltd.
// All rights reserved. Use is subject to license terms.
// For more information contact Interact at info@interact-uk.com

function AL_Index_Deserialise (iobj)
{
  var obj = new AL_Index ();

  obj.keys    = iobj.keys;
  obj.entries = iobj.entries;
  obj.monik   = iobj.monik;
  obj.assoc   = iobj.assoc;
  obj.moniker = iobj.moniker;

  return (obj);
}

function AL_Index (adoc, isset, hasAssoc)
{
  this.keys    = new Array ();
  this.entries = new Array ();
  this.monik   = null;
  this.assoc   = hasAssoc ? new Array () : null;
  this.moniker = 0;

  this.serialiser   = "AL_Index_Deserialise";
  this.canSerialise = true;

  this.monitor = function (on)
  {
    if (on)
    {
      if (!this.monik)
      {
        this.monik = new Array ();
        
        var idx = keys.length;
        
        while (idx-- > 0)
        {
          this.monik.push (this.moniker);
        }
      }
    }
    else
    {
      this.monik = null;
    }
  }
  
  this.setMoniker = function (moniker)
  {
    if (arguments.length == 1)
    {
      this.moniker = moniker;
    }
    else
    {
      this.moniker++;
    }
  }
  
  this.purgeRecent = function ()
  {
    if (this.monik)
    {
      var idx = this.keys.length;
      
      while (idx-- > 0)
      {
        if (this.monik[idx] >= this.moniker)
        {
          this.removeAt (idx);
        }
      }
    }
  }
  
  this.purgePast = function ()
  {
    if (this.monik)
    {
      var idx = this.keys.length;
      
      while (idx-- > 0)
      {
        if (this.monik[idx] < this.moniker)
        {
          this.removeAt (idx);
        }
      }
    }
  }
  
  this.clone = function ()
  {
    var aset  = new AL_Index ();
    var idx   = -1;
    var lim   = this.keys.length;
    
    while (++idx < lim)
    {
      aset.keys.push    (this.keys[idx]);
      aset.entries.push (this.entries[idx]);
    }

    if (this.assoc)
    {
      idx = -1;
      lim = this.assoc.length;
    
      while (++idx < lim)
      {
        aset.assoc.push (this.assoc[idx]);
      }
    }
    
    aset.moniker = this.moniker;
    
    if (this.monik)
    {
      idx = -1;
      lim = this.monik.length;
    
      while (++idx < lim)
      {
        aset.monik.push (this.monik[idx]);
      }
    }
    
    return (aset);
  }
  
  this.transfer = function (donor, replace)
  {
    if (replace)
    {
      this.purge ();
    }
    
    if (donor)
    {
      var idx   = -1;
      var lim   = donor.keys.length;
      
      while (++idx < lim)
      {
        this.keys.push    (donor.keys[idx]);
        this.entries.push (donor.entries[idx]);
      }

      if (this.assoc)
      {
        idx = -1;
      
        if (donor.assoc)
        {
          while (++idx < lim)
          {
            this.assoc.push (donor.assoc[idx]);
          }
        }
        else
        {
          while (++idx < lim)
          {
            this.assoc.push (null);
          }
        }
      }
      
      if (this.monik)
      {
        idx = -1;

        while (++idx < lim)
        {
          this.monik.push (this.moniker);
        }
      }
    }
  }
  
  this.destroy = function (zapper)
  {
    this.purge (zapper ? zapper : null);

    this.keys    = null;
    this.entries = null;
    this.assoc   = null;
    this.monik   = null;
  }
  
  this.purge = function (zapper)
  {
    var index = this.keys.length;
    
    if (zapper)
    {
      while (index-- > 0)
      {
                this.keys.pop    ();
        zapper (this.entries.pop ());

        if (this.assoc) this.assoc.pop ();
        if (this.monik) this.monik.pop ();
      }
    }
    else
    {
      while (index-- > 0)
      {
        this.keys.pop    ();
        this.entries.pop ();

        if (this.assoc) this.assoc.pop ();
        if (this.monik) this.monik.pop ();
      }
    }
  }
  
  this.size = function ()
  {
    return (this.keys.length);
  }

  this.valueString = function (vsep, assoc)
  {
    var vtxt  = "";
    var vidx  = -1;
    var vlim  = this.entries.length;

    if (!this.assoc)
    {
      assoc = false;
    }
  
    vsep = vsep ? vsep : ',';
    
    while (++vidx < vlim)
    {
      vtxt += vsep;
      vtxt += this.entries[vidx];

      if (assoc)
      {
        vtxt += ':';
        vtxt += this.assoc[vidx];
      }
    }
    
    return (vtxt);
  }
  
  this.insert = function (key, value, override, assoc, zap)
  {
    key = key.toString ();
    
    var index = this.locate (key);
    
    if (index < 0)
    {
      index = -1 - index;

      if (index < this.keys.length)
      {
        this.keys.splice    (index, 0, key);
        this.entries.splice (index, 0, value);
      
        if (this.assoc) this.assoc.splice (index, 0, assoc);
        if (this.monik) this.monik.splice (index, 0, this.moniker);
      }
      else
      {
        this.keys[index]    = key;
        this.entries[index] = value;
      
        if (this.assoc) this.assoc[index] = assoc;
        if (this.monik) this.monik[index] = this.moniker;
      }
    }
    else if (override)
    {
      if (zap)
      {
        try
        {
          if (this.entries[index].destroy)
          {
            this.entries[index].destroy ();
          }
          else
          {
            destroyObject (this.entries[index]);
          }
        }

        catch (e)
        {
        }
      }

      this.entries[index] = value;
      
      if (this.assoc) this.assoc[index] = assoc;
      if (this.monik) this.monik[index] = this.moniker;
    }
    
    return (index);
  }

  this.put = function (key, value, assoc, zap)
  {
    return (this.insert (key, value, true, assoc, zap ? true : false));
  }
  
  this.deDuplicate = function (index)
  {
    var count = 0;
    var iidx  = -1;
    
    if (index) if (index.size () > 0)
    {
      var key = index.keys;
      var idx = key.length;
      
      while (idx-- > 0)
      {
        iidx = this.locate (key[idx]);
        
        if (iidx >= 0)
        {
          this.removeAt (iidx); count++;
        }
      }
    }
    
    return (count);
  }
  
  this.removeAt = function (index)
  {
    var obj   = null;
    
    if ((index >= 0) && (index < this.keys.length))
    {
      obj = this.entries[index];
      
      this.keys.splice    (index, 1);
      this.entries.splice (index, 1);
      
      if (this.assoc) this.assoc.splice (index, 1);
      if (this.monik) this.monik.splice (index, 1);
    }
    
    return (obj);
  }
  
  this.remove = function (key)
  {
    return (this.removeAt (this.locate (key.toString ())));
  }
  
  this.pop = function ()
  {
    var obj = null;
    
    if (this.entries.length > 0)
    {
      if (this.assoc) this.assoc.pop ();
      if (this.monik) this.monik.pop ();
      
            this.keys.pop    ();
      obj = this.entries.pop ();
    }
    
    return (obj);
  }
  
  this.drop = function ()
  {
    var obj = null;
    
    if (this.entries.length > 0)
    {
      obj = this.entries[0];
      
      this.keys    = this.keys.slice    (1);
      this.entries = this.entries.slice (1);
      
      if (this.assoc) this.assoc.slice (1);
      if (this.monik) this.monik.slice (1);
    }
    
    return (obj);
  }
  
  this.resolveNumber = function (key, dval, sep)
  {
    if (key != undefined)
    {
      if (!sep)
      {
        sep = '~';
      }
      
      try
      {
        key = key.toString ();
        
        if (key.indexOf (sep) > 0)
        {
          var bits  = key.split (sep);
          var bidx  = -1;
          var kidx  = -1;

          while (++bidx < bits.length)
          {
            kidx = this.locate (bits[bidx]);
            
            if (kidx >= 0)
            {
              dval = new Number (this.entries[kidx]);
              bidx = bits.length;
            }
          }
        }
        else
        {
          var kidx  = this.locate (key);
          
          if (kidx >= 0)
          {
            dval = new Number (this.entries[kidx]);
          }
        }
      }
      
      catch (e)
      {
      }
    }
    
    return (dval);
  }

  this.resolveBoolean = function (key, alt, sep)
  {
    if (key != undefined)
    {
      if (!sep)
      {
        sep = '~';
      }
      
      key = key.toString ();
      
      if (key.indexOf (sep) > 0)
      {
        var bits  = key.split (sep);
        var bidx  = -1;
        var kidx  = -1;

        while (++bidx < bits.length)
        {
          kidx = this.locate (bits[bidx]);
          
          if (kidx >= 0)
          {
            alt  = (this.entries[kidx] == "true") ? true : false;
            bidx = bits.length;
          }
        }
      }
      else
      {
        var kidx  = this.locate (key);
        
        if (kidx >= 0)
        {
          alt  = (this.entries[kidx] == "true") ? true : false;
        }
      }
    }
    
    return (alt);
  }
  
  this.resolve = function (key, alt)
  {
    var index = (key != undefined) ? this.locate (key.toString ()) : -1;
    
    return ((index >= 0) ? this.entries[index] : alt);
  }
  
  this.next = function (key)
  {
    key = key.toString ();
    
    var index = this.locate (key);
    
    if (index < 0)
    {
      index = 0;
    }
    else
    {
      index++;
      
      if (index == this.keys.length)
      {
        index = 0;
      }
    }

    return (this.valueAt (index, null));
  }
  
  this.previous = function (key)
  {
    key = key.toString ();
    
    var index = this.locate (key);
    
    if (index >= 0) index--;
    if (index <  0) index = this.keys.length - 1;

    return (this.valueAt (index, null));
  }
  
  this.keyAt = function (index, alt)
  {
    return (((index >= 0) && (index < this.keys.length)) ? this.keys[index] : alt);
  }
  
  this.valueAt = function (index, alt)
  {
    return (((index >= 0) && (index < this.entries.length)) ? this.entries[index] : alt);
  }
  
  this.contains = function (key)
  {
    key = key.toString ();
    
    return ((this.locate (key) >= 0) ? true : false);
  }
  
  this.buildargs = function (key)
  {
    var pos = this.locate (key);
    var lim = this.keys.length;
    var arg = null;
    
    if (pos < 0)
    {
      pos = -1 - pos;
    }
    else
    {
      arg  = this.entries[pos];
      pos += 1;
    }
    
    while (pos < lim)
    {
      if (this.keys[pos].indexOf (key) == 0)
      {
        if (arg == null)
        {
          arg = "";
        }
        else
        {
          arg += "&";
        }
        
        arg += this.keys[pos].substr (key.length);
        arg += "=";
        arg += encodeURIComponent (this.entries[pos]);
        
        pos++;
      }
      else
      {
        pos = lim;
      }
    }
    
    return (arg);
  }
  
  this.locateStart = function (key)
  {
    var index = this.locateStart (key);
    var done  = false;
    
    if (index < 0)
    {
      key   = key.toLowerCase ();
      index = -1 - index;
      
      while (!done && (index > 0))
      {
        if (this.keys[index - 1].indexOf (key) == 0)
        {
          index--;
        }
        else
        {
          done = true;
        }
      }
    }
    
    return (index);
  }
  
  this.locate = function (key)
  {
    var index = -1;
    var count = this.keys.length;

    if (count > 0)
    {
        var found   = false;
        var right   = count;
        var left    = 0;
        var next    = 0;
        var diff    = 0;
        var vdiff   = 0;
        
        key = key.toLowerCase ();
        
        do
        {
            diff  = right - left;
            next  = left + (diff >> 1);
            vdiff = (key != this.keys[next].toLowerCase ()) ? ((key > this.keys[next].toLowerCase ()) ? 1 : -1) : 0;

            if (vdiff != 0)
            {
                if (vdiff > 0)
                {
                    left = next;
                }
                else
                {
                    right = next;
                }
            }
            else
            {
                index = next;
                found = true;
            }

        } while (!found && ((right - left) != diff));

        if (!found)
        {
            if (vdiff > 0)
            {
                index = - (next + 2);
            }
            else
            {
                index = - (next + 1);
            }
        }
    }

    return (index);
  }
  
  this.setupXml = function (adoc, override, isset)
  {
    if (adoc) if (adoc != null)
    {
      var attribs = null;
      
      if (isset)
      {
        attribs = adoc;
      }
      else
      {
        attribs = adoc.selectNodes ("./attribute");
      }
      
      if (attribs != null)
      {
        var index   = -1;
        var limit   = attribs.length;
        
        while (++index < limit)
        {
          this.insert (getSingleNode (attribs[index], "name",  "", false),
                       getSingleNode (attribs[index], "value", "", false),
                       override);
        }
      }
    }
  }
  
  this.setupArgs = function (atxt, override)
  {
    if (atxt) if (atxt.length > 0)
    {
      var parts = atxt.split ("&");
      var index = -1;
      var pos   = 0;
      var name  = null;
      var value = null;
      
      override = override ? override : true;
      
      while (++index < parts.length)
      {
        pos = parts[index].indexOf ('=');
        
        if (pos > 0)
        {
          name  = compressLR (unescape (parts[index].substr (0, pos)));
          value = compressLR (unescape (parts[index].substr (pos + 1)));
          
          if (name.length > 0)
          {
            this.insert (name,
                         value,
                         override);
          }
        }
      }
    }
    
    return (this);
  }
  
  this.merge = function (donor, override)
  {
    if (donor != null)
    {
      var index = -1;
      var limit = donor.keys.length;
      
      while (++index < limit)
      {
        this.insert (donor.keys[index],
                     donor.entries[index],
                     override);
      }
    }
  }
  
  this.toString = function (xml, xtag)
  {
    var txt   = "";
    var idx   = -1;
    var name  = null;
    
    while (++idx < this.keys.length)
    {
      if (xml)
      {
        if (xtag)
        {
          txt += '<' + xtag + '><name>';
          txt += text2xml (this.keys[idx].toString ());
          txt += '</name><value>';
          txt += text2xml (this.entries[idx].toString ());
          txt += '</value></' + xtag + '>';
        }
        else
        {
          name = this.keys[idx].toString ().replace (/\\s/g, '_');
          
          txt += '<' + name + '>';
          txt += text2xml (this.entries[idx].toString ());
          txt += '</' + name + '>';
        }
      }
      else
      {
        txt += "{";
        txt += this.keys[idx].toString ();
        txt += " = ";
        txt += this.entries[idx].toString ();
        txt += "}";
      }
    }
    
    return (txt);
  }
  
  if (adoc)
  {
    var   x = typeof (adoc);
    
    if (typeof (adoc) == "string")
    {
      this.setupArgs (adoc, isset);
    }
    else
    {
      this.setupXml (adoc, true, isset);
    }
  }
}

