#!/usr/bin/env jx
var CoreModule = {};
CoreModule['crypto']='crypto';
CoreModule['util']='util';
CoreModule['http']='http';
CoreModule['fs']='fs';
CoreModule['stream']='stream';
CoreModule['url']='url';
CoreModule['os']='os';
CoreModule['net']='net';
CoreModule['zlib']='zlib';
CoreModule['path']='path';
CoreModule['dgram']='dgram';
CoreModule['child_process']='child_process';
CoreModule['events']='events';
CoreModule['string_decoder']='string_decoder';
CoreModule['assert']='assert';
CoreModule['buffer']='buffer';

var BundleModuleCode=[];
var BundleObjectCode=[];
var BundleModules = [];
function _isdir(path) {
  var stats=Fs.statSync(path);
  return stats && stats.isDirectory()};
function _search(index,file) {
  if (PATH.length==index) return file;
  var path=PATH[index];
  if (Fs.existsSync(path+"/"+file+".js")) return path+"/"+file+".js";
  else if (Fs.existsSync(path+"/"+file) && !_isdir(path+"/"+fil)) return path+"/"+file;
  else return _search(index+1,file);
 }
var Fs = require("fs");
if (typeof __dirname == 'undefined') __dirname = '';
if (typeof __filename == 'undefined') __filename = '/home/sbosse/proj/jam/js/top/jamsh.js';
if (typeof global == 'undefined') global={};
PATH=[process.cwd(),".","/home/sbosse/proj/jam/js"];
function search(index,file) {
  if (file.indexOf("/")==-1) return file;
  if (PATH.length==index) return file;
  var path=PATH[index];
  if (Fs.existsSync(path+"/"+file+".js")) return path+"/"+file+".js";
  else if (Fs.existsSync(path+"/"+file)) return path+"/"+file;
  else return search(index+1,file);
 }
function Require(modupath) { 
  var file,filepath;
  if (BundleModules[modupath]) return BundleModules[modupath];
  var exports={}; var module={exports:exports};
  if (CoreModule[modupath]!=undefined) modupath=CoreModule[modupath];
  if (modupath=='') return undefined;
  if (BundleModuleCode[modupath]) BundleModuleCode[modupath](module,exports);
  else if (BundleObjectCode[modupath]) BundleObjectCode[modupath](module,exports);
  else { try {  file=search(0,modupath); module = require(file)}
  catch (e) { var more="";
   if ((e.name==="SyntaxError"||e.name==="TypeError") && file) {
      var src=Fs.readFileSync(file,"utf8");
      var Esprima = Require("parser/esprima");
      try {
        var ast = Esprima.parse(src, { tolerant: true, loc:true });
        if (ast.errors && ast.errors.length>0) more = ", "+ast.errors[0];
      } catch (e) {
        if (e.lineNumber) more = ", in line "+e.lineNumber;
      }
   }
   console.log("Require import of "+file+" failed: "+e+more);
   // if (e.stack) console.log(e.stack);
   throw e; // process.exit(-1);
  }}
  BundleModules[modupath]=module.exports||module;
  return module.exports||module;};
FilesEmbedded=global.FilesEmbedded = {};
FileEmbedd=global.FileEmbedd = function (path,format) {};
FileEmbedded=global.FileEmbedded = function (path,format) {return FilesEmbedded[path](format);};
global.TARGET='node';

BundleModuleCode['com/io']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2006-2020 bLAB
 **    $CREATED:     sbosse on 28-3-15.
 **    $VERSION:     1.12.1
 **
 **    $INFO:
 *
 * This module encapsulates all IO operations (except networking) supporting
 * node.js applications.
 *
 **    $ENDOFINFO
 */
/*
 ************
 ** Node.js/jxcore
 ************
 */
var util = Require('util');
var os = Require('os');
var child = Require('child_process');
var GetEnv = Require('os/getenv');
var Base64 = Require('os/base64');
var Fs = Require('fs');

Require('os/polyfill')

var stderr_fun = function (str) { process.stderr.write(str); };
var stdout_fun = function (str) { process.stdout.write(str); };

/*
 ** node.js specific
 */

var tracefile = undefined;
var tracing = false;
var timestamp = false;

/**
* Open a module and append all exported properties to the context (e.g., global or this).
* (top-level scope)
*/
global.open = function(name,context,as) {
  var module = Require(name);
  if (!context) context=global;
  for (var p in module) {
    context[p] = module[p];
  };
  if (as) context[as]=module;
}
global.print = console.log;
var NL = '\n';

global.checkOptions = function(options,defaultOptions) {
  return Object.assign({}, defaultOptions||{}, options) };
global.checkOption = function (option,defaultOption) { 
 return option==undefined? defaultOption:option };


var io = {
  options: {
    columns:  undefined,
    rows:     undefined,
    log:      console.log.bind(console),
    err:      console.err,
    warn:     console.warn,
  },
   /**
    *
    * @param fd
    */
  close: function (fd) {
    Fs.closeSync(fd);
  },
  /**
  **  Return current date in year-month-day format
  */
  Date: function ()
  {
        var now = new Date();
        var year = "" + now.getFullYear();
        var month = "0" + (now.getMonth()+1);
        month = month.substring(month.length-2);
        var date = "0" + now.getDate();
        date = date.substring(date.length-2);
        return year + "-" + month + "-" + date;
  },
   /**
    *
    * @param msg
    */
  debug: function (msg) {
    io.options.err('Debug: ' + msg);
  },
  /**
    *
    * @param path
    */
  exists: function (path) {
    return Fs.existsSync(path);
  },
  error: undefined,
   /**
    *
    * @param msg
    */
  err: function (msg) {
    io.options.err('Error: ' + msg);
    throw Error(msg);
  },
  exit: function (n) {
       process.exit(n);
  },
   /**
    *
    * @param msg
    */
  fail: function (msg) {
    io.options.err('Fatal Error: ' + msg);
    process.exit(0);
  },
   /**
    *
    * @param path
    */
  file_exists: function (path) {
      return Fs.existsSync(path);
  },
   /** Search a file by iterating global PATH variable.
    *
    * @param name  File name or partial (relative) path
    */
  file_search: function (name) {
       // Expecting global PATH variable !?
       if (io.file_exists(name)) return name; 
       else if (typeof PATH !== 'undefined') {
         for (var p in PATH) {
           if (io.file_exists(PATH[p]+'/'+name)) return (PATH[p]+'/'+name);
         }
         return undefined;
       } else return undefined;
  },
   /**
    *
    * @param path
    * @returns {number}
    */
  file_size: function (path) {
       var stat = Fs.statSync(path);
       if (stat != undefined)
           return stat.size;
       else
           return -1;
  },
   /**
    *
    * @param path
    * @param timekind a c m
    * @returns {number}
    */
  file_time: function (path,timekind) {
       var stat = Fs.statSync(path);
       if (stat != undefined)
           switch (timekind) {
               case 'a': return stat.atime.getTime()/1000;
               case 'c': return stat.ctime.getTime()/1000;
               case 'm': return stat.mtime.getTime()/1000;
               default: return stat.mtime.getTime()/1000;
           }
       else
           return -1;
  },

   /**
    *  @return {string []}
    */
  getargs: function () {
       return process.argv;
  },
  getenv: function (name, def) {
       return GetEnv(name, def);
  },



   /**
    *
    * @param obj
    */
  inspect: util.inspect,

   /**
    *
    * @param {boolean|string} condmsg conditional message var log=X;  log((log lt. N)||(msg))
    */
  log: function (condmsg) {
      if (condmsg==true) return;
      if (!timestamp) console.warn(condmsg);
      else {
        var date = new Date();
        var time = Math.floor(date.getTime());
        console.warn('['+process.pid+':'+time+']'+condmsg);
      }
  },
   /**
    *
    * @returns {*} RSS HEAP in kBytes {data,heap}
    */
  mem: function () {
       var mem = process.memoryUsage();
       return {data:(mem.rss/1024)|0,heap:(mem.heapUsed/1024)|0};
  },
   /**
    *
    * @param path
    * @param mode
    * @returns {*}
    */
  open: function (path, mode) {
       return Fs.openSync(path, mode);
  },

   /**
    *
    * @param msg
    */
  out: function (msg) {
    io.options.log(msg)
  },
   /**
    *
    * @param e
    * @param where
    */
  printstack: function (e,where) {
       if (!e.stack) e=new Error(e);
       if (!e.stack) e.stack ='empty stack';
       var stack = e.stack //.replace(/^[^\(]+?[\n$]/gm, '')
           .replace(/^\s+at\s+/gm, '')
           .replace(/^Object.<anonymous>\s*\(/gm, '{anonymous}()@')
           .split('\n');
       if (where==undefined) io.out(e);
       else io.out(where+': '+e);
       io.out('Stack Trace');
       io.out('--------------------------------');
       for(var i in stack) {
           if (i>0) {
               var line = stack[i];
               if(line.indexOf('Module.',0)>=0) break;
               io.out('   at '+line);
           }
       }
       io.out('--------------------------------');
  },
   /**
    *
    * @param fd
    * @param len
    * @param foff
    */
  read: function (fd, len, foff) {
       // TODO
  },
   /**
    *
    * @param path
    * @returns {string|undefined}
    */
  read_file: function (path) {
       try {
          return Fs.readFileSync(path,'utf8');
       } catch (e) {
          io.error=e;
          return undefined;
       }
  },
   /**
    *
    * @param path
    * @returns {*}
    */
  read_file_bin: function (path) {
       try {
          return Fs.readFileSync(path);
       } catch (e) {
          io.error=e;
          return undefined;
       }
  },
   /**
    *
    * @param fd
    */
  read_line: function (fd) {
       // TODO
  },
   /**
    *
    * @param fd
    * @param buf
    * @param boff
    * @param len
    * @param [foff]
    * @returns {number}
    */
  read_buf: function (fd, buf, boff, len, foff) {
       return Fs.readSync(fd, buf, boff, len, foff);
  },
   /**
    *
    * @param e
    * @param where
    */
  sprintstack: function (e) {
       var str='';
       if (e==_ || !e.stack) e=new Error(e);
       if (!e.stack) e.stack ='empty stack';
       var stack = e.stack //.replace(/^[^\(]+?[\n$]/gm, '')
           .replace(/^\s+at\s+/gm, '')
           .replace(/^Object.<anonymous>\s*\(/gm, '{anonymous}()@')
           .replace(/^Object.eval\s*\(/gm, '')
           .split('\n');
       for(var i in stack) {
           if (i>0) {
               var line = stack[i];
               if(line.indexOf('Module.',0)>=0) break;
               if (str!='') str += '\n';
               str += '  at '+line;
           }
       }
       return str;
  },
   /**
    * 
    */
  stacktrace: function () {
       var e = new Error('dummy');
       if (!e.stack) e.stack ='empty stack';
       var stack = e.stack.replace(/^[^\(]+?[\n$]/gm, '')
           .replace(/^\s+at\s+/gm, '')
           .replace(/^Object.<anonymous>\s*\(/gm, '{anonymous}()@')
           .split('\n');
       io.out('Stack Trace');
       io.out('--------------------------------');
       for(var i in stack) {
           if (i>0) {
               var line = stack[i];
               if(line.indexOf('Module.',0)>=0) break;
               io.out('  at '+line);
           }
       }
       io.out('--------------------------------');
  },
   /**
    *
    * @param fun
    */
  set_stderr: function(fun) {
       stderr_fun=fun;
  },
   /**
    *
    * @param fun
    */
  set_stdout: function(fun) {
       stdout_fun=fun;
  },
   /**
    *
    * @param msg
    */
  stderr: function (msg) {
       stderr_fun(msg);
  },
  // sleep(ms)
  sleep: function(delay) {
     var start = new Date().getTime();
     while (new Date().getTime() < start + delay);
  },

   /**
    *
    * @param msg
    */
  stdout: function (msg) {
       stdout_fun(msg);
  },
   /**
    *
    * @param fd
    */
  sync: function (fd) {
       Fs.fsyncSync(fd);
  },
  date: function () {
     var date = Date();
     return date.split(' ').slice(1,5).join(' ');
  },
  /** Return system time in milliseconds
    */
  time: function () {
     var date = new Date();
     return Math.floor(date.getTime());
  },
  /**
  **  Return current time in hour:minute:second:milli format
  */
  Time: function ()
  {
        var now = new Date();
        var hour = "0" + now.getHours();
        hour = hour.substring(hour.length-2);
        var minute = "0" + now.getMinutes();
        minute = minute.substring(minute.length-2);
        var second = "0" + now.getSeconds();
        second = second.substring(second.length-2);
        var milli = "0" + Math.floor(now.getMilliseconds()/10);
        milli = milli.substring(milli.length-2);
        return hour + ":" + minute + ":" + second+':'+milli;
  },
   /** Write a message with a time stamp written to the trace file.
    *
    * @param {boolean|string} condmsg conditional message var trace=Io.tracing;  trace(trace||(msg))
    */
  trace: function (condmsg) {
       if (condmsg != true && tracefile != undefined) {
           var date = new Date();
           var time = Math.floor(date.getTime());
           Fs.writeSync(tracefile, '[' + time + '] ' + condmsg + '\n');
       }
  },
  tracing: tracing,
   /**
    *
    * @param {string} path
    */
  trace_open: function (path) {
       tracefile = Fs.openSync(path, 'w+');
       if (tracefile != undefined) io.tracing = false;
  },
   /**
    *
    * @param msg
    */
  warn: function (msg) {
      if (!timestamp) io.options.warn('Warning: ' + msg);
      else {
        var date = new Date();
        var time = Math.floor(date.getTime());
        console.warn('['+process.pid+':'+time+'] Warning: '+msg);
      }   
  },
  workdir: function () {
       return io.getenv('PWD',io.getenv('CWD',''));
  },
   /**
    *
    * @param fd
    * @param data
    * @param [foff]
    * @returns {number}
    */
  write: function (fd, data, foff) {
       return Fs.writeSync(fd, data, foff);
  },
   /**
    *
    * @param fd
    * @param buf
    * @param bpos
    * @param blen
    * @param [foff]
    * @returns {number}
    */
  write_buf: function (fd, buf, bpos, blen, foff) {
       return Fs.writeSync(fd, buf, bpos, blen, foff);
  },
   /**
    *
    * @param path
    * @param {string} buf
    */
  write_file: function (path,str) {
       try {
           Fs.writeFileSync(path, str, 'utf8');
           return str.length;
       } catch (e) {
           return -1;
       }
  },
   /**
    *
    * @param path
    * @param buf
    * @returns {*}
    */
  write_file_bin: function (path,buf) {
       try {
          Fs.writeFileSync(path, buf, 'binary');
          return buf.length;
       } catch (e) {
          io.error=e;
          return -1;
       }
  },
   /**
    *
    * @param fd
    * @param {string} str
    * @returns {number}
    */
  write_line: function (fd, str) {
       return Fs.writeSync(fd, str+NL);
  },




 
   /**
    *  Process management
    */
   fork: child.fork,
   exec: child.exec,
   execSync: child.execSync,
   spawn: child.spawn,

   /**
    * OS
    */
   hostname: os.hostname
};

module.exports = io;
};
BundleModuleCode['os/getenv']=function (module,exports){
var util = require("util");
//var url = require("url");

var fallbacksDisabled = false;

function _value(varName, fallback) {
  var value = process.env[varName];
  if (value === undefined) {
    if (fallback === undefined) {
      throw new Error('GetEnv.Nonexistent: ' + varName + ' does not exist ' +
                      'and no fallback value provided.');
    }
    if (fallbacksDisabled) {
      throw new Error('GetEnv.DisabledFallbacks: ' + varName + ' relying on fallback ' + 
                      'when fallbacks have been disabled');
    }
    return '' + fallback;
  }
  return value;
}

var convert = {
  string: function(value) {
    return '' + value;
  },
  int: function(value) {
    var isInt = value.match(/^-?\d+$/);
    if (!isInt) {
      throw new Error('GetEnv.NoInteger: ' + value + ' is not an integer.');
    }

    return +value;
  },
  float: function(value) {
    var isInfinity = (+value === Infinity || +value === -Infinity);
    if (isInfinity) {
      throw new Error('GetEnv.Infinity: ' + value + ' is set to +/-Infinity.');
    }

    var isFloat = !(isNaN(value) || value === '');
    if (!isFloat) {
      throw new Error('GetEnv.NoFloat: ' + value + ' is not a number.');
    }

    return +value;
  },
  bool: function(value) {
    var isBool = (value === 'true' || value === 'false');
    if (!isBool) {
      throw new Error('GetEnv.NoBoolean: ' + value + ' is not a boolean.');
    }

    return (value === 'true');
  }
  // , url: url.parse
};

function converter(type) {
  return function(varName, fallback) {
    if(typeof varName == 'string') { // default
      var value = _value(varName, fallback);
      return convert[type](value);
    } else { // multibert!
      return getenv.multi(varName);
    }
  };
};

var getenv = converter('string');

Object.keys(convert).forEach(function(type) {
  getenv[type] = converter(type);
});

getenv.array = function array(varName, type, fallback) {
  type = type || 'string';
  if (Object.keys(convert).indexOf(type) === -1) {
    throw new Error('GetEnv.ArrayUndefinedType: Unknown array type ' + type);
  }
  var value = _value(varName, fallback);
  return value.split(/\s*,\s*/).map(convert[type]);
};

getenv.multi = function multi(spec) {
  var key, value;
  var result = {};
  for(var key in spec) {
    var value = spec[key];
    if(util.isArray(value)) { // default value & typecast
      switch(value.length) {
        case 1: // no default value
        case 2: // no type casting
          result[key] = getenv(value[0], value[1]); // dirty, when case 1: value[1] is undefined
        break;
        case 3: // with typecast
          result[key] = getenv[value[2]](value[0], value[1]);
          break;
        default: // wtf?
          throw('getenv.multi(): invalid spec');
          break;
      }
    } else { // value or throw
      result[key] = getenv(value);
    }
  }
  return result;
};

getenv.disableFallbacks = function() {
  fallbacksDisabled = true;
};

getenv.enableFallbacks = function() {
  fallbacksDisabled = false;
};

module.exports = getenv;
};
BundleModuleCode['os/base64']=function (module,exports){
var keyStr = "ABCDEFGHIJKLMNOP" +
               "QRSTUVWXYZabcdef" +
               "ghijklmnopqrstuv" +
               "wxyz0123456789+/" +
               "=";

var Base64 = {
  encode: function (input) {
     input = escape(input);
     var output = "";
     var chr1, chr2, chr3 = "";
     var enc1, enc2, enc3, enc4 = "";
     var i = 0;

     do {
        chr1 = input.charCodeAt(i++);
        chr2 = input.charCodeAt(i++);
        chr3 = input.charCodeAt(i++);

        enc1 = chr1 >> 2;
        enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
        enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
        enc4 = chr3 & 63;

        if (isNaN(chr2)) {
           enc3 = enc4 = 64;
        } else if (isNaN(chr3)) {
           enc4 = 64;
        }

        output = output +
           keyStr.charAt(enc1) +
           keyStr.charAt(enc2) +
           keyStr.charAt(enc3) +
           keyStr.charAt(enc4);
        chr1 = chr2 = chr3 = "";
        enc1 = enc2 = enc3 = enc4 = "";
     } while (i < input.length);

     return output;
  },

  encodeBuf: function (input) {
     var output = "";
     var NaN = output.charCodeAt(2);
     var chr1, chr2, chr3 = "";
     var enc1, enc2, enc3, enc4 = "";
     var i = 0;
     var len = input.length;
     do {
        chr1 = input.readUInt8(i++);
        chr2 = (i<len)?input.readUInt8(i++):NaN;
        chr3 = (i<len)?input.readUInt8(i++):NaN;

        enc1 = chr1 >> 2;
        enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
        enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
        enc4 = chr3 & 63;

        if (isNaN(chr2)) {
           enc3 = enc4 = 64;
        } else if (isNaN(chr3)) {
           enc4 = 64;
        }

        output = output +
           keyStr.charAt(enc1) +
           keyStr.charAt(enc2) +
           keyStr.charAt(enc3) +
           keyStr.charAt(enc4);
        chr1 = chr2 = chr3 = "";
        enc1 = enc2 = enc3 = enc4 = "";
     } while (i < len);

     return output;
  },

  decode: function (input) {
     var output = "";
     var chr1, chr2, chr3 = "";
     var enc1, enc2, enc3, enc4 = "";
     var i = 0;

     input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");

     do {
        enc1 = keyStr.indexOf(input.charAt(i++));
        enc2 = keyStr.indexOf(input.charAt(i++));
        enc3 = keyStr.indexOf(input.charAt(i++));
        enc4 = keyStr.indexOf(input.charAt(i++));

        chr1 = (enc1 << 2) | (enc2 >> 4);
        chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
        chr3 = ((enc3 & 3) << 6) | enc4;

        output = output + String.fromCharCode(chr1);

        if (enc3 != 64) {
           output = output + String.fromCharCode(chr2);
        }
        if (enc4 != 64) {
           output = output + String.fromCharCode(chr3);
        }

        chr1 = chr2 = chr3 = "";
        enc1 = enc2 = enc3 = enc4 = "";

     } while (i < input.length);

     return unescape(output);
  },
  decodeBuf: function (input) {
     var len = input.length;
     var buf = new Buffer(len);
     var chr1, chr2, chr3 = "";
     var enc1, enc2, enc3, enc4 = "";
     var i = 0;
     var buflen = 0;
     input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
     buf.fill(0);
     do {
        enc1 = keyStr.indexOf(input.charAt(i++));
        enc2 = keyStr.indexOf(input.charAt(i++));
        enc3 = keyStr.indexOf(input.charAt(i++));
        enc4 = keyStr.indexOf(input.charAt(i++));

        chr1 = (enc1 << 2) | (enc2 >> 4);
        chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
        chr3 = ((enc3 & 3) << 6) | enc4;

        buf.writeUInt8(chr1,buflen);
        buflen++;
        if (enc3 != 64) {
          buf.writeUInt8(chr2,buflen);
          buflen++;
        }
        if (enc4 != 64) {
            buf.writeUInt8(chr3,buflen);
            buflen++;
        }

        chr1 = chr2 = chr3 = "";
        enc1 = enc2 = enc3 = enc4 = "";

     } while (i < input.length);

     return buf.slice(0,buflen);
  }

};


module.exports = Base64;
};
BundleModuleCode['os/polyfill']=function (module,exports){

/********** OBJECT **************/

Object.addProperty = function (obj,name,fun) {
  if (obj.prototype[name]) return;
  obj.prototype[name]=fun;
  Object.defineProperty(obj.prototype, name, {enumerable: false});
};

Object.updateProperty = function (obj,name,fun) {
  obj.prototype[name]=fun;
  Object.defineProperty(obj.prototype, name, {enumerable: false});
};

if (typeof Object.assign != 'function') {
  // Must be writable: true, enumerable: false, configurable: true
  Object.defineProperty(Object, "assign", {
    value: function assign(target, varArgs) { // .length of function is 2
      'use strict';
      if (target == null) { // TypeError if undefined or null
        throw new TypeError('Cannot convert undefined or null to object');
      }

      var to = Object(target);

      for (var index = 1; index < arguments.length; index++) {
        var nextSource = arguments[index];

        if (nextSource != null) { // Skip over if undefined or null
          for (var nextKey in nextSource) {
            // Avoid bugs when hasOwnProperty is shadowed
            if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
              to[nextKey] = nextSource[nextKey];
            }
          }
        }
      }
      return to;
    },
    writable: true,
    configurable: true
  });
}

/************** ARRAY ********************/

if (!Array.prototype.find) {
  Object.addProperty(Array, 'find', function(predicate) {
     // 1. Let O be ? ToObject(this value).
      if (this == null) {
        throw new TypeError('"this" is null or not defined');
      }

      var o = Object(this);

      // 2. Let len be ? ToLength(? Get(O, "length")).
      var len = o.length >>> 0;

      // 3. If IsCallable(predicate) is false, throw a TypeError exception.
      if (typeof predicate !== 'function') {
        throw new TypeError('predicate must be a function');
      }

      // 4. If thisArg was supplied, let T be thisArg; else let T be undefined.
      var thisArg = arguments[1];

      // 5. Let k be 0.
      var k = 0;

      // 6. Repeat, while k < len
      while (k < len) {
        // a. Let Pk be ! ToString(k).
        // b. Let kValue be ? Get(O, Pk).
        // c. Let testResult be ToBoolean(? Call(predicate, T, ( kValue, k, O ))).
        // d. If testResult is true, return kValue.
        var kValue = o[k];
        if (predicate.call(thisArg, kValue, k, o)) {
          return kValue;
        }
        // e. Increase k by 1.
        k++;
      }

      // 7. Return undefined.
      return undefined;
  });
}

// String prototype extensions
if (!String.prototype.contains){
  Object.addProperty(String,'contains', function (el) {
    return this.indexOf(el)!=-1
  })
}

// Check Options Extension
checkOptions = function(options,defaultOptions) {
  return Object.assign({}, defaultOptions||{}, options) };
checkOption = function (option,defaultOption) { 
 return option==undefined? defaultOption:option };
};
BundleModuleCode['com/path']=function (module,exports){
var Fs = Require('fs');

var _process = process || {};
(function () {
  "use strict";

// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.


var isWindows = _process.platform === 'win32';
var util = Require('util');
if (!util.deprecate) util.deprecate=function(f,w) {return f;};

// resolves . and .. elements in a path array with directory names there
// must be no slashes, empty elements, or device names (c:\) in the array
// (so also no leading and trailing slashes - it does not distinguish
// relative and absolute paths)
function normalizeArray(parts, allowAboveRoot) {
  // if the path tries to go above the root, `up` ends up > 0
  var up = 0;
  for (var i = parts.length - 1; i >= 0; i--) {
    var last = parts[i];
    if (last === '.') {
      parts.splice(i, 1);
    } else if (last === '..') {
      parts.splice(i, 1);
      up++;
    } else if (up) {
      parts.splice(i, 1);
      up--;
    }
  }

  // if the path is allowed to go above the root, restore leading ..s
  if (allowAboveRoot) {
    for (; up--; up) {
      parts.unshift('..');
    }
  }

  return parts;
}


if (isWindows) {
  // Regex to split a windows path into three parts: [*, device, slash,
  // tail] windows-only
  var splitDeviceRe =
      /^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/]+[^\\\/]+)?([\\\/])?([\s\S]*?)$/;

  // Regex to split the tail part of the above into [*, dir, basename, ext]
  var splitTailRe =
      /^([\s\S]*?)((?:\.{1,2}|[^\\\/]+?|)(\.[^.\/\\]*|))(?:[\\\/]*)$/;

  // Function to split a filename into [root, dir, basename, ext]
  // windows version
  var splitPath = function(filename) {
    // Separate device+slash from tail
    var result = splitDeviceRe.exec(filename),
        device = (result[1] || '') + (result[2] || ''),
        tail = result[3] || '';
    // Split the tail into dir, basename and extension
    var result2 = splitTailRe.exec(tail),
        dir = result2[1],
        basename = result2[2],
        ext = result2[3];
    return [device, dir, basename, ext];
  };

  var normalizeUNCRoot = function(device) {
    return '\\\\' + device.replace(/^[\\\/]+/, '').replace(/[\\\/]+/g, '\\');
  };

  // path.resolve([from ...], to)
  // windows version
  exports.resolve = function() {
    var resolvedDevice = '',
        resolvedTail = '',
        resolvedAbsolute = false;

    for (var i = arguments.length - 1; i >= -1; i--) {
      var path;
      if (i >= 0) {
        path = arguments[i];
      } else if (!resolvedDevice) {
        path = _process.cwd();
      } else {
        // Windows has the concept of drive-specific current working
        // directories. If we've resolved a drive letter but not yet an
        // absolute path, get cwd for that drive. We're sure the device is not
        // an unc path at this points, because unc paths are always absolute.
        path = _process.env['=' + resolvedDevice];
        // Verify that a drive-local cwd was found and that it actually points
        // to our drive. If not, default to the drive's root.
        if (!path || path.substr(0, 3).toLowerCase() !==
            resolvedDevice.toLowerCase() + '\\') {
          path = resolvedDevice + '\\';
        }
      }

      // Skip empty and invalid entries
      if (!util.isString(path)) {
        throw new TypeError('Arguments to path.resolve must be strings');
      } else if (!path) {
        continue;
      }

      var result = splitDeviceRe.exec(path),
          device = result[1] || '',
          isUnc = device && device.charAt(1) !== ':',
          isAbsolute = exports.isAbsolute(path),
          tail = result[3];

      if (device &&
          resolvedDevice &&
          device.toLowerCase() !== resolvedDevice.toLowerCase()) {
        // This path points to another device so it is not applicable
        continue;
      }

      if (!resolvedDevice) {
        resolvedDevice = device;
      }
      if (!resolvedAbsolute) {
        resolvedTail = tail + '\\' + resolvedTail;
        resolvedAbsolute = isAbsolute;
      }

      if (resolvedDevice && resolvedAbsolute) {
        break;
      }
    }

    // Convert slashes to backslashes when `resolvedDevice` points to an UNC
    // root. Also squash multiple slashes into a single one where appropriate.
    if (isUnc) {
      resolvedDevice = normalizeUNCRoot(resolvedDevice);
    }

    // At this point the path should be resolved to a full absolute path,
    // but handle relative paths to be safe (might happen when process.cwd()
    // fails)

    // Normalize the tail path

    function f(p) {
      return !!p;
    }

    resolvedTail = normalizeArray(resolvedTail.split(/[\\\/]+/).filter(f),
                                  !resolvedAbsolute).join('\\');

    return (resolvedDevice + (resolvedAbsolute ? '\\' : '') + resolvedTail) ||
           '.';
  };

  // windows version
  exports.normalize = function(path) {
    var result = splitDeviceRe.exec(path),
        device = result[1] || '',
        isUnc = device && device.charAt(1) !== ':',
        isAbsolute = exports.isAbsolute(path),
        tail = result[3],
        trailingSlash = /[\\\/]$/.test(tail);

    // If device is a drive letter, we'll normalize to lower case.
    if (device && device.charAt(1) === ':') {
      device = device[0].toLowerCase() + device.substr(1);
    }

    // Normalize the tail path
    tail = normalizeArray(tail.split(/[\\\/]+/).filter(function(p) {
      return !!p;
    }), !isAbsolute).join('\\');

    if (!tail && !isAbsolute) {
      tail = '.';
    }
    if (tail && trailingSlash) {
      tail += '\\';
    }

    // Convert slashes to backslashes when `device` points to an UNC root.
    // Also squash multiple slashes into a single one where appropriate.
    if (isUnc) {
      device = normalizeUNCRoot(device);
    }

    return device + (isAbsolute ? '\\' : '') + tail;
  };

  // windows version
  exports.isAbsolute = function(path) {
    var result = splitDeviceRe.exec(path),
        device = result[1] || '',
        isUnc = !!device && device.charAt(1) !== ':';
    // UNC paths are always absolute
    return !!result[2] || isUnc;
  };

  // windows version
  exports.join = function() {
    function f(p) {
      if (!util.isString(p)) {
        throw new TypeError('Arguments to path.join must be strings');
      }
      return p;
    }

    var paths = Array.prototype.filter.call(arguments, f);
    var joined = paths.join('\\');

    // Make sure that the joined path doesn't start with two slashes, because
    // normalize() will mistake it for an UNC path then.
    //
    // This step is skipped when it is very clear that the user actually
    // intended to point at an UNC path. This is assumed when the first
    // non-empty string arguments starts with exactly two slashes followed by
    // at least one more non-slash character.
    //
    // Note that for normalize() to treat a path as an UNC path it needs to
    // have at least 2 components, so we don't filter for that here.
    // This means that the user can use join to construct UNC paths from
    // a server name and a share name; for example:
    //   path.join('//server', 'share') -> '\\\\server\\share\')
    if (!/^[\\\/]{2}[^\\\/]/.test(paths[0])) {
      joined = joined.replace(/^[\\\/]{2,}/, '\\');
    }

    return exports.normalize(joined);
  };

  // path.relative(from, to)
  // it will solve the relative path from 'from' to 'to', for instance:
  // from = 'C:\\orandea\\test\\aaa'
  // to = 'C:\\orandea\\impl\\bbb'
  // The output of the function should be: '..\\..\\impl\\bbb'
  // windows version
  exports.relative = function(from, to) {
    from = exports.resolve(from);
    to = exports.resolve(to);

    // windows is not case sensitive
    var lowerFrom = from.toLowerCase();
    var lowerTo = to.toLowerCase();

    function trim(arr) {
      var start = 0;
      for (; start < arr.length; start++) {
        if (arr[start] !== '') break;
      }

      var end = arr.length - 1;
      for (; end >= 0; end--) {
        if (arr[end] !== '') break;
      }

      if (start > end) return [];
      return arr.slice(start, end + 1);
    }

    var toParts = trim(to.split('\\'));

    var lowerFromParts = trim(lowerFrom.split('\\'));
    var lowerToParts = trim(lowerTo.split('\\'));

    var length = Math.min(lowerFromParts.length, lowerToParts.length);
    var samePartsLength = length;
    for (var i = 0; i < length; i++) {
      if (lowerFromParts[i] !== lowerToParts[i]) {
        samePartsLength = i;
        break;
      }
    }

    if (samePartsLength == 0) {
      return to;
    }

    var outputParts = [];
    for (var i = samePartsLength; i < lowerFromParts.length; i++) {
      outputParts.push('..');
    }

    outputParts = outputParts.concat(toParts.slice(samePartsLength));

    return outputParts.join('\\');
  };

  exports.sep = '\\';
  exports.delimiter = ';';

} else /* posix */ {

  // Split a filename into [root, dir, basename, ext], unix version
  // 'root' is just a slash, or nothing.
  var splitPathRe =
      /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;
  var splitPath = function(filename) {
    return splitPathRe.exec(filename).slice(1);
  };

  // path.resolve([from ...], to)
  // posix version
  exports.resolve = function() {
    var resolvedPath = '',
        resolvedAbsolute = false;

    for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) {
      var path = (i >= 0) ? arguments[i] : _process.cwd();

      // Skip empty and invalid entries
      if (!util.isString(path)) {
        throw new TypeError('Arguments to path.resolve must be strings');
      } else if (!path) {
        continue;
      }

      resolvedPath = path + '/' + resolvedPath;
      resolvedAbsolute = path.charAt(0) === '/';
    }

    // At this point the path should be resolved to a full absolute path, but
    // handle relative paths to be safe (might happen when process.cwd() fails)

    // Normalize the path
    resolvedPath = normalizeArray(resolvedPath.split('/').filter(function(p) {
      return !!p;
    }), !resolvedAbsolute).join('/');

    return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.';
  };

  // path.normalize(path)
  // posix version
  exports.normalize = function(path) {
    var isAbsolute = exports.isAbsolute(path),
        trailingSlash = path[path.length - 1] === '/',
        segments = path.split('/'),
        nonEmptySegments = [];

    // Normalize the path
    for (var i = 0; i < segments.length; i++) {
      if (segments[i]) {
        nonEmptySegments.push(segments[i]);
      }
    }
    path = normalizeArray(nonEmptySegments, !isAbsolute).join('/');

    if (!path && !isAbsolute) {
      path = '.';
    }
    if (path && trailingSlash) {
      path += '/';
    }

    return (isAbsolute ? '/' : '') + path;
  };

  // posix version
  exports.isAbsolute = function(path) {
    return path.charAt(0) === '/';
  };

  // posix version
  exports.join = function() {
    var path = '';
    for (var i = 0; i < arguments.length; i++) {
      var segment = arguments[i];
      if (!util.isString(segment)) {
        throw new TypeError('Arguments to path.join must be strings');
      }
      if (segment) {
        if (!path) {
          path += segment;
        } else {
          path += '/' + segment;
        }
      }
    }
    return exports.normalize(path);
  };


  // path.relative(from, to)
  // posix version
  exports.relative = function(from, to) {
    from = exports.resolve(from).substr(1);
    to = exports.resolve(to).substr(1);

    function trim(arr) {
      var start = 0;
      for (; start < arr.length; start++) {
        if (arr[start] !== '') break;
      }

      var end = arr.length - 1;
      for (; end >= 0; end--) {
        if (arr[end] !== '') break;
      }

      if (start > end) return [];
      return arr.slice(start, end + 1);
    }

    var fromParts = trim(from.split('/'));
    var toParts = trim(to.split('/'));

    var length = Math.min(fromParts.length, toParts.length);
    var samePartsLength = length;
    for (var i = 0; i < length; i++) {
      if (fromParts[i] !== toParts[i]) {
        samePartsLength = i;
        break;
      }
    }

    var outputParts = [];
    for (var i = samePartsLength; i < fromParts.length; i++) {
      outputParts.push('..');
    }

    outputParts = outputParts.concat(toParts.slice(samePartsLength));

    return outputParts.join('/');
  };

  exports.sep = '/';
  exports.delimiter = ':';
}

exports.dirname = function(path) {
  var result = splitPath(path),
      root = result[0],
      dir = result[1];

  if (!root && !dir) {
    // No dirname whatsoever
    return '.';
  }

  if (dir) {
    // It has a dirname, strip trailing slash
    dir = dir.substr(0, dir.length - 1);
  }

  return root + dir;
};


exports.basename = function(path, ext) {
  var f = splitPath(path)[2];
  // TODO: make this comparison case-insensitive on windows?
  if (ext && f.substr(-1 * ext.length) === ext) {
    f = f.substr(0, f.length - ext.length);
  }
  return f;
};


exports.extname = function(path) {
  return splitPath(path)[3];
};


exports.exists = util.deprecate(function(path, callback) {
  if (Fs) Fs.exists(path, callback);
  else callback(false);
}, 'path.exists is now called `fs.exists`.');


exports.existsSync = util.deprecate(function(path) {
  if (Fs) return Fs.existsSync(path);
  else return false;
}, 'path.existsSync is now called `fs.existsSync`.');


if (isWindows) {
  exports._makeLong = function(path) {
    // Note: this will *probably* throw somewhere.
    if (!util.isString(path))
      return path;

    if (!path) {
      return '';
    }

    var resolvedPath = exports.resolve(path);

    if (/^[a-zA-Z]\:\\/.test(resolvedPath)) {
      // path is local filesystem path, which needs to be converted
      // to long UNC path.
      return '\\\\?\\' + resolvedPath;
    } else if (/^\\\\[^?.]/.test(resolvedPath)) {
      // path is network UNC path, which needs to be converted
      // to long UNC path.
      return '\\\\?\\UNC\\' + resolvedPath.substring(2);
    }

    return path;
  };
} else {
  exports._makeLong = function(path) {
    return path;
  };
}
}());
};
BundleModuleCode['com/sprintf']=function (module,exports){
(function(window) {
    var re = {
        not_string: /[^s]/,
        number: /[diefg]/,
        json: /[j]/,
        not_json: /[^j]/,
        text: /^[^\x25]+/,
        modulo: /^\x25{2}/,
        placeholder: /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-gijosuxX])/,
        key: /^([a-z_][a-z_\d]*)/i,
        key_access: /^\.([a-z_][a-z_\d]*)/i,
        index_access: /^\[(\d+)\]/,
        sign: /^[\+\-]/
    }

    function sprintf() {
        var key = arguments[0], cache = sprintf.cache
        if (!(cache[key] && cache.hasOwnProperty(key))) {
            cache[key] = sprintf.parse(key)
        }
        return sprintf.format.call(null, cache[key], arguments)
    }

    sprintf.format = function(parse_tree, argv) {
        var cursor = 1, tree_length = parse_tree.length, node_type = "", arg, output = [], i, k, match, pad, pad_character, pad_length, is_positive = true, sign = ""
        for (i = 0; i < tree_length; i++) {
            node_type = get_type(parse_tree[i])
            if (node_type === "string") {
                output[output.length] = parse_tree[i]
            }
            else if (node_type === "array") {
                match = parse_tree[i] // convenience purposes only
                if (match[2]) { // keyword argument
                    arg = argv[cursor]
                    for (k = 0; k < match[2].length; k++) {
                        if (!arg.hasOwnProperty(match[2][k])) {
                            throw new Error(sprintf("[sprintf] property '%s' does not exist", match[2][k]))
                        }
                        arg = arg[match[2][k]]
                    }
                }
                else if (match[1]) { // positional argument (explicit)
                    arg = argv[match[1]]
                }
                else { // positional argument (implicit)
                    arg = argv[cursor++]
                }

                if (get_type(arg) == "function") {
                    arg = arg()
                }

                if (re.not_string.test(match[8]) && re.not_json.test(match[8]) && (get_type(arg) != "number" && isNaN(arg))) {
                    throw new TypeError(sprintf("[sprintf] expecting number but found %s", get_type(arg)))
                }

                if (re.number.test(match[8])) {
                    is_positive = arg >= 0
                }

                switch (match[8]) {
                    case "b":
                        arg = arg.toString(2)
                    break
                    case "c":
                        arg = String.fromCharCode(arg)
                    break
                    case "d":
                    case "i":
                        arg = parseInt(arg, 10)
                    break
                    case "j":
                        arg = JSON.stringify(arg, null, match[6] ? parseInt(match[6]) : 0)
                    break
                    case "e":
                        arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential()
                    break
                    case "f":
                        arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg)
                    break
                    case "g":
                        arg = match[7] ? parseFloat(arg).toPrecision(match[7]) : parseFloat(arg)
                    break
                    case "o":
                        arg = arg.toString(8)
                    break
                    case "s":
                        arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg)
                    break
                    case "u":
                        arg = arg >>> 0
                    break
                    case "x":
                        arg = arg.toString(16)
                    break
                    case "X":
                        arg = arg.toString(16).toUpperCase()
                    break
                }
                if (re.json.test(match[8])) {
                    output[output.length] = arg
                }
                else {
                    if (re.number.test(match[8]) && (!is_positive || match[3])) {
                        sign = is_positive ? "+" : "-"
                        arg = arg.toString().replace(re.sign, "")
                    }
                    else {
                        sign = ""
                    }
                    pad_character = match[4] ? match[4] === "0" ? "0" : match[4].charAt(1) : " "
                    pad_length = match[6] - (sign + arg).length
                    pad = match[6] ? (pad_length > 0 ? str_repeat(pad_character, pad_length) : "") : ""
                    output[output.length] = match[5] ? sign + arg + pad : (pad_character === "0" ? sign + pad + arg : pad + sign + arg)
                }
            }
        }
        return output.join("")
    }

    sprintf.cache = {}

    sprintf.parse = function(fmt) {
        var _fmt = fmt, match = [], parse_tree = [], arg_names = 0
        while (_fmt) {
            if ((match = re.text.exec(_fmt)) !== null) {
                parse_tree[parse_tree.length] = match[0]
            }
            else if ((match = re.modulo.exec(_fmt)) !== null) {
                parse_tree[parse_tree.length] = "%"
            }
            else if ((match = re.placeholder.exec(_fmt)) !== null) {
                if (match[2]) {
                    arg_names |= 1
                    var field_list = [], replacement_field = match[2], field_match = []
                    if ((field_match = re.key.exec(replacement_field)) !== null) {
                        field_list[field_list.length] = field_match[1]
                        while ((replacement_field = replacement_field.substring(field_match[0].length)) !== "") {
                            if ((field_match = re.key_access.exec(replacement_field)) !== null) {
                                field_list[field_list.length] = field_match[1]
                            }
                            else if ((field_match = re.index_access.exec(replacement_field)) !== null) {
                                field_list[field_list.length] = field_match[1]
                            }
                            else {
                                throw new SyntaxError("[sprintf] failed to parse named argument key")
                            }
                        }
                    }
                    else {
                        throw new SyntaxError("[sprintf] failed to parse named argument key")
                    }
                    match[2] = field_list
                }
                else {
                    arg_names |= 2
                }
                if (arg_names === 3) {
                    throw new Error("[sprintf] mixing positional and named placeholders is not (yet) supported")
                }
                parse_tree[parse_tree.length] = match
            }
            else {
                throw new SyntaxError("[sprintf] unexpected placeholder")
            }
            try {_fmt = _fmt.substring(match[0].length)} catch (e) {throw new SyntaxError("[sprintf] unexpected fromat")}
        }
        return parse_tree
    }

    var vsprintf = function(fmt, argv, _argv) {
        _argv = (argv || []).slice(0)
        _argv.splice(0, 0, fmt)
        return sprintf.apply(null, _argv)
    }

    /**
     * helpers
     */
    function get_type(variable) {
        return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase()
    }

    function str_repeat(input, multiplier) {
        return Array(multiplier + 1).join(input)
    }

    /**
     * export to either browser or node.js
     */
    if (typeof exports !== "undefined") {
        exports.sprintf = sprintf
        exports.vsprintf = vsprintf
    }
    else {
        window.sprintf = sprintf
        window.vsprintf = vsprintf

        if (typeof define === "function" && define.amd) {
            define(function() {
                return {
                    sprintf: sprintf,
                    vsprintf: vsprintf
                }
            })
        }
    }
})(typeof window === "undefined" ? this : window);
};
BundleModuleCode['/home/sbosse/proj/jam/js/top/jamsh.js']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2006-2022 bLAB
 **    $CREATED:     1-3-18 by sbosse.
 **    $VERSION:     1.2.1
 **
 **    $INFO:
 **
 **  JAM Shell Interpreter (Front end)
 **
 **    $ENDOFINFO
 */
/* Soem hacks */
process.noDeprecation = true

var Comp = Require('com/compat');
var Io = Require('com/io');
var doc = Require('doc/doc');
var table = Require('doc/table');
var readline = Require('com/readline');
var readlineSync = Require('term/readlineSync');
var renderer = doc.Renderer({lazy:true});
var http = Require('http');
var httpserv = Require('http/https');  // a file server!
try { var https = require('https'); } catch (e) {}; if (!https.request) https=undefined;
var geoip = Require('geoip/geoip');  // a geo location database server 
var util = Require('util')
var sip = Require('top/rendezvous');
var db = Require('db/db');
// JAM Shell Interpreter (Back end)
var JamShell = Require('shell/shell');

var nlp = Require('nlp/nlp');

var ml    = Require('ml/ml')
var nn    = Require('nn/nn')
var csp   = Require('csp/csp')
var sat   = Require('logic/sat')
var logic = Require('logic/prolog')
var csv   = Require('parser/papaparse');
var ampCOM = Require('jam/ampCOM');
var numerics = Require('numerics/numerics')
var osutils = Require('os/osutils');
var UI    = Require('ui/app/app');
var Des48 = Require('dos/des48');

var p;

var options= {
  args:[],
  echo: true,
  modules : {
    csp : csp,
    csv : csv,
    db : db,
    des48 : Des48,
    doc : doc,
    geoip : geoip,
    http : http,
    httpserv : httpserv,
    https : https,
    logic:logic,
    ml:ml,
    nlp:nlp,
    nn:nn,
    numerics:numerics,
    os:osutils,
    readline : readline,
    readlineSync : readlineSync,
    sat : sat,
    sip : sip,
    table : table,
    UI: UI,
  },
  extensions : {
    url2addr: ampCOM.url2addr,
    sleep: process.watchdog&&process.watchdog.sleep?
      function (milli) {
        process.watchdog.sleep(milli)
      }:undefined
  },
  nameopts : {length:8, memorable:true, lowercase:true},
  Nameopts : {length:8, memorable:true, uppercase:true},
  output : null,
  renderer : renderer,
  server : false,
  verbose : 0,
}

process.on('uncaughtException', function (err) {
  console.error(err.stack);
  console.log("jamsh not exiting...");
});

if ((p=process.argv.indexOf('--'))>0) {
  options.args=process.argv.slice(p+1,process.argv.length);
  process.argv=process.argv.slice(0,p);
}

if (process.argv[1].match(/jamsh$/)||process.argv[1].match(/jamsh\.debug$/)) {
  var ind;
  if (process.argv.indexOf('-h')>0) return print('usage: jamsh [-v -s] [script] [-e "shell commands"]');
  if ((ind=process.argv.indexOf('-e'))>0) {
    options.server=true;
    options.output=console.log;
    options.exec=process.argv[ind+1];
  } else if (process.argv.length>2) {
    var script = process.argv.filter(function (arg,ind) {  
      return ind>1 && arg.indexOf(':') == -1 && arg.indexOf('-') != 0;
    });
    if (script.length == 1) {
      options.script=script[0];
      options.output=console.log;
      options.server=true;
    }
  }
  process.argv.forEach(function (arg) { 
    switch (arg) {
      case '-v': options.verbose++; break; 
      case '-s': options.server=false; break; 
    }
  })
  if (!options.server && options.verbose==0) options.verbose=1; 
  JamShell(options).init();
}
};
BundleModuleCode['com/compat']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2006-2021 bLAB
 **    $CREATED:     30-3-15 by sbosse.
 **    $VERSION:     1.24.1
 **
 **    $INFO:
 **
 **  JavaScript-OCaML Compatibility Module
 **
 **    $ENDOFINFO
 */
var Io = Require('com/io');
var Path = Require('com/path');
var Sprintf = Require('com/sprintf');

/*******************************
** Some global special "values"
********************************/

/** A matching template pattern matching any value
 *
 * @type {undefined}
 */
global.any = undefined;
/** A matching template pattern matching any value
 *
 * @type {undefined}
 */
global._ = undefined;

/**
 *
 * @type {null}
 */
global.none = null;
/**
 *
 * @type {null}
 */
global.empty = null;

global.NL = '\n';

global.int = function (v) {return v|0};
global.div = function (a,b) {return a/b|0};

if (!Object.prototype.forEach) {
	Object.defineProperties(Object.prototype, {
		'forEach': {
			value: function (callback) {
				if (this == null) {
					throw new TypeError('Not an object');
				}
				var obj = this;
				for (var key in obj) {
					if (obj.hasOwnProperty(key)) {
						callback.call(obj, obj[key], key, obj);
					}
				}
			},
			writable: true
		}
	});
}
/** Just transfer parent prototypes to child
 *
 */
function inherit(child,parent) {
  for(var p in parent.prototype) {
    if (p == '__proto__') continue;
    child.prototype[p]=parent.prototype[p];
  }
}

/** Portable class inheritance and instanceOf polyfill
 *
 */
// SomeObject.prototype.__proto__=SomeObject2.prototype;
// Child class inherits prototype from parent using __proto__
function inheritPrototype(child,parent) {
  var __proto__=child.__proto__;
  child.prototype.__proto__=parent.prototype;
  if (!__proto__) for(var p in parent.prototype) {
    if (p == '__proto__') continue;
    child.prototype[p]=parent.prototype[p];
  }
}
// Polyfill fir o instanceof c with inheritance check (checking __proto__)
function instanceOf(obj,cla) {
  var p=obj.__proto__;
  if (obj instanceof cla) return true;
  while (p) {
    if (p === cla.prototype) return true;
    p=p.__proto__
  }
  return false;
}
// Polyfill for __defineGetter__ / __defineSetter__
function defineGetter(cla,prop,fun) {
  Object.defineProperty(cla.prototype,prop,{
    configurable:true,
    get:fun
  });
}
function defineSetter(cla,prop,fun) {
  Object.defineProperty(cla.prototype,prop,{
    configurable:true,
    set:fun
  });

}

Object.addProperty = function (obj,name,fun) {
  if (obj.prototype[name]) return;
  obj.prototype[name]=fun;
  Object.defineProperty(obj.prototype, name, {enumerable: false});
};

Object.updateProperty = function (obj,name,fun) {
  obj.prototype[name]=fun;
  Object.defineProperty(obj.prototype, name, {enumerable: false});
};

Object.addProperty(Array,'contains',function (el) { return this.indexOf(el)!=-1 });
Object.addProperty(Array,'last',function () { return this[this.length-1] });

global.inherit = inherit;
global.inheritPrototype = inheritPrototype;
global.instanceOf = instanceOf;
global.defineGetter = defineGetter;
global.defineSetter = defineSetter;

/**
 *
 */
var assert = function(condmsg) {
    if (condmsg != true) {
        Io.out('** Assertion failed: '+condmsg+' **');
        Io.stacktrace();
        throw Error(condmsg);
    }
};
global.assert=assert;

function forof(obj,f) {
  var _iteratorNormalCompletion = true;
  var _didIteratorError = false;
  var _iteratorError = undefined;

  try {
    for (var _iterator = obj[Symbol.iterator](), _step; 
         !(_iteratorNormalCompletion = (_step = _iterator.next()).done); 
         _iteratorNormalCompletion = true) {
      element = _step.value;

      f(element);
    }
  } catch (err) {
    _didIteratorError = true;
    _iteratorError = err;
  } finally {
    try {
      if (!_iteratorNormalCompletion && _iterator.return) {
        _iterator.return();
      }
    } finally {
      if (_didIteratorError) {
        throw _iteratorError;
      }
    }
  }
}


global.forof=forof;

/** OBJ
 *
 */
var obj = {
    /** Compact an object:
     * [{a:b},[c:d},..] -> {a:b,c:d,..}
     * {a:[b]} -> {a:b}
     *
     */
    compact: function (o) {
      var a;
      if (obj.isArray(o)) {
        if (o.length==1 && obj.isObject(o[0])) return obj.compact(o[0]);
        else return o;
      } else if (obj.isObject(o)) for (a in o) {
          var elem=o[a];
          o[a]=obj.compact(elem);
      }
      return o;
    },
    copy: function (o) {
      if (o === null || typeof o !== 'object') {
        return o;
      }
 
      var temp = (o instanceof Array) ? [] : {};
      for (var key in o) {
        temp[key] = obj.copy(o[key]);
      }
 
      return temp;    
    },
    equal: function (o1,o2) {
      if (!o1 || !o2) return false;
      for(var i in o1) if (o1[i]!=o2[i]) return false;
      for(var i in o2) if (o1[i]!=o2[i]) return false;
      return true;
    },
    extend: function (o1,o2) {
      for(var i in o2) o1[i]=o2[i];
      return o1;
    },
    find: function(obj,fun) {
      var p;
      for(p in obj) {
          if (fun(obj[p],p)) return obj[p];
      }
    },

    hasProperty: function (o,p) {
      return o[p]!=undefined || (p in o);
    },
    head:function(o) {
      for (var p in o) return p;
      return undefined;
    },
    // transfer src attributes to dst recusively (no object overwrite)
    inherit: function (dst,src) {
      for(var i in src) {
        if (typeof dst[i] == 'object' && typeof src[i] == 'object')
          inherit(dst[i],src[i]);
        else if (typeof dst[i] == 'undefined')
          dst[i]=src[i];
      }
      return dst;
    },
    isArray:function (o) {
      if (o==_ || o ==null) return false;
      else return typeof o == "array" || (typeof o == "object" && o.constructor === Array);
    },
    isMatrix:function (o) {
      if (o==_ || o ==null) return false;
      else return obj.isArray(o) &&
                  obj.isArray(o[0]);
    },
    isEmpty: function (o) {
      for(var prop in o) {
         if (o[prop]!=undefined) return false;
      }
      return true;  
    },
    isError: function (o) {
      return o instanceof Error
    },
    isFunction: function (o) {
        return typeof o == "function";
    },
    isObj:function (o) {
        return typeof o == "object";
    },
    isObject:function (o) {
        return typeof o == "object";
    },
    isRegex: function (o) {
        return o instanceof RegExp;
    },
    isString: function (o) {
        return typeof o == "string" || (typeof o == "object" && o.constructor === String);
    },
    isNumber: function (o) {
        return typeof o == "number" || (typeof o == "object" && o.constructor === Number);
    },


    iter: function(obj,fun) {
      var p;
      for(p in obj) {
        fun(obj[p],p)
      }
    }
};

/** ARRAY
 *
 */
var array = {
    /** Evaluate a function returning a boolean value for each member of the array and
     *  compute the boolean conjunction.
     *
     * @param {* []} array
     * @param {function(*,number)} fun
     */
    and: function(array,fun) {
        var res=true;
        var i=0;
        var len=array.length;
        for(i=0;i<len;i++) {
            var element=array[i];
            res=res&&fun(element,i)
        }
        return res;
    },
    /** Append one element at the end of the array.
     *
     * @param {* []} array
     * @param {*} element
     * @returns {* []}
     */
    append : function(array,element) {
        array.push(element);
        return array;
    },
    /**
     *
     * @param {* []} array
     * @param {function(*,number)} fun
     */
    call: function(array,args) {
        var i=0;
        var len=array.length;
        for(i=0;i<len;i++) {
            var element=array[i];
            element()
        }
    },
    /** Check for an elenment in the array by using a check function.
     *
     * @param array
     * @param fun
     * @returns {boolean}
     */
    check: function(array,fun) {
        var i,exist;
        exist=false;
        loop: for(i in array) {
            var element=array[i];
            if (fun(element,i)) {
                exist=true;
                break loop;
            }
        }
        return exist;
    },
    /** Append array2 at the end of array inplace. The extended array is returned.
     *  Source array (1) will be modified.
     *
     * @param {*[]} array
     * @param {*[]} array2
     * @returns {*[]}
     */
    concat : function(array,array2) {
        for(var i in array2) {
            array.push(array2[i]);
        }
        return array;
    },
    /** Create the conjunction set of two arrays
     *
     */
    conjunction :function (set1,set2,fun) {
      return array.union(set1,set2,fun);
    },
    /**
     *
     * @param {*[]} array
     * @param {number|string|*|*[]} elements
     * @param {function} [fun] Optional equality test function
     * @returns {boolean}
     */
    contains : function(array,elements,fun) {
        var i = array.length;
        if (!fun) fun=function(o1,o2) {return o1===o2};
        if (obj.isArray(elements)) {
          while (i--) {
            var j = elements.length;
            while (j--) {
              if (fun(array[i],elements[j])) {
                  return true;
              }          
            }
          }
        }
        else while (i--) {
            if (fun(array[i],elements)) {
                return true;
            }
        }
        return false;
    },
    /** Return a fresh copy of the source array or copy src array to dst.
     *
     * @param array
     * @returns {Array.<T>|string|Blob|ArrayBuffer}
     */
    copy: function(src,dst) {
        var i;
        if (dst) {
          for(i in src) dst[i]=src[i];  
        } else return src.slice();
    },
    /** Create a new array with initial element values.
     *
     * @param length
     * @param init
     * @returns {Array}
     */
    create : function(length,init) {
        var arr = [], i = length;
        while (i--) {
          arr[i] = init;
        }
        return arr;
    },
    /** Create a matrix (array of array) with initial element values.
     *
     */
    create_matrix : function(rows,cols,init) {
        var m = [];
        var r = [];
        var i,j;
        for (i = 0; i < rows; i++) {
            r=[];
            for(j=0;j<cols;j++) r.push(init);
            m.push(r);
        }
        return m;
    },
    /** Create the (inclusive) disjunction set of two arrays.
     *  Source arrays will not be modified.
     *
     */
    disjunction :function (set1,set2,fun) {
      return array.merge(set1,set2);
    },
    /**
     *
     * @param array
     * @returns {boolean}
     */
    empty : function (array) {
      return (array==undefined ||
              array.length==0)
    },
    
    /** Test for equality
    */
    equal: function (a1,a2) {
      if (a1.length != a2.length) return false;
      for(var i in a1) if (a1[i]!=a2[i]) return false;
      return true;
    },
    
    /** Create the (exclusive) disjunction set of two arrays. 
     *  Source arrays will not be modified.
     *
     */
    exclusive :function (set1,set2,fun) {
        var i,j,found,res = [];
        for (i in set1) {
          found=false;
          loop1: for (j in set2) {
            if (fun != undefined && fun(set1[i],set2[j])) {found=true; break loop1;}
            else if (fun == undefined && set1[i]==set2[j]) {found=true; break loop1;};
          }
          if (!found) res.push(set1[i]);
        }
        for (i in set2) {
          found=false;
          loop2: for (j in set1) {
            if (fun != undefined && fun(set2[i],set1[j])) {found=true; break loop2;}
            else if (fun == undefined && set2[i]==set1[j]) {found=true; break loop2;};
          }
          if (!found) res.push(set2[i]);
        }
        return res;
    },
    /** Find an element in an array and return it (or none);
     *
     * @param array
     * @param fun
     * @returns {undefined|*}
     */
    find: function(array,fun) {
        var i;
        for(i in array) {
          if (fun(array[i],i)) return array[i];
        }
        return none;
    },
    /** Search and map an element of an array using a test&map function.
     *
     * @param array
     * @param {function(*,number):*} fun
     * @returns {undefined|*}
     */
    findmap: function(array,fun) {
        var i,found;
        for(i in array) {
          found=fun(array[i],i);
          if (found) return found;
        }
        return none;
    },
    /** Filter out elements using a test function.
     *
     * @param {* []} array
     * @param {function(*,number):boolean} fun
     * @returns {* []}
     */
    filter: function(array,fun) {
      if (array.filter) return array.filter(fun);
      else {
        var res=[],
            len=array.length,
            element,i;
        for(i=0;i<len;i++) {
            element=array[i];
            if (fun(element,i)) res.push(element);
        }
        return res;
      }
    },
    /** Filter out and map elements using a test&map function.
     *
     * @param {* []} array
     * @param {function(*,number):*|undefined} fun
     * @returns {* []}
     */
    filtermap: function(array,fun) {
        var res=[],
            len=array.length,
            element,mapped,i;
        for(i=0;i<len;i++) {
            element=array[i];
            mapped=fun(element,i);
            if (mapped!=undefined) res.push(mapped);
        }
        return res;
    },
    /** Flattens an array consting of arrays (and elements)
     *
     * @param array
     * @returns {Array}
     */
    flatten: function (array) {
        var res=[];
        var len=array.length;
        var i;
        for(i=0;i<len;i++) {
            var element=array[i];
            if (!obj.isArray(element)) res.push(element);
            else {
                var j;
                var len2=element.length;
                for(j=0;j<len2;j++) {
                    var element2=element[j];
                    res.push(element2);
                }
            }
        }
        return res;

    },
    /**
     *
     * @param array
     * @returns {*}
     */
    head : function(array) {
        return array[0];
    },
    /**
     *
     * @param length
     * @param fun
     * @returns {Array}
     */
    init : function(length,fun) {
        var arr = [], i = length;
        while (i--) {
          arr[i] = fun(i);
        }
        return arr;
    },
    /**
     *
     * @param {* []} array
     * @param {function(*,number)} fun
     */
    iter: function(array,fun) {
      /*
        var i=0;
        var len=array.length;
        for(i=0;i<len;i++) {
            fun(array[i],i)
        }
      */
      array.forEach(fun);
    },
    /**
     *
     * @param {* []} array1
     * @param {* []} array2
     * @param {function(*,*,number)} fun
     */
    iter2: function(array1,array2,fun) {
        var i=0;
        assert((array1.length == array2.length)||('Array.iter2: arrays of different lengths'));
        /*
        var len=array1.length;
        for(i=0;i<len;i++) {
            fun(array1[i],array2[i],i)
        }
        */
        array1.forEach(function (e1,i) { fun(e1,array2[i],i) });
    },
    /**
     *
     * @param {* []} array
     * @param {function(*,number)} fun Returning a true value leaves iteration loop
     */
    iter_break: function(array,fun) {
        var i=0;
        var len=array.length;
        for(i=0;i<len;i++) {
            var element=array[i];
            if (fun(element,i)) return;
        }
    },
    /**
     *
     * @param {* []} array
     * @param {function(*,number)} fun
     */
    iter_rev: function(array,fun) {
        var i;
        var len=array.length;
        for(i=len-1;i>=0;i--) {
            fun(array[i],i)
        }
    },
    /** Return last element of array.
     *
     */
    last : function(array) {
      var len=array.length;
      if (len==0) return none;
      else return array[len-1];
    },
    
    length : function(array) {
        return array.length;
    },
    /**
     *
     * @param {* []} array1
     * @param {* []} array2
     * @param {function(*,*,number)} fun
     * @returns {* []}
     */
    map2: function(array1,array2,fun) {
        var i=0;
        assert((array1.length == array2.length)||('Array.map2: arrays of different lengths'));
        var len=array1.length;
        var res=[];
        for(i=0;i<len;i++) {
            res.push(fun(array1[i],array2[i],i));
        }
        return res;
    },
    /**
     *
     * @param {* []} array
     * @param {function(*,number)} fun
     * @returns {* []}
     */
    map: function(array,fun) {
        var i=0;
        var len=array.length;
        var res=[];
        for(i=0;i<len;i++) {
            var element=array[i];
            res.push(fun(element,i));
        }
        return res;
    },
    /**
     *
     * @param {* []} array
     * @param {Function} fun_hdtl  - function(hd,tl)
     * @param {Function} [fun_empty] - function()
     */
    match: function(array,fun_hdtl,fun_empty) {
        if (array.length == 0) {
            if (fun_empty) fun_empty();
        } else if (array.length>1) {
            var hd = this.head(array);
            var tl = this.tail(array);
            fun_hdtl(hd,tl);
        } else fun_hdtl(this.head(array),[]);
    },
    /**
     *
     * @param {* []} array
     * @param {Function} fun_hd1hd2  - function(hd1,hd2)
     * @param {Function} [fun_hdtl]  - function(hd,tl)
     * @param {Function} [fun_empty] - function()
     */
    match2: function(array,fun_hd1hd2,fun_hdtl,fun_empty) {
        if (array.length == 0 && fun_empty)
            fun_empty();
        else if (array.length == 2) {
            var hd1 = this.head(array);
            var hd2 = this.second(array);
            fun_hd1hd2(hd1,hd2);
        }
        else if (array.length>1 && fun_hdtl) {
            var hd = this.head(array);
            var tl = this.tail(array);
            fun_hdtl(hd,tl);
        } else if (fun_hdtl) fun_hdtl(this.head(array),[]);
    },
    /** Return the maximum element of an array applying
     *  an optional mapping function.
     *
     * @param {* []} array
     * @param [fun]
     * @returns {number|undefined}
     */
    max : function (array,fun) {        
        var res,max,num;
        for(var i in array) {
            if (fun) num=fun(array[i]); else num=array[i];
            if (max==undefined) { max=num; res=array[i] } 
            else if (num > max) { max=num; res=array[i] }
        }
        return res;
    },
    /** Return the minimum element of an array applying
     *  an optional mapping function.
     *
     * @param {* []} array
     * @param [fun]
     * @returns {number|undefined}
     */
    min : function (array,fun) {        
        var res,min,num;
        for(var i in array) {
            if (fun) num=fun(array[i]); else num=array[i];
            if (min==undefined) { min=num; res=array[i] }
            else if (num < min) { min=num; res=array[i] }
        }
        return res;
    },
    /** Check for an element in the array.
     *
     * @param {(number|string|boolean) []} array
     * @param {number|string|boolean} element
     * @returns {boolean}
     */
    member: function(array,element) {
        var i,exist;
        var len=array.length;
        exist=false;
        loop: for(i=0;i<len;i++) {
            var _element=array[i];
            if (_element==element) {
                exist=true;
                break loop;
            }
        }
        return exist;
    },
    /** Merge all arrays and return a new array.
     *
     * @param {Array} array1
     * @param {Array} array2
     * @param {Array} [array3]
     * @param {Array} [array4]
     * @returns {Array}
     */
    merge: function(array1,array2,array3,array4) {
        var arraynew=array1.slice();
        arraynew=arraynew.concat(array2);
        if (array3!=undefined) arraynew=arraynew.concat(array3);
        if (array4!=undefined) arraynew=arraynew.concat(array4);
        return arraynew;
    },
    /** Return the next element from array after val (next element after last is first!)
     * @param {Array} array
     * @param {number|string} val
     * @returns {number|string}
     */
    next: function(array,val) {
        var i;
        var len=array.length;
        if (obj.isString(val))
          for(i=0;i<len;i++) {
            if (string.equal(array[i],val)) {
              if (i==len-1) return array[0];
              else return array[i+1];
            }
          }
        else
          for(i=0;i<len;i++) {
            if (array[i]==val) {
              if (i==len-1) return array[0];
              else return array[i+1];
            }
          }
          
        return none;
    },
    /** Evaluate a function returning a boolean value for each member of the array and
     *  compute the boolean disjunction.
     *
     * @param {* []} array
     * @param {function(*,number)} fun
     */
    or: function(array,fun) {
        var res=false;
        var i=0;
        var len=array.length;
        for(i=0;i<len;i++) {
            var element=array[i];
            res=res||fun(element,i)
        }
        return res;
    },
    
   /**
     * Gets the property value of `key` from all elements in `collection`.
     *
     * var users = [
     *   { 'user': 'barney', 'age': 36 },
     *   { 'user': 'fred',   'age': 40 }
     * ];
     *
     * pluck(users, 'user');
     * // => ['barney', 'fred']
     */
    pluck: function(collection, key) {
      return collection.map(function(object) {
          return object == null ? undefined : object[key];
        });
    },
    /*
     ** Push/pop head elements (Stack behaviour)
     */
    /** Remove and return top element of array.
     *
     * @param array
     * @returns {*}
     */
    pop : function(array) {
        var element=array[0];
        array.shift();
        return element;
    },
    print: function(array) {
        var i;
        var len=array.length;
        var str='[';
        for(i=0;i<len;i++) {
            var cell=array[i];
            str=str+cell;
        }
        return str+']';
    },
    /** Add new element at top of array.
     *
     * @param array
     * @param element
     */
    push : function(array,element) {
        array.unshift(element);
    },
    /** Create an ordered array of numbers {a,a+1,..b}
     *
     * @param a
     * @param b
     * @returns {Array}
     */
    range : function(a,b) {
        var i;
        var array=[];
        for(i=a;i<=b;i++) array.push(i);
        return array;
    },
    /** Remove elements from an array.
     *  [1,2,3,4,5,6] (begin=2,end=4) => [1,2,6]
     * @param {* []} array
     * @returns {* []}
     */
    remove: function(array,begin,end) {
      var i,a;
      if (end==undefined) end=begin+1;
      if (begin<0 || end >= array.length) return [];
      a=array.slice(0,begin);
      for(i=end;i<array.length;i++) a.push(array[i]);
      return a;
    },
    
    second : function(array) {
        return array[1];
    },
    /**
     *
     * @param {* []} array
     * @param {function(*,*):number} fun   (1:a gt. b by the ordering criterion,-1: a lt. b, 0: a eq. b)
     * @returns {* []}
     */
    sort: function(array,fun) {
        var array2=array.slice();
        array2.sort(fun);
        return array2;
    },
    /** Split an array at position 'pos', i.e., remove 'len' (1) elements starting at 
     *  position 'pos'.
     *  ==> use remove!!! split should return two arrays!!
     *
     * @param array
     * @param pos
     * @param [len]
     * @param element
     */    
    split: function(array,pos,len) {
      if (pos==0) return array.slice((len||1));
      else {
        var a1=array.slice(0,pos);
        var a2=array.slice(pos+(len||1));
        return a1.concat(a2);
      }
    },
    /** Return the sum number of an array applying
     *  an optional mapping function.
     *
     * @param {* []} array
     * @param [fun]
     * @returns {number|undefined}
     */
    sum : function (array,fun) {        
        var res=0;
        for(var i in array) {
            var num=0;
            if (fun) num=fun(array[i]); else num=array[i];
            if (!obj.isNumber(num)) return undefined;
            res += num;
        }
        return res;
    },
    /** Return a new array w/o the head element (or optional 
     *  w/o the first top elements).
     *
     */
    tail : function(array,top) {
        var array2=array.slice();
        array2.shift();
        if (top) for(;top>1;top--) array2.shift();
        return array2;
    },
    /** Return union of two sets (== conjunction set)
     *
     * @param {* []} set1 
     * @param {* []} set2
     * @param {function} [fun]  Equality test
     * @returns {* []}
     */
    union : function(set1,set2,fun) {
        var i,j,res = [];
        for (i in set1) {
          for (j in set2) {
            if (fun != undefined && fun(set1[i],set2[j])) res.push(set1[i]);
            else if (fun == undefined && set1[i]==set2[j]) res.push(set1[i]);
          }
        }
        return res;
    },
    
    /**
     * Creates a duplicate-free version of an array
     */
    unique: function(array) {
      var length = array ? array.length : 0;
      function baseUniq(array) {
        var index = -1,
            length = array.length,
            seen,
            result = [];

        seen = result;
        outer:
        while (++index < length) {
          var value = array[index];
          var seenIndex = seen.length;
          while (seenIndex--) {
            if (seen[seenIndex] === value) {
              continue outer;
            }
          }
          result.push(value);
        }
        return result;
      }
      if (!length) {
        return [];
      }
      return baseUniq(array);
    },
    
    /**
     * Creates an array excluding all provided values
     * without([1, 2, 1, 3], 1, 2);
     * // => [3]
     */
    without: function () {
      var array,
          values=[];
      for(var i in arguments) {
        if (i==0) array=arguments[0];
        else values.push(arguments[i]);
      }
      return array.filter(function (e) {
        return values.indexOf(e) == -1;
      });
    },
    /** Test for zero elements {0, '', false, undefined, ..}
    */
    zero: function (array) {
      for(var i in array) if (!!array[i]) return false;
      return true;
    },
};

/** STRING
 *
 */
var string = {
    /** Is pattern conatined in template?
     *
     */
    contains: function (template,pattern) {
      return template.indexOf(pattern)>-1;
    },
    copy: function(src) {
        var i;
        var dst='';
        for(i=0;i<src.length;i++) dst=dst+src.charAt(i);
        return dst;
    },
    /**
     *
     * @param {number} size
     * @returns {string} filled with spaces
     */
    create: function(size)
    {
        var i;
        var s='';
        var init=' ';
        for(i=0;i<size;i++) s=s+init;
        return s;
    },
    endsWith : function (str,tail) {
        return str.indexOf(tail)==(str.length-tail.length);
    },
    empty: function (str) {
      return this.equal(str,'');
    },
    equal:  function(str1,str2) {
        var i;
        var eq=true;
        if (str1.length != str2.length) return false;
        for(i=0;i<str1.length;i++) { if (string.get(str1,i)!=string.get(str2,i)) eq=false;}
        return eq;
    },
    find: function (search,str) {
        return str.indexOf(search);
    },
    format_hex: function (n,len) {
        // format a hexadecimal number with 'len' figures.
        switch (len) {
            case 2: return (((n>>4) & 0xf).toString(16))+
                            ((n&0xf).toString(16));
            case 4: return (((n>>12) & 0xf).toString(16)+
                            ((n>>8) & 0xf).toString(16)+
                            ((n>>4) & 0xf).toString(16)+
                            (n&0xf).toString(16));
            case 6: return (((n>>20) & 0xf).toString(16)+
                            ((n>>16) & 0xf).toString(16)+
                            ((n>>12) & 0xf).toString(16)+
                            ((n>>8) & 0xf).toString(16)+
                            ((n>>4) & 0xf).toString(16)+
                            (n&0xf).toString(16));
            case 8: return (((n>>28) & 0xf).toString(16)+
                            ((n>>24) & 0xf).toString(16)+
                            ((n>>20) & 0xf).toString(16)+
                            ((n>>16) & 0xf).toString(16)+
                            ((n>>12) & 0xf).toString(16)+
                            ((n>>8) & 0xf).toString(16)+
                            ((n>>4) & 0xf).toString(16)+
                            (n&0xf).toString(16));
            default: return 'format_hex??';
        }
    },
    /**
     *
     * @param {string} str
     * @param {number} index
     * @returns {string}
     */
    get: function (str,index) {
        assert((str != undefined && index < str.length && index >= 0)||('string.get ('+str.length+')'));
        return str.charAt(index);
    },
    isBoolean: function (str) {
        return (str=='true' || str=='false')
    },
    isNumeric: function (str) {
        return !isNaN(parseFloat(str)) && isFinite(str);
    },
    isText: function (s) {
      var is_text=true;
      string.iter(s,function (ch,i) {
        string.match(ch,[
          ['a','z',function () {}],
          ['A','Z',function () {}],
          ['0','9',function () {if (i==0) is_text=false;}],
          function () {is_text=false;}
        ]);
      });
      return is_text;
    },
    /**
     *
     * @param {string} str
     * @param {function(string,number)} fun
     */
    iter: function(str,fun) {
        var i;
        var len=str.length;
        for (i = 0; i < len; i++)  {
            var c = str.charAt(i);
            fun(c,i);
        }
    },
    /**
     *
     * @param str
     * @returns {*}
     */
    length: function(str) {
        if (str!=undefined) return str.length;
        else return 0;
    },
    /**
     *
     * @param str
     * @returns {string}
     */
    lowercase : function (str) {
        return str.toLowerCase();
    },
    /**
     *
     * @param {number} size
     * @param {string} init
     * @returns {string}
     */
    make: function(size,init)
    {
        var i;
        var s='';
        for(i=0;i<size;i++) s=s+init;
        return s;
    },
    /** Map a string with a set of (test,reuslt) transformation rules.
     * 
     * @param {string} str
     * @param {* [] []} case - ([string,string] | fun) []
     */
    map: function(str,mapping) {
        var i;
        var map;
        for(i in mapping) {
            map=mapping[i];
            if (obj.isFunction(map)) return map(str);
            else if (this.equal(str,map[0])) return map[1];
        }          
    },
    /** Match a string with different patterns and apply a matching function.
     *
     * @param {string} str
     * @param {* [] []} cases - ([string,fun] | [string [<case1>,<case2>,..],fun] | [<range1>:string,<range2>:string,fun] | fun) []
     */
    match: function(str,cases) {
        var i,j;
        var cas,cex,cv;
        for(i in cases) {
            cas=cases[i];
            if (obj.isArray(cas)) {
              switch (cas.length) {
                case 2:
                  // Multi-value-case
                  cex=cas[0];
                  if (!obj.isArray(cex)) {
                      if (this.equal(str,cex)) {
                          cas[1]();
                          return;
                      }
                  } else {
                      for(j in cex) {
                          cv=cex[j];
                          if (this.equal(str,cv)) {
                              cas[1]();
                              return;
                          }
                      }
                  }
                  break;
                case 3:
                  // Character range check
                  try {
                    j=pervasives.int_of_char(str);
                    if (j>= pervasives.int_of_char(cas[0]) && j<=pervasives.int_of_char(cas[1])) {
                      cas[2](str);
                      return;
                    }
                  } catch(e) {
                    return
                  };
                  break;
                case 1:
                  cas[0](str); // Default case - obsolete
                  return;
                default: 
                  throw 'String.match #args';
              }
            } else if (obj.isFunction(cas)) {
                // Default case
                cas(str);
                return;
            }
        }
    },
    /** Pad a string on the left (pre-str.length) if pre>0,
     *  right (post-str.length) if post>0, or centered (pre>0&post>0).
     *
     */
     
    pad: function (str,pre,post,char) {
      var len = str.length;
      if (pre>0 && post==0) return string.make(len-pre,char||' ')+str;
      else if (post>0 && pre==0) return str+string.make(post-len,char||' ');
      else return string.make(len-pre/2,char||' ')+str+string.make(len-post/2,char||' ');
    },
    /**
     *
     * @param str
     * @param pos
     * @param len
     * @returns {Number}
     */
    parse_hex: function (str,pos,len) {
        // parse a hexadecimal number in string 'str' starting at position 'pos' with 'len' figures.
        return parseInt(this.sub(str,pos,len),16);
    },
    /** Return the sub-string after a point in the source string ('.' or optional point string).
     * If there is no splitting point, the original string is returned.
     *
     * @param str
     * @param [point]
     * @returns {string}
     */
    postfix: function (str,point) {
      var n = str.indexOf(point||'.');
        if (n <= 0) return str;
        else return str.substr(n+1);
    },
    /** Return the sub-string before a point in the source string ('.' or optional point string)
     * If there is no splitting point, the original string is returned.
     *
     * @param str
     * @param [point]
     * @returns {string}
     */
    prefix: function (str,point) {
        var n = str.indexOf(point||'.');
        if (n <= 0) return str;
        else return str.substr(0,n);
    },
    replace_first: function (pat,repl,str) {
        return str.replace(pat,repl);
    },
    replace_all: function (pat,repl,str) {
        return str.replace('/'+pat+'/g',repl);
    },
    /**
     *
     * @param str
     * @param index
     * @param char
     * @returns {string}
     */
    set: function (str,index,char) {
        assert((str != undefined && index < str.length && index >= 0)||'string.get');
        return str.substr(0, index) + char + str.substr(index+1)
    },
    /**
     *
     * @param delim
     * @param str
     * @returns {*|Array}
     */
    split: function (delim,str) {
        return str.split(delim);
    },
    startsWith : function (str,head) {
        return !str.indexOf(head);
    },
    /** Return a sub-string.
     * 
     * @param str
     * @param off
     * @param [len] If not give, return a sub-string from off to end
     * @returns {string}
     */
    sub: function (str,off,len) {
        if (len)
            return str.substr(off,len);
        else
            return str.substr(off);
    },
    /** Remove leading and trailing characters from string
     *
     * @param str
     * @param {number} pref number of head characters to remove
     * @param {number} post number of tail characters to remove
     * @returns {*}
     */
    trim: function (str,pref,post) {
        if (str.length==0 ||
            pref>str.length ||
            post>str.length ||
            pref < 0 || post < 0 ||
            (pref==0 && post==0)
        ) return str;
        return str.substr(pref,str.length-pref-post);
    },
    /** Return a string with all characters converted to uppercase letters.
     *
     * @param str
     * @returns {string}
     */
    uppercase : function (str) {
        return str.toUpperCase();
    },
    /** Return a string with first character converted to uppercase letter.
     *
     * @param str
     * @returns {string}
     */
    Uppercase : function (str) {
        var len = str.length;
        if (len > 1) {
            var head = str.substr(0,1);
            var tail = str.substr(1,len-1);
            return head.toUpperCase()+tail.toLowerCase()
        } if (len==1) return str.toUpperCase();
        else return '';
    }
};

/** RANDOM
 *
 */
var rnd = Math.random;
/* Antti Syk\E4ri's algorithm adapted from Wikipedia MWC
** Returns a random generator function [0.0,1.0| with seed initialization
*/
var seeder = function(s) {
    var m_w  = s;
    var m_z  = 987654321;
    var mask = 0xffffffff;

    return function() {
      m_z = (36969 * (m_z & 65535) + (m_z >> 16)) & mask;
      m_w = (18000 * (m_w & 65535) + (m_w >> 16)) & mask;

      var result = ((m_z << 16) + m_w) & mask;
      result /= 4294967296;

      return result + 0.5;
    }
}
 
var random = {
    float: function(max) {
        return rnd()*max
    }, 
    int: function(max) {
        return Math.floor(rnd()*max+0)
    },
    // integer
    interval: function(min,max) {
        return Math.round(min+rnd()*(max-min))
    },
    // float
    range: function(min,max) {
        return min+rnd()*(max-min)
    },
    seed: function (s) {
      // Create a new initialized random generator
      rnd=seeder(s);
    }
};

/** PRINTF
 *
 */
var printf = {
    /** Trim string(s).
     *
     * @param str
     * @param indent
     * @param [width]
     * @param {string} [tab]
     * @returns {string}
     */
    align: function (str,indent,width,tab) {
        var lines = string.split('\n',str);
        var form = '';
        var sp = printf.spaces(indent);
        var spbreak = sp;

        array.iter(lines,function(line){
            var rest;
            function breakit(spbreak,str) {
                if (width < (str.length + spbreak.length)) {
                    return spbreak+string.sub(str,0,width-spbreak.length)+'\n'+
                           breakit(spbreak,string.sub(str,width-spbreak.length,str.length-width+spbreak.length));
                } else return spbreak+str+'\n';
            }
            if (width && width < (line.length + indent)) {
                if (tab) {
                    var pos = string.find(tab,line);
                    if (pos > 0 && pos < width) spbreak=printf.spaces(pos+indent+1);
                    else spbreak=sp;
                }
                form=form+sp+string.sub(line,0,width-indent)+'\n';
                rest=string.sub(line,width-indent,line.length-width+indent);
                form=form+breakit(spbreak,rest);
            }
            else
                form=form+sp+line+'\n';
        });
        return form;
    },
    /** Format a list of array elements using the (optional) mapping
     *  function <fun> and the separator <sep> (optional, too, default is ',').
     * 
     */
    list: function (array,fun,sep) {
      var i, str='';
      if (sep==undefined) sep=',';
      if (fun==undefined) fun=function (s) {return s;};
      if (!obj.isArray(array)) array=[array];
      for (i in array) {
        if (str==='') str=fun(array[i]);
        else str=str+sep+fun(array[i]);
      }
      return str;
    },
    /**
     *
     * @param n
     * @returns {string}
     */
    spaces: function (n){
        return string.make(n,' ');
    },
    /** Formatted printer (simplified)
     *
     * @param {* []} args (['%format',arg]|string) []  format=%s,%d,%f,%c,%x,%#d,%#s,..
     * @returns {string}
     */
    sprintf2: function(args) {
        var str='';
        array.iter(args,function(fmtarg) {
            var len, n,fs;
            if (obj.isArray(fmtarg)) {
                if (fmtarg.length==2) {
                    var fmt=fmtarg[0];
                    var arg=fmtarg[1];
                    var fc='';
                    var fn=0;
                    string.iter(fmt,function(c) {
                        if (c=='s' || c=='d' || c=='f' || c=='x') {
                            fc=c;
                        } else if (c!='%') {
                            fn=fn*10;
                            n=parseInt(c);
                            if (!isNaN(n)) fn=fn+n;
                        }
                    });
                    if (fc=='s' && obj.isString(arg)) {
                        str=str+arg;
                        if (fn!=0) {
                            len=arg.length;
                            if (len<fn) str=str+string.create(fn-len);
                        }
                    } else if (fc=='d' && obj.isNumber(arg)) {
                        fs = pervasives.string_of_int(arg);
                        if (fn!=0) {
                            len = fs.length;
                            if (len < fn) {
                                str=str+string.create(fn-len);
                            }
                        }
                        str=str+fs;
                    } else if (fc=='x' && obj.isNumber(arg)) {
                        fs = string.format_hex(arg,fn||8);
                        str=str+fs;
                    }
                }
            } else if (obj.isString(fmtarg)) {
                str = str + fmtarg;
            }
        });
        return str;
    },
    sprintf:Sprintf.sprintf
};

/** FILENAME
 *
 */
var filename = {
    /**
     *
     * @param path
     * @returns {string}
     */
    basename : function (path) {
        return Path.basename(path);
    },
    /**
     *
     * @param path
     * @returns {string}
     */
    dirname : function (path) {
        return Path.dirname(path);
    },
    /**
     *
     * @param path
     * @returns {string}
     */
    extname : function (path) {
        return Path.extname(path)
    },
    /**
     *
     * @param path
     * @returns {boolean}
     */
    is_relative: function(path) {
        return !(path.length > 0 && path[0] == '/');
    },
    /**
     *
     * @param pathl
     * @param absolute
     * @returns {string}
     */
    join: function (pathl,absolute) {
        var path=(absolute?'/':'');
        array.iter(pathl,function (name,index) {
            if (index>0) {
                path=path+'/'+name;
            }
            else {
                path=path+name;
            }
        });
        return path;
    },
    /**
     *
     * @param path
     * @returns {string}
     */
    normalize : function (path) {
        return Path.normalize(path)
    },
    /**
     *
     * @param path
     * @returns {*}
     */
    path_absolute: function (path) {
        if (this.is_relative(path)) {
            var workdir = Io.workdir();
            return this.path_normalize(workdir + '/' + path);
        } else return this.path_normalize(path);
    },
    /** Duplicate of Path.normalize!?
     *
     * @param path
     * @returns {string}
     */
    path_normalize: function (path) {
        var i;
        if (string.equal(path, '')) path = '/';
        var relpath = !(string.get(path, 0) == '/');
        var pathlist = path.split('/');
        var pathlist2 = pathlist.filter(function (s) {
            return (!string.equal(s, '') && !string.equal(s, '.'))
        });
        var pathlist3 = [];
        array.iter(pathlist2, function (pe) {
            if (!string.equal(pe, '..')) {
                array.push(pathlist3, pe)
            } else {
                if (pathlist3.length == 0) return '';
                else
                    pathlist3 = array.tail(pathlist3);
            }
        });
        var path2 = '';
        i = 0;
        array.iter(pathlist3, function (pe) {
            var sep;
            if (i == 0) sep = ''; else sep = '/';
            path2 = pe + sep + path2;
            i++;
        });
        if (relpath) return path2; else return '/' + path2;
    },
    removeext: function (path) {
      return path.substr(0, path.lastIndexOf('.'));
    }
};

/** PERVASIVES
 *
 *
 */
var pervasives = {
    assert:assert,
    char_of_int: function (i) {return String.fromCharCode(i)},
    div: function(a,b) {return a/b|0;},
    failwith: function(msg) {Io.err(msg);},
    float_of_string: function(s) {var num=parseFloat(s); if (isNaN(num)) throw 'NaN'; else return num;},
    int_of_char: function(c) {return c.charCodeAt()},
    int_of_float: function(f) {return f|0;},
    int_of_string: function(s) {      
      var num=parseInt(s); if (isNaN(num)) throw 'NaN'; else return num;
    },

    /** Try to find a value in a search list and return a mapping value.
     *
     * @param {*} value
     * @param {* []} mapping [testval,mapval] []
     * @returns {*}
     */
    map: function(value,mapping) {
        function eq(v1,v2) {
            if (v1==v2) return true;
            if (obj.isString(v1) && obj.isString(v2)) return string.equal(v1,v2);
            return false;
        }
        if (!array.empty(mapping)) {
          var hd=array.head(mapping);
          var tl=array.tail(mapping);
          if (eq(hd[0],value)) return hd[1];
          else return pervasives.map(value,tl);
        }  else return undefined;
    },
    /** Apply a matcher function to a list of cases with case handler functions.
     * A case is matched if the matcher function returns a value/object.
     *
     * The result of the matcher function is passed as an argument ot the case handler function.
     * The return value of the case handler fucntion is finally returned by this match function
     * or undefined if there was no matching case.
     *
     * @param {function(*,*):*} matcher function(expr,pat)
     * @param {*} expr
     * @param {*[]} cases (pattern,handler function | handler function) []
     * @returns {*|undefined}
     */
    match: function (matcher,expr,cases) {
        var ret = undefined;
        array.iter_break(cases, function (match) {
            var quit, succ, pat, fun;

            if (match.length == 2) {
                /*
                 ** Pattern, Function
                 */
                pat = match[0];
                fun = match[1];
                succ = matcher(expr, pat);
                if (succ) ret = fun(succ);
                quit = succ!=undefined;
            } else if (match.length == 1) {
                /*
                 ** Default case, Function
                 */
                fun = match[0];
                ret = fun();
                quit= true;
            }
            return quit;
        });
        return ret;
    },
    mtime: function () {var time = new Date(); return time.getTime();},
    min: function(a,b) { return (a<b)?a:b},
    max: function(a,b) { return (a>b)?a:b},
    string_of_float: function(f) {return f.toString()},
    string_of_int: function(i) {return i.toString()},
    string_of_int64: function(i) {return i.toString()},
    time: function () {var time = new Date(); return (time.getTime()/1000)|0;}
};

/** BIT
 *
 */
var bit = {
    get: function (v,b) {return (v >> b) && 1;},
    isSet: function (v,b) {return ((v >> b) && 1)==1;},
    set: function (v,b) {return v & (1 << b);}
};

/** ARGS
 *
 */
var args = {
    /** Parse process or command line arguments (array argv). The first offset [1] arguments are
     ** ignored. The numarg pattern '*' consumes all remaining arguments.
     *
     * @param {string []} argv
     * @param {*[]} map  [<argname>,<numargs:0..3|'*'>,<handler(up to 3 arguments|[])>]|[<defhandler(val)>] []
     * @param {number} [offset]
     */
    parse: function(argv,map,offset) {
        var shift=undefined,
            in_shift=0,
            shift_args=[],
            names,
            mapfun,
            numarg,
            len=argv.length;

        if (offset==undefined) offset=1;

        argv.forEach(function (val, index) {
            var last=index==(len-1);
            if(index>=offset) {
                if (in_shift==0) {
                    array.check(map,function (onemap) {
                        assert(onemap!=undefined||'map');
                        if (onemap.length==3) {
                            names  = onemap[0];
                            numarg = onemap[1];
                            mapfun = onemap[2];
                            if (!obj.isArray(names)) names=[names];
                            var found = array.find(names,function (name) {
                                if (string.equal(val, name)) return name; else _;
                            });
                            if (found) {
                                if (numarg==0) mapfun(found);
                                else {
                                    in_shift=numarg;
                                    shift_args=[];
                                    shift=mapfun;
                                }
                                return true;
                            }
                        } else if (obj.isFunction(onemap)) {
                          onemap(val);
                          return true;                        
                        } else if (onemap.length==1) {
                            mapfun = onemap[0];
                            mapfun(val);
                            return true;
                        }
                        return false;
                    });
                } else {
                    shift_args.push(val);
                    if (in_shift!='*') in_shift--;
                    if (in_shift==0 && shift!=undefined) {
                        numarg=shift_args.length;
                        switch (numarg) {
                            case 0: shift(val);break;
                            case 1: shift(shift_args[0],val); break;
                            case 2: shift(shift_args[0],shift_args[1],val); break;
                            case 3: shift(shift_args[0],shift_args[1],shift_args[2],val); break;
                            default: break;
                        }
                        shift=undefined;
                    } else if (in_shift=='*' && last) shift(shift_args);
                }
            }
        });
    }

};

/** HASHTBL
 *
 */
var hashtbl = {
    add: function(hash,key,data) {
        hash[key]=data;
    },
    create: function(initial) {
        return [];
    },
    empty: function(hash) {
        for (var key in hash) return false;
        return true;
    },
    find: function(hash,key) {
        return hash[key];
    },
    invalidate: function(hash,key) {
        hash[key]=undefined;
    },
    iter: function(hash,fun) {
        for (var key in hash) {
            if (hash[key]!=undefined) fun(key,hash[key]);
        }
    },
    mem: function(hash,key) {
        return hash[key] != undefined;
    },
    remove: function(hash,key) {
        // TODO: check, its wrong!
        if (!hash.hasOwnProperty(key))
            return;
        if (isNaN(parseInt(key)) || !(hash instanceof Array))
            delete hash[key];
        else
            hash.splice(key, 1)
    }
};

var types = [];
/**
 * 
 * @param name
 * @returns {number}
 */
function register_type(name) {
    var typoff = 1000+types.length*1000;
    if (array.member(types,name)) throw('[COMP] register_type: type '+name+' exists already.');
    types.push(name);
    return typoff;
}

/**
 *
 * @typedef {{v1:*, v2:*, v3:*, v4:*, v5:*, v6:*, v7:*, v8:*, v9:*  }} tuple
 */
/**
 *
 * @typedef {{t:number, v1:*, v2:*, v3:*, v4:*, v5:*, v6:*, v7:*, v8:*, v9:*  }} tagged_tuple
 */

module.exports = {
    args:args,
    assert: assert,
    array:array,
    bit:bit,
    copy:obj.copy,
    div:pervasives.div,
    filename:filename,
    hashtbl:hashtbl,
    isNodeJS: function () {
        return (typeof global !== "undefined" &&
                {}.toString.call(global) == '[object global]');
    },
    obj:obj,
    pervasives:pervasives,
    printf:printf,
    random:random,
    string:string,
    isArray: obj.isArray,
    isString: obj.isString,
    isNumber: obj.isNumber,

    register_type:register_type,
    /**
     *
     * @param tag
     * @param [val1]
     * @param [val2]
     * @param [val3]
     * @returns {(tagged_tuple)}
     */
    Tuple: function (tag,val1,val2,val3) {
        if(val3) return {t:tag,v1:val1,v2:val2,v3:val3};
        else if (val2) return {t:tag,v1:val1,v2:val2};
        else if (val1) return {t:tag,v1:val1};
        else return {t:tag};
    }
};
};
BundleModuleCode['doc/doc']=function (module,exports){
/** Markdown renderer for highlighted and formatted terminal output
 *  by embedding (escaped) terminal control sequences
 *
 */

var Marked = Require('doc/marked');
var Colors = Require('doc/colors');
var List   = Require('doc/list');
var Table  = Require('doc/cli-table');
var NL = '\n';

function id (x) {return x}

// default css
var css = {
  bold:Colors.black.bold,
  italic:Colors.underline,
  h1:Colors.bold,
  h2:Colors.blue.bold,
  h3:Colors.red.bold,
  
  ol:{
    label:['1','a','i'],
  },
  ul:{
    label:['*','-','+'],
  },
  
}

// Increment numeric/alphanumeric list label
function incr(label,start) {
  switch (label) {
    case '1': return start.toString();
  }
  return label;
}

function B (text) { return css.bold(text) }
function I (text) { return css.italic(text) }
function P (text) { return text+'\n' }

function H (text, level) {
  var color,
      escapedText = text.toLowerCase().replace(/[^\w]+/g, '-');
  
  switch (level) {
    case 1: color=css.h1; break;
    case 2: color=css.h2; break;
    case 3: color=css.h3; break;
    default: color=id;
  }

  return color(text+'\n');
};

function CD(text) {
  return text+'\n';
}


function DL(body) {
  var item;
  list=new List({type:'dl',tab:2});
  while (this._data.stack.length && this._data.stack[0].dt != undefined) {
    item=this._data.stack.shift();
    //print(item)
    list.unshift({dt:css.bold(item.dt),dd:item.dd});
  }
  return list.toString()+NL;
    
}
function DT(body) {
  this._data.stack.unshift({dt:body});
}
function DD(body) {
  if (this._data.stack.length && this._data.stack[0].dt!=undefined)
     this._data.stack[0].dd=body;
}

function L(body, ordered, start) {
  var list,label;
  if (ordered) label=incr(css.ol.label[this._data.olist],start);
  else label=css.ul.label[this._data.ulist];
  list=new List({type:label});

  if (ordered) this._data.olist++; else this._data.ulist++; 
  
  while (this._data.stack.length && this._data.stack[0].item != undefined) {
    list.unshift(this._data.stack.shift().item);
  }
    
  if (ordered) this._data.olist--; else this._data.ulist--; 
  return list.toString()+NL;
}

function LI(text) {
  this._data.stack.unshift({item:text});
}

function text(text) {
  return text.replace(/&quot;/g,'"').
              replace(/&gt;/g,'>').
              replace(/&lt;/g,'<');
}

// Terminal MarkDown Renderer
function Renderer (options) {
  var marked = Marked(),
      renderer = new marked.Renderer();

  renderer.heading = H.bind(renderer);
  renderer.list = L.bind(renderer);
  renderer.listitem = LI.bind(renderer);
  renderer.paragraph = P.bind(renderer);
  renderer.strong = B.bind(renderer);
  renderer.em = I.bind(renderer);
  renderer._data={stack:[],ulist:0,olist:0};
  renderer.dt = DT.bind(renderer);
  renderer.dd = DD.bind(renderer);
  renderer.dl = DL.bind(renderer);
  renderer.code = CD.bind(renderer);
  renderer.text = text;
  
  marked.setOptions({
    renderer: renderer,
    highlight: function(code) {
      return require('highlight.js').highlightAuto(code).value;
    },
    pedantic: false,
    gfm: true,
    tables: true,
    breaks: false,
    sanitize: false,
    smartLists: true,
    smartypants: false,
    xhtml: false
  });  
  if (options.lazy) return function (text) { try { return marked(text) } catch (e) { return text }};
  else return marked;
}

module.exports = {
  Colors:Colors,
  List:List,
  Marked:Marked,
  Renderer:Renderer,
  Table:Table
}
};
BundleModuleCode['doc/marked']=function (module,exports){
/**
 * marked - a markdown parser
 * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed)
 * 2016-2019 (c) Dr. Stefan Bosse
 * https://github.com/markedjs/marked
 *
 * Version 1.2.2
 */

module.exports = function() {
'use strict';

/**
 * Block-Level Grammar
 */

var block = {
  newline: /^\n+/,
  code: /^( {4}[^\n]+\n*)+/,
  fences: noop,
  hr: /^ {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)/,
  heading: /^ *(#{1,6}) *([^\n]+?) *(?:#+ *)?(?:\n+|$)/,
  nptable: noop,
  blockquote: /^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/,
  list: /^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,
  html: '^ {0,3}(?:' // optional indentation
    + '<(script|pre|style)[\\s>][\\s\\S]*?(?:</\\1>[^\\n]*\\n+|$)' // (1)
    + '|comment[^\\n]*(\\n+|$)' // (2)
    + '|<\\?[\\s\\S]*?\\?>\\n*' // (3)
    + '|<![A-Z][\\s\\S]*?>\\n*' // (4)
    + '|<!\\[CDATA\\[[\\s\\S]*?\\]\\]>\\n*' // (5)
    + '|</?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:\\n{2,}|$)' // (6)
    + '|<(?!script|pre|style)([a-z][\\w-]*)(?:attribute)*? */?>(?=\\h*\\n)[\\s\\S]*?(?:\\n{2,}|$)' // (7) open tag
    + '|</(?!script|pre|style)[a-z][\\w-]*\\s*>(?=\\h*\\n)[\\s\\S]*?(?:\\n{2,}|$)' // (7) closing tag
    + ')',
  def: /^ {0,3}\[(label)\]: *\n? *<?([^\s>]+)>?(?:(?: +\n? *| *\n *)(title))? *(?:\n+|$)/,
  dl:  /^ *(dt)\n: *(dd)/, 
  table: noop,
  lheading: /^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/,
  paragraph: /^([^\n]+(?:\n(?!hr|heading|lheading| {0,3}>|<\/?(?:tag)(?: +|\\n|\/?>)|<(?:script|pre|style|!--))[^\n]+)+)/,
  text: /^[^\n]+/
};

block._label = /(?!\s*\])(?:\\[\[\]]|[^\[\]])+/;
block._title = /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/;
block.def = edit(block.def)
  .replace('label', block._label)
  .replace('title', block._title)
  .getRegex();
  

block.bullet = /(?:[*+-]|\d+\.)/;
block.item = /^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/;
block.item = edit(block.item, 'gm')
  .replace(/bull/g, block.bullet)
  .getRegex();

block.list = edit(block.list)
  .replace(/bull/g, block.bullet)
  .replace('hr', '\\n+(?=\\1?(?:(?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$))')
  .replace('def', '\\n+(?=' + block.def.source + ')')
  .getRegex();

block._tag = 'address|article|aside|base|basefont|blockquote|body|caption'
  + '|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption'
  + '|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe'
  + '|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option'
  + '|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr'
  + '|track|ul';
block._comment = /<!--(?!-?>)[\s\S]*?-->/;
block.html = edit(block.html, 'i')
  .replace('comment', block._comment)
  .replace('tag', block._tag)
  .replace('attribute', / +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/)
  .getRegex();

block.paragraph = edit(block.paragraph)
  .replace('hr', block.hr)
  .replace('heading', block.heading)
  .replace('lheading', block.lheading)
  .replace('tag', block._tag) // pars can be interrupted by type (6) html blocks
  .getRegex();

block.blockquote = edit(block.blockquote)
  .replace('paragraph', block.paragraph)
  .getRegex();

block.dl = edit(block.dl)
  .replace('dt', block.text)
  .replace('dd', block.text)
  .getRegex();

/**
 * Normal Block Grammar
 */

block.normal = merge({}, block);

/**
 * GFM Block Grammar
 */

block.gfm = merge({}, block.normal, {
  fences: /^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\n? *\1 *(?:\n+|$)/,
  paragraph: /^/,
  heading: /^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/
});

block.gfm.paragraph = edit(block.paragraph)
  .replace('(?!', '(?!'
    + block.gfm.fences.source.replace('\\1', '\\2') + '|'
    + block.list.source.replace('\\1', '\\3') + '|')
  .getRegex();

/**
 * GFM + Tables Block Grammar
 */

block.tables = merge({}, block.gfm, {
  nptable: /^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,
  table: /^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/
});

/**
 * Pedantic grammar
 */

block.pedantic = merge({}, block.normal, {
  html: edit(
    '^ *(?:comment *(?:\\n|\\s*$)'
    + '|<(tag)[\\s\\S]+?</\\1> *(?:\\n{2,}|\\s*$)' // closed tag
    + '|<tag(?:"[^"]*"|\'[^\']*\'|\\s[^\'"/>\\s]*)*?/?> *(?:\\n{2,}|\\s*$))')
    .replace('comment', block._comment)
    .replace(/tag/g, '(?!(?:'
      + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub'
      + '|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)'
      + '\\b)\\w+(?!:|[^\\w\\s@]*@)\\b')
    .getRegex(),
  def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/
});

/**
 * Block Lexer
 */

function Lexer(options) {
  this.tokens = [];
  this.tokens.links = {};
  this.options = options || marked.defaults;
  this.rules = block.normal;

  if (this.options.pedantic) {
    this.rules = block.pedantic;
  } else if (this.options.gfm) {
    if (this.options.tables) {
      this.rules = block.tables;
    } else {
      this.rules = block.gfm;
    }
  }
}

/**
 * Expose Block Rules
 */

Lexer.rules = block;

/**
 * Static Lex Method
 */

Lexer.lex = function(src, options) {
  var lexer = new Lexer(options);
  return lexer.lex(src);
};

/**
 * Preprocessing
 */

Lexer.prototype.lex = function(src) {
  src = src
    .replace(/\r\n|\r/g, '\n')
    .replace(/\t/g, '    ')
    .replace(/\u00a0/g, ' ')
    .replace(/\u2424/g, '\n');

  return this.token(src, true);
};

/**
 * Lexing
 */

Lexer.prototype.token = function(src, top) {
  src = src.replace(/^ +$/gm, '');
  var next,
      loose,
      cap,
      bull,
      b,
      item,
      space,
      i,
      tag,
      l,
      isordered;

  while (src) {
    // newline
    if (cap = this.rules.newline.exec(src)) {
      src = src.substring(cap[0].length);
      if (cap[0].length > 1) {
        this.tokens.push({
          type: 'space'
        });
      }
    }

    // code
    if (cap = this.rules.code.exec(src)) {
      src = src.substring(cap[0].length);
      cap = cap[0].replace(/^ {4}/gm, '');
      this.tokens.push({
        type: 'code',
        text: !this.options.pedantic
          ? cap.replace(/\n+$/, '')
          : cap
      });
      continue;
    }

    // fences (gfm)
    if (cap = this.rules.fences.exec(src)) {
      src = src.substring(cap[0].length);
      this.tokens.push({
        type: 'code',
        lang: cap[2],
        text: cap[3] || ''
      });
      continue;
    }

    // heading
    if (cap = this.rules.heading.exec(src)) {
      src = src.substring(cap[0].length);
      this.tokens.push({
        type: 'heading',
        depth: cap[1].length,
        text: cap[2]
      });
      continue;
    }

    // table no leading pipe (gfm)
    if (top && (cap = this.rules.nptable.exec(src))) {
      src = src.substring(cap[0].length);

      item = {
        type: 'table',
        header: splitCells(cap[1].replace(/^ *| *\| *$/g, '')),
        align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
        cells: cap[3].replace(/\n$/, '').split('\n')
      };

      for (i = 0; i < item.align.length; i++) {
        if (/^ *-+: *$/.test(item.align[i])) {
          item.align[i] = 'right';
        } else if (/^ *:-+: *$/.test(item.align[i])) {
          item.align[i] = 'center';
        } else if (/^ *:-+ *$/.test(item.align[i])) {
          item.align[i] = 'left';
        } else {
          item.align[i] = null;
        }
      }

      for (i = 0; i < item.cells.length; i++) {
        item.cells[i] = splitCells(item.cells[i]);
      }

      this.tokens.push(item);

      continue;
    }

    // hr
    if (cap = this.rules.hr.exec(src)) {
      src = src.substring(cap[0].length);
      this.tokens.push({
        type: 'hr'
      });
      continue;
    }

    // blockquote
    if (cap = this.rules.blockquote.exec(src)) {
      src = src.substring(cap[0].length);

      this.tokens.push({
        type: 'blockquote_start'
      });

      cap = cap[0].replace(/^ *> ?/gm, '');

      // Pass `top` to keep the current
      // "toplevel" state. This is exactly
      // how markdown.pl works.
      this.token(cap, top);

      this.tokens.push({
        type: 'blockquote_end'
      });

      continue;
    }

    // list
    if (cap = this.rules.list.exec(src)) {
      src = src.substring(cap[0].length);
      bull = cap[2];
      isordered = bull.length > 1;

      this.tokens.push({
        type: 'list_start',
        ordered: isordered,
        start: isordered ? +bull : ''
      });

      // Get each top-level item.
      cap = cap[0].match(this.rules.item);

      next = false;
      l = cap.length;
      i = 0;

      for (; i < l; i++) {
        item = cap[i];

        // Remove the list item's bullet
        // so it is seen as the next token.
        space = item.length;
        item = item.replace(/^ *([*+-]|\d+\.) +/, '');

        // Outdent whatever the
        // list item contains. Hacky.
        if (~item.indexOf('\n ')) {
          space -= item.length;
          item = !this.options.pedantic
            ? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '')
            : item.replace(/^ {1,4}/gm, '');
        }

        // Determine whether the next list item belongs here.
        // Backpedal if it does not belong in this list.
        if (this.options.smartLists && i !== l - 1) {
          b = block.bullet.exec(cap[i + 1])[0];
          if (bull !== b && !(bull.length > 1 && b.length > 1)) {
            src = cap.slice(i + 1).join('\n') + src;
            i = l - 1;
          }
        }

        // Determine whether item is loose or not.
        // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/
        // for discount behavior.
        loose = next || /\n\n(?!\s*$)/.test(item);
        if (i !== l - 1) {
          next = item.charAt(item.length - 1) === '\n';
          if (!loose) loose = next;
        }

        this.tokens.push({
          type: loose
            ? 'loose_item_start'
            : 'list_item_start'
        });

        // Recurse.
        this.token(item, false);

        this.tokens.push({
          type: 'list_item_end'
        });
      }

      this.tokens.push({
        type: 'list_end'
      });

      continue;
    }


    // dl
    if (cap = this.rules.dl.exec(src)) {
      // TODO
      this.tokens.push({
        type: 'dl_start',
      });
      this.tokens.push({
        type: 'dt_start',
      });
      this.tokens.push({
        type: 'text',
        text: cap[1]
      });
      this.tokens.push({
        type: 'dt_end',
      });
      
      this.tokens.push({
        type: 'dd_start',
      });
      this.tokens.push({
        type: 'text',
        text: cap[2]
      });
      this.tokens.push({
        type: 'dd_end',
      });
      src = src.substring(cap[0].length);
      this.tokens.push({
        type: 'dl_end'
      });
      continue;
    }
    
    // html
    if (cap = this.rules.html.exec(src)) {
      src = src.substring(cap[0].length);
      this.tokens.push({
        type: this.options.sanitize
          ? 'paragraph'
          : 'html',
        pre: !this.options.sanitizer
          && (cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style'),
        text: cap[0]
      });
      continue;
    }

    // def
    if (top && (cap = this.rules.def.exec(src))) {
      src = src.substring(cap[0].length);
      if (cap[3]) cap[3] = cap[3].substring(1, cap[3].length - 1);
      tag = cap[1].toLowerCase().replace(/\s+/g, ' ');
      if (!this.tokens.links[tag]) {
        this.tokens.links[tag] = {
          href: cap[2],
          title: cap[3]
        };
      }
      continue;
    }

    // table (gfm)
    if (top && (cap = this.rules.table.exec(src))) {
      src = src.substring(cap[0].length);

      item = {
        type: 'table',
        header: splitCells(cap[1].replace(/^ *| *\| *$/g, '')),
        align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
        cells: cap[3].replace(/(?: *\| *)?\n$/, '').split('\n')
      };

      for (i = 0; i < item.align.length; i++) {
        if (/^ *-+: *$/.test(item.align[i])) {
          item.align[i] = 'right';
        } else if (/^ *:-+: *$/.test(item.align[i])) {
          item.align[i] = 'center';
        } else if (/^ *:-+ *$/.test(item.align[i])) {
          item.align[i] = 'left';
        } else {
          item.align[i] = null;
        }
      }

      for (i = 0; i < item.cells.length; i++) {
        item.cells[i] = splitCells(
          item.cells[i].replace(/^ *\| *| *\| *$/g, ''));
      }

      this.tokens.push(item);

      continue;
    }

    // lheading
    if (cap = this.rules.lheading.exec(src)) {
      src = src.substring(cap[0].length);
      this.tokens.push({
        type: 'heading',
        depth: cap[2] === '=' ? 1 : 2,
        text: cap[1]
      });
      continue;
    }

    // top-level paragraph
    if (top && (cap = this.rules.paragraph.exec(src))) {
      src = src.substring(cap[0].length);
      this.tokens.push({
        type: 'paragraph',
        text: cap[1].charAt(cap[1].length - 1) === '\n'
          ? cap[1].slice(0, -1)
          : cap[1]
      });
      continue;
    }

    // text
    if (cap = this.rules.text.exec(src)) {
      // Top-level should never reach here.
      src = src.substring(cap[0].length);
      this.tokens.push({
        type: 'text',
        text: cap[0]
      });
      continue;
    }

    if (src) {
      throw new Error('Infinite loop on byte: ' + src.charCodeAt(0));
    }
  }

  return this.tokens;
};

/**
 * Inline-Level Grammar
 */

var inline = {
  escape: /^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/,
  autolink: /^<(scheme:[^\s\x00-\x1f<>]*|email)>/,
  url: noop,
  tag: '^comment'
    + '|^</[a-zA-Z][\\w:-]*\\s*>' // self-closing tag
    + '|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>' // open tag
    + '|^<\\?[\\s\\S]*?\\?>' // processing instruction, e.g. <?php ?>
    + '|^<![a-zA-Z]+\\s[\\s\\S]*?>' // declaration, e.g. <!DOCTYPE html>
    + '|^<!\\[CDATA\\[[\\s\\S]*?\\]\\]>', // CDATA section
  link: /^!?\[(label)\]\(href(?:\s+(title))?\s*\)/,
  reflink: /^!?\[(label)\]\[(?!\s*\])((?:\\[\[\]]?|[^\[\]\\])+)\]/,
  nolink: /^!?\[(?!\s*\])((?:\[[^\[\]]*\]|\\[\[\]]|[^\[\]])*)\](?:\[\])?/,
  // fix *x* **x** one character em/strong formatters
  // strong: /^__([^\s][\s\S]*?[^\s])__(?!_)|^\*\*([^\s][\s\S]*?[^\s])\*\*(?!\*)|^__([^\s])__(?!_)|^\*\*([^\s])\*\*(?!\*)/,
  // em: /^_([^\s][\s\S]*?[^\s_])_(?!_)|^_([^\s_][\s\S]*?[^\s])_(?!_)|^\*([^\s][\s\S]*?[^\s*])\*(?!\*)|^\*([^\s*][\s\S]*?[^\s])\*(?!\*)|^_([^\s_])_(?!_)|^\*([^\s*])\*(?!\*)/,
  strong: /^__[^\s_\*]__|^\*\*[^\s]\*\*|^__([^\s][\s\S]*?[^\s])__(?!_)|^\*\*([^\s][\s\S]*?[^\s])\*\*(?!\*)|^__([^\s])__(?!_)|^\*\*([^\s])\*\*(?!\*)/,
  em: /^_[^\s_\*]_|^\*[^\s_\*]\*|^_([^\s][\s\S]*?[^\s_])_(?!_)|^_([^\s_][\s\S]*?[^\s])_(?!_)|^\*([^\s][\s\S]*?[^\s*])\*(?!\*)|^\*([^\s*][\s\S]*?[^\s])\*(?!\*)|^_([^\s_])_(?!_)|^\*([^\s*])\*(?!\*)/,
  code: /^(`+)\s*([\s\S]*?[^`]?)\s*\1(?!`)/,
  br: /^ {2,}\n(?!\s*$)/,
  del: noop,
  text: /^[\s\S]+?(?=[\\<!\[`*]|\b_| {2,}\n|$)/
};

inline._escapes = /\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/g;

inline._scheme = /[a-zA-Z][a-zA-Z0-9+.-]{1,31}/;
inline._email = /[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/;
inline.autolink = edit(inline.autolink)
  .replace('scheme', inline._scheme)
  .replace('email', inline._email)
  .getRegex();

inline._attribute = /\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/;

inline.tag = edit(inline.tag)
  .replace('comment', block._comment)
  .replace('attribute', inline._attribute)
  .getRegex();

inline._label = /(?:\[[^\[\]]*\]|\\[\[\]]?|`[^`]*`|[^\[\]\\])*?/;
inline._href = /\s*(<(?:\\[<>]?|[^\s<>\\])*>|(?:\\[()]?|\([^\s\x00-\x1f()\\]*\)|[^\s\x00-\x1f()\\])*?)/;
inline._title = /"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/;

inline.link = edit(inline.link)
  .replace('label', inline._label)
  .replace('href', inline._href)
  .replace('title', inline._title)
  .getRegex();

inline.reflink = edit(inline.reflink)
  .replace('label', inline._label)
  .getRegex();

/**
 * Normal Inline Grammar
 */

inline.normal = merge({}, inline);

/**
 * Pedantic Inline Grammar
 */

inline.pedantic = merge({}, inline.normal, {
  strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,
  em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/,
  link: edit(/^!?\[(label)\]\((.*?)\)/)
    .replace('label', inline._label)
    .getRegex(),
  reflink: edit(/^!?\[(label)\]\s*\[([^\]]*)\]/)
    .replace('label', inline._label)
    .getRegex()
});

/**
 * GFM Inline Grammar
 */

inline.gfm = merge({}, inline.normal, {
  escape: edit(inline.escape).replace('])', '~|])').getRegex(),
  url: edit(/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/)
    .replace('email', inline._email)
    .getRegex(),
  _backpedal: /(?:[^?!.,:;*_~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_~)]+(?!$))+/,
  del: /^~~(?=\S)([\s\S]*?\S)~~/,
  text: edit(inline.text)
    .replace(']|', '~]|')
    .replace('|', '|https?://|ftp://|www\\.|[a-zA-Z0-9.!#$%&\'*+/=?^_`{\\|}~-]+@|')
    .getRegex()
});

/**
 * GFM + Line Breaks Inline Grammar
 */

inline.breaks = merge({}, inline.gfm, {
  br: edit(inline.br).replace('{2,}', '*').getRegex(),
  text: edit(inline.gfm.text).replace('{2,}', '*').getRegex()
});

/**
 * Inline Lexer & Compiler
 */

function InlineLexer(links, options) {
  this.options = options || marked.defaults;
  this.links = links;
  this.rules = inline.normal;
  this.renderer = this.options.renderer || new Renderer();
  this.renderer.options = this.options;

  if (!this.links) {
    throw new Error('Tokens array requires a `links` property.');
  }

  if (this.options.pedantic) {
    this.rules = inline.pedantic;
  } else if (this.options.gfm) {
    if (this.options.breaks) {
      this.rules = inline.breaks;
    } else {
      this.rules = inline.gfm;
    }
  }
}

/**
 * Expose Inline Rules
 */

InlineLexer.rules = inline;

/**
 * Static Lexing/Compiling Method
 */

InlineLexer.output = function(src, links, options) {
  var inline = new InlineLexer(links, options);
  return inline.output(src);
};

/**
 * Lexing/Compiling
 */

InlineLexer.prototype.output = function(src) {
  var out = '',
      link,
      text,
      href,
      title,
      cap;

  while (src) {
    // escape
    if (cap = this.rules.escape.exec(src)) {
      src = src.substring(cap[0].length);
      out += cap[1];
      continue;
    }

    // autolink
    if (cap = this.rules.autolink.exec(src)) {
      src = src.substring(cap[0].length);
      if (cap[2] === '@') {
        text = escape(this.mangle(cap[1]));
        href = 'mailto:' + text;
      } else {
        text = escape(cap[1]);
        href = text;
      }
      out += this.renderer.link(href, null, text);
      continue;
    }

    // url (gfm)
    if (!this.inLink && (cap = this.rules.url.exec(src))) {
      cap[0] = this.rules._backpedal.exec(cap[0])[0];
      src = src.substring(cap[0].length);
      if (cap[2] === '@') {
        text = escape(cap[0]);
        href = 'mailto:' + text;
      } else {
        text = escape(cap[0]);
        if (cap[1] === 'www.') {
          href = 'http://' + text;
        } else {
          href = text;
        }
      }
      out += this.renderer.link(href, null, text);
      continue;
    }

    // tag
    if (cap = this.rules.tag.exec(src)) {
      if (!this.inLink && /^<a /i.test(cap[0])) {
        this.inLink = true;
      } else if (this.inLink && /^<\/a>/i.test(cap[0])) {
        this.inLink = false;
      }
      src = src.substring(cap[0].length);
      out += this.options.sanitize
        ? this.options.sanitizer
          ? this.options.sanitizer(cap[0])
          : escape(cap[0])
        : cap[0]
      continue;
    }

    // link
    if (cap = this.rules.link.exec(src)) {
      src = src.substring(cap[0].length);
      this.inLink = true;
      href = cap[2];
      if (this.options.pedantic) {
        link = /^([^'"]*[^\s])\s+(['"])(.*)\2/.exec(href);

        if (link) {
          href = link[1];
          title = link[3];
        } else {
          title = '';
        }
      } else {
        title = cap[3] ? cap[3].slice(1, -1) : '';
      }
      href = href.trim().replace(/^<([\s\S]*)>$/, '$1');
      out += this.outputLink(cap, {
        href: InlineLexer.escapes(href),
        title: InlineLexer.escapes(title)
      });
      this.inLink = false;
      continue;
    }

    // reflink, nolink
    if ((cap = this.rules.reflink.exec(src))
        || (cap = this.rules.nolink.exec(src))) {
      src = src.substring(cap[0].length);
      link = (cap[2] || cap[1]).replace(/\s+/g, ' ');
      link = this.links[link.toLowerCase()];
      if (!link || !link.href) {
        out += cap[0].charAt(0);
        src = cap[0].substring(1) + src;
        continue;
      }
      this.inLink = true;
      out += this.outputLink(cap, link);
      this.inLink = false;
      continue;
    }

    // strong
    if (cap = this.rules.strong.exec(src)) {
      src = src.substring(cap[0].length);
      out += this.renderer.strong(this.output(cap[4] || cap[3] || cap[2] || cap[1]));
      continue;
    }

    // em
    if (cap = this.rules.em.exec(src)) {
      src = src.substring(cap[0].length);
      out += this.renderer.em(this.output(cap[6] || cap[5] || cap[4] || cap[3] || cap[2] || cap[1]));
      continue;
    }

    // code
    if (cap = this.rules.code.exec(src)) {
      src = src.substring(cap[0].length);
      out += this.renderer.codespan(escape(cap[2].trim(), true));
      continue;
    }

    // br
    if (cap = this.rules.br.exec(src)) {
      src = src.substring(cap[0].length);
      out += this.renderer.br();
      continue;
    }

    // del (gfm)
    if (cap = this.rules.del.exec(src)) {
      src = src.substring(cap[0].length);
      out += this.renderer.del(this.output(cap[1]));
      continue;
    }

    // text
    if (cap = this.rules.text.exec(src)) {
      src = src.substring(cap[0].length);
      out += this.renderer.text(escape(this.smartypants(cap[0])));
      continue;
    }

    if (src) {
      throw new Error('Infinite loop on byte: ' + src.charCodeAt(0));
    }
  }

  return out;
};

InlineLexer.escapes = function(text) {
  return text ? text.replace(InlineLexer.rules._escapes, '$1') : text;
}

/**
 * Compile Link
 */

InlineLexer.prototype.outputLink = function(cap, link) {
  var href = link.href,
      title = link.title ? escape(link.title) : null;

  return cap[0].charAt(0) !== '!'
    ? this.renderer.link(href, title, this.output(cap[1]))
    : this.renderer.image(href, title, escape(cap[1]));
};

/**
 * Smartypants Transformations
 */

InlineLexer.prototype.smartypants = function(text) {
  if (!this.options.smartypants) return text;
  return text
    // em-dashes
    .replace(/---/g, '\u2014')
    // en-dashes
    .replace(/--/g, '\u2013')
    // opening singles
    .replace(/(^|[-\u2014/(\[{"\s])'/g, '$1\u2018')
    // closing singles & apostrophes
    .replace(/'/g, '\u2019')
    // opening doubles
    .replace(/(^|[-\u2014/(\[{\u2018\s])"/g, '$1\u201c')
    // closing doubles
    .replace(/"/g, '\u201d')
    // ellipses
    .replace(/\.{3}/g, '\u2026');
};

/**
 * Mangle Links
 */

InlineLexer.prototype.mangle = function(text) {
  if (!this.options.mangle) return text;
  var out = '',
      l = text.length,
      i = 0,
      ch;

  for (; i < l; i++) {
    ch = text.charCodeAt(i);
    if (Math.random() > 0.5) {
      ch = 'x' + ch.toString(16);
    }
    out += '&#' + ch + ';';
  }

  return out;
};

/**
 * Renderer
 */

function Renderer(options) {
  this.options = options || marked.defaults;
}

Renderer.prototype.code = function(code, lang, escaped) {
  if (this.options.highlight) {
    var out = this.options.highlight(code, lang);
    if (out != null && out !== code) {
      escaped = true;
      code = out;
    }
  }

  if (!lang) {
    return '<pre><code>'
      + (escaped ? code : escape(code, true))
      + '\n</code></pre>';
  }

  return '<pre><code class="'
    + this.options.langPrefix
    + escape(lang, true)
    + '">'
    + (escaped ? code : escape(code, true))
    + '\n</code></pre>\n';
};

Renderer.prototype.blockquote = function(quote) {
  return '<blockquote>\n' + quote + '</blockquote>\n';
};

Renderer.prototype.html = function(html) {
  return html;
};

Renderer.prototype.heading = function(text, level, raw) {
  if (this.options.headerIds) {
    return '<h'
      + level
      + ' id="'
      + this.options.headerPrefix
      + raw.toLowerCase().replace(/[^\w]+/g, '-')
      + '">'
      + text
      + '</h'
      + level
      + '>\n';
  }
  // ignore IDs
  return '<h' + level + '>' + text + '</h' + level + '>\n';
};

Renderer.prototype.hr = function() {
  return this.options.xhtml ? '<hr/>\n' : '<hr>\n';
};

Renderer.prototype.list = function(body, ordered, start) {
  var type = ordered ? 'ol' : 'ul',
      startatt = (ordered && start !== 1) ? (' start="' + start + '"') : '';
  return '<' + type + startatt + '>\n' + body + '</' + type + '>\n';
};

Renderer.prototype.listitem = function(text) {
  return '<li>' + text + '</li>\n';
};

Renderer.prototype.dl = function(body) {
  return '<dl>\n' + body + '</dl>\n';
};
Renderer.prototype.dt = function(body) {
  return '<dt>' + body + '</dt>\n';
};
Renderer.prototype.dd = function(body) {
  return '<dd>' + body + '</dd>\n';
};

Renderer.prototype.paragraph = function(text) {
  return '<p>' + text + '</p>\n';
};

Renderer.prototype.table = function(header, body) {
  return '<table>\n'
    + '<thead>\n'
    + header
    + '</thead>\n'
    + '<tbody>\n'
    + body
    + '</tbody>\n'
    + '</table>\n';
};

Renderer.prototype.tablerow = function(content) {
  return '<tr>\n' + content + '</tr>\n';
};

Renderer.prototype.tablecell = function(content, flags) {
  var type = flags.header ? 'th' : 'td';
  var tag = flags.align
    ? '<' + type + ' style="text-align:' + flags.align + '">'
    : '<' + type + '>';
  return tag + content + '</' + type + '>\n';
};

// span level renderer
Renderer.prototype.strong = function(text) {
  return '<strong>' + text + '</strong>';
};

Renderer.prototype.em = function(text) {
  return '<em>' + text + '</em>';
};

Renderer.prototype.codespan = function(text) {
  return '<code>' + text + '</code>';
};

Renderer.prototype.br = function() {
  return this.options.xhtml ? '<br/>' : '<br>';
};

Renderer.prototype.del = function(text) {
  return '<del>' + text + '</del>';
};

Renderer.prototype.link = function(href, title, text) {
  if (this.options.sanitize) {
    try {
      var prot = decodeURIComponent(unescape(href))
        .replace(/[^\w:]/g, '')
        .toLowerCase();
    } catch (e) {
      return text;
    }
    if (prot.indexOf('javascript:') === 0 || prot.indexOf('vbscript:') === 0 || prot.indexOf('data:') === 0) {
      return text;
    }
  }
  if (this.options.baseUrl && !originIndependentUrl.test(href)) {
    href = resolveUrl(this.options.baseUrl, href);
  }
  try {
    href = encodeURI(href).replace(/%25/g, '%');
  } catch (e) {
    return text;
  }
  var out = '<a href="' + escape(href) + '"';
  if (title) {
    out += ' title="' + title + '"';
  }
  out += '>' + text + '</a>';
  return out;
};

Renderer.prototype.image = function(href, title, text) {
  if (this.options.baseUrl && !originIndependentUrl.test(href)) {
    href = resolveUrl(this.options.baseUrl, href);
  }
  var out = '<img src="' + href + '" alt="' + text + '"';
  if (title) {
    out += ' title="' + title + '"';
  }
  out += this.options.xhtml ? '/>' : '>';
  return out;
};

Renderer.prototype.text = function(text) {
  return text;
};

/**
 * TextRenderer
 * returns only the textual part of the token
 */

function TextRenderer() {}

// no need for block level renderers

TextRenderer.prototype.strong =
TextRenderer.prototype.em =
TextRenderer.prototype.codespan =
TextRenderer.prototype.del =
TextRenderer.prototype.text = function (text) {
  return text;
}

TextRenderer.prototype.link =
TextRenderer.prototype.image = function(href, title, text) {
  return '' + text;
}

TextRenderer.prototype.br = function() {
  return '';
}

/**
 * Parsing & Compiling
 */

function Parser(options) {
  this.tokens = [];
  this.token = null;
  this.options = options || marked.defaults;
  this.options.renderer = this.options.renderer || new Renderer();
  this.renderer = this.options.renderer;
  this.renderer.options = this.options;
}

/**
 * Static Parse Method
 */

Parser.parse = function(src, options) {
  var parser = new Parser(options);
  return parser.parse(src);
};

/**
 * Parse Loop
 */

Parser.prototype.parse = function(src) {
  this.inline = new InlineLexer(src.links, this.options);
  // use an InlineLexer with a TextRenderer to extract pure text
  this.inlineText = new InlineLexer(
    src.links,
    merge({}, this.options, {renderer: new TextRenderer()})
  );
  this.tokens = src.reverse();

  var out = '';
  while (this.next()) {
    out += this.tok();
  }

  return out;
};

/**
 * Next Token
 */

Parser.prototype.next = function() {
  return this.token = this.tokens.pop();
};

/**
 * Preview Next Token
 */

Parser.prototype.peek = function() {
  return this.tokens[this.tokens.length - 1] || 0;
};

/**
 * Parse Text Tokens
 */

Parser.prototype.parseText = function() {
  var body = this.token.text;

  while (this.peek().type === 'text') {
    body += '\n' + this.next().text;
  }

  return this.inline.output(body);
};

/**
 * Parse Current Token
 */

Parser.prototype.tok = function() {
  switch (this.token.type) {
    case 'space': {
      return '';
    }
    case 'hr': {
      return this.renderer.hr();
    }
    case 'heading': {
      return this.renderer.heading(
        this.inline.output(this.token.text),
        this.token.depth,
        unescape(this.inlineText.output(this.token.text)));
    }
    case 'code': {
      return this.renderer.code(this.token.text,
        this.token.lang,
        this.token.escaped);
    }
    case 'table': {
      var header = '',
          body = '',
          i,
          row,
          cell,
          j;

      // header
      cell = '';
      for (i = 0; i < this.token.header.length; i++) {
        cell += this.renderer.tablecell(
          this.inline.output(this.token.header[i]),
          { header: true, align: this.token.align[i] }
        );
      }
      header += this.renderer.tablerow(cell);

      for (i = 0; i < this.token.cells.length; i++) {
        row = this.token.cells[i];

        cell = '';
        for (j = 0; j < row.length; j++) {
          cell += this.renderer.tablecell(
            this.inline.output(row[j]),
            { header: false, align: this.token.align[j] }
          );
        }

        body += this.renderer.tablerow(cell);
      }
      return this.renderer.table(header, body);
    }
    case 'blockquote_start': {
      body = '';

      while (this.next().type !== 'blockquote_end') {
        body += this.tok();
      }

      return this.renderer.blockquote(body);
    }
    case 'list_start': {
      body = '';
      var ordered = this.token.ordered,
          start = this.token.start;

      while (this.next().type !== 'list_end') {
        body += this.tok();
      }

      return this.renderer.list(body, ordered, start);
    }
    case 'list_item_start': {
      body = '';

      while (this.next().type !== 'list_item_end') {
        body += this.token.type === 'text'
          ? this.parseText()
          : this.tok();
      }

      return this.renderer.listitem(body);
    }
    case 'loose_item_start': {
      body = '';

      while (this.next().type !== 'list_item_end') {
        body += this.tok();
      }

      return this.renderer.listitem(body);
    }
    case 'dl_start': {
      body = '';
      while (this.next().type !== 'dl_end') {
        body += this.tok();
      }
      return this.renderer.dl(body);    
    }
    case 'dt_start': {
      body = '';
      while (this.next().type !== 'dt_end') {
        body += this.parseText();
      }
      return this.renderer.dt(body);    
    }
    case 'dd_start': {
      body = '';
      while (this.next().type !== 'dd_end') {
        body += this.parseText();
      }
      return this.renderer.dd(body);    
    }
    case 'html': {
      // TODO parse inline content if parameter markdown=1
      return this.renderer.html(this.token.text);
    }
    case 'paragraph': {
      return this.renderer.paragraph(this.inline.output(this.token.text));
    }
    case 'text': {
      return this.renderer.paragraph(this.parseText());
    }
  }
};

/**
 * Helpers
 */

function escape(html, encode) {
  return html
    .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;');
}

function unescape(html) {
  // explicitly match decimal, hex, and named HTML entities
  return html.replace(/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig, function(_, n) {
    n = n.toLowerCase();
    if (n === 'colon') return ':';
    if (n.charAt(0) === '#') {
      return n.charAt(1) === 'x'
        ? String.fromCharCode(parseInt(n.substring(2), 16))
        : String.fromCharCode(+n.substring(1));
    }
    return '';
  });
}

function edit(regex, opt) {
  regex = regex.source || regex;
  opt = opt || '';
  return {
    replace: function(name, val) {
      val = val.source || val;
      val = val.replace(/(^|[^\[])\^/g, '$1');
      regex = regex.replace(name, val);
      return this;
    },
    getRegex: function() {
      return new RegExp(regex, opt);
    }
  };
}

function resolveUrl(base, href) {
  if (!baseUrls[' ' + base]) {
    // we can ignore everything in base after the last slash of its path component,
    // but we might need to add _that_
    // https://tools.ietf.org/html/rfc3986#section-3
    if (/^[^:]+:\/*[^/]*$/.test(base)) {
      baseUrls[' ' + base] = base + '/';
    } else {
      baseUrls[' ' + base] = base.replace(/[^/]*$/, '');
    }
  }
  base = baseUrls[' ' + base];

  if (href.slice(0, 2) === '//') {
    return base.replace(/:[\s\S]*/, ':') + href;
  } else if (href.charAt(0) === '/') {
    return base.replace(/(:\/*[^/]*)[\s\S]*/, '$1') + href;
  } else {
    return base + href;
  }
}
var baseUrls = {};
var originIndependentUrl = /^$|^[a-z][a-z0-9+.-]*:|^[?#]/i;

function noop() {}
noop.exec = noop;

function merge(obj) {
  var i = 1,
      target,
      key;

  for (; i < arguments.length; i++) {
    target = arguments[i];
    for (key in target) {
      if (Object.prototype.hasOwnProperty.call(target, key)) {
        obj[key] = target[key];
      }
    }
  }

  return obj;
}

function splitCells(tableRow) {
  var cells = tableRow.replace(/([^\\])\|/g, '$1 |').split(/ +\| */),
      i = 0;

  for (; i < cells.length; i++) {
    cells[i] = cells[i].replace(/\\\|/g, '|');
  }
  return cells;
}

/**
 * Marked
 */

function marked(src, opt, callback) {
  // throw error in case of non string input
  if (typeof src === 'undefined' || src === null) {
    throw new Error('marked(): input parameter is undefined or null');
  }
  if (typeof src !== 'string') {
    throw new Error('marked(): input parameter is of type '
      + Object.prototype.toString.call(src) + ', string expected');
  }

  if (callback || typeof opt === 'function') {
    if (!callback) {
      callback = opt;
      opt = null;
    }

    opt = merge({}, marked.defaults, opt || {});

    var highlight = opt.highlight,
        tokens,
        pending,
        i = 0;

    try {
      tokens = Lexer.lex(src, opt)
    } catch (e) {
      return callback(e);
    }

    pending = tokens.length;

    var done = function(err) {
      if (err) {
        opt.highlight = highlight;
        return callback(err);
      }

      var out;

      try {
        out = Parser.parse(tokens, opt);
      } catch (e) {
        err = e;
      }

      opt.highlight = highlight;

      return err
        ? callback(err)
        : callback(null, out);
    };

    if (!highlight || highlight.length < 3) {
      return done();
    }

    delete opt.highlight;

    if (!pending) return done();

    for (; i < tokens.length; i++) {
      (function(token) {
        if (token.type !== 'code') {
          return --pending || done();
        }
        return highlight(token.text, token.lang, function(err, code) {
          if (err) return done(err);
          if (code == null || code === token.text) {
            return --pending || done();
          }
          token.text = code;
          token.escaped = true;
          --pending || done();
        });
      })(tokens[i]);
    }

    return;
  }
  try {
    if (opt) opt = merge({}, marked.defaults, opt);
    return Parser.parse(Lexer.lex(src, opt), opt);
  } catch (e) {
    e.message += '\nPlease report this to https://github.com/markedjs/marked.';
    if ((opt || marked.defaults).silent) {
      return '<p>An error occurred:</p><pre>'
        + escape(e.message + '', true)
        + '</pre>';
    }
    throw e;
  }
}

/**
 * Options
 */

marked.options =
marked.setOptions = function(opt) {
  merge(marked.defaults, opt);
  return marked;
};

marked.getDefaults = function () {
  return {
    baseUrl: null,
    breaks: false,
    gfm: true,
    headerIds: true,
    headerPrefix: '',
    highlight: null,
    langPrefix: 'lang-',
    mangle: true,
    pedantic: false,
    renderer: new Renderer(),
    sanitize: false,
    sanitizer: null,
    silent: false,
    smartLists: false,
    smartypants: false,
    tables: true,
    xhtml: false
  };
}

marked.defaults = marked.getDefaults();

/**
 * Expose
 */

marked.Parser = Parser;
marked.parser = Parser.parse;

marked.Renderer = Renderer;
marked.TextRenderer = TextRenderer;

marked.Lexer = Lexer;
marked.lexer = Lexer.lex;

marked.InlineLexer = InlineLexer;
marked.inlineLexer = InlineLexer.output;

marked.parse = marked;
return marked;

}
};
BundleModuleCode['doc/colors']=function (module,exports){
/*

The MIT License (MIT)

Original Library 
  - Copyright (c) Marak Squires

Additional functionality
 - Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

*/

var colors = {};
module['exports'] = colors;

colors.themes = {};

var ansiStyles = colors.styles = Require('doc/styles');
var defineProps = Object.defineProperties;

colors.supportsColor = Require('doc/system/supports-colors').supportsColor;

if (typeof colors.enabled === "undefined") {
  colors.enabled = colors.supportsColor() !== false;
}

colors.stripColors = colors.strip = function(str){
  return ("" + str).replace(/\x1B\[\d+m/g, '');
};


var stylize = colors.stylize = function stylize (str, style) {
  if (!colors.enabled) {
    return str+'';
  }

  return ansiStyles[style].open + str + ansiStyles[style].close;
}

var matchOperatorsRe = /[|\\{}()[\]^$+*?.]/g;
var escapeStringRegexp = function (str) {
  if (typeof str !== 'string') {
    throw new TypeError('Expected a string');
  }
  return str.replace(matchOperatorsRe,  '\\$&');
}

function build(_styles) {
  var builder = function builder() {
    return applyStyle.apply(builder, arguments);
  };
  builder._styles = _styles;
  // __proto__ is used because we must return a function, but there is
  // no way to create a function with a different prototype.
  builder.__proto__ = proto;
  return builder;
}

var styles = (function () {
  var ret = {};
  ansiStyles.grey = ansiStyles.gray;
  Object.keys(ansiStyles).forEach(function (key) {
    ansiStyles[key].closeRe = new RegExp(escapeStringRegexp(ansiStyles[key].close), 'g');
    ret[key] = {
      get: function () {
        return build(this._styles.concat(key));
      }
    };
  });
  return ret;
})();

var proto = defineProps(function colors() {}, styles);

function applyStyle() {
  var args = arguments;
  var argsLen = args.length;
  var str = argsLen !== 0 && String(arguments[0]);
  if (argsLen > 1) {
    for (var a = 1; a < argsLen; a++) {
      str += ' ' + args[a];
    }
  }

  if (!colors.enabled || !str) {
    return str;
  }

  var nestedStyles = this._styles;

  var i = nestedStyles.length;
  while (i--) {
    var code = ansiStyles[nestedStyles[i]];
    str = code.open + str.replace(code.closeRe, code.open) + code.close;
  }

  return str;
}

colors.setTheme = function (theme) {
  if (typeof theme === 'string') {
    console.log('colors.setTheme now only accepts an object, not a string.  ' +
      'If you are trying to set a theme from a file, it is now your (the caller\'s) responsibility to require the file.  ' +
      'The old syntax looked like colors.setTheme(__dirname + \'/../themes/generic-logging.js\'); ' +
      'The new syntax looks like colors.setTheme(require(__dirname + \'/../themes/generic-logging.js\'));');
    return;
  }
  for (var style in theme) {
    (function(style){
      colors[style] = function(str){
        if (typeof theme[style] === 'object'){
          var out = str;
          for (var i in theme[style]){
            out = colors[theme[style][i]](out);
          }
          return out;
        }
        return colors[theme[style]](str);
      };
    })(style)
  }
}

function init() {
  var ret = {};
  Object.keys(styles).forEach(function (name) {
    ret[name] = {
      get: function () {
        return build([name]);
      }
    };
  });
  return ret;
}

var sequencer = function sequencer (map, str) {
  var exploded = str.split(""), i = 0;
  exploded = exploded.map(map);
  return exploded.join("");
};

// custom formatter methods
colors.trap =   Require('doc/custom/trap');
colors.zalgo =  Require('doc/custom/zalgo');

// maps
colors.maps = {};
colors.maps.america = (function() {
  return function (letter, i, exploded) {
    if(letter === " ") return letter;
    switch(i%3) {
      case 0: return colors.red(letter);
      case 1: return colors.white(letter)
      case 2: return colors.blue(letter)
    }
  }
})();

colors.maps.zebra =   function (letter, i, exploded) {
  return i % 2 === 0 ? letter : colors.inverse(letter);
}
colors.maps.rainbow = (function () {
  var rainbowColors = ['red', 'yellow', 'green', 'blue', 'magenta']; //RoY G BiV
  return function (letter, i, exploded) {
    if (letter === " ") {
      return letter;
    } else {
      return colors[rainbowColors[i++ % rainbowColors.length]](letter);
    }
  };
})();
colors.maps.random =  (function () {
  var available = ['underline', 'inverse', 'grey', 'yellow', 'red', 'green', 'blue', 'white', 'cyan', 'magenta'];
  return function(letter, i, exploded) {
    return letter === " " ? letter : colors[available[Math.round(Math.random() * (available.length - 1))]](letter);
  };
})();

for (var map in colors.maps) {
  (function(map){
    colors[map] = function (str) {
      return sequencer(colors.maps[map], str);
    }
  })(map)
}

defineProps(colors, init());
};
BundleModuleCode['doc/styles']=function (module,exports){
/*
The MIT License (MIT)

Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

*/

var styles = {};
module['exports'] = styles;

var codes = {
  reset: [0, 0],

  bold: [1, 22],
  dim: [2, 22],
  italic: [3, 23],
  underline: [4, 24],
  inverse: [7, 27],
  hidden: [8, 28],
  strikethrough: [9, 29],

  black: [30, 39],
  red: [31, 39],
  green: [32, 39],
  yellow: [33, 39],
  blue: [34, 39],
  magenta: [35, 39],
  cyan: [36, 39],
  white: [37, 39],
  gray: [90, 39],
  grey: [90, 39],

  bgBlack: [40, 49],
  bgRed: [41, 49],
  bgGreen: [42, 49],
  bgYellow: [43, 49],
  bgBlue: [44, 49],
  bgMagenta: [45, 49],
  bgCyan: [46, 49],
  bgWhite: [47, 49],

  // legacy styles for colors pre v1.0.0
  blackBG: [40, 49],
  redBG: [41, 49],
  greenBG: [42, 49],
  yellowBG: [43, 49],
  blueBG: [44, 49],
  magentaBG: [45, 49],
  cyanBG: [46, 49],
  whiteBG: [47, 49]

};

Object.keys(codes).forEach(function (key) {
  var val = codes[key];
  var style = styles[key] = [];
  style.open = '\u001b[' + val[0] + 'm';
  style.close = '\u001b[' + val[1] + 'm';
});};
BundleModuleCode['doc/system/supports-colors']=function (module,exports){
/*
The MIT License (MIT)

Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

*/

'use strict';

var os = Require('os');
var hasFlag = Require('doc/system/has-flag.js');

var env = process.env;

var forceColor = void 0;
if (hasFlag('no-color') || hasFlag('no-colors') || hasFlag('color=false')) {
	forceColor = false;
} else if (hasFlag('color') || hasFlag('colors') || hasFlag('color=true') || hasFlag('color=always')) {
	forceColor = true;
}
if ('FORCE_COLOR' in env) {
	forceColor = env.FORCE_COLOR.length === 0 || parseInt(env.FORCE_COLOR, 10) !== 0;
}

function translateLevel(level) {
	if (level === 0) {
		return false;
	}

	return {
		level: level,
		hasBasic: true,
		has256: level >= 2,
		has16m: level >= 3
	};
}

function supportsColor(stream) {
	if (forceColor === false) {
		return 0;
	}

	if (hasFlag('color=16m') || hasFlag('color=full') || hasFlag('color=truecolor')) {
		return 3;
	}

	if (hasFlag('color=256')) {
		return 2;
	}

	if (stream && !stream.isTTY && forceColor !== true) {
		return 0;
	}

	var min = forceColor ? 1 : 0;

	if (process.platform === 'win32') {
		// Node.js 7.5.0 is the first version of Node.js to include a patch to
		// libuv that enables 256 color output on Windows. Anything earlier and it
		// won't work. However, here we target Node.js 8 at minimum as it is an LTS
		// release, and Node.js 7 is not. Windows 10 build 10586 is the first Windows
		// release that supports 256 colors. Windows 10 build 14931 is the first release
		// that supports 16m/TrueColor.
		var osRelease = os.release().split('.');
		if (Number(process.versions.node.split('.')[0]) >= 8 && Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
			return Number(osRelease[2]) >= 14931 ? 3 : 2;
		}

		return 1;
	}

	if ('CI' in env) {
		if (['TRAVIS', 'CIRCLECI', 'APPVEYOR', 'GITLAB_CI'].some(function (sign) {
			return sign in env;
		}) || env.CI_NAME === 'codeship') {
			return 1;
		}

		return min;
	}

	if ('TEAMCITY_VERSION' in env) {
		return (/^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0
		);
	}

	if ('TERM_PROGRAM' in env) {
		var version = parseInt((env.TERM_PROGRAM_VERSION || '').split('.')[0], 10);

		switch (env.TERM_PROGRAM) {
			case 'iTerm.app':
				return version >= 3 ? 3 : 2;
			case 'Hyper':
				return 3;
			case 'Apple_Terminal':
				return 2;
			// No default
		}
	}

	if (/-256(color)?$/i.test(env.TERM)) {
		return 2;
	}

	if (/^screen|^xterm|^vt100|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) {
		return 1;
	}

	if ('COLORTERM' in env) {
		return 1;
	}

	if (env.TERM === 'dumb') {
		return min;
	}

	return min;
}

function getSupportLevel(stream) {
	var level = supportsColor(stream);
	return translateLevel(level);
}

module.exports = {
	supportsColor: getSupportLevel,
	stdout: getSupportLevel(process.stdout),
	stderr: getSupportLevel(process.stderr)
};
};
BundleModuleCode['doc/system/has-flag.js']=function (module,exports){
/*
MIT License

Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

'use strict';

module.exports = function (flag, argv) {
	argv = argv || process.argv;

	var terminatorPos = argv.indexOf('--');
	var prefix = /^-{1,2}/.test(flag) ? '' : '--';
	var pos = argv.indexOf(prefix + flag);

	return pos !== -1 && (terminatorPos === -1 ? true : pos < terminatorPos);
};
};
BundleModuleCode['doc/custom/trap']=function (module,exports){
module['exports'] = function runTheTrap (text, options) {
  var result = "";
  text = text || "Run the trap, drop the bass";
  text = text.split('');
  var trap = {
    a: ["\u0040", "\u0104", "\u023a", "\u0245", "\u0394", "\u039b", "\u0414"],
    b: ["\u00df", "\u0181", "\u0243", "\u026e", "\u03b2", "\u0e3f"],
    c: ["\u00a9", "\u023b", "\u03fe"],
    d: ["\u00d0", "\u018a", "\u0500" , "\u0501" ,"\u0502", "\u0503"],
    e: ["\u00cb", "\u0115", "\u018e", "\u0258", "\u03a3", "\u03be", "\u04bc", "\u0a6c"],
    f: ["\u04fa"],
    g: ["\u0262"],
    h: ["\u0126", "\u0195", "\u04a2", "\u04ba", "\u04c7", "\u050a"],
    i: ["\u0f0f"],
    j: ["\u0134"],
    k: ["\u0138", "\u04a0", "\u04c3", "\u051e"],
    l: ["\u0139"],
    m: ["\u028d", "\u04cd", "\u04ce", "\u0520", "\u0521", "\u0d69"],
    n: ["\u00d1", "\u014b", "\u019d", "\u0376", "\u03a0", "\u048a"],
    o: ["\u00d8", "\u00f5", "\u00f8", "\u01fe", "\u0298", "\u047a", "\u05dd", "\u06dd", "\u0e4f"],
    p: ["\u01f7", "\u048e"],
    q: ["\u09cd"],
    r: ["\u00ae", "\u01a6", "\u0210", "\u024c", "\u0280", "\u042f"],
    s: ["\u00a7", "\u03de", "\u03df", "\u03e8"],
    t: ["\u0141", "\u0166", "\u0373"],
    u: ["\u01b1", "\u054d"],
    v: ["\u05d8"],
    w: ["\u0428", "\u0460", "\u047c", "\u0d70"],
    x: ["\u04b2", "\u04fe", "\u04fc", "\u04fd"],
    y: ["\u00a5", "\u04b0", "\u04cb"],
    z: ["\u01b5", "\u0240"]
  }
  text.forEach(function(c){
    c = c.toLowerCase();
    var chars = trap[c] || [" "];
    var rand = Math.floor(Math.random() * chars.length);
    if (typeof trap[c] !== "undefined") {
      result += trap[c][rand];
    } else {
      result += c;
    }
  });
  return result;

}
};
BundleModuleCode['doc/custom/zalgo']=function (module,exports){
// please no
module['exports'] = function zalgo(text, options) {
  text = text || "   he is here   ";
  var soul = {
    "up" : [
      '̍', '̎', '̄', '̅',
      '̿', '̑', '̆', '̐',
      '͒', '͗', '͑', '̇',
      '̈', '̊', '͂', '̓',
      '̈', '͊', '͋', '͌',
      '̃', '̂', '̌', '͐',
      '̀', '́', '̋', '̏',
      '̒', '̓', '̔', '̽',
      '̉', 'ͣ', 'ͤ', 'ͥ',
      'ͦ', 'ͧ', 'ͨ', 'ͩ',
      'ͪ', 'ͫ', 'ͬ', 'ͭ',
      'ͮ', 'ͯ', '̾', '͛',
      '͆', '̚'
    ],
    "down" : [
      '̖', '̗', '̘', '̙',
      '̜', '̝', '̞', '̟',
      '̠', '̤', '̥', '̦',
      '̩', '̪', '̫', '̬',
      '̭', '̮', '̯', '̰',
      '̱', '̲', '̳', '̹',
      '̺', '̻', '̼', 'ͅ',
      '͇', '͈', '͉', '͍',
      '͎', '͓', '͔', '͕',
      '͖', '͙', '͚', '̣'
    ],
    "mid" : [
      '̕', '̛', '̀', '́',
      '͘', '̡', '̢', '̧',
      '̨', '̴', '̵', '̶',
      '͜', '͝', '͞',
      '͟', '͠', '͢', '̸',
      '̷', '͡', ' ҉'
    ]
  },
  all = [].concat(soul.up, soul.down, soul.mid),
  zalgo = {};

  function randomNumber(range) {
    var r = Math.floor(Math.random() * range);
    return r;
  }

  function is_char(character) {
    var bool = false;
    all.filter(function (i) {
      bool = (i === character);
    });
    return bool;
  }
  

  function heComes(text, options) {
    var result = '', counts, l;
    options = options || {};
    options["up"] =   typeof options["up"]   !== 'undefined' ? options["up"]   : true;
    options["mid"] =  typeof options["mid"]  !== 'undefined' ? options["mid"]  : true;
    options["down"] = typeof options["down"] !== 'undefined' ? options["down"] : true;
    options["size"] = typeof options["size"] !== 'undefined' ? options["size"] : "maxi";
    text = text.split('');
    for (l in text) {
      if (is_char(l)) {
        continue;
      }
      result = result + text[l];
      counts = {"up" : 0, "down" : 0, "mid" : 0};
      switch (options.size) {
      case 'mini':
        counts.up = randomNumber(8);
        counts.mid = randomNumber(2);
        counts.down = randomNumber(8);
        break;
      case 'maxi':
        counts.up = randomNumber(16) + 3;
        counts.mid = randomNumber(4) + 1;
        counts.down = randomNumber(64) + 3;
        break;
      default:
        counts.up = randomNumber(8) + 1;
        counts.mid = randomNumber(6) / 2;
        counts.down = randomNumber(8) + 1;
        break;
      }

      var arr = ["up", "mid", "down"];
      for (var d in arr) {
        var index = arr[d];
        for (var i = 0 ; i <= counts[index]; i++) {
          if (options[index]) {
            result = result + soul[index][randomNumber(soul[index].length)];
          }
        }
      }
    }
    return result;
  }
  // don't summon him
  return heComes(text, options);
}
};
BundleModuleCode['doc/list']=function (module,exports){
var NL='\n',SP=' ';
function spaces(n) {var s=''; while(n) s+=SP,n--; return s;}

/** List Constructor
 *  typeof @options = {
 *    type:string=' '|'*'|'-'|'+'|'1'|'2'|..|'a'|'b'|..|'dl',
 *  }
 */
function List (options) {
  this.type = options.type|| '*';
  this.margin = options.margin || {left:0,right:0,top:0,bottom:0};
  this.width = options.width || 80;
  this.tab = options.tab || 2;
  this.tab2 = options.tab || 4;
}
/**
 * Inherit from Array. Each item of the list is one array element.
 */

List.prototype.__proto__ = Array.prototype;

/** List formatter
 *
 */
List.prototype.render
List.prototype.toString = function (){
  var i,self=this,ret='',line='',lines=[],label,
      tokens,
      textwidth=this.width-this.margin.left-this.margin.right-this.tab;
  for(i=0;i<this.margin.top;i++) lines.push([]);
  this.forEach(function (item,index) {
    label=self.type;
    line = spaces (self.margin.left);
    switch (label) {
      case '*':
      case '+':
      case '-':
        line += label+SP; break;
      case ' ': break;
      case '1': line += (index+1).toString()+'.'+SP; break;
      case '2': line += (index+2).toString()+'.'+SP; break;
      case '3': line += (index+3).toString()+'.'+SP; break;
      case '4': line += (index+4).toString()+'.'+SP; break;
      case '5': line += (index+5).toString()+'.'+SP; break;
      case '6': line += (index+6).toString()+'.'+SP; break;
      case '7': line += (index+7).toString()+'.'+SP; break;
      case '8': line += (index+8).toString()+'.'+SP; break;
      case '9': line += (index+9).toString()+'.'+SP; break;
      case 'dl':
        line += item.dt+NL; label=undefined; item=item.dd; break;
      default:
        break;
    }
    line += label?spaces(self.tab-label.length-1):spaces(self.tab);
    if (item.length < textwidth) {
      line += item;
      lines.push(line);
    } else {
      tokens=item.split(SP); // TODO: preserve nbsp?
      tokens.forEach(function (token) {
        if ((line.length+token.length+1)<textwidth)
          line += (token+SP);
        else {
          lines.push(line);
          line = spaces(self.margin.left+self.tab)+token+SP;
        }
      });
      lines.push(line);
    }
  });
  for(i=0;i<this.margin.bottom;i++) lines.push([]);
  return lines.join(NL);
}


module.exports = List;
};
BundleModuleCode['doc/cli-table']=function (module,exports){

/**
 * Module dependencies.
 */

var colors =  Require('doc/colors')
  , utils =   Require('doc/cli-utils')
  , repeat = utils.repeat
  , truncate = utils.truncate
  , pad = utils.pad;

/**
 * Table constructor
 *
 * @param {Object} options
 * @api public
 */

function Table (options){
  this.options = utils.options({
      chars: {
          'top': '─'
        , 'top-mid': '┬'
        , 'top-left': '┌'
        , 'top-right': '┐'
        , 'bottom': '─'
        , 'bottom-mid': '┴'
        , 'bottom-left': '└'
        , 'bottom-right': '┘'
        , 'left': '│'
        , 'left-mid': '├'
        , 'mid': '─'
        , 'mid-mid': '┼'
        , 'right': '│'
        , 'right-mid': '┤'
        , 'middle': '│'
      }
    , truncate: '…'
    , colWidths: []
    , colAligns: []
    , style: {
          'padding-left': 1
        , 'padding-right': 1
        , head: ['red']
        , border: ['grey']
        , compact : false
      }
    , head: []
  }, options);
};

/**
 * Inherit from Array.
 */

Table.prototype.__proto__ = Array.prototype;

/**
 * Width getter
 *
 * @return {Number} width
 * @api public
 */
/* Depricated

Table.prototype.__defineGetter__('width', function (){
  var str = this.toString().split("\n");
  if (str.length) return str[0].length;
  return 0;
});
*/
Object.defineProperty(Table.prototype,'width',{
  get: function() {
    var str = this.toString().split("\n");
    if (str.length) return str[0].length;
    return 0;
  }
});
/**
 * Render to a string.
 *
 * @return {String} table representation
 * @api public
 */

Table.prototype.render
Table.prototype.toString = function (){
  var ret = ''
    , options = this.options
    , style = options.style
    , head = options.head
    , chars = options.chars
    , truncater = options.truncate
      , colWidths = options.colWidths || new Array(this.head.length)
      , totalWidth = 0;

    if (!head.length && !this.length) return '';

    if (!colWidths.length){
      var all_rows = this.slice(0);
      if (head.length) { all_rows = all_rows.concat([head]) };

      all_rows.forEach(function(cells){
        // horizontal (arrays)
        if (typeof cells === 'object' && cells.length) {
          extractColumnWidths(cells);

        // vertical (objects)
        } else {
          var header_cell = Object.keys(cells)[0]
            , value_cell = cells[header_cell];

          colWidths[0] = Math.max(colWidths[0] || 0, get_width(header_cell) || 0);

          // cross (objects w/ array values)
          if (typeof value_cell === 'object' && value_cell.length) {
            extractColumnWidths(value_cell, 1);
          } else {
            colWidths[1] = Math.max(colWidths[1] || 0, get_width(value_cell) || 0);
          }
        }
    });
  };

  totalWidth = (colWidths.length == 1 ? colWidths[0] : colWidths.reduce(
    function (a, b){
      return a + b
    })) + colWidths.length + 1;

  function extractColumnWidths(arr, offset) {
    var offset = offset || 0;
    arr.forEach(function(cell, i){
      colWidths[i + offset] = Math.max(colWidths[i + offset] || 0, get_width(cell) || 0);
    });
  };

  function get_width(obj) {
    return typeof obj == 'object' && obj && obj.width != undefined
         ? obj.width
         : ((typeof obj == 'object' && obj !== null ? utils.strlen(obj.text) : utils.strlen(obj)) + (style['padding-left'] || 0) + (style['padding-right'] || 0))
  }

  // draws a line
  function line (line, left, right, intersection){
    var width = 0
      , line =
          left
        + repeat(line, totalWidth - 2)
        + right;

    colWidths.forEach(function (w, i){
      if (i == colWidths.length - 1) return;
      width += w + 1;
      line = line.substr(0, width) + intersection + line.substr(width + 1);
    });

    return applyStyles(options.style.border, line);
  };

  // draws the top line
  function lineTop (){
    var l = line(chars.top
               , chars['top-left'] || chars.top
               , chars['top-right'] ||  chars.top
               , chars['top-mid']);
    if (l)
      ret += l + "\n";
  };

  function generateRow (items, style) {
    var cells = []
      , max_height = 0;

    // prepare vertical and cross table data
    if (!Array.isArray(items) && typeof items === "object") {
      var key = Object.keys(items)[0]
        , value = items[key]
        , first_cell_head = true;

      if (Array.isArray(value)) {
        items = value;
        items.unshift(key);
      } else {
        items = [key, value];
      }
    }

    // transform array of item strings into structure of cells
    items.forEach(function (item, i) {
      var contents = (item == null ? '' : item).toString().split("\n").reduce(function (memo, l) {
        memo.push(string(l, i));
        return memo;
      }, [])

      var height = contents.length;
      if (height > max_height) { max_height = height };

      cells.push({ contents: contents , height: height });
    });

    // transform vertical cells into horizontal lines
    var lines = new Array(max_height);
    cells.forEach(function (cell, i) {
      cell.contents.forEach(function (line, j) {
        if (!lines[j]) { lines[j] = [] };
        if (style || (first_cell_head && i === 0 && options.style.head)) {
          line = applyStyles(options.style.head, line)
        }

        lines[j].push(line);
      });

      // populate empty lines in cell
      for (var j = cell.height, l = max_height; j < l; j++) {
        if (!lines[j]) { lines[j] = [] };
        lines[j].push(string('', i));
      }
    });
    var ret = "";
    lines.forEach(function (line, index) {
      if (ret.length > 0) {
        ret += "\n" + applyStyles(options.style.border, chars.left);
      }

      ret += line.join(applyStyles(options.style.border, chars.middle)) + applyStyles(options.style.border, chars.right);
    });

    return applyStyles(options.style.border, chars.left) + ret;
  };

  function applyStyles(styles, subject) {
    if (!subject)
      return '';
    styles.forEach(function(style) {
      subject = colors[style](subject);
    });
    return subject;
  };

  // renders a string, by padding it or truncating it
  function string (str, index){
    var str = String(typeof str == 'object' && str.text ? str.text : str)
      , length = utils.strlen(str)
      , width = colWidths[index]
          - (style['padding-left'] || 0)
          - (style['padding-right'] || 0)
      , align = options.colAligns[index] || 'left';

    return repeat(' ', style['padding-left'] || 0)
         + (length == width ? str :
             (length < width
              ? pad(str, ( width + (str.length - length) ), ' ', align == 'left' ? 'right' :
                  (align == 'middle' ? 'both' : 'left'))
              : (truncater ? truncate(str, width, truncater) : str))
           )
         + repeat(' ', style['padding-right'] || 0);
  };

  if (head.length){
    lineTop();

    ret += generateRow(head, style.head) + "\n"
  }

  if (this.length)
    this.forEach(function (cells, i){
      if (!head.length && i == 0)
        lineTop();
      else {
        if (!style.compact || i<(!!head.length) ?1:0 || cells.length == 0){
          var l = line(chars.mid
                     , chars['left-mid']
                     , chars['right-mid']
                     , chars['mid-mid']);
          if (l)
            ret += l + "\n"
        }
      }

      if (cells.hasOwnProperty("length") && !cells.length) {
        return
      } else {
        ret += generateRow(cells) + "\n";
      };
    });

  var l = line(chars.bottom
             , chars['bottom-left'] || chars.bottom
             , chars['bottom-right'] || chars.bottom
             , chars['bottom-mid']);
  if (l)
    ret += l;
  else
    // trim the last '\n' if we didn't add the bottom decoration
    ret = ret.slice(0, -1);

  return ret;
};

/**
 * Module exports.
 */

module.exports = Table;

module.exports.version = '0.0.1';
};
BundleModuleCode['doc/cli-utils']=function (module,exports){

/**
 * Repeats a string.
 *
 * @param {String} char(s)
 * @param {Number} number of times
 * @return {String} repeated string
 */

exports.repeat = function (str, times){
  return Array(times + 1).join(str);
};

/**
 * Pads a string
 *
 * @api public
 */

exports.pad = function (str, len, pad, dir) {
  if (len + 1 >= str.length)
    switch (dir){
      case 'left':
        str = Array(len + 1 - str.length).join(pad) + str;
        break;

      case 'both':
        var right = Math.ceil((padlen = len - str.length) / 2);
        var left = padlen - right;
        str = Array(left + 1).join(pad) + str + Array(right + 1).join(pad);
        break;

      default:
        str = str + Array(len + 1 - str.length).join(pad);
    };

  return str;
};

/**
 * Truncates a string
 *
 * @api public
 */

exports.truncate = function (str, length, chr){
  chr = chr || '…';
  return str.length >= length ? str.substr(0, length - chr.length) + chr : str;
};

/**
 * Copies and merges options with defaults.
 *
 * @param {Object} defaults
 * @param {Object} supplied options
 * @return {Object} new (merged) object
 */

function options(defaults, opts) {
  for (var p in opts) {
    if (opts[p] && opts[p].constructor && opts[p].constructor === Object) {
      defaults[p] = defaults[p] || {};
      options(defaults[p], opts[p]);
    } else {
      defaults[p] = opts[p];
    }
  }
  return defaults;
};
exports.options = options;

//
// For consideration of terminal "color" programs like colors.js,
// which can add ANSI escape color codes to strings,
// we destyle the ANSI color escape codes for padding calculations.
//
// see: http://en.wikipedia.org/wiki/ANSI_escape_code
//
exports.strlen = function(str){
  var code = /\u001b\[(?:\d*;){0,5}\d*m/g;
  var stripped = ("" + (str != null ? str : '')).replace(code,'');
  var split = stripped.split("\n");
  return split.reduce(function (memo, s) { return (s.length > memo) ? s.length : memo }, 0);
}
};
BundleModuleCode['doc/table']=function (module,exports){
var doc = Require('doc/doc');
var com = Require('com/compat');

function Table (data,options) {
  var totalWidth=(process.stdout.columns)-2;
  if (com.obj.isArray(options)) options={head:options};
  options=options||{};
  var head=options.head,table;
  if (com.obj.isMatrix(data)) {
  } else if (com.obj.isArray(data) && com.obj.isObject(data[0])) {
    options.head=true;
    head=Object.keys(data[0]);
    data=data.map(function (row) {
      return head.map(function (key) { return row[key] })
    });
  } else return new Error('Table: Inavlid data');
  if (!options.colWidths) {
    totalWidth-= ((head||data[0]).length-1);
    options.colWidths=(head||data[0]).map(function (x,i) {
      return Math.max(4,Math.floor(totalWidth/(head||data[0]).length));
    });
  }
  if (head) 
    table = new doc.Table({
      head : head,
      colWidths :options.colWidths,
    });
  else 
    table = new doc.Table({
      colWidths : options.colWidths,
    });
  data.forEach(function (row,rowi) {
    table.push(row);
  });
  print(table.toString());
}

module.exports = Table;
};
BundleModuleCode['com/readline']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Joyent, Inc. and other Node contributors, Stefan Bosse
 **    $INITIAL:     (C) 2006-2018 bLAB
 **    $VERSION:     1.2.5
 **
 **    $INFO:
 **

// Inspiration for this code comes from Salvatore Sanfilippo's linenoise.
// https://github.com/antirez/linenoise
// Reference:
// * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
// * http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html

*/

var kHistorySize = 30;

var util = Require('util');
var Buffer = Require('buffer').Buffer;
var inherits = Require('util').inherits;
var EventEmitter = Require('events').EventEmitter;
var StringDecoder = Require('string_decoder').StringDecoder;

// listenerCount isn't in node 0.10, so here's a basic polyfill
EventEmitter._listenerCount = EventEmitter._listenerCount || function (ee, event) {
  var listeners = ee && ee._events && ee._events[event]
  if (Array.isArray(listeners)) {
    return listeners.length
  } else if (typeof listeners === 'function') {
    return 1
  } else {
    return 0
  }
}

exports.createInterface = function(input, output, completer, terminal) {
  var rl;
  if (arguments.length === 1) {
    rl = new Interface(input);
  } else {
    rl = new Interface(input, output, completer, terminal);
  }
  return rl;
};


function Interface(input, output, completer, terminal) {
  if (!(this instanceof Interface)) {
    return new Interface(input, output, completer, terminal);
  }

  this._sawReturn = false;

  EventEmitter.call(this);

  if (arguments.length === 1) {
    // an options object was given
    output = input.output;
    completer = input.completer;
    terminal = input.terminal;
    input = input.input;
  }

  completer = completer || function() { return []; };

  if (!util.isFunction(completer)) {
    throw new TypeError('Argument \'completer\' must be a function');
  }

  // backwards compat; check the isTTY prop of the output stream
  //  when `terminal` was not specified
  if (util.isUndefined(terminal) && !util.isNullOrUndefined(output)) {
    terminal = !!output.isTTY;
  }

  var self = this;

  this.output = output;
  this.input = input;

  // Check arity, 2 - for async, 1 for sync
  this.completer = completer.length === 2 ? completer : function(v, callback) {
    callback(null, completer(v));
  };

  this.setPrompt('> ');

  this.terminal = !!terminal;

  function ondata(data) {
    self._normalWrite(data);
  }

  function onend() {
    if (util.isString(self._line_buffer) && self._line_buffer.length > 0) {
      self.emit('line', self._line_buffer);
    }
    self.close();
  }

  function ontermend() {
    if (util.isString(self.line) && self.line.length > 0) {
      self.emit('line', self.line);
    }
    self.close();
  }

  function onkeypress(s, key) {
    self._ttyWrite(s, key);
  }

  function onresize() {
    self._refreshLine();
  }

  if (!this.terminal) {
    input.on('data', ondata);
    input.on('end', onend);
    self.once('close', function() {
      input.removeListener('data', ondata);
      input.removeListener('end', onend);
    });
    this._decoder = new StringDecoder('utf8');

  } else {

    exports.emitKeypressEvents(input);

    // input usually refers to stdin
    input.on('keypress', onkeypress);
    input.on('end', ontermend);

    // Current line
    this.line = '';

    this._setRawMode(true);
    this.terminal = true;

    // Cursor position on the line.
    this.cursor = 0;

    this.history = [];
    this.historyIndex = -1;

    if (!util.isNullOrUndefined(output))
      output.on('resize', onresize);

    self.once('close', function() {
      input.removeListener('keypress', onkeypress);
      input.removeListener('end', ontermend);
      if (!util.isNullOrUndefined(output)) {
        output.removeListener('resize', onresize);
      }
    });
  }

  input.resume();
}

inherits(Interface, EventEmitter);

Object.defineProperty(Interface.prototype,'columns',{
  get: function() {
    var columns = Infinity;
    if (this.output && this.output.columns)
      columns = this.output.columns;
    return columns;
  }
});
/* Depricated
Interface.prototype.__defineGetter__('columns', function() {
  var columns = Infinity;
  if (this.output && this.output.columns)
    columns = this.output.columns;
  return columns;
});
*/

Interface.prototype.setPrompt = function(prompt) {
  this._prompt = prompt;
};


Interface.prototype._setRawMode = function(mode) {
  if (util.isFunction(this.input.setRawMode)) {
    return this.input.setRawMode(mode);
  }
};


Interface.prototype.prompt = function(preserveCursor) {
  if (this.paused) this.resume();
  if (this.terminal) {
    if (!preserveCursor) this.cursor = 0;
    this._refreshLine();
  } else {
    this._writeToOutput(this._prompt);
  }
};


Interface.prototype.question = function(query, cb) {
  if (util.isFunction(cb)) {
    if (this._questionCallback) {
      this.prompt();
    } else {
      this._oldPrompt = this._prompt;
      this.setPrompt(query);
      this._questionCallback = cb;
      this.prompt();
    }
  }
};


Interface.prototype._onLine = function(line) {
  if (this._questionCallback) {
    var cb = this._questionCallback;
    this._questionCallback = null;
    this.setPrompt(this._oldPrompt);
    cb(line);
  } else {
    this.emit('line', line);
  }
};

Interface.prototype._writeToOutput = function _writeToOutput(stringToWrite) {
  if (!util.isString(stringToWrite))
    throw new TypeError('stringToWrite must be a string');

  if (!util.isNullOrUndefined(this.output))
    this.output.write(stringToWrite);
};

Interface.prototype._addHistory = function() {
  if (this.line.length === 0) return '';

  if (this.history.length === 0 || this.history[0] !== this.line) {
    this.history.unshift(this.line);

    // Only store so many
    if (this.history.length > kHistorySize) this.history.pop();
  }

  this.historyIndex = -1;
  return this.history[0];
};


Interface.prototype._refreshLine = function() {
  // line length
  var line = this._prompt + this.line;
  var dispPos = this._getDisplayPos(line);
  var lineCols = dispPos.cols;
  var lineRows = dispPos.rows;

  // cursor position
  var cursorPos = this._getCursorPos();

  // first move to the bottom of the current line, based on cursor pos
  var prevRows = this.prevRows || 0;
  if (prevRows > 0) {
    exports.moveCursor(this.output, 0, -prevRows);
  }

  // Cursor to left edge.
  exports.cursorTo(this.output, 0);
  // erase data
  exports.clearScreenDown(this.output);

  // Write the prompt and the current buffer content.
  this._writeToOutput(line);

  // Force terminal to allocate a new line
  if (lineCols === 0) {
    this._writeToOutput(' ');
  }

  // Move cursor to original position.
  exports.cursorTo(this.output, cursorPos.cols);

  var diff = lineRows - cursorPos.rows;
  if (diff > 0) {
    exports.moveCursor(this.output, 0, -diff);
  }

  this.prevRows = cursorPos.rows;
};


Interface.prototype.close = function() {
  if (this.closed) return;
  this.pause();
  if (this.terminal) {
    this._setRawMode(false);
  }
  this.closed = true;
  this.emit('close');
};


Interface.prototype.pause = function() {
  if (this.paused) return;
  this.input.pause();
  this.paused = true;
  this.emit('pause');
  return this;
};


Interface.prototype.resume = function() {
  if (!this.paused) return;
  this.input.resume();
  this.paused = false;
  this.emit('resume');
  return this;
};


Interface.prototype.write = function(d, key) {
  if (this.paused) this.resume();
  this.terminal ? this._ttyWrite(d, key) : this._normalWrite(d);
};

// \r\n, \n, or \r followed by something other than \n
var lineEnding = /\r?\n|\r(?!\n)/;
Interface.prototype._normalWrite = function(b) {
  if (util.isUndefined(b)) {
    return;
  }
  var string = this._decoder.write(b);
  if (this._sawReturn) {
    string = string.replace(/^\n/, '');
    this._sawReturn = false;
  }

  // Run test() on the new string chunk, not on the entire line buffer.
  var newPartContainsEnding = lineEnding.test(string);

  if (this._line_buffer) {
    string = this._line_buffer + string;
    this._line_buffer = null;
  }
  if (newPartContainsEnding) {
    this._sawReturn = /\r$/.test(string);

    // got one or more newlines; process into "line" events
    var lines = string.split(lineEnding);
    // either '' or (concievably) the unfinished portion of the next line
    string = lines.pop();
    this._line_buffer = string;
    lines.forEach(function(line) {
      this._onLine(line);
    }, this);
  } else if (string) {
    // no newlines this time, save what we have for next time
    this._line_buffer = string;
  }
};

Interface.prototype._insertString = function(c) {
  //BUG: Problem when adding tabs with following content.
  //     Perhaps the bug is in _refreshLine(). Not sure.
  //     A hack would be to insert spaces instead of literal '\t'.
  if (this.cursor < this.line.length) {
    var beg = this.line.slice(0, this.cursor);
    var end = this.line.slice(this.cursor, this.line.length);
    this.line = beg + c + end;
    this.cursor += c.length;
    this._refreshLine();
  } else {
    this.line += c;
    this.cursor += c.length;

    if (this._getCursorPos().cols === 0) {
      this._refreshLine();
    } else {
      this._writeToOutput(c);
    }

    // a hack to get the line refreshed if it's needed
    this._moveCursor(0);
  }
};

Interface.prototype._tabComplete = function() {
  var self = this;

  self.pause();
  self.completer(self.line.slice(0, self.cursor), function(err, rv) {
    self.resume();

    if (err) {
      // XXX Log it somewhere?
      return;
    }

    var completions = rv[0],
        completeOn = rv[1];  // the text that was completed
    if (completions && completions.length) {
      // Apply/show completions.
      if (completions.length === 1) {
        self._insertString(completions[0].slice(completeOn.length));
      } else {
        self._writeToOutput('\r\n');
        var width = completions.reduce(function(a, b) {
          return a.length > b.length ? a : b;
        }).length + 2;  // 2 space padding
        var maxColumns = Math.floor(self.columns / width) || 1;
        var group = [], c;
        for (var i = 0, compLen = completions.length; i < compLen; i++) {
          c = completions[i];
          if (c === '') {
            handleGroup(self, group, width, maxColumns);
            group = [];
          } else {
            group.push(c);
          }
        }
        handleGroup(self, group, width, maxColumns);

        // If there is a common prefix to all matches, then apply that
        // portion.
        var f = completions.filter(function(e) { if (e) return e; });
        var prefix = commonPrefix(f);
        if (prefix.length > completeOn.length) {
          self._insertString(prefix.slice(completeOn.length));
        }

      }
      self._refreshLine();
    }
  });
};

// this = Interface instance
function handleGroup(self, group, width, maxColumns) {
  if (group.length == 0) {
    return;
  }
  var minRows = Math.ceil(group.length / maxColumns);
  for (var row = 0; row < minRows; row++) {
    for (var col = 0; col < maxColumns; col++) {
      var idx = row * maxColumns + col;
      if (idx >= group.length) {
        break;
      }
      var item = group[idx];
      self._writeToOutput(item);
      if (col < maxColumns - 1) {
        for (var s = 0, itemLen = item.length; s < width - itemLen;
             s++) {
          self._writeToOutput(' ');
        }
      }
    }
    self._writeToOutput('\r\n');
  }
  self._writeToOutput('\r\n');
}

function commonPrefix(strings) {
  if (!strings || strings.length == 0) {
    return '';
  }
  var sorted = strings.slice().sort();
  var min = sorted[0];
  var max = sorted[sorted.length - 1];
  for (var i = 0, len = min.length; i < len; i++) {
    if (min[i] != max[i]) {
      return min.slice(0, i);
    }
  }
  return min;
}


Interface.prototype._wordLeft = function() {
  if (this.cursor > 0) {
    var leading = this.line.slice(0, this.cursor);
    var match = leading.match(/([^\w\s]+|\w+|)\s*$/);
    this._moveCursor(-match[0].length);
  }
};


Interface.prototype._wordRight = function() {
  if (this.cursor < this.line.length) {
    var trailing = this.line.slice(this.cursor);
    var match = trailing.match(/^(\s+|\W+|\w+)\s*/);
    this._moveCursor(match[0].length);
  }
};


Interface.prototype._deleteLeft = function() {
  if (this.cursor > 0 && this.line.length > 0) {
    this.line = this.line.slice(0, this.cursor - 1) +
                this.line.slice(this.cursor, this.line.length);

    this.cursor--;
    this._refreshLine();
  }
};


Interface.prototype._deleteRight = function() {
  this.line = this.line.slice(0, this.cursor) +
              this.line.slice(this.cursor + 1, this.line.length);
  this._refreshLine();
};


Interface.prototype._deleteWordLeft = function() {
  if (this.cursor > 0) {
    var leading = this.line.slice(0, this.cursor);
    var match = leading.match(/([^\w\s]+|\w+|)\s*$/);
    leading = leading.slice(0, leading.length - match[0].length);
    this.line = leading + this.line.slice(this.cursor, this.line.length);
    this.cursor = leading.length;
    this._refreshLine();
  }
};


Interface.prototype._deleteWordRight = function() {
  if (this.cursor < this.line.length) {
    var trailing = this.line.slice(this.cursor);
    var match = trailing.match(/^(\s+|\W+|\w+)\s*/);
    this.line = this.line.slice(0, this.cursor) +
                trailing.slice(match[0].length);
    this._refreshLine();
  }
};


Interface.prototype._deleteLineLeft = function() {
  this.line = this.line.slice(this.cursor);
  this.cursor = 0;
  this._refreshLine();
};


Interface.prototype._deleteLineRight = function() {
  this.line = this.line.slice(0, this.cursor);
  this._refreshLine();
};


Interface.prototype.clearLine = function() {
  this._moveCursor(+Infinity);
  this._writeToOutput('\r\n');
  this.line = '';
  this.cursor = 0;
  this.prevRows = 0;
};

// Get current input line content
Interface.prototype.getLine = function() {
  return this.line;  
};


// Insert a message before actual prompt input line
Interface.prototype.insertOutput = function(msg) {
  this._moveCursor(+Infinity);
  this._writeToOutput('\r');
  this._writeToOutput(msg+'\n');
  this._writeToOutput(this._prompt+this.line);
  
};

Interface.prototype._line = function() {
  var line = this._addHistory();
  this.clearLine();
  this._onLine(line);
};


Interface.prototype._historyNext = function() {
  if (this.historyIndex > 0) {
    this.historyIndex--;
    this.line = this.history[this.historyIndex];
    this.cursor = this.line.length; // set cursor to end of line.
    this._refreshLine();

  } else if (this.historyIndex === 0) {
    this.historyIndex = -1;
    this.cursor = 0;
    this.line = '';
    this._refreshLine();
  }
};


Interface.prototype._historyPrev = function() {
  if (this.historyIndex + 1 < this.history.length) {
    this.historyIndex++;
    this.line = this.history[this.historyIndex];
    this.cursor = this.line.length; // set cursor to end of line.

    this._refreshLine();
  }
};


// Returns the last character's display position of the given string
Interface.prototype._getDisplayPos = function(str) {
  var offset = 0;
  var col = this.columns;
  var row = 0;
  var code;
  str = stripVTControlCharacters(str);
  for (var i = 0, len = str.length; i < len; i++) {
    code = codePointAt(str, i);
    if (code >= 0x10000) { // surrogates
      i++;
    }
    if (code === 0x0a) { // new line \n
      offset = 0;
      row += 1;
      continue;
    }
    if (isFullWidthCodePoint(code)) {
      if ((offset + 1) % col === 0) {
        offset++;
      }
      offset += 2;
    } else {
      offset++;
    }
  }
  var cols = offset % col;
  var rows = row + (offset - cols) / col;
  return {cols: cols, rows: rows};
};


// Returns current cursor's position and line
Interface.prototype._getCursorPos = function() {
  var columns = this.columns;
  var strBeforeCursor = this._prompt + this.line.substring(0, this.cursor);
  var dispPos = this._getDisplayPos(stripVTControlCharacters(strBeforeCursor));
  var cols = dispPos.cols;
  var rows = dispPos.rows;
  // If the cursor is on a full-width character which steps over the line,
  // move the cursor to the beginning of the next line.
  if (cols + 1 === columns &&
      this.cursor < this.line.length &&
      isFullWidthCodePoint(codePointAt(this.line, this.cursor))) {
    rows++;
    cols = 0;
  }
  return {cols: cols, rows: rows};
};


// This function moves cursor dx places to the right
// (-dx for left) and refreshes the line if it is needed
Interface.prototype._moveCursor = function(dx) {
  var oldcursor = this.cursor;
  var oldPos = this._getCursorPos();
  this.cursor += dx;

  // bounds check
  if (this.cursor < 0) this.cursor = 0;
  else if (this.cursor > this.line.length) this.cursor = this.line.length;

  var newPos = this._getCursorPos();

  // check if cursors are in the same line
  if (oldPos.rows === newPos.rows) {
    var diffCursor = this.cursor - oldcursor;
    var diffWidth;
    if (diffCursor < 0) {
      diffWidth = -getStringWidth(
          this.line.substring(this.cursor, oldcursor)
          );
    } else if (diffCursor > 0) {
      diffWidth = getStringWidth(
          this.line.substring(this.cursor, oldcursor)
          );
    }
    exports.moveCursor(this.output, diffWidth, 0);
    this.prevRows = newPos.rows;
  } else {
    this._refreshLine();
  }
};


// handle a write from the tty
Interface.prototype._ttyWrite = function(s, key) {
  key = key || {};

  // Ignore escape key - Fixes #2876
  if (key.name == 'escape') return;

  if (key.ctrl && key.shift) {
    /* Control and shift pressed */
    switch (key.name) {
      case 'backspace':
        this._deleteLineLeft();
        break;

      case 'delete':
        this._deleteLineRight();
        break;
    }

  } else if (key.ctrl) {
    /* Control key pressed */

    switch (key.name) {
      case 'c':
        if (EventEmitter.listenerCount(this, 'SIGINT') > 0) {
          this.emit('SIGINT');
        } else {
          // This readline instance is finished
          this.close();
        }
        break;

      case 'h': // delete left
        this._deleteLeft();
        break;

      case 'd': // delete right or EOF
        if (this.cursor === 0 && this.line.length === 0) {
          // This readline instance is finished
          this.close();
        } else if (this.cursor < this.line.length) {
          this._deleteRight();
        }
        break;

      case 'u': // delete the whole line
        this.cursor = 0;
        this.line = '';
        this._refreshLine();
        break;

      case 'k': // delete from current to end of line
        this._deleteLineRight();
        break;

      case 'a': // go to the start of the line
        this._moveCursor(-Infinity);
        break;

      case 'e': // go to the end of the line
        this._moveCursor(+Infinity);
        break;

      case 'b': // back one character
        this._moveCursor(-1);
        break;

      case 'f': // forward one character
        this._moveCursor(+1);
        break;

      case 'l': // clear the whole screen
        exports.cursorTo(this.output, 0, 0);
        exports.clearScreenDown(this.output);
        this._refreshLine();
        break;

      case 'n': // next history item
        this._historyNext();
        break;

      case 'p': // previous history item
        this._historyPrev();
        break;

      case 'z':
        if (process.platform == 'win32') break;
        if (EventEmitter.listenerCount(this, 'SIGTSTP') > 0) {
          this.emit('SIGTSTP');
        } else {
          process.once('SIGCONT', (function(self) {
            return function() {
              // Don't raise events if stream has already been abandoned.
              if (!self.paused) {
                // Stream must be paused and resumed after SIGCONT to catch
                // SIGINT, SIGTSTP, and EOF.
                self.pause();
                self.emit('SIGCONT');
              }
              // explicitly re-enable "raw mode" and move the cursor to
              // the correct position.
              // See https://github.com/joyent/node/issues/3295.
              self._setRawMode(true);
              self._refreshLine();
            };
          })(this));
          this._setRawMode(false);
          process.kill(process.pid, 'SIGTSTP');
        }
        break;

      case 'w': // delete backwards to a word boundary
      case 'backspace':
        this._deleteWordLeft();
        break;

      case 'delete': // delete forward to a word boundary
        this._deleteWordRight();
        break;

      case 'left':
        this._wordLeft();
        break;

      case 'right':
        this._wordRight();
        break;
    }

  } else if (key.meta) {
    /* Meta key pressed */

    switch (key.name) {
      case 'b': // backward word
        this._wordLeft();
        break;

      case 'f': // forward word
        this._wordRight();
        break;

      case 'd': // delete forward word
      case 'delete':
        this._deleteWordRight();
        break;

      case 'backspace': // delete backwards to a word boundary
        this._deleteWordLeft();
        break;
    }

  } else {
    /* No modifier keys used */

    // \r bookkeeping is only relevant if a \n comes right after.
    if (this._sawReturn && key.name !== 'enter')
      this._sawReturn = false;

    switch (key.name) {
      case 'return':  // carriage return, i.e. \r
        this._sawReturn = true;
        this._line();
        break;

      case 'enter':
        if (this._sawReturn)
          this._sawReturn = false;
        else
          this._line();
        break;

      case 'backspace':
        this._deleteLeft();
        break;

      case 'delete':
        this._deleteRight();
        break;

      case 'tab': // tab completion
        this._tabComplete();
        break;

      case 'left':
        this._moveCursor(-1);
        break;

      case 'right':
        this._moveCursor(+1);
        break;

      case 'home':
        this._moveCursor(-Infinity);
        break;

      case 'end':
        this._moveCursor(+Infinity);
        break;

      case 'up':
        this._historyPrev();
        break;

      case 'down':
        this._historyNext();
        break;

      default:
        if (util.isBuffer(s))
          s = s.toString('utf-8');

        if (s) {
          var lines = s.split(/\r\n|\n|\r/);
          for (var i = 0, len = lines.length; i < len; i++) {
            if (i > 0) {
              this._line();
            }
            this._insertString(lines[i]);
          }
        }
    }
  }
};


exports.Interface = Interface;



/**
 * accepts a readable Stream instance and makes it emit "keypress" events
 */

function emitKeypressEvents(stream) {
  if (stream._keypressDecoder) return;
  var StringDecoder = Require('string_decoder').StringDecoder; // lazy load
  stream._keypressDecoder = new StringDecoder('utf8');

  function onData(b) {
    if (EventEmitter.listenerCount(stream, 'keypress') > 0) {
      var r = stream._keypressDecoder.write(b);
      if (r) emitKeys(stream, r);
    } else {
      // Nobody's watching anyway
      stream.removeListener('data', onData);
      stream.on('newListener', onNewListener);
    }
  }

  function onNewListener(event) {
    if (event == 'keypress') {
      stream.on('data', onData);
      stream.removeListener('newListener', onNewListener);
    }
  }

  if (EventEmitter.listenerCount(stream, 'keypress') > 0) {
    stream.on('data', onData);
  } else {
    stream.on('newListener', onNewListener);
  }
}
exports.emitKeypressEvents = emitKeypressEvents;

/*
  Some patterns seen in terminal key escape codes, derived from combos seen
  at http://www.midnight-commander.org/browser/lib/tty/key.c

  ESC letter
  ESC [ letter
  ESC [ modifier letter
  ESC [ 1 ; modifier letter
  ESC [ num char
  ESC [ num ; modifier char
  ESC O letter
  ESC O modifier letter
  ESC O 1 ; modifier letter
  ESC N letter
  ESC [ [ num ; modifier char
  ESC [ [ 1 ; modifier letter
  ESC ESC [ num char
  ESC ESC O letter

  - char is usually ~ but $ and ^ also happen with rxvt
  - modifier is 1 +
                (shift     * 1) +
                (left_alt  * 2) +
                (ctrl      * 4) +
                (right_alt * 8)
  - two leading ESCs apparently mean the same as one leading ESC
*/

// Regexes used for ansi escape code splitting
var metaKeyCodeReAnywhere = /(?:\x1b)([a-zA-Z0-9])/;
var metaKeyCodeRe = new RegExp('^' + metaKeyCodeReAnywhere.source + '$');
var functionKeyCodeReAnywhere = new RegExp('(?:\x1b+)(O|N|\\[|\\[\\[)(?:' + [
  '(\\d+)(?:;(\\d+))?([~^$])',
  '(?:M([@ #!a`])(.)(.))', // mouse
  '(?:1;)?(\\d+)?([a-zA-Z])'
].join('|') + ')');
var functionKeyCodeRe = new RegExp('^' + functionKeyCodeReAnywhere.source);
var escapeCodeReAnywhere = new RegExp([
  functionKeyCodeReAnywhere.source, metaKeyCodeReAnywhere.source, /\x1b./.source
].join('|'));

function emitKeys(stream, s) {
  if (util.isBuffer(s)) {
    if (s[0] > 127 && util.isUndefined(s[1])) {
      s[0] -= 128;
      s = '\x1b' + s.toString(stream.encoding || 'utf-8');
    } else {
      s = s.toString(stream.encoding || 'utf-8');
    }
  }

  var buffer = [];
  var match;
  while (match = escapeCodeReAnywhere.exec(s)) {
    buffer = buffer.concat(s.slice(0, match.index).split(''));
    buffer.push(match[0]);
    s = s.slice(match.index + match[0].length);
  }
  buffer = buffer.concat(s.split(''));

  buffer.forEach(function(s) {
    var ch,
        key = {
          sequence: s,
          name: undefined,
          ctrl: false,
          meta: false,
          shift: false
        },
        parts;

    if (s === '\r') {
      // carriage return
      key.name = 'return';

    } else if (s === '\n') {
      // enter, should have been called linefeed
      key.name = 'enter';

    } else if (s === '\t') {
      // tab
      key.name = 'tab';

    } else if (s === '\b' || s === '\x7f' ||
               s === '\x1b\x7f' || s === '\x1b\b') {
      // backspace or ctrl+h
      key.name = 'backspace';
      key.meta = (s.charAt(0) === '\x1b');

    } else if (s === '\x1b' || s === '\x1b\x1b') {
      // escape key
      key.name = 'escape';
      key.meta = (s.length === 2);

    } else if (s === ' ' || s === '\x1b ') {
      key.name = 'space';
      key.meta = (s.length === 2);

    } else if (s.length === 1 && s <= '\x1a') {
      // ctrl+letter
      key.name = String.fromCharCode(s.charCodeAt(0) + 'a'.charCodeAt(0) - 1);
      key.ctrl = true;

    } else if (s.length === 1 && s >= 'a' && s <= 'z') {
      // lowercase letter
      key.name = s;

    } else if (s.length === 1 && s >= 'A' && s <= 'Z') {
      // shift+letter
      key.name = s.toLowerCase();
      key.shift = true;

    } else if (parts = metaKeyCodeRe.exec(s)) {
      // meta+character key
      key.name = parts[1].toLowerCase();
      key.meta = true;
      key.shift = /^[A-Z]$/.test(parts[1]);

    } else if (parts = functionKeyCodeRe.exec(s)) {
      // ansi escape sequence

      // reassemble the key code leaving out leading \x1b's,
      // the modifier key bitflag and any meaningless "1;" sequence
      var code = (parts[1] || '') + (parts[2] || '') +
                 (parts[4] || '') + (parts[9] || ''),
          modifier = (parts[3] || parts[8] || 1) - 1;

      // Parse the key modifier
      key.ctrl = !!(modifier & 4);
      key.meta = !!(modifier & 10);
      key.shift = !!(modifier & 1);
      key.code = code;

      // Parse the key itself
      switch (code) {
        /* xterm/gnome ESC O letter */
        case 'OP': key.name = 'f1'; break;
        case 'OQ': key.name = 'f2'; break;
        case 'OR': key.name = 'f3'; break;
        case 'OS': key.name = 'f4'; break;

        /* xterm/rxvt ESC [ number ~ */
        case '[11~': key.name = 'f1'; break;
        case '[12~': key.name = 'f2'; break;
        case '[13~': key.name = 'f3'; break;
        case '[14~': key.name = 'f4'; break;

        /* from Cygwin and used in libuv */
        case '[[A': key.name = 'f1'; break;
        case '[[B': key.name = 'f2'; break;
        case '[[C': key.name = 'f3'; break;
        case '[[D': key.name = 'f4'; break;
        case '[[E': key.name = 'f5'; break;

        /* common */
        case '[15~': key.name = 'f5'; break;
        case '[17~': key.name = 'f6'; break;
        case '[18~': key.name = 'f7'; break;
        case '[19~': key.name = 'f8'; break;
        case '[20~': key.name = 'f9'; break;
        case '[21~': key.name = 'f10'; break;
        case '[23~': key.name = 'f11'; break;
        case '[24~': key.name = 'f12'; break;

        /* xterm ESC [ letter */
        case '[A': key.name = 'up'; break;
        case '[B': key.name = 'down'; break;
        case '[C': key.name = 'right'; break;
        case '[D': key.name = 'left'; break;
        case '[E': key.name = 'clear'; break;
        case '[F': key.name = 'end'; break;
        case '[H': key.name = 'home'; break;

        /* xterm/gnome ESC O letter */
        case 'OA': key.name = 'up'; break;
        case 'OB': key.name = 'down'; break;
        case 'OC': key.name = 'right'; break;
        case 'OD': key.name = 'left'; break;
        case 'OE': key.name = 'clear'; break;
        case 'OF': key.name = 'end'; break;
        case 'OH': key.name = 'home'; break;

        /* xterm/rxvt ESC [ number ~ */
        case '[1~': key.name = 'home'; break;
        case '[2~': key.name = 'insert'; break;
        case '[3~': key.name = 'delete'; break;
        case '[4~': key.name = 'end'; break;
        case '[5~': key.name = 'pageup'; break;
        case '[6~': key.name = 'pagedown'; break;

        /* putty */
        case '[[5~': key.name = 'pageup'; break;
        case '[[6~': key.name = 'pagedown'; break;

        /* rxvt */
        case '[7~': key.name = 'home'; break;
        case '[8~': key.name = 'end'; break;

        /* rxvt keys with modifiers */
        case '[a': key.name = 'up'; key.shift = true; break;
        case '[b': key.name = 'down'; key.shift = true; break;
        case '[c': key.name = 'right'; key.shift = true; break;
        case '[d': key.name = 'left'; key.shift = true; break;
        case '[e': key.name = 'clear'; key.shift = true; break;

        case '[2$': key.name = 'insert'; key.shift = true; break;
        case '[3$': key.name = 'delete'; key.shift = true; break;
        case '[5$': key.name = 'pageup'; key.shift = true; break;
        case '[6$': key.name = 'pagedown'; key.shift = true; break;
        case '[7$': key.name = 'home'; key.shift = true; break;
        case '[8$': key.name = 'end'; key.shift = true; break;

        case 'Oa': key.name = 'up'; key.ctrl = true; break;
        case 'Ob': key.name = 'down'; key.ctrl = true; break;
        case 'Oc': key.name = 'right'; key.ctrl = true; break;
        case 'Od': key.name = 'left'; key.ctrl = true; break;
        case 'Oe': key.name = 'clear'; key.ctrl = true; break;

        case '[2^': key.name = 'insert'; key.ctrl = true; break;
        case '[3^': key.name = 'delete'; key.ctrl = true; break;
        case '[5^': key.name = 'pageup'; key.ctrl = true; break;
        case '[6^': key.name = 'pagedown'; key.ctrl = true; break;
        case '[7^': key.name = 'home'; key.ctrl = true; break;
        case '[8^': key.name = 'end'; key.ctrl = true; break;

        /* misc. */
        case '[Z': key.name = 'tab'; key.shift = true; break;
        default: key.name = 'undefined'; break;

      }
    }

    // Don't emit a key if no name was found
    if (util.isUndefined(key.name)) {
      key = undefined;
    }

    if (s.length === 1) {
      ch = s;
    }

    if (key || ch) {
      stream.emit('keypress', ch, key);
    }
  });
}


/**
 * moves the cursor to the x and y coordinate on the given stream
 */

function cursorTo(stream, x, y) {
  if (util.isNullOrUndefined(stream))
    return;

  if (!util.isNumber(x) && !util.isNumber(y))
    return;

  if (!util.isNumber(x))
    throw new Error("Can't set cursor row without also setting it's column");

  if (!util.isNumber(y)) {
    stream.write('\x1b[' + (x + 1) + 'G');
  } else {
    stream.write('\x1b[' + (y + 1) + ';' + (x + 1) + 'H');
  }
}
exports.cursorTo = cursorTo;


/**
 * moves the cursor relative to its current location
 */

function moveCursor(stream, dx, dy) {
  if (util.isNullOrUndefined(stream))
    return;

  if (dx < 0) {
    stream.write('\x1b[' + (-dx) + 'D');
  } else if (dx > 0) {
    stream.write('\x1b[' + dx + 'C');
  }

  if (dy < 0) {
    stream.write('\x1b[' + (-dy) + 'A');
  } else if (dy > 0) {
    stream.write('\x1b[' + dy + 'B');
  }
}
exports.moveCursor = moveCursor;


/**
 * clears the current line the cursor is on:
 *   -1 for left of the cursor
 *   +1 for right of the cursor
 *    0 for the entire line
 */

function clearLine(stream, dir) {
  if (util.isNullOrUndefined(stream))
    return;

  if (dir < 0) {
    // to the beginning
    stream.write('\x1b[1K');
  } else if (dir > 0) {
    // to the end
    stream.write('\x1b[0K');
  } else {
    // entire line
    stream.write('\x1b[2K');
  }
}
exports.clearLine = clearLine;


/**
 * clears the screen from the current position of the cursor down
 */

function clearScreenDown(stream) {
  if (util.isNullOrUndefined(stream))
    return;

  stream.write('\x1b[0J');
}
exports.clearScreenDown = clearScreenDown;


/**
 * Returns the number of columns required to display the given string.
 */

function getStringWidth(str) {
  var width = 0;
  str = stripVTControlCharacters(str);
  for (var i = 0, len = str.length; i < len; i++) {
    var code = codePointAt(str, i);
    if (code >= 0x10000) { // surrogates
      i++;
    }
    if (isFullWidthCodePoint(code)) {
      width += 2;
    } else {
      width++;
    }
  }
  return width;
}
exports.getStringWidth = getStringWidth;


/**
 * Returns true if the character represented by a given
 * Unicode code point is full-width. Otherwise returns false.
 */

function isFullWidthCodePoint(code) {
  if (isNaN(code)) {
    return false;
  }

  // Code points are derived from:
  // http://www.unicode.org/Public/UNIDATA/EastAsianWidth.txt
  if (code >= 0x1100 && (
      code <= 0x115f ||  // Hangul Jamo
      0x2329 === code || // LEFT-POINTING ANGLE BRACKET
      0x232a === code || // RIGHT-POINTING ANGLE BRACKET
      // CJK Radicals Supplement .. Enclosed CJK Letters and Months
      (0x2e80 <= code && code <= 0x3247 && code !== 0x303f) ||
      // Enclosed CJK Letters and Months .. CJK Unified Ideographs Extension A
      0x3250 <= code && code <= 0x4dbf ||
      // CJK Unified Ideographs .. Yi Radicals
      0x4e00 <= code && code <= 0xa4c6 ||
      // Hangul Jamo Extended-A
      0xa960 <= code && code <= 0xa97c ||
      // Hangul Syllables
      0xac00 <= code && code <= 0xd7a3 ||
      // CJK Compatibility Ideographs
      0xf900 <= code && code <= 0xfaff ||
      // Vertical Forms
      0xfe10 <= code && code <= 0xfe19 ||
      // CJK Compatibility Forms .. Small Form Variants
      0xfe30 <= code && code <= 0xfe6b ||
      // Halfwidth and Fullwidth Forms
      0xff01 <= code && code <= 0xff60 ||
      0xffe0 <= code && code <= 0xffe6 ||
      // Kana Supplement
      0x1b000 <= code && code <= 0x1b001 ||
      // Enclosed Ideographic Supplement
      0x1f200 <= code && code <= 0x1f251 ||
      // CJK Unified Ideographs Extension B .. Tertiary Ideographic Plane
      0x20000 <= code && code <= 0x3fffd)) {
    return true;
  }
  return false;
}
exports.isFullWidthCodePoint = isFullWidthCodePoint;


/**
 * Returns the Unicode code point for the character at the
 * given index in the given string. Similar to String.charCodeAt(),
 * but this function handles surrogates (code point >= 0x10000).
 */

function codePointAt(str, index) {
  var code = str.charCodeAt(index);
  var low;
  if (0xd800 <= code && code <= 0xdbff) { // High surrogate
    low = str.charCodeAt(index + 1);
    if (!isNaN(low)) {
      code = 0x10000 + (code - 0xd800) * 0x400 + (low - 0xdc00);
    }
  }
  return code;
}
exports.codePointAt = codePointAt;


/**
 * Tries to remove all VT control characters. Use to estimate displayed
 * string width. May be buggy due to not running a real state machine
 */
function stripVTControlCharacters(str) {
  str = str.replace(new RegExp(functionKeyCodeReAnywhere.source, 'g'), '');
  return str.replace(new RegExp(metaKeyCodeReAnywhere.source, 'g'), '');
}
exports.stripVTControlCharacters = stripVTControlCharacters;

};
BundleModuleCode['term/readlineSync']=function (module,exports){
/*
 * readlineSync
 * https://github.com/anseki/readline-sync
 *
 * Copyright (c) 2018 anseki
 * Licensed under the MIT license.
 */

'use strict';

var
  IS_WIN = process.platform === 'win32',

  ALGORITHM_CIPHER = 'aes-256-cbc',
  ALGORITHM_HASH = 'sha256',
  DEFAULT_ERR_MSG = 'The current environment doesn\'t support interactive reading from TTY.',

  fs = require('fs'),
  TTY = process.binding('tty_wrap').TTY,
  childProc = require('child_process'),
  pathUtil = require('path'),

  defaultOptions = {
    /* eslint-disable key-spacing */
    prompt:             '> ',
    hideEchoBack:       false,
    mask:               '*',
    limit:              [],
    limitMessage:       'Input another, please.$<( [)limit(])>',
    defaultInput:       '',
    trueValue:          [],
    falseValue:         [],
    caseSensitive:      false,
    keepWhitespace:     false,
    encoding:           'utf8',
    bufferSize:         1024,
    print:              void 0,
    history:            true,
    cd:                 false,
    phContent:          void 0,
    preCheck:           void 0
    /* eslint-enable key-spacing */
  },

  fdR = 'none', fdW, ttyR, isRawMode = false,
  extHostPath, extHostArgs, tempdir, salt = 0,
  lastInput = '', inputHistory = [], rawInput,
  _DBG_useExt = false, _DBG_checkOptions = false, _DBG_checkMethod = false;

function getHostArgs(options) {
  // Send any text to crazy Windows shell safely.
  function encodeArg(arg) {
    return arg.replace(/[^\w\u0080-\uFFFF]/g, function(chr) {
      return '#' + chr.charCodeAt(0) + ';';
    });
  }

  return extHostArgs.concat((function(conf) {
    var args = [];
    Object.keys(conf).forEach(function(optionName) {
      if (conf[optionName] === 'boolean') {
        if (options[optionName]) { args.push('--' + optionName); }
      } else if (conf[optionName] === 'string') {
        if (options[optionName]) {
          args.push('--' + optionName, encodeArg(options[optionName]));
        }
      }
    });
    return args;
  })({
    /* eslint-disable key-spacing */
    display:        'string',
    displayOnly:    'boolean',
    keyIn:          'boolean',
    hideEchoBack:   'boolean',
    mask:           'string',
    limit:          'string',
    caseSensitive:  'boolean'
    /* eslint-enable key-spacing */
  }));
}

// piping via files (for Node.js v0.10-)
function _execFileSync(options, execOptions) {

  function getTempfile(name) {
    var filepath, suffix = '', fd;
    tempdir = tempdir || require('os').tmpdir();

    while (true) {
      filepath = pathUtil.join(tempdir, name + suffix);
      try {
        fd = fs.openSync(filepath, 'wx');
      } catch (e) {
        if (e.code === 'EEXIST') {
          suffix++;
          continue;
        } else {
          throw e;
        }
      }
      fs.closeSync(fd);
      break;
    }
    return filepath;
  }

  var hostArgs, shellPath, shellArgs, res = {}, exitCode, extMessage,
    pathStdout = getTempfile('readline-sync.stdout'),
    pathStderr = getTempfile('readline-sync.stderr'),
    pathExit = getTempfile('readline-sync.exit'),
    pathDone = getTempfile('readline-sync.done'),
    crypto = require('crypto'), shasum, decipher, password;

  shasum = crypto.createHash(ALGORITHM_HASH);
  shasum.update('' + process.pid + (salt++) + Math.random());
  password = shasum.digest('hex');
  decipher = crypto.createDecipher(ALGORITHM_CIPHER, password);

  hostArgs = getHostArgs(options);
  if (IS_WIN) {
    shellPath = process.env.ComSpec || 'cmd.exe';
    process.env.Q = '"'; // The quote (") that isn't escaped.
    // `()` for ignore space by echo
    shellArgs = ['/V:ON', '/S', '/C',
      '(%Q%' + shellPath + '%Q% /V:ON /S /C %Q%' + /* ESLint bug? */ // eslint-disable-line no-path-concat
        '%Q%' + extHostPath + '%Q%' +
          hostArgs.map(function(arg) { return ' %Q%' + arg + '%Q%'; }).join('') +
        ' & (echo !ERRORLEVEL!)>%Q%' + pathExit + '%Q%%Q%) 2>%Q%' + pathStderr + '%Q%' +
      ' |%Q%' + process.execPath + '%Q% %Q%' + __dirname + '\\encrypt.js%Q%' +
        ' %Q%' + ALGORITHM_CIPHER + '%Q% %Q%' + password + '%Q%' +
        ' >%Q%' + pathStdout + '%Q%' +
      ' & (echo 1)>%Q%' + pathDone + '%Q%'];
  } else {
    shellPath = '/bin/sh';
    shellArgs = ['-c',
      // Use `()`, not `{}` for `-c` (text param)
      '("' + extHostPath + '"' + /* ESLint bug? */ // eslint-disable-line no-path-concat
          hostArgs.map(function(arg) { return " '" + arg.replace(/'/g, "'\\''") + "'"; }).join('') +
        '; echo $?>"' + pathExit + '") 2>"' + pathStderr + '"' +
      ' |"' + process.execPath + '" "' + __dirname + '/encrypt.js"' +
        ' "' + ALGORITHM_CIPHER + '" "' + password + '"' +
        ' >"' + pathStdout + '"' +
      '; echo 1 >"' + pathDone + '"'];
  }
  if (_DBG_checkMethod) { _DBG_checkMethod('_execFileSync', hostArgs); }
  try {
    childProc.spawn(shellPath, shellArgs, execOptions);
  } catch (e) {
    res.error = new Error(e.message);
    res.error.method = '_execFileSync - spawn';
    res.error.program = shellPath;
    res.error.args = shellArgs;
  }

  while (fs.readFileSync(pathDone, {encoding: options.encoding}).trim() !== '1') {} // eslint-disable-line no-empty
  if ((exitCode =
      fs.readFileSync(pathExit, {encoding: options.encoding}).trim()) === '0') {
    res.input =
      decipher.update(fs.readFileSync(pathStdout, {encoding: 'binary'}),
        'hex', options.encoding) +
      decipher.final(options.encoding);
  } else {
    extMessage = fs.readFileSync(pathStderr, {encoding: options.encoding}).trim();
    res.error = new Error(DEFAULT_ERR_MSG + (extMessage ? '\n' + extMessage : ''));
    res.error.method = '_execFileSync';
    res.error.program = shellPath;
    res.error.args = shellArgs;
    res.error.extMessage = extMessage;
    res.error.exitCode = +exitCode;
  }

  fs.unlinkSync(pathStdout);
  fs.unlinkSync(pathStderr);
  fs.unlinkSync(pathExit);
  fs.unlinkSync(pathDone);

  return res;
}

function readlineExt(options) {
  var hostArgs, res = {}, extMessage,
    execOptions = {env: process.env, encoding: options.encoding};

  if (!extHostPath) {
    if (IS_WIN) {
      if (process.env.PSModulePath) { // Windows PowerShell
        extHostPath = 'powershell.exe';
        extHostArgs = ['-ExecutionPolicy', 'Bypass', '-File', __dirname + '\\read.ps1']; // eslint-disable-line no-path-concat
      } else {                        // Windows Script Host
        extHostPath = 'cscript.exe';
        extHostArgs = ['//nologo', __dirname + '\\read.cs.js']; // eslint-disable-line no-path-concat
      }
    } else {
      extHostPath = '/bin/sh';
      extHostArgs = [__dirname + '/read.sh']; // eslint-disable-line no-path-concat
    }
  }
  if (IS_WIN && !process.env.PSModulePath) { // Windows Script Host
    // ScriptPW (Win XP and Server2003) needs TTY stream as STDIN.
    // In this case, If STDIN isn't TTY, an error is thrown.
    execOptions.stdio = [process.stdin];
  }

  if (childProc.execFileSync) {
    hostArgs = getHostArgs(options);
    if (_DBG_checkMethod) { _DBG_checkMethod('execFileSync', hostArgs); }
    try {
      res.input = childProc.execFileSync(extHostPath, hostArgs, execOptions);
    } catch (e) { // non-zero exit code
      extMessage = e.stderr ? (e.stderr + '').trim() : '';
      res.error = new Error(DEFAULT_ERR_MSG + (extMessage ? '\n' + extMessage : ''));
      res.error.method = 'execFileSync';
      res.error.program = extHostPath;
      res.error.args = hostArgs;
      res.error.extMessage = extMessage;
      res.error.exitCode = e.status;
      res.error.code = e.code;
      res.error.signal = e.signal;
    }
  } else {
    res = _execFileSync(options, execOptions);
  }
  if (!res.error) {
    res.input = res.input.replace(/^\s*'|'\s*$/g, '');
    options.display = '';
  }

  return res;
}

/*
  display:            string
  displayOnly:        boolean
  keyIn:              boolean
  hideEchoBack:       boolean
  mask:               string
  limit:              string (pattern)
  caseSensitive:      boolean
  keepWhitespace:     boolean
  encoding, bufferSize, print
*/
function _readlineSync(options) {
  var input = '', displaySave = options.display,
    silent = !options.display &&
      options.keyIn && options.hideEchoBack && !options.mask;

  function tryExt() {
    var res = readlineExt(options);
    if (res.error) { throw res.error; }
    return res.input;
  }

  if (_DBG_checkOptions) { _DBG_checkOptions(options); }

  (function() { // open TTY
    var fsB, constants, verNum;

    function getFsB() {
      if (!fsB) {
        fsB = process.binding('fs'); // For raw device path
        constants = process.binding('constants');
      }
      return fsB;
    }

    if (typeof fdR !== 'string') { return; }
    fdR = null;

    if (IS_WIN) {
      // iojs-v2.3.2+ input stream can't read first line. (#18)
      // ** Don't get process.stdin before check! **
      // Fixed v5.1.0
      // Fixed v4.2.4
      // It regressed again in v5.6.0, it is fixed in v6.2.0.
      verNum = (function(ver) { // getVerNum
        var nums = ver.replace(/^\D+/, '').split('.');
        var verNum = 0;
        if ((nums[0] = +nums[0])) { verNum += nums[0] * 10000; }
        if ((nums[1] = +nums[1])) { verNum += nums[1] * 100; }
        if ((nums[2] = +nums[2])) { verNum += nums[2]; }
        return verNum;
      })(process.version);
      if (!(verNum >= 20302 && verNum < 40204 || verNum >= 50000 && verNum < 50100 || verNum >= 50600 && verNum < 60200) &&
          process.stdin.isTTY) {
        process.stdin.pause();
        fdR = process.stdin.fd;
        ttyR = process.stdin._handle;
      } else {
        try {
          // The stream by fs.openSync('\\\\.\\CON', 'r') can't switch to raw mode.
          // 'CONIN$' might fail on XP, 2000, 7 (x86).
          fdR = getFsB().open('CONIN$', constants.O_RDWR, parseInt('0666', 8));
          ttyR = new TTY(fdR, true);
        } catch (e) { /* ignore */ }
      }

      if (process.stdout.isTTY) {
        fdW = process.stdout.fd;
      } else {
        try {
          fdW = fs.openSync('\\\\.\\CON', 'w');
        } catch (e) { /* ignore */ }
        if (typeof fdW !== 'number') { // Retry
          try {
            fdW = getFsB().open('CONOUT$', constants.O_RDWR, parseInt('0666', 8));
          } catch (e) { /* ignore */ }
        }
      }

    } else {
      if (process.stdin.isTTY) {
        process.stdin.pause();
        try {
          fdR = fs.openSync('/dev/tty', 'r'); // device file, not process.stdin
          ttyR = process.stdin._handle;
        } catch (e) { /* ignore */ }
      } else {
        // Node.js v0.12 read() fails.
        try {
          fdR = fs.openSync('/dev/tty', 'r');
          ttyR = new TTY(fdR, false);
        } catch (e) { /* ignore */ }
      }

      if (process.stdout.isTTY) {
        fdW = process.stdout.fd;
      } else {
        try {
          fdW = fs.openSync('/dev/tty', 'w');
        } catch (e) { /* ignore */ }
      }
    }
  })();

  (function() { // try read
    var atEol, limit,
      isCooked = !options.hideEchoBack && !options.keyIn,
      buffer, reqSize, readSize, chunk, line;
    rawInput = '';

    // Node.js v0.10- returns an error if same mode is set.
    function setRawMode(mode) {
      if (mode === isRawMode) { return true; }
      if (ttyR.setRawMode(mode) !== 0) { return false; }
      isRawMode = mode;
      return true;
    }

    if (_DBG_useExt || !ttyR ||
        typeof fdW !== 'number' && (options.display || !isCooked)) {
      input = tryExt();
      return;
    }

    if (options.display) {
      fs.writeSync(fdW, options.display);
      options.display = '';
    }
    if (options.displayOnly) { return; }

    if (!setRawMode(!isCooked)) {
      input = tryExt();
      return;
    }

    reqSize = options.keyIn ? 1 : options.bufferSize;
    // Check `allocUnsafe` to make sure of the new API.
    buffer = Buffer.allocUnsafe && Buffer.alloc ? Buffer.alloc(reqSize) : new Buffer(reqSize);

    if (options.keyIn && options.limit) {
      limit = new RegExp('[^' + options.limit + ']',
        'g' + (options.caseSensitive ? '' : 'i'));
    }

    while (true) {
      readSize = 0;
      try {
        readSize = fs.readSync(fdR, buffer, 0, reqSize);
      } catch (e) {
        if (e.code !== 'EOF') {
          setRawMode(false);
          input += tryExt();
          return;
        }
      }
      if (readSize > 0) {
        chunk = buffer.toString(options.encoding, 0, readSize);
        rawInput += chunk;
      } else {
        chunk = '\n';
        rawInput += String.fromCharCode(0);
      }

      if (chunk && typeof (line = (chunk.match(/^(.*?)[\r\n]/) || [])[1]) === 'string') {
        chunk = line;
        atEol = true;
      }

      // other ctrl-chars
      // eslint-disable-next-line no-control-regex
      if (chunk) { chunk = chunk.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, ''); }
      if (chunk && limit) { chunk = chunk.replace(limit, ''); }

      if (chunk) {
        if (!isCooked) {
          if (!options.hideEchoBack) {
            fs.writeSync(fdW, chunk);
          } else if (options.mask) {
            fs.writeSync(fdW, (new Array(chunk.length + 1)).join(options.mask));
          }
        }
        input += chunk;
      }

      if (!options.keyIn && atEol ||
        options.keyIn && input.length >= reqSize) { break; }
    }

    if (!isCooked && !silent) { fs.writeSync(fdW, '\n'); }
    setRawMode(false);
  })();

  if (options.print && !silent) {
    options.print(displaySave + (options.displayOnly ? '' :
        (options.hideEchoBack ? (new Array(input.length + 1)).join(options.mask)
          : input) + '\n'), // must at least write '\n'
      options.encoding);
  }

  return options.displayOnly ? '' :
    (lastInput = options.keepWhitespace || options.keyIn ? input : input.trim());
}

function flattenArray(array, validator) {
  var flatArray = [];
  function _flattenArray(array) {
    if (array == null) {
      return;
    } else if (Array.isArray(array)) {
      array.forEach(_flattenArray);
    } else if (!validator || validator(array)) {
      flatArray.push(array);
    }
  }
  _flattenArray(array);
  return flatArray;
}

function escapePattern(pattern) {
  return pattern.replace(/[\x00-\x7f]/g, // eslint-disable-line no-control-regex
    function(s) { return '\\x' + ('00' + s.charCodeAt().toString(16)).substr(-2); });
}

// margeOptions(options1, options2 ... )
// margeOptions(true, options1, options2 ... )
//    arg1=true : Start from defaultOptions and pick elements of that.
function margeOptions() {
  var optionsList = Array.prototype.slice.call(arguments),
    optionNames, fromDefault;

  if (optionsList.length && typeof optionsList[0] === 'boolean') {
    fromDefault = optionsList.shift();
    if (fromDefault) {
      optionNames = Object.keys(defaultOptions);
      optionsList.unshift(defaultOptions);
    }
  }

  return optionsList.reduce(function(options, optionsPart) {
    if (optionsPart == null) { return options; }

    // ======== DEPRECATED ========
    if (optionsPart.hasOwnProperty('noEchoBack') &&
        !optionsPart.hasOwnProperty('hideEchoBack')) {
      optionsPart.hideEchoBack = optionsPart.noEchoBack;
      delete optionsPart.noEchoBack;
    }
    if (optionsPart.hasOwnProperty('noTrim') &&
        !optionsPart.hasOwnProperty('keepWhitespace')) {
      optionsPart.keepWhitespace = optionsPart.noTrim;
      delete optionsPart.noTrim;
    }
    // ======== /DEPRECATED ========

    if (!fromDefault) { optionNames = Object.keys(optionsPart); }
    optionNames.forEach(function(optionName) {
      var value;
      if (!optionsPart.hasOwnProperty(optionName)) { return; }
      value = optionsPart[optionName];
      switch (optionName) {
                           // _readlineSync <- *    * -> defaultOptions
        // ================ string
        case 'mask':                        // *    *
        case 'limitMessage':                //      *
        case 'defaultInput':                //      *
        case 'encoding':                    // *    *
          value = value != null ? value + '' : '';
          if (value && optionName !== 'limitMessage') { value = value.replace(/[\r\n]/g, ''); }
          options[optionName] = value;
          break;
        // ================ number(int)
        case 'bufferSize':                  // *    *
          if (!isNaN(value = parseInt(value, 10)) && typeof value === 'number') {
            options[optionName] = value; // limited updating (number is needed)
          }
          break;
        // ================ boolean
        case 'displayOnly':                 // *
        case 'keyIn':                       // *
        case 'hideEchoBack':                // *    *
        case 'caseSensitive':               // *    *
        case 'keepWhitespace':              // *    *
        case 'history':                     //      *
        case 'cd':                          //      *
          options[optionName] = !!value;
          break;
        // ================ array
        case 'limit':                       // *    *     to string for readlineExt
        case 'trueValue':                   //      *
        case 'falseValue':                  //      *
          options[optionName] = flattenArray(value, function(value) {
            var type = typeof value;
            return type === 'string' || type === 'number' ||
              type === 'function' || value instanceof RegExp;
          }).map(function(value) {
            return typeof value === 'string' ? value.replace(/[\r\n]/g, '') : value;
          });
          break;
        // ================ function
        case 'print':                       // *    *
        case 'phContent':                   //      *
        case 'preCheck':                    //      *
          options[optionName] = typeof value === 'function' ? value : void 0;
          break;
        // ================ other
        case 'prompt':                      //      *
        case 'display':                     // *
          options[optionName] = value != null ? value : '';
          break;
        // no default
      }
    });
    return options;
  }, {});
}

function isMatched(res, comps, caseSensitive) {
  return comps.some(function(comp) {
    var type = typeof comp;
    return type === 'string' ?
        (caseSensitive ? res === comp : res.toLowerCase() === comp.toLowerCase()) :
      type === 'number' ? parseFloat(res) === comp :
      type === 'function' ? comp(res) :
      comp instanceof RegExp ? comp.test(res) : false;
  });
}

function replaceHomePath(path, expand) {
  var homePath = pathUtil.normalize(
    IS_WIN ? (process.env.HOMEDRIVE || '') + (process.env.HOMEPATH || '') :
    process.env.HOME || '').replace(/[\/\\]+$/, '');
  path = pathUtil.normalize(path);
  return expand ? path.replace(/^~(?=\/|\\|$)/, homePath) :
    path.replace(new RegExp('^' + escapePattern(homePath) +
      '(?=\\/|\\\\|$)', IS_WIN ? 'i' : ''), '~');
}

function replacePlaceholder(text, generator) {
  var PTN_INNER = '(?:\\(([\\s\\S]*?)\\))?(\\w+|.-.)(?:\\(([\\s\\S]*?)\\))?',
    rePlaceholder = new RegExp('(\\$)?(\\$<' + PTN_INNER + '>)', 'g'),
    rePlaceholderCompat = new RegExp('(\\$)?(\\$\\{' + PTN_INNER + '\\})', 'g');

  function getPlaceholderText(s, escape, placeholder, pre, param, post) {
    var text;
    return escape || typeof (text = generator(param)) !== 'string' ? placeholder :
      text ? (pre || '') + text + (post || '') : '';
  }

  return text.replace(rePlaceholder, getPlaceholderText)
    .replace(rePlaceholderCompat, getPlaceholderText);
}

function array2charlist(array, caseSensitive, collectSymbols) {
  var values, group = [], groupClass = -1, charCode = 0, symbols = '', suppressed;
  function addGroup(groups, group) {
    if (group.length > 3) { // ellipsis
      groups.push(group[0] + '...' + group[group.length - 1]);
      suppressed = true;
    } else if (group.length) {
      groups = groups.concat(group);
    }
    return groups;
  }

  values = array.reduce(
      function(chars, value) { return chars.concat((value + '').split('')); }, [])
    .reduce(function(groups, curChar) {
      var curGroupClass, curCharCode;
      if (!caseSensitive) { curChar = curChar.toLowerCase(); }
      curGroupClass = /^\d$/.test(curChar) ? 1 :
        /^[A-Z]$/.test(curChar) ? 2 : /^[a-z]$/.test(curChar) ? 3 : 0;
      if (collectSymbols && curGroupClass === 0) {
        symbols += curChar;
      } else {
        curCharCode = curChar.charCodeAt(0);
        if (curGroupClass && curGroupClass === groupClass &&
            curCharCode === charCode + 1) {
          group.push(curChar);
        } else {
          groups = addGroup(groups, group);
          group = [curChar];
          groupClass = curGroupClass;
        }
        charCode = curCharCode;
      }
      return groups;
    }, []);
  values = addGroup(values, group); // last group
  if (symbols) { values.push(symbols); suppressed = true; }
  return {values: values, suppressed: suppressed};
}

function joinChunks(chunks, suppressed) {
  return chunks.join(chunks.length > 2 ? ', ' : suppressed ? ' / ' : '/');
}

function getPhContent(param, options) {
  var text, values, resCharlist = {}, arg;
  if (options.phContent) {
    text = options.phContent(param, options);
  }
  if (typeof text !== 'string') {
    switch (param) {
      case 'hideEchoBack':
      case 'mask':
      case 'defaultInput':
      case 'caseSensitive':
      case 'keepWhitespace':
      case 'encoding':
      case 'bufferSize':
      case 'history':
      case 'cd':
        text = !options.hasOwnProperty(param) ? '' :
          typeof options[param] === 'boolean' ? (options[param] ? 'on' : 'off') :
          options[param] + '';
        break;
      // case 'prompt':
      // case 'query':
      // case 'display':
      //   text = options.hasOwnProperty('displaySrc') ? options.displaySrc + '' : '';
      //   break;
      case 'limit':
      case 'trueValue':
      case 'falseValue':
        values = options[options.hasOwnProperty(param + 'Src') ? param + 'Src' : param];
        if (options.keyIn) { // suppress
          resCharlist = array2charlist(values, options.caseSensitive);
          values = resCharlist.values;
        } else {
          values = values.filter(function(value) {
            var type = typeof value;
            return type === 'string' || type === 'number';
          });
        }
        text = joinChunks(values, resCharlist.suppressed);
        break;
      case 'limitCount':
      case 'limitCountNotZero':
        text = options[options.hasOwnProperty('limitSrc') ?
          'limitSrc' : 'limit'].length;
        text = text || param !== 'limitCountNotZero' ? text + '' : '';
        break;
      case 'lastInput':
        text = lastInput;
        break;
      case 'cwd':
      case 'CWD':
      case 'cwdHome':
        text = process.cwd();
        if (param === 'CWD') {
          text = pathUtil.basename(text);
        } else if (param === 'cwdHome') {
          text = replaceHomePath(text);
        }
        break;
      case 'date':
      case 'time':
      case 'localeDate':
      case 'localeTime':
        text = (new Date())['to' +
          param.replace(/^./, function(str) { return str.toUpperCase(); }) +
          'String']();
        break;
      default: // with arg
        if (typeof (arg = (param.match(/^history_m(\d+)$/) || [])[1]) === 'string') {
          text = inputHistory[inputHistory.length - arg] || '';
        }
    }
  }
  return text;
}

function getPhCharlist(param) {
  var matches = /^(.)-(.)$/.exec(param), text = '', from, to, code, step;
  if (!matches) { return null; }
  from = matches[1].charCodeAt(0);
  to = matches[2].charCodeAt(0);
  step = from < to ? 1 : -1;
  for (code = from; code !== to + step; code += step) { text += String.fromCharCode(code); }
  return text;
}

// cmd "arg" " a r g " "" 'a"r"g' "a""rg" "arg
function parseCl(cl) {
  var reToken = new RegExp(/(\s*)(?:("|')(.*?)(?:\2|$)|(\S+))/g), matches,
    taken = '', args = [], part;
  cl = cl.trim();
  while ((matches = reToken.exec(cl))) {
    part = matches[3] || matches[4] || '';
    if (matches[1]) {
      args.push(taken);
      taken = '';
    }
    taken += part;
  }
  if (taken) { args.push(taken); }
  return args;
}

function toBool(res, options) {
  return (
    (options.trueValue.length &&
      isMatched(res, options.trueValue, options.caseSensitive)) ? true :
    (options.falseValue.length &&
      isMatched(res, options.falseValue, options.caseSensitive)) ? false : res);
}

function getValidLine(options) {
  var res, forceNext, limitMessage,
    matches, histInput, args, resCheck;

  function _getPhContent(param) { return getPhContent(param, options); }
  function addDisplay(text) { options.display += (/[^\r\n]$/.test(options.display) ? '\n' : '') + text; }

  options.limitSrc = options.limit;
  options.displaySrc = options.display;
  options.limit = ''; // for readlineExt
  options.display = replacePlaceholder(options.display + '', _getPhContent);

  while (true) {
    res = _readlineSync(options);
    forceNext = false;
    limitMessage = '';

    if (options.defaultInput && !res) { res = options.defaultInput; }

    if (options.history) {
      if ((matches = /^\s*\!(?:\!|-1)(:p)?\s*$/.exec(res))) { // `!!` `!-1` +`:p`
        histInput = inputHistory[0] || '';
        if (matches[1]) { // only display
          forceNext = true;
        } else { // replace input
          res = histInput;
        }
        // Show it even if it is empty (NL only).
        addDisplay(histInput + '\n');
        if (!forceNext) { // Loop may break
          options.displayOnly = true;
          _readlineSync(options);
          options.displayOnly = false;
        }
      } else if (res && res !== inputHistory[inputHistory.length - 1]) {
        inputHistory = [res];
      }
    }

    if (!forceNext && options.cd && res) {
      args = parseCl(res);
      switch (args[0].toLowerCase()) {
        case 'cd':
          if (args[1]) {
            try {
              process.chdir(replaceHomePath(args[1], true));
            } catch (e) {
              addDisplay(e + '');
            }
          }
          forceNext = true;
          break;
        case 'pwd':
          addDisplay(process.cwd());
          forceNext = true;
          break;
        // no default
      }
    }

    if (!forceNext && options.preCheck) {
      resCheck = options.preCheck(res, options);
      res = resCheck.res;
      if (resCheck.forceNext) { forceNext = true; } // Don't switch to false.
    }

    if (!forceNext) {
      if (!options.limitSrc.length ||
        isMatched(res, options.limitSrc, options.caseSensitive)) { break; }
      if (options.limitMessage) {
        limitMessage = replacePlaceholder(options.limitMessage, _getPhContent);
      }
    }

    addDisplay((limitMessage ? limitMessage + '\n' : '') +
      replacePlaceholder(options.displaySrc + '', _getPhContent));
  }
  return toBool(res, options);
}

// for dev
exports._DBG_set_useExt = function(val) { _DBG_useExt = val; };
exports._DBG_set_checkOptions = function(val) { _DBG_checkOptions = val; };
exports._DBG_set_checkMethod = function(val) { _DBG_checkMethod = val; };
exports._DBG_clearHistory = function() { lastInput = ''; inputHistory = []; };

// ------------------------------------

exports.setDefaultOptions = function(options) {
  defaultOptions = margeOptions(true, options);
  return margeOptions(true); // copy
};

exports.question = function(query, options) {
  /* eslint-disable key-spacing */
  return getValidLine(margeOptions(margeOptions(true, options), {
    display:            query
  }));
  /* eslint-enable key-spacing */
};

exports.prompt = function(options) {
  var readOptions = margeOptions(true, options);
  readOptions.display = readOptions.prompt;
  return getValidLine(readOptions);
};

exports.keyIn = function(query, options) {
  /* eslint-disable key-spacing */
  var readOptions = margeOptions(margeOptions(true, options), {
    display:            query,
    keyIn:              true,
    keepWhitespace:     true
  });
  /* eslint-enable key-spacing */

  // char list
  readOptions.limitSrc = readOptions.limit.filter(function(value) {
    var type = typeof value;
    return type === 'string' || type === 'number';
  })
  .map(function(text) { return replacePlaceholder(text + '', getPhCharlist); });
  // pattern
  readOptions.limit = escapePattern(readOptions.limitSrc.join(''));

  ['trueValue', 'falseValue'].forEach(function(optionName) {
    readOptions[optionName] = readOptions[optionName].reduce(function(comps, comp) {
      var type = typeof comp;
      if (type === 'string' || type === 'number') {
        comps = comps.concat((comp + '').split(''));
      } else { comps.push(comp); }
      return comps;
    }, []);
  });

  readOptions.display = replacePlaceholder(readOptions.display + '',
    function(param) { return getPhContent(param, readOptions); });

  return toBool(_readlineSync(readOptions), readOptions);
};

// ------------------------------------

exports.questionEMail = function(query, options) {
  if (query == null) { query = 'Input e-mail address: '; }
  /* eslint-disable key-spacing */
  return exports.question(query, margeOptions({
    // -------- default
    hideEchoBack:       false,
    // http://www.w3.org/TR/html5/forms.html#valid-e-mail-address
    limit:              /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/,
    limitMessage:       'Input valid e-mail address, please.',
    trueValue:          null,
    falseValue:         null
  }, options, {
    // -------- forced
    keepWhitespace:     false,
    cd:                 false
  }));
  /* eslint-enable key-spacing */
};

exports.questionNewPassword = function(query, options) {
  /* eslint-disable key-spacing */
  var resCharlist, min, max,
    readOptions = margeOptions({
      // -------- default
      hideEchoBack:       true,
      mask:               '*',
      limitMessage:       'It can include: $<charlist>\n' +
                            'And the length must be: $<length>',
      trueValue:          null,
      falseValue:         null,
      caseSensitive:      true
    }, options, {
      // -------- forced
      history:            false,
      cd:                 false,
      // limit (by charlist etc.),
      phContent: function(param) {
        return param === 'charlist' ? resCharlist.text :
          param === 'length' ? min + '...' + max : null;
      }
    }),
    // added:     charlist, min, max, confirmMessage, unmatchMessage
    charlist, confirmMessage, unmatchMessage,
    limit, limitMessage, res1, res2;
  /* eslint-enable key-spacing */
  options = options || {};

  charlist = replacePlaceholder(
    options.charlist ? options.charlist + '' : '$<!-~>', getPhCharlist);
  if (isNaN(min = parseInt(options.min, 10)) || typeof min !== 'number') { min = 12; }
  if (isNaN(max = parseInt(options.max, 10)) || typeof max !== 'number') { max = 24; }
  limit = new RegExp('^[' + escapePattern(charlist) +
    ']{' + min + ',' + max + '}$');
  resCharlist = array2charlist([charlist], readOptions.caseSensitive, true);
  resCharlist.text = joinChunks(resCharlist.values, resCharlist.suppressed);

  confirmMessage = options.confirmMessage != null ? options.confirmMessage :
    'Reinput a same one to confirm it: ';
  unmatchMessage = options.unmatchMessage != null ? options.unmatchMessage :
    'It differs from first one.' +
      ' Hit only the Enter key if you want to retry from first one.';

  if (query == null) { query = 'Input new password: '; }

  limitMessage = readOptions.limitMessage;
  while (!res2) {
    readOptions.limit = limit;
    readOptions.limitMessage = limitMessage;
    res1 = exports.question(query, readOptions);

    readOptions.limit = [res1, ''];
    readOptions.limitMessage = unmatchMessage;
    res2 = exports.question(confirmMessage, readOptions);
  }

  return res1;
};

function _questionNum(query, options, parser) {
  var validValue;
  function getValidValue(value) {
    validValue = parser(value);
    return !isNaN(validValue) && typeof validValue === 'number';
  }
  /* eslint-disable key-spacing */
  exports.question(query, margeOptions({
    // -------- default
    limitMessage:       'Input valid number, please.'
  }, options, {
    // -------- forced
    limit:              getValidValue,
    cd:                 false
    // trueValue, falseValue, caseSensitive, keepWhitespace don't work.
  }));
  /* eslint-enable key-spacing */
  return validValue;
}
exports.questionInt = function(query, options) {
  return _questionNum(query, options, function(value) { return parseInt(value, 10); });
};
exports.questionFloat = function(query, options) {
  return _questionNum(query, options, parseFloat);
};

exports.questionPath = function(query, options) {
  /* eslint-disable key-spacing */
  var validPath, error = '',
    readOptions = margeOptions({
      // -------- default
      hideEchoBack:       false,
      limitMessage:       '$<error(\n)>Input valid path, please.' +
                            '$<( Min:)min>$<( Max:)max>',
      history:            true,
      cd:                 true
    }, options, {
      // -------- forced
      keepWhitespace:     false,
      limit: function(value) {
        var exists, stat, res;
        value = replaceHomePath(value, true);
        error = ''; // for validate
        // mkdir -p
        function mkdirParents(dirPath) {
          dirPath.split(/\/|\\/).reduce(function(parents, dir) {
            var path = pathUtil.resolve((parents += dir + pathUtil.sep));
            if (!fs.existsSync(path)) {
              fs.mkdirSync(path);
            } else if (!fs.statSync(path).isDirectory()) {
              throw new Error('Non directory already exists: ' + path);
            }
            return parents;
          }, '');
        }

        try {
          exists = fs.existsSync(value);
          validPath = exists ? fs.realpathSync(value) : pathUtil.resolve(value);
          // options.exists default: true, not-bool: no-check
          if (!options.hasOwnProperty('exists') && !exists ||
              typeof options.exists === 'boolean' && options.exists !== exists) {
            error = (exists ? 'Already exists' : 'No such file or directory') +
              ': ' + validPath;
            return false;
          }
          if (!exists && options.create) {
            if (options.isDirectory) {
              mkdirParents(validPath);
            } else {
              mkdirParents(pathUtil.dirname(validPath));
              fs.closeSync(fs.openSync(validPath, 'w')); // touch
            }
            validPath = fs.realpathSync(validPath);
          }
          if (exists && (options.min || options.max ||
              options.isFile || options.isDirectory)) {
            stat = fs.statSync(validPath);
            // type check first (directory has zero size)
            if (options.isFile && !stat.isFile()) {
              error = 'Not file: ' + validPath;
              return false;
            } else if (options.isDirectory && !stat.isDirectory()) {
              error = 'Not directory: ' + validPath;
              return false;
            } else if (options.min && stat.size < +options.min ||
                options.max && stat.size > +options.max) {
              error = 'Size ' + stat.size + ' is out of range: ' + validPath;
              return false;
            }
          }
          if (typeof options.validate === 'function' &&
              (res = options.validate(validPath)) !== true) {
            if (typeof res === 'string') { error = res; }
            return false;
          }
        } catch (e) {
          error = e + '';
          return false;
        }
        return true;
      },
      // trueValue, falseValue, caseSensitive don't work.
      phContent: function(param) {
        return param === 'error' ? error :
          param !== 'min' && param !== 'max' ? null :
          options.hasOwnProperty(param) ? options[param] + '' : '';
      }
    });
    // added:     exists, create, min, max, isFile, isDirectory, validate
  /* eslint-enable key-spacing */
  options = options || {};

  if (query == null) { query = 'Input path (you can "cd" and "pwd"): '; }

  exports.question(query, readOptions);
  return validPath;
};

// props: preCheck, args, hRes, limit
function getClHandler(commandHandler, options) {
  var clHandler = {}, hIndex = {};
  if (typeof commandHandler === 'object') {
    Object.keys(commandHandler).forEach(function(cmd) {
      if (typeof commandHandler[cmd] === 'function') {
        hIndex[options.caseSensitive ? cmd : cmd.toLowerCase()] = commandHandler[cmd];
      }
    });
    clHandler.preCheck = function(res) {
      var cmdKey;
      clHandler.args = parseCl(res);
      cmdKey = clHandler.args[0] || '';
      if (!options.caseSensitive) { cmdKey = cmdKey.toLowerCase(); }
      clHandler.hRes =
        cmdKey !== '_' && hIndex.hasOwnProperty(cmdKey) ?
          hIndex[cmdKey].apply(res, clHandler.args.slice(1)) :
        hIndex.hasOwnProperty('_') ? hIndex._.apply(res, clHandler.args) : null;
      return {res: res, forceNext: false};
    };
    if (!hIndex.hasOwnProperty('_')) {
      clHandler.limit = function() { // It's called after preCheck.
        var cmdKey = clHandler.args[0] || '';
        if (!options.caseSensitive) { cmdKey = cmdKey.toLowerCase(); }
        return hIndex.hasOwnProperty(cmdKey);
      };
    }
  } else {
    clHandler.preCheck = function(res) {
      clHandler.args = parseCl(res);
      clHandler.hRes = typeof commandHandler === 'function' ?
        commandHandler.apply(res, clHandler.args) : true; // true for break loop
      return {res: res, forceNext: false};
    };
  }
  return clHandler;
}

exports.promptCL = function(commandHandler, options) {
  /* eslint-disable key-spacing */
  var readOptions = margeOptions({
      // -------- default
      hideEchoBack:       false,
      limitMessage:       'Requested command is not available.',
      caseSensitive:      false,
      history:            true
    }, options),
      // -------- forced
      // trueValue, falseValue, keepWhitespace don't work.
      // preCheck, limit (by clHandler)
    clHandler = getClHandler(commandHandler, readOptions);
  /* eslint-enable key-spacing */
  readOptions.limit = clHandler.limit;
  readOptions.preCheck = clHandler.preCheck;
  exports.prompt(readOptions);
  return clHandler.args;
};

exports.promptLoop = function(inputHandler, options) {
  /* eslint-disable key-spacing */
  var readOptions = margeOptions({
    // -------- default
    hideEchoBack:       false,
    trueValue:          null,
    falseValue:         null,
    caseSensitive:      false,
    history:            true
  }, options);
  /* eslint-enable key-spacing */
  while (true) { if (inputHandler(exports.prompt(readOptions))) { break; } }
  return;
};

exports.promptCLLoop = function(commandHandler, options) {
  /* eslint-disable key-spacing */
  var readOptions = margeOptions({
      // -------- default
      hideEchoBack:       false,
      limitMessage:       'Requested command is not available.',
      caseSensitive:      false,
      history:            true
    }, options),
      // -------- forced
      // trueValue, falseValue, keepWhitespace don't work.
      // preCheck, limit (by clHandler)
    clHandler = getClHandler(commandHandler, readOptions);
  /* eslint-enable key-spacing */
  readOptions.limit = clHandler.limit;
  readOptions.preCheck = clHandler.preCheck;
  while (true) {
    exports.prompt(readOptions);
    if (clHandler.hRes) { break; }
  }
  return;
};

exports.promptSimShell = function(options) {
  /* eslint-disable key-spacing */
  return exports.prompt(margeOptions({
    // -------- default
    hideEchoBack:       false,
    history:            true
  }, options, {
    // -------- forced
    prompt:             (function() {
      return IS_WIN ?
        '$<cwd>>' :
        // 'user@host:cwd$ '
        (process.env.USER || '') +
        (process.env.HOSTNAME ?
          '@' + process.env.HOSTNAME.replace(/\..*$/, '') : '') +
        ':$<cwdHome>$ ';
    })()
  }));
  /* eslint-enable key-spacing */
};

function _keyInYN(query, options, limit) {
  var res;
  if (query == null) { query = 'Are you sure? '; }
  if ((!options || options.guide !== false) && (query += '')) {
    query = query.replace(/\s*:?\s*$/, '') + ' [y/n]: ';
  }
  /* eslint-disable key-spacing */
  res = exports.keyIn(query, margeOptions(options, {
    // -------- forced
    hideEchoBack:       false,
    limit:              limit,
    trueValue:          'y',
    falseValue:         'n',
    caseSensitive:      false
    // mask doesn't work.
  }));
  // added:     guide
  /* eslint-enable key-spacing */
  return typeof res === 'boolean' ? res : '';
}
exports.keyInYN = function(query, options) { return _keyInYN(query, options); };
exports.keyInYNStrict = function(query, options) { return _keyInYN(query, options, 'yn'); };

exports.keyInPause = function(query, options) {
  if (query == null) { query = 'Continue...'; }
  if ((!options || options.guide !== false) && (query += '')) {
    query = query.replace(/\s+$/, '') + ' (Hit any key)';
  }
  /* eslint-disable key-spacing */
  exports.keyIn(query, margeOptions({
    // -------- default
    limit:              null
  }, options, {
    // -------- forced
    hideEchoBack:       true,
    mask:               ''
  }));
  // added:     guide
  /* eslint-enable key-spacing */
  return;
};

exports.keyInSelect = function(items, query, options) {
  /* eslint-disable key-spacing */
  var readOptions = margeOptions({
      // -------- default
      hideEchoBack:       false
    }, options, {
      // -------- forced
      trueValue:          null,
      falseValue:         null,
      caseSensitive:      false,
      // limit (by items),
      phContent: function(param) {
        return param === 'itemsCount' ? items.length + '' :
          param === 'firstItem' ? (items[0] + '').trim() :
          param === 'lastItem' ? (items[items.length - 1] + '').trim() : null;
      }
    }),
    // added:     guide, cancel
    keylist = '', key2i = {}, charCode = 49 /* '1' */, display = '\n';
  /* eslint-enable key-spacing */
  if (!Array.isArray(items) || !items.length || items.length > 35) {
    throw '`items` must be Array (max length: 35).';
  }

  items.forEach(function(item, i) {
    var key = String.fromCharCode(charCode);
    keylist += key;
    key2i[key] = i;
    display += '[' + key + '] ' + (item + '').trim() + '\n';
    charCode = charCode === 57 /* '9' */ ? 97 /* 'a' */ : charCode + 1;
  });
  if (!options || options.cancel !== false) {
    keylist += '0';
    key2i['0'] = -1;
    display += '[0] ' +
      (options && options.cancel != null && typeof options.cancel !== 'boolean' ?
        (options.cancel + '').trim() : 'CANCEL') + '\n';
  }
  readOptions.limit = keylist;
  display += '\n';

  if (query == null) { query = 'Choose one from list: '; }
  if ((query += '')) {
    if (!options || options.guide !== false) {
      query = query.replace(/\s*:?\s*$/, '') + ' [$<limit>]: ';
    }
    display += query;
  }

  return key2i[exports.keyIn(display, readOptions).toLowerCase()];
};

exports.getRawInput = function() { return rawInput; };

// ======== DEPRECATED ========
function _setOption(optionName, args) {
  var options;
  if (args.length) { options = {}; options[optionName] = args[0]; }
  return exports.setDefaultOptions(options)[optionName];
}
exports.setPrint = function() { return _setOption('print', arguments); };
exports.setPrompt = function() { return _setOption('prompt', arguments); };
exports.setEncoding = function() { return _setOption('encoding', arguments); };
exports.setMask = function() { return _setOption('mask', arguments); };
exports.setBufferSize = function() { return _setOption('bufferSize', arguments); };
};
BundleModuleCode['http/https']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      BSSLAB, Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2006-2022 bLAB
 **    $CREATED:     18-05-15 by sbosse.
 **    $RCS:         $Id:$
 **    $VERSION:     1.3.1
 **
 **    $INFO:
 **
 **  HTTP(S) File Server Module.
 **
 *
 **    $ENDOFINFO
 */


"use strict";
var log = 0;

var Io = Require('com/io');
var util = Require('util');
var http = Require('http');
var https; try { https = require('https'); } catch (e) {}
var fs = Require('fs');
var Comp = Require('com/compat');
var Perv = Comp.pervasives;
var String = Comp.string;
var Array = Comp.array;
var Filename = Comp.filename;
var trace = Io.tracing;
var div = Perv.div;

var isNode = Comp.isNodeJS();

/*********************************************
**  HTTP File SERVER
 *********************************************/
/** Auxiliary File Server
 *
 * typeof @options  = { sip,ipport,dir,verbose?,index?,log? } 
 * typeof File = constructor
 */
var HTTPSrv = function(options) {
    if (!(this instanceof HTTPSrv)) return new  HTTPSrv(options);
    this.srv_ip     = options.ip;     // URL
    this.srv_ipport = options.ipport; // URL:port
    this.dir        = options.dir;    // Local file directory to be served
    this.proto      = options.proto||'http';
    this.https=undefined;
    this.verbose=options.verbose||0;
    this.index=options.index||'index.html';
    this.log=options.log||Io.log;
    this.options=options;
};

HTTPSrv.prototype.init=function () {
    var self=this,
        stat='';
    this.dir=Filename.path_absolute(this.dir);
    
    function handler(request, response) {
        //Io.inspect(request);
        response.origin=request.headers.origin;
        var path=String.prefix(request.url,'?');
        String.match(request.method,[
            ['GET',function() {
              // TODO
              if (self.verbose>2) self.log('[HTTP] Get: '+path);
              var data='';
              try {
                    path=self.dir+'/'+Filename.path_normalize(path=='/'?self.index:path);
                    data=Io.read_file_bin(path);
                    stat='OK';
              } catch (e) {
                    data='File server: failed to read file '+path+' , '+util.inspect(e);
                    stat=data;
              }
              if (data == undefined) {
                  stat='Failed: no data read.';
                  if (self.verbose>1) self.log('[HTTP] : Failed to get data for file '+path);
                  data='Error: Not found: '+path;
              }
              if (self.verbose>2) self.log('[HTTP] Get: '+request.url+' -> '+stat+' ['+(data?data.length:0)+']');

              if (data!=undefined) {
                    //response.writeHead(200);
                if (response.origin!=undefined)
                      response.writeHead(200,{'Access-Control-Allow-Origin': response.origin,
                                              'Access-Control-Allow-Credentials': 'true',
                                             });
                else
                      // response.writeHead(200,{'Content-Type': 'text/html'});
                      response.writeHead(200,{'Access-Control-Allow-Origin': '*'});
                response.write(data);
                response.end();
              }
            }]
        ])
    };
    if (this.proto=='http') this.https = http.createServer(handler);
    else if (this.proto=='https' && https) {
      // Dummy certs, can be overriden by options
      var _options={
        key:"-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCj1c0IRFOg2FZt\ncDtVgdQetk0RtOmU5ukMs09xw+irPHZeHmtu0gWy11yCHfqHwaqsrYdmnC1EAJsr\nlyBgdoiOn2MJNxW52/x7/I1ZVUke6p4OPyhNGaQHcCPmp/dBzMH9yY6K/HHPDqR/\ncDR1ait3ttpsvMxFT0baHZsxm/bajKUETSkGOW5gugq32egyjAKfHYSbbSY2zm8R\n3g1kluYKGvhjt/SvTPjcGYiTMwkyKBTuvpfZqxRArkaMlQdKKKBT+X3cY47ZD4I3\n0Hy8kirTeLvPf91THeI8pcTVU8a6qPryttOB9cRruzYJF4Z0sdnAzTPmVugPjRqn\n6BFPb0v1AgMBAAECggEASTMAHV5xwt6FlvXa/LQ58tLekjezWRzmKQ+AQkMWlFM6\nS4jp1SSu+R2xrkz4n2kO+YG6ikTjEIv4yDwIcjDjiF18ISTkZxr7ruXCvZQWTGLk\n5VagifoXyF75G1gWZ+a1Ec/ZCQ4LR0iyhGG8fm1GKIGhC4468giejltF+J9HZpNT\nJTcOZ/d5+WtwFa67o1vEqp8tIZ6bA6as9Jp4brmWifXSNZpGh3oIa6eQcVAl9b32\nxnh9F1oBwAz5D5TbHZ7RfiRsoUKeEprsJ8XEfVwO5R8xd7IMc5eXqDcZIZHJEWeV\nRqY0GOGRCdBWZydrHnyIpkCcJ9TytN4nx3OD0BsCYQKBgQDRrXDM88lVWW3htatu\nZiEZQIVkJ3Lj/S9/YByeU22UBUr7UZfWAQWEF7nhDnoa3NeQULMekgeH8O4Yd7Qd\nsGHm9DwiqPiyw2MRUU2eM074GiDpgy1K+oP669YHSMe+Vq5TnW1deNDuPYm4R85V\nGqG0rpG5yN6FojMmQsn+0qTxDQKBgQDIB7E8AMLFV7g1e8mor4uMa68GyScl1bFK\ngQ3Yoq+yLUV0zziFIcR9IwGxopC81QN4qXynb1JnlQyTASEPxJT558wFIUqRwnND\nxbwfwcNL5KVN7F1yTn55mmKHuxYGURs3Au8ErwQ+cdDu3bFsQxk8eBEob3OEzAd1\nxEW1yAh8iQKBgGaU4y3yS1rtULvvhHqTjrfrABe60RPHp7g6jmXLTT3wxPllttIl\nV8yDSxZXXdfMmc3qHWfka7jPX70quz0XMR6r+MvAPURAITS0wTOXyJfLOLTlz3/y\nRiW5wdF4gviVMd6Ik5v6YsVb6Af3YXPzfo+GJJdvNabNbxbV8DsyVS31AoGAGaTy\n0fB/B/HRCfpOxjOLPntnuwT64dzdl+GntshUohEvwGP4qQjFOg3M38sppyvgAA4q\njwS0mdb//7C7XlwjhU50V4wHFVzKjjvBfIjI0ugDUVQmPstVZ52lWCViE3k+dfUI\nU59keeT5lkYRwwFvMNNrz7VKKBJIOo7pKP72J5ECgYAygg6zNNUzrcUuuZHSpyEM\nx5uy5nmoD81RwlGS0N8m5SwN8jW+c7C7ROUdGakXV69zubMXzAsz4xMJTTSQBep2\nsNTNjlV71UikZhhx/spLZqaLb0ZIgCxj4dfNZS3XRh7Wi1bYuf2III+SUf4zitG0\nuGKHIqJgcSumSzjYGiMSAA==\n-----END PRIVATE KEY-----\n",
        cert:"-----BEGIN CERTIFICATE-----\nMIIDITCCAgmgAwIBAgIJAKMxU7sE4FnyMA0GCSqGSIb3DQEBCwUAMCcxCzAJBgNV\nBAYTAlVTMRgwFgYDVQQDDA9FeGFtcGxlLVJvb3QtQ0EwHhcNMjIwNjA1MTEzMDMy\nWhcNMjUwMzI1MTEzMDMyWjAnMQswCQYDVQQGEwJVUzEYMBYGA1UEAwwPRXhhbXBs\nZS1Sb290LUNBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAo9XNCERT\noNhWbXA7VYHUHrZNEbTplObpDLNPccPoqzx2Xh5rbtIFstdcgh36h8GqrK2HZpwt\nRACbK5cgYHaIjp9jCTcVudv8e/yNWVVJHuqeDj8oTRmkB3Aj5qf3QczB/cmOivxx\nzw6kf3A0dWord7babLzMRU9G2h2bMZv22oylBE0pBjluYLoKt9noMowCnx2Em20m\nNs5vEd4NZJbmChr4Y7f0r0z43BmIkzMJMigU7r6X2asUQK5GjJUHSiigU/l93GOO\n2Q+CN9B8vJIq03i7z3/dUx3iPKXE1VPGuqj68rbTgfXEa7s2CReGdLHZwM0z5lbo\nD40ap+gRT29L9QIDAQABo1AwTjAdBgNVHQ4EFgQU763HyX73limLTXAwJ4SwpVGv\nD/AwHwYDVR0jBBgwFoAU763HyX73limLTXAwJ4SwpVGvD/AwDAYDVR0TBAUwAwEB\n/zANBgkqhkiG9w0BAQsFAAOCAQEAaO662eNFN2wWtMsUrITX8pwUAJRKxkFvxEQI\nt0HgtfxxvZeTgYjLeTv5U0Jmv8K+6QnNnFIfoc9CD0fFaETw9Z6a+mzliMnHwzZ2\ndI+3eahIcRZ86VuvwisJsDzpW1931Jz+/arIEZprTTSfCPkJs9U790W4wfA6/7Cc\nyZ57EWiug8sP/0NcgofKNNCiixlnlNhXJIOh7/7gXw+zJVdyoKUHMJMoii1UElzN\nVTm6YKSTiuOc+rOIbC4Aw5gQqRDtUqbf/Vcr2IEdOqlL7r4vW9urH+/p3sLVF20C\n8ssjea8dmHcrb5Omu0tUMbhzMM1/eHZS3iwcauu2VWzBDOOjeQ==\n-----END CERTIFICATE-----\n"
      }
      if (fs.existsSync(this.options.key)) { console.log('Loading '+_this.options.key); _options.key=fs.readFileSync(this.options.key,'utf8') };
      if (fs.existsSync(this.options.cert)) { console.log('Loading '+this.options.cert); _options.cert=fs.readFileSync(this.options,'utf8') };
      this.https = https.createServer(_options,handler);
    } else throw "ENOTSUPPORTED";
    this.https.on("connection", function (socket) {
      socket.setNoDelay(true);
    });
    this.log('[HTTP] servicing directory: ' + this.dir);

};

HTTPSrv.prototype.start=function () {
    var self=this;
    if (self.verbose) Io.out('[HTTP] Starting ..');
    this.https.listen(this.srv_ipport, function () {
        self.log('[HTTP] listen: listening on *:' + self.srv_ipport);
    });
};

HTTPSrv.prototype.stop=function () {
  if (this.https) this.https.close();
}

module.exports = {
    /** Auxiliary File/HTML Server
     *
     */
    // typeof @options = {ip,ipport,dir,verbose?,index?}
    HTTPSrv: HTTPSrv
}
};
BundleModuleCode['geoip/geoip']=function (module,exports){
/**
 **      ==============================
 **       OOOO        O      O   OOOO
 **       O   O       O     O O  O   O
 **       O   O       O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2006-2020 bLAB
 **    $CREATED:     20-10-16 by sbosse.
 **    $VERSION:     1.2.1
 **
 **    $INFO:
 **
 **  GEO IP Database Services and http/https Server
 **  Database source: GeoLiteCit
 **  Compatibility: ip-api.com/json 
 **  Can be used as a proxy to ip-api.com to overcome ad-blockers!
 **
 **  Look-up returns:
 ** {
 **   country: string, // country
 **   countryCode: string, // countryCode
 **   region: string, // region
 **   city: string, // city
 **   zip: string, // postal code
 **   lat: string, // latitude
 **   lon: string  // longitude
 **  }; 
 **
 **    $ENDOFINFO
 */
var fs = require("fs");
var sat = Require('dos/ext/satelize');

var out = function (msg) { console.log('[GEOIP] '+msg) };

var geoip = module.exports = {
    dir : process.cwd()||__dirname,
    ipblocks : [],
    locations : [],
	midpoints : [],
	numblocks : 0,
    ready : 0,
    verbose : 0,
    config : function (a,v) { geoip[a]=v }, 

    // Cold start, load and compile CSV DB
    init : function (cb) {
      out ('Loading '+ geoip.dir + "/GeoLiteCity-Blocks.csv ..");
      var block = fs.createReadStream(geoip.dir + "/GeoLiteCity-Blocks.csv");
      out ('Loading '+ geoip.dir + "/GeoLiteCity-Location.csv ..");
      var location = fs.createReadStream(geoip.dir + "/GeoLiteCity-Location.csv");
      var buffer1 = "",buffer2 = "";

      block.addListener("data", function(data) {
          buffer1 += data.toString().replace(/"/g, "");
      });

      block.addListener("end", function() {
          var entries = buffer1.split("\n");
          out ('Compiling GeoLiteCity-Blocks ..');
          for(var i=0; i<entries.length; i++) {
              if (i<2) continue;        
              var entry = entries[i].split(",");
              if (parseInt(entry[0])) geoip.ipblocks.push({
                a: parseInt(entry[0]), // ip start
                b: parseInt(entry[1]), // ip end
                i: parseInt(entry[2])  // location id
              });
          }

          geoip.ipblocks.sort(function(a, b) {
              return a.a - b.a;
          });
          geoip.numblocks = geoip.ipblocks.length;
          geoip.midpoints=[];
          var n = Math.floor(geoip.numblocks / 2);
          while(n >= 1) {
              n = Math.floor(n / 1.5);
              geoip.midpoints.push(n);
          }

		  geoip.ready++;
          if (cb && geoip.ready==2) cb();
      });

      location.addListener("data", function(data) {
          buffer2 += data.toString().replace(/"/g, "");
      });

      location.addListener("end", function() {

          var entries = buffer2.split("\n");
          var locid=0;
          out ('Compiling GeoLiteCity-Location ..');

          for(var i=0; i<entries.length; i++) {
              if (i<2) continue;        
              var entry = entries[i].split(",");
              locid=parseInt(entry[0]);
              geoip.locations[locid] = {
                cn: entry[1], // country
                re: entry[2], // region
                ci: entry[3], // city
                pc: entry[4], // postal code
                la: entry[5], // latitude
                lo: entry[6]  // longitude
              };
          }

		  geoip.ready++;
          if (cb && geoip.ready==2) cb();
      });    
    },

    // Load and parse JSON DB
    load : function (cb) {
      // Read DB
      out ('Loading '+geoip.dir + "/GeoLiteCity-Blocks.json ..");
      var block = fs.createReadStream(geoip.dir + "/GeoLiteCity-Blocks.json");
      out ('Loading '+geoip.dir + "/GeoLiteCity-Location.json ..");
      var location = fs.createReadStream(geoip.dir + "/GeoLiteCity-Location.json");
      var buffer1 = "",buffer2 = "";
      block.on('error', function (e) { out(e); cb(e); });
      location.on('error', function (e) { out(e); cb(e); });
      
      block.addListener("data", function(data) {
          buffer1 += data.toString();
      });

      block.addListener("end", function() {
          out ('Parsing GeoLiteCity-Blocks ..');
          geoip.ipblocks = JSON.parse(buffer1);
          out ('Parsing GeoLiteCity-Blocks done.');
          geoip.ipblocks.sort(function(a, b) {
              return a.a - b.a;
          });
          geoip.numblocks = geoip.ipblocks.length;
          var n = Math.floor(geoip.numblocks / 2);
          geoip.midpoints=[];
          while(n >= 1) {
              n = Math.floor(n / 1.5);
              geoip.midpoints.push(n);
          }
		  geoip.ready++;
          if (cb && geoip.ready==2) cb();
      });

      location.addListener("data", function(data) {
          buffer2 += data.toString();
      });

      location.addListener("end", function() {
          out ('Parsing GeoLiteCity-Location ..');
          geoip.locations = JSON.parse(buffer2);
          out ('Parsing GeoLiteCity-Location done.');

		  geoip.ready++;
          if (cb && geoip.ready==2) cb();
      });
      
    },

    // Search a matching GEO entry
    lookup: function(ip) {

        if(geoip.ready<2) {
            return { error: "GeoIP not ready" };
        }

        var ipl = iplong(ip);

        if(ipl == 0) {
            return { error: "Invalid ip address " + ip + " -> " + ipl + " as integer" };
        }

        var found = find(ipl);
        if (found) {
          var loc = geoip.locations[found.i]; 
          return {
            status:"success",
            country: getCountryName(loc.cn),
            countryCode:loc.cn,
            city:loc.ci,
            region:loc.re,
            zip:loc.pc,
            lon:loc.lo,
            lat:loc.la,
          }
        } else return none;
    },
    
    // ip-api.com relay using satelize module!
    proxy : function (options) {
      options=options||{http:9999};
      var http = require('http');
      var https;
      try { https = require('https') } catch (e) { }
      if (options.http) {
        var httpSrv = http.createServer(function (request,response) {
          var url=request.url,body,header,sep,query,now,
              remote=request.connection.remoteAddress;
          if (request.url.length) 
            query=parseQueryString(request.remote||request.url);
          else 
            query={}
          if (url.match(/\/json\/([0-9\.]+)/)) query.ip=url.match(/\/json\/([0-9\.]+)/)[1];
          if (geoip.verbose>0) print(url,query,remote);
          switch (request.method) {
            case 'GET':
              sat.satelize({ip:query.ip||remote},function (err,info) {
                   if (err) {
                     return reply(response,JSON.stringify({error:err}))
                   } else {
                     if (request.headers && request.headers.host) info.proxy=request.headers.host;
                     return reply(response,JSON.stringify(info))                       
                   }
              })

              break;
          }
        });
        httpSrv.on("connection", function (socket) {
            // socket.setNoDelay(true);
        });

        httpSrv.on("error", function (err) {
          out(err)
        });

        httpSrv.listen(options.http,function (err) {
          out('HTTP server started on port '+options.http);
        });
      };
      if (options.https && https && options.pem) {
          // requires options.pem={key,cert} 
        var httpsSrv = https.createServer(options.pem,function (request,response) {
          var url=request.url,body,header,sep,query,now,
              remote=request.connection.remoteAddress;
          if (request.url.length) 
            query=parseQueryString(request.remote||request.url);
          else 
            query={}
          if (url.match(/\/json\/([0-9\.]+)/)) query.ip=url.match(/\/json\/([0-9\.]+)/)[1];
          if (geoip.verbose>0) print(url,query,remote);
          switch (request.method) {
            case 'GET':
              sat.satelize({ip:query.ip||remote},function (err,info) {
                   if (err) {
                     return reply(response,JSON.stringify({error:err}))
                   } else {
                     if (request.headers && request.headers.host) info.proxy=request.headers.host;
                     return reply(response,JSON.stringify(info))                       
                   }
              })

              break;
          }
        });
        httpsSrv.on("connection", function (socket) {
            // socket.setNoDelay(true);
        });

        httpsSrv.on("error", function (err) {
          out(err)
        });

        httpsSrv.listen(options.https,function (err) {
          out('HTTPS server started on port '+options.https);
        });
      };
    },
    
    // Start an ip-api.com compatible web server API
    server : function (options) {
      options=options||{http:{address:'localhost',port:9999}};
      var http = require('http');
      var https;
      try { https = require('https') } catch (e) { }
      geoip.load(function (err) {
        if (err) return;
        if (options.http) {
          var httpSrv = http.createServer(function (request,response) {
            var url=request.url,body,header,sep,query,now,
                remote=request.connection.remoteAddress;
            if (request.url.length) 
              query=parseQueryString(request.remote||request.url);
            else 
              query={}
            if (url.match(/\/json\/([0-9\.]+)/)) query.ip=url.match(/\/json\/([0-9\.]+)/)[1];
            if (geoip.verbose>0) print(url,query,remote);
            switch (request.method) {
              case 'GET':
                reply(response,JSON.stringify(geoip.lookup(query.ip||remote)))
                break;
            }
          })

          httpSrv.on("connection", function (socket) {
              // socket.setNoDelay(true);
          });

          httpSrv.on("error", function (err) {
            out(err)
          });

          httpSrv.listen(options.http.port,function (err) {
            out('HTTP server started on port '+options.http.port);
          });
        }
        if (options.https && https && options.pem) {
          // requires options.pem={key,cert} 
          var httpsSrv = https.createServer(options.pem,function (request,response) {
            var url=request.url,body,header,sep,query,now,
                remote=request.connection.remoteAddress;
            if (request.url.length) 
              query=parseQueryString(request.remote||request.url);
            else 
              query={}
            if (url.match(/\/json\/([0-9\.]+)/)) query.ip=url.match(/\/json\/([0-9\.]+)/)[1];
            if (geoip.verbose>0) print(url,query,remote);
            switch (request.method) {
              case 'GET':
                reply(response,JSON.stringify(geoip.lookup(query.ip||remote)))
                break;
            }
          })

          httpsSrv.on("connection", function (socket) {
              // socket.setNoDelay(true);
          });

          httpsSrv.on("error", function (err) {
            out(err)
          });

          httpsSrv.listen(options.http.port,function (err) {
            out('HTTPS server started on port '+options.http.port);
          });
        }
      });
    },

    // Save the DB in JSON format
    save : function () {
      out ('Saving '+geoip.dir + "/GeoLiteCity-Blocks.json ..");
      var jsblocks = JSON.stringify(geoip.ipblocks);
      fs.writeFileSync(geoip.dir + "/GeoLiteCity-Blocks.json", jsblocks, 'utf8');
      out ('Saving '+geoip.dir + "/GeoLiteCity-Location.json ..");
      var jslocations = JSON.stringify(geoip.locations);
      fs.writeFileSync(geoip.dir + "/GeoLiteCity-Location.json", jslocations, 'utf8');
    },
    
};

function iplong(ip) {

    if(!ip) {
        return 0;
    }

    ip = ip.toString();

    if(isNaN(ip) && ip.indexOf(".") == -1) {
        return 0;
    }

    if(ip.indexOf(".") == -1) {

        try {
            ip = parseFloat(ip);
            return ip < 0 || ip > 4294967296 ? 0 : ip;
        }
        catch(s) {
        }
    }

    var parts = ip.split(".");

    if(parts.length != 4) {
        return 0;
    }

    var ipl = 0;

    for(var i=0; i<4; i++) {
        parts[i] = parseInt(parts[i], 10);

        if(parts[i] < 0 || parts[i] > 255) {
            return 0;
        }

        ipl += parts[3-i] * (Math.pow(256, i));
    }

    return ipl > 4294967296 ? 0 : ipl;
}

/**
 * A qcuick little binary search
 * @param ip the ip we're looking for
 * @return {*}
 */
function find(ipl) {

    var mpi = 0;
    var n = geoip.midpoints[0];
    var step;
    var current;
    var next;
    var prev;
    var nn;
    var pn;
    while(true) {

        step = geoip.midpoints[mpi];
        mpi++;
        current = geoip.ipblocks[n];
        nn = n + 1;
        pn = n - 1;

        next = nn < geoip.numblocks ? geoip.ipblocks[nn] : null;
        prev = pn > -1 ? geoip.ipblocks[pn] : null;
        
		// take another step?
        if(step > 0) {
            if(!next || next.a < ipl) {
                n += step;
            } else {
                n -= step;
            }

            continue;
        }

        // we're either current, next or previous depending on which is closest to ipl
        var cd = Math.abs(ipl - current.a);
        var nd = next && next.a < ipl ? ipl - next.a : 1000000000;
        var pd = prev && prev.a < ipl ? ipl - prev.a : 1000000000;


        // current wins
        if(cd < nd && cd < pd) {
            return current;
        }

         // next wins
        if(nd < cd && nd < pd) {
            return next;

        }

        // prev wins
        return prev;
    }
    return none;
}

// https://gist.github.com/maephisto

var isoCountries = {
    'AF' : 'Afghanistan',
    'AX' : 'Aland Islands',
    'AL' : 'Albania',
    'DZ' : 'Algeria',
    'AS' : 'American Samoa',
    'AD' : 'Andorra',
    'AO' : 'Angola',
    'AI' : 'Anguilla',
    'AQ' : 'Antarctica',
    'AG' : 'Antigua And Barbuda',
    'AR' : 'Argentina',
    'AM' : 'Armenia',
    'AW' : 'Aruba',
    'AU' : 'Australia',
    'AT' : 'Austria',
    'AZ' : 'Azerbaijan',
    'BS' : 'Bahamas',
    'BH' : 'Bahrain',
    'BD' : 'Bangladesh',
    'BB' : 'Barbados',
    'BY' : 'Belarus',
    'BE' : 'Belgium',
    'BZ' : 'Belize',
    'BJ' : 'Benin',
    'BM' : 'Bermuda',
    'BT' : 'Bhutan',
    'BO' : 'Bolivia',
    'BA' : 'Bosnia And Herzegovina',
    'BW' : 'Botswana',
    'BV' : 'Bouvet Island',
    'BR' : 'Brazil',
    'IO' : 'British Indian Ocean Territory',
    'BN' : 'Brunei Darussalam',
    'BG' : 'Bulgaria',
    'BF' : 'Burkina Faso',
    'BI' : 'Burundi',
    'KH' : 'Cambodia',
    'CM' : 'Cameroon',
    'CA' : 'Canada',
    'CV' : 'Cape Verde',
    'KY' : 'Cayman Islands',
    'CF' : 'Central African Republic',
    'TD' : 'Chad',
    'CL' : 'Chile',
    'CN' : 'China',
    'CX' : 'Christmas Island',
    'CC' : 'Cocos (Keeling) Islands',
    'CO' : 'Colombia',
    'KM' : 'Comoros',
    'CG' : 'Congo',
    'CD' : 'Congo, Democratic Republic',
    'CK' : 'Cook Islands',
    'CR' : 'Costa Rica',
    'CI' : 'Cote D\'Ivoire',
    'HR' : 'Croatia',
    'CU' : 'Cuba',
    'CY' : 'Cyprus',
    'CZ' : 'Czech Republic',
    'DK' : 'Denmark',
    'DJ' : 'Djibouti',
    'DM' : 'Dominica',
    'DO' : 'Dominican Republic',
    'EC' : 'Ecuador',
    'EG' : 'Egypt',
    'SV' : 'El Salvador',
    'GQ' : 'Equatorial Guinea',
    'ER' : 'Eritrea',
    'EE' : 'Estonia',
    'ET' : 'Ethiopia',
    'FK' : 'Falkland Islands (Malvinas)',
    'FO' : 'Faroe Islands',
    'FJ' : 'Fiji',
    'FI' : 'Finland',
    'FR' : 'France',
    'GF' : 'French Guiana',
    'PF' : 'French Polynesia',
    'TF' : 'French Southern Territories',
    'GA' : 'Gabon',
    'GM' : 'Gambia',
    'GE' : 'Georgia',
    'DE' : 'Germany',
    'GH' : 'Ghana',
    'GI' : 'Gibraltar',
    'GR' : 'Greece',
    'GL' : 'Greenland',
    'GD' : 'Grenada',
    'GP' : 'Guadeloupe',
    'GU' : 'Guam',
    'GT' : 'Guatemala',
    'GG' : 'Guernsey',
    'GN' : 'Guinea',
    'GW' : 'Guinea-Bissau',
    'GY' : 'Guyana',
    'HT' : 'Haiti',
    'HM' : 'Heard Island & Mcdonald Islands',
    'VA' : 'Holy See (Vatican City State)',
    'HN' : 'Honduras',
    'HK' : 'Hong Kong',
    'HU' : 'Hungary',
    'IS' : 'Iceland',
    'IN' : 'India',
    'ID' : 'Indonesia',
    'IR' : 'Iran, Islamic Republic Of',
    'IQ' : 'Iraq',
    'IE' : 'Ireland',
    'IM' : 'Isle Of Man',
    'IL' : 'Israel',
    'IT' : 'Italy',
    'JM' : 'Jamaica',
    'JP' : 'Japan',
    'JE' : 'Jersey',
    'JO' : 'Jordan',
    'KZ' : 'Kazakhstan',
    'KE' : 'Kenya',
    'KI' : 'Kiribati',
    'KR' : 'Korea',
    'KW' : 'Kuwait',
    'KG' : 'Kyrgyzstan',
    'LA' : 'Lao People\'s Democratic Republic',
    'LV' : 'Latvia',
    'LB' : 'Lebanon',
    'LS' : 'Lesotho',
    'LR' : 'Liberia',
    'LY' : 'Libyan Arab Jamahiriya',
    'LI' : 'Liechtenstein',
    'LT' : 'Lithuania',
    'LU' : 'Luxembourg',
    'MO' : 'Macao',
    'MK' : 'Macedonia',
    'MG' : 'Madagascar',
    'MW' : 'Malawi',
    'MY' : 'Malaysia',
    'MV' : 'Maldives',
    'ML' : 'Mali',
    'MT' : 'Malta',
    'MH' : 'Marshall Islands',
    'MQ' : 'Martinique',
    'MR' : 'Mauritania',
    'MU' : 'Mauritius',
    'YT' : 'Mayotte',
    'MX' : 'Mexico',
    'FM' : 'Micronesia, Federated States Of',
    'MD' : 'Moldova',
    'MC' : 'Monaco',
    'MN' : 'Mongolia',
    'ME' : 'Montenegro',
    'MS' : 'Montserrat',
    'MA' : 'Morocco',
    'MZ' : 'Mozambique',
    'MM' : 'Myanmar',
    'NA' : 'Namibia',
    'NR' : 'Nauru',
    'NP' : 'Nepal',
    'NL' : 'Netherlands',
    'AN' : 'Netherlands Antilles',
    'NC' : 'New Caledonia',
    'NZ' : 'New Zealand',
    'NI' : 'Nicaragua',
    'NE' : 'Niger',
    'NG' : 'Nigeria',
    'NU' : 'Niue',
    'NF' : 'Norfolk Island',
    'MP' : 'Northern Mariana Islands',
    'NO' : 'Norway',
    'OM' : 'Oman',
    'PK' : 'Pakistan',
    'PW' : 'Palau',
    'PS' : 'Palestinian Territory, Occupied',
    'PA' : 'Panama',
    'PG' : 'Papua New Guinea',
    'PY' : 'Paraguay',
    'PE' : 'Peru',
    'PH' : 'Philippines',
    'PN' : 'Pitcairn',
    'PL' : 'Poland',
    'PT' : 'Portugal',
    'PR' : 'Puerto Rico',
    'QA' : 'Qatar',
    'RE' : 'Reunion',
    'RO' : 'Romania',
    'RU' : 'Russian Federation',
    'RW' : 'Rwanda',
    'BL' : 'Saint Barthelemy',
    'SH' : 'Saint Helena',
    'KN' : 'Saint Kitts And Nevis',
    'LC' : 'Saint Lucia',
    'MF' : 'Saint Martin',
    'PM' : 'Saint Pierre And Miquelon',
    'VC' : 'Saint Vincent And Grenadines',
    'WS' : 'Samoa',
    'SM' : 'San Marino',
    'ST' : 'Sao Tome And Principe',
    'SA' : 'Saudi Arabia',
    'SN' : 'Senegal',
    'RS' : 'Serbia',
    'SC' : 'Seychelles',
    'SL' : 'Sierra Leone',
    'SG' : 'Singapore',
    'SK' : 'Slovakia',
    'SI' : 'Slovenia',
    'SB' : 'Solomon Islands',
    'SO' : 'Somalia',
    'ZA' : 'South Africa',
    'GS' : 'South Georgia And Sandwich Isl.',
    'ES' : 'Spain',
    'LK' : 'Sri Lanka',
    'SD' : 'Sudan',
    'SR' : 'Suriname',
    'SJ' : 'Svalbard And Jan Mayen',
    'SZ' : 'Swaziland',
    'SE' : 'Sweden',
    'CH' : 'Switzerland',
    'SY' : 'Syrian Arab Republic',
    'TW' : 'Taiwan',
    'TJ' : 'Tajikistan',
    'TZ' : 'Tanzania',
    'TH' : 'Thailand',
    'TL' : 'Timor-Leste',
    'TG' : 'Togo',
    'TK' : 'Tokelau',
    'TO' : 'Tonga',
    'TT' : 'Trinidad And Tobago',
    'TN' : 'Tunisia',
    'TR' : 'Turkey',
    'TM' : 'Turkmenistan',
    'TC' : 'Turks And Caicos Islands',
    'TV' : 'Tuvalu',
    'UG' : 'Uganda',
    'UA' : 'Ukraine',
    'AE' : 'United Arab Emirates',
    'GB' : 'United Kingdom',
    'US' : 'United States',
    'UM' : 'United States Outlying Islands',
    'UY' : 'Uruguay',
    'UZ' : 'Uzbekistan',
    'VU' : 'Vanuatu',
    'VE' : 'Venezuela',
    'VN' : 'Viet Nam',
    'VG' : 'Virgin Islands, British',
    'VI' : 'Virgin Islands, U.S.',
    'WF' : 'Wallis And Futuna',
    'EH' : 'Western Sahara',
    'YE' : 'Yemen',
    'ZM' : 'Zambia',
    'ZW' : 'Zimbabwe'
};

function getCountryName (countryCode) {
    if (isoCountries.hasOwnProperty(countryCode)) {
        return isoCountries[countryCode];
    } else {
        return countryCode;
    }
}
/*
** Parse query string '?attr=val&attr=val... and return parameter record
*/
function parseQueryString( url ) {
    var queryString = url.substring( url.indexOf('?') + 1 );
    if (queryString == url) return [];
    var params = {}, queries, temp, i, l;
    // Split into key/value pairs
    queries = queryString.split("&");
    // Convert the array of strings into an object
    for ( i = 0, l = queries.length; i < l; i++ ) {
        temp = queries[i].split('=');
        if (temp[1]==undefined) temp[1]='true';
        params[temp[0]] = temp[1].replace('%20',' ');
    }
    return params;
}

function reply(response,body,mimetype) {
    header={'Access-Control-Allow-Origin': '*',
            'Access-Control-Allow-Credentials': 'true',
            'Content-Type': mimetype||'text/plain'};

   response.writeHead(200,header);
   response.write(body);
   response.end();
}
};
BundleModuleCode['dos/ext/satelize']=function (module,exports){
/* 
* satelize - v0.1.3
*
* (c) 2013 Julien VALERY https://github.com/darul75/satelize, 2018-2020 modfied by bLAB Dr. Stefan Bosse
*
* Usage: satelize(ip:string|undefined,function (err,info))
*
* License: MIT 
*/

  
var http=Require("http"),
    serviceHost="ip-api.com",
    servicePort=80,
    servicePath="/json",
    serviceJSONP="";

function Satelize(options){
  this.init()
}

Satelize.prototype.init=function(options){
  return this
}

Satelize.prototype.satelize=function(a,b){
  var c=(a.ip?"/"+a.ip:"")+(a.JSONP?serviceJSONP:""),
      d=a.timeout||1e3,
      h=a.url||a.host||serviceHost,
      p=a.port||servicePort,
      e,
      f;
  if (!http) return b('ENOTSUPPORTED',null);
  if (!http.xhr && http.request) {
    // server
    e={hostname:h,path:servicePath+c,method:"GET",port:p};
    f=http.request(e,function(a){
        a.setEncoding("utf8");
        var c="";
        a.on("data",function(a){c+=a}),
        a.on("end",function(){
          try {
            return b(null,JSON.parse(c));
          } catch (err) {
            b(err.toString()+', '+e.hostname+':'+e.port);
          }
        })
    });
    return f.on("error",function(a){return b(a)}),
           f.setTimeout(d,function(){return b(new Error("timeout"))}),
           f.end(),this;
  } else {
    // Browser
    e={uri:a.url?a.url:(a.proto?a.proto:'http')+'://'+h+':'+p+servicePath+c,
       method:"GET",
       headers:{}};
    console.log(e);
    http.request(e,function(err,xhr,body){
      if (err) return b(err);
      else try { b(null,JSON.parse(body)); } catch (err) { b(err.toString()+', '+e.uri) }
    })
    return this;
  }
}

var sat = new Satelize
module.exports=sat;
};
BundleModuleCode['top/rendezvous']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.io
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2006-2018 bLAB
 **    $CREATED:     30-11-17 by sbosse.
 **    $RCS:         $Id: rendezvous.js,v 1.1 2020/02/03 09:45:01 sbosse Exp sbosse $
 **    $VERSION:     1.2.2
 **
 **    $INFO:
 **  
 **   Simple public P2P rendezvous (pairing) server with associative naming service.
 **   Primary use: Enabling JAM2JAM connections with JAMs behind NATs 
 **   (hosts in different private networks).  
 **   Uses hole-punching technique to overcome router limitations occuring with NAT traversal of
 **   UDP streams.
 **
 **
 **   A host stores tokens in a cache, One token is removed on each pairing request or if the lifetime
 **   of the token has expired. There is an upper limit of tokens that are cached.
 **
 **   $ENDOFINFO
 */
global.config={simulation:false,nonetwork:false};
var Comp = Require('com/compat');
var Io = Require('com/io');
var Chan = Require('jam/chan');
var Amp = Require('jam/amp');
var Buf = Require('dos/buf');
var Net = Require('dos/network');
var sprintf = Comp.printf.sprintf;
var ipnet = Require('net');
var dgram = Require('dgram');
var sprintf = Comp.printf.sprintf;

var onexit=false;
var start=false;

var options = {
  connport:Net.uniqport(),
  http: {address:'134.102.50.219',port:80},
  ip :  {address:'0.0.0.0',port:10001},
  verbose:1,
  CACHETMO:60000,
  MAXTOKENS: 4,  // maximal cached register tokens from each host 
  TIMER:200,
  TRIES:3,
  version:'1.2.2'
}



var usage = function (exit) {
  out('Usage: rendezvous [-h] [verbose:#] [port:#]');
  if (exit) onexit=true,start=false;
}

if (process.argv[1].indexOf('ampbroker')!=-1 || process.argv[1].indexOf('rendezvous')!=-1) 
  start=true,process.argv.forEach(function (arg) {
  var tokens=arg.split(':');
  if (arg=='-h' || arg=='-help') usage(true);
  if (tokens.length!=2) return;
  switch (tokens[0]) {
    case 'verbose':  options.verbose=Number(tokens[1]); break;
    case 'port': options.ip.port=Number(tokens[1]); break;
  }
});

// Use remote TCP connection to get this host IP (private address if behind NAT) 
function getNetworkIP(callback) {
  var socket = ipnet.createConnection(options.http.port, options.http.address);
  socket.on('connect', function() {
    callback(undefined, socket.address().address);
      socket.end();
  });
  socket.on('error', function(e) {
    callback(e, 'error');
  });
}

function timestamp() {
  return Date.now();
}

// typeof @ip = { address:string, port:number }
function Broker (_options) {
  var self=this;
  if (!(this instanceof Broker)) return new Broker(_options);

  this.options=options;
  for (var p in _options) if (_options[p]!=undefined) options[p]=_options[p];

  this.out = function (msg) {console.log('[RED '+Chan.addr2url(options.ip)+' '+Io.Time()+'] '+msg)};
  
  this.udp = dgram.createSocket('udp4');

  // The rendezvous cache (register tokens)
  this.clients = {};

  function doUntil(interval, fn, cond, arg) {
    if (cond()) return;
    fn(arg);
    return setTimeout(function() {
      doUntil(interval, fn, cond, arg);
    }, interval);  
  }
  
  // Compare two client db entries
  function eq(client1,client2) {
    var p;
    if (!client1 || !client2 ||
        client1.name != client2.name) return false;
    for(p in client1.connections) {
      if (client1.connections[p].address != client2.connections[p].address ||
          client1.connections[p].port != client2.connections[p].port) return false;       
    } 
    return true;
  }

  // Store and lookup
  function store(name,client) {
    client.time=timestamp()
    // Note: Old obsolete tokens of a client (changed IP/PORT) must be flushed!
    if (!self.clients[name] || !eq(client,self.clients[name][0])) self.clients[name]=[client];
    else if (self.clients[name].length<options.MAXTOKENS) {
      self.clients[name].push(client);
      self.clients[name].forEach(function (client) { client.time=timestamp() });
    }
  }

  // TODO don't return self entry (from:public address)
  function lookup(pat,from,all) {
    var isRegex=pat.indexOf('*')!=-1;
    if (!isRegex) {
      if (!self.clients[pat]) return all?[]:undefined;
      return all?self.clients[pat]:self.clients[pat].pop();
    } else {
      // TODO pattern search
    }
  }
  
  function search(pat) {
    var isRegex=pat.indexOf('*')!=-1,regex,result=[];
    if (!isRegex) {
      return self.clients[pat] && self.clients[pat].length?[self.clients[pat][0]]:[];
    } else {
      regex=RegExp(pat.replace(/\//g,'\\/').replace(/\*/g,'.+'));
      for(var p in self.clients) {
        if (self.clients[p] && self.clients[p].length && regex.test(p)) result.push(p);
      }
      return result;
    }  
  }
  
  function send(host, port, msg, cb) {
    var buf = Buf.Buffer();
    var data = JSON.stringify(msg);
    Buf.buf_put_int16(buf,Amp.AMMessageType.AMMCONTROL);
    Buf.buf_put_port(buf,options.connport);
    Buf.buf_put_string(buf,data);

    self.udp.send(buf.data, 0, Buf.length(buf), port, host, function(err, bytes) {
      if (err) {
        udp.close();
        self.out(sprintf('# stopped due to error: %s', err));
        process.exit(-1);
      } else {
        if (options.verbose>1) self.out('# sent '+msg.type+' to '+host+':'+port);
        if (cb) cb();
      }
    });
  }

  this.udp.on('listening', function() {
    var address = self.udp.address();
    if (options.verbose) self.out(sprintf ('# listening [%s:%s]', address.address, address.port));
  });

  this.udp.on('message', function(message, rinfo) {
    var buf = Buf.Buffer(),reply,
        port,data,msg,obj,i,j,newreg=false;

    Buf.buf_init(buf);
    Buf.buf_of_str(buf,message);
    msgtyp=Buf.buf_get_int16(buf);

    if (msgtyp != Amp.AMMessageType.AMMCONTROL) {
      if (options.verbose)
        self.out(sprintf('# Invalid message from %s:%s', 
                    rinfo.address, rinfo.port));
      return;
    }
    port = Buf.buf_get_port(buf);
    data = Buf.buf_get_string(buf);

    try {
      msg = JSON.parse(data);
    } catch (e) {
      self.out(sprintf('! Couldn\'t parse data (%s):\n%s', e, data));
      return;
    }
    
    switch (msg.type) {
      case 'lookup':
        reply=search(msg.data);
        console.log(msg.data,reply)
        send(rinfo.address,rinfo.port,{type:'lookup',from:'BROKER', data:reply, path:msg.data});        
        break;
        
      case 'register':
        obj={
            name: msg.name,
            connections: {
              local: msg.linfo, 
              public: rinfo
            },
        };
        // copy optional attributes
        for(p in msg) {
          switch (p) {
            case 'name':
            case 'linfo':
            case 'type':
              continue;
            default:
              obj[p]=msg[p];
          }
        }
        store(msg.name,obj);
        if (self.clients[msg.name].length==1) newreg=1; 
        if (options.verbose && newreg)
          self.out(sprintf('# Client registered: P %s@[%s:%s | L %s:%s]', msg.name,
                       rinfo.address, rinfo.port, msg.linfo.address, msg.linfo.port));
        send(rinfo.address,rinfo.port,{type:'registered',from:'BROKER'});
        break;

      case 'pair':
        // Pair request from one client
        var couple = [lookup(msg.from,rinfo), lookup(msg.to,rinfo) ], counter=options.TRIES;
        if (options.verbose>1)
          self.out(sprintf('# Pair request:  %s@[%s:%s] to %s [%b,%b]', msg.from,
                       rinfo.address, rinfo.port, msg.to, couple[0]!=undefined,couple[1]!=undefined));
        else if (options.verbose && couple[0]!=undefined && couple[1]!=undefined)
          self.out(sprintf('# Pairing %s@[%s:%d] and %s@[%s:%d]', 
                       msg.from,couple[0].connections.public.address, couple[0].connections.public.port,
                       msg.to,couple[1].connections.public.address, couple[1].connections.public.port));

        for (i=0; i<couple.length; i++) {
          if (!couple[i]) {
            // restore not consumed client conenction
            for(j = 0; j<couple.length; j++) {
              if (couple[j]) store(couple[j].name,couple[j]);
            }
            return self.out('Client '+(i+1)+' unknown! '+(i==0?msg.from:msg.to));
          }
        }
        // Start pairing with punch messages on both clients
        // Echo pairing to minimize deadlock possibility if pairing messages are lost
        doUntil(options.TIMER,function () {
          for (var i=0; i<couple.length; i++) {
            send(couple[i].connections.public.address, couple[i].connections.public.port, {
              type: 'pairing',
              client: couple[(i+1)%couple.length],
            });
          }
          counter--;
        },function () { return counter==0}); 
        // Only one pairing can be peformed; next time a new registering is required
        break;
    };
  });

  
}
Broker.prototype.init = function () {
  var self=this;
  // Start GC
  this.gc = setInterval(function () {
    var time=timestamp();
    for (var p in self.clients) {
      if (self.clients[p]) self.clients[p]=self.clients[p].filter(function (conn) {
        // console.log(p,time,conn.time)
        return time>conn.time+self.options.CACHETMO;
      });
    }
  },this.options.CACHETMO);
}

Broker.prototype.start = function () {
  var self=this;
  getNetworkIP(function (err,addr) {
    if (!err) {
      self.options.ip.address=addr;
      self.out('# got IP '+addr);
    }
  });
  this.udp.bind(options.ip.port,options.ip.address);  
}

Broker.prototype.stop = function () {
  if (this.gc) clearInterval(this.gc),this.gc=undefined;
}

if (start) {
  var bs = new Broker(options);
  bs.start()
}

module.exports = { Broker:Broker };
};
BundleModuleCode['jam/chan']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2006-2022 bLAB
 **    $CREATED:     09-02-16 by sbosse.
 **    $RCS:         $Id: chan.js,v 1.4 2020/02/03 09:45:01 sbosse Exp sbosse $
 **    $VERSION:     1.16.1
 **
 **    $INFO:
 **
 **  JavaScript AIOS Agent Node Communication Module offering P2P communication with another nodes
 **
 **  1. Virtual Link: Connecting virtual (logical) nodes using buffers
 **
 **  2. Physical Link: Connecting physical nodes (on the same physical host or remote hosts) 
 **  using AMP protocol and IP communication (including endpoint pairing across NAT routers
 **  using a rendezvous broker service)
 **    
 **  3. Physical Link: Connecting node processes (in a cluster on the same physical host) using process streams 
 **
 **   For IP-based communication ports an internal IP router is provided offering operation 
 **   of multiple ports and connections.
 **
 **   Communciation link object provided by 1.-3.:
 **
 **     type link = {
 **       on: method (@event,@handler) with @event={'agent'|'signal'|'class'},
 **       send: method (@msg) with @msg:{agent:string|object,to:dir}|{signal:string|object},to:dir},
 **       status: method (dir) -> boolean,
 **       count: method () -> number is returning number of received (phy only) and sent bytes,
 **       connect?:method (@to),
 **       disconnect?:method (@to),
 **       start?:method,
 **       stop?:method
 **     } 
 **
 **
 ** Events, emitter: link+  link-  error(err="link"|string,arg?)
 **
 **
 ** TODO:
 **   - Phy capability protected communication and operations
 **
 **    $ENDOFINFO
 */
var Io = Require('com/io');
var Lz = Require('os/lz-string');
var Comp = Require('com/compat');
var Buf = Require('dos/buf');
var Net = Require('dos/network');
var Command = Net.Command;
var Status = Net.Status;
var current=none;
var Aios=none;
var CBL = Require('com/cbl');
var Amp = Require('jam/amp');
var Sec = Require('jam/security');

var options = {
  debug:{},
  verbose:1,
  version:'1.15.6'
}
module.exports.options=options;

var SLINK = {
  INIT:'INIT',
  INITED:'INITED',
  RUNNING:'RUNNING'
}

/******************** 
 *  Virtual Circuit
 ********************
 */
 
var virtual= function (node1,node2,dir,options) {
  var self=this;
  this.node1=node1;
  this.node2=node2;
  this.dir=dir; // node1 -> node2
  this.buffer1=[];
  this.buffer2=[];
  this.count1={rcv:0,snd:0};
  this.count2={rcv:0,snd:0};
  this.compress=options.compress;

  /* NEWCOMM */
  this.handler1=[];
  this.handler2=[];
  
  // External API
  this.link1 = {
    control : function (msg,to,callback) {
      // TODO
    },
    on: function (event,callback) {
      var data;
      self.handler1[event]=callback;
      if (event=='agent' && self.buffer2.length>0) {
          // Agent receiver
          data=Comp.array.pop(self.buffer2);        
          if (self.compress) data=Lz.decompress(data);
          callback(data);
      }
    },

    send: function (msg) {
      var data;
      if (msg.agent) {
        // Agent migration
        data=msg.agent;
        if (self.compress) data=Lz.compress(data);
        if (self.handler2.agent) self.handler2.agent(self.compress?Lz.decompress(data):data);
        else self.buffer1.push(data);
        if (data.length) self.count1.snd += data.length; else self.count1.snd++;
      } else if (msg.signal) {
        // Signal propagation - signals are not queued
        data=msg.signal;
        if (data.length) self.count1.snd += data.length; else self.count1.snd++;
        if (self.handler2.signal) self.handler2.signal(data);
      }
    },
    count: function () {return self.count1.snd},
    status: function () {return true},      // Linked?
    virtual:true

  }

  this.link2 = {
    control : function (msg,to,callback) {
      // TODO
    },
    on: function (event,callback) {
      var data;
      self.handler2[event]=callback;
      if (event=='agent' && self.buffer1.length>0) {
          // Agent receiver
          data=Comp.array.pop(self.buffer1);        
          if (self.compress) data=Lz.decompress(data);
          callback(data);
      }
    },

    send: function (msg) {
      var data;
      if (msg.agent) {
        // Agent migration
        data=msg.agent;
        if (self.compress) data=Lz.compress(data);
        if (self.handler1.agent) self.handler1.agent(self.compress?Lz.decompress(data):data);
        else self.buffer2.push(data);
        if (data.length) self.count2.snd += data.length; else self.count2.snd++;
      } else if (msg.signal) {
        // Signal propagation - signals are not queued
        data=msg.signal;
        if (data.length) self.count2.snd += data.length; else self.count2.snd++;
        if (self.handler1.signal) self.handler1.signal(data);
      }
    },
    count: function () {return self.count2.snd},
    status: function () {return true},      // Linked?
    virtual:true

  }
};

virtual.prototype.init  = function () {};
virtual.prototype.start = function () {};
virtual.prototype.stop  = function () {};

var Virtual = function (node1,node2,dir,options) {
  var obj=new virtual(node1,node2,dir,options);
  return obj;
}

module.exports.Virtual=Virtual;
module.exports.current=function (module) { current=module.current; Aios=module; Amp.current(module); };




if (global.config.nonetwork) return;
/******************************* PHYSICAL *************************************/


/********************* 
 ** Physical Circuit
 *********************
 *  
 * Using AMP or process stream connections (TODO)
 * typeof options={
 *   broker?:url is UDP hole punching rendezvous broker 
 *   compress?:boolean,
 *   device?:string,
 *   name?:string is optional name of the comm. port e.g. the JAM node name,
 *   on?: { } is event handler object,
 *   oneway?:boolean,
 *   out?:function,
 *   proto?:'udp'|'tcp'|'http'|'hardware',
 *   rcv:url is this endpoint address,
 *   secure?:port string,
 *   snd?:url is remote endpoint address,
 *   stream?:boolean,
 *   verbose?
 *   ..
 *  }
 *  with type url = "<name>:<ipport>" | "<ip>:<ipport>" | "<ipport>"
 *  and type ipport = (1-65535) | "*"
 */
var physical= function (node,dir,options) {
  var self=this;
  options=checkOptions(options,{});
  this.options=options;
  
  this.ip=none;
  if (options.rcv) this.ip=url2addr(options.rcv);
  else this.ip={address:Amp.options.localhost,port:undefined};
  if (options.proto && this.ip) this.ip.proto=options.proto;
  
  this.node=node;
  this.dir=dir; // outgoing port (node -> dst), e.g., IP
  this.count=0;
  this.broker=options.broker;
  
  this.mode=this.options.compress?Amp.AMMode.AMO_COMPRESS:0;

  this.state = SLINK.INIT;
  this.linked = 0;
  
  this.events = [];
  this.callbacks = [];

  this.out=function (msg,async) { async?Aios.logAsync(msg):Aios.log(msg) };
  
  if (this.ip.parameter && this.ip.parameter.secure) {
    this.options.secure=Sec.Port.ofString(this.ip.parameter.secure);
    delete this.ip.parameter;
    this.dir.ip=addr2url(this.ip);
  }
  
  this.amp= Amp.Amp({
      broker:options.broker?url2addr(options.broker,this.ip.address):undefined,
      dir:this.dir,
      keepAlive : options.keepAlive,
      mode:this.options.mode,
      multicast:this.options.multicast,
      name:this.options.name,
      node:node,
      nodeid:this.options.nodeid,
      oneway:this.options.oneway,
      proto:this.options.proto,  
      pem:this.options.pem,
      rcv:this.ip,
      secure:this.options.secure,
      snd:options.snd?url2addr(options.snd):undefined,
      sharedSocket:options.sharedSocket,
      sock:options.sock,
      verbose:options.verbose,
    });

  // External API
  this.link = {
    // Control RPC
    // STD_STATUS/PS_STUN/...
    control : function (msg,to,callback) {
      var buf,data,addr=to?url2addr(to):{};
      buf=Buf.Buffer();
      msg.tid=Comp.random.int(65536/2);
      self.callbacks[msg.tid]=callback;
      Buf.buf_put_int16(buf,msg.tid);
      Buf.buf_put_string(buf,JSON.stringify(msg.args||{}));
      self.amp.request(msg.cmd,
                       buf, 
                       self.amp.mode & Amp.AMMode.AMO_MULTICAST? addr:undefined);        
      
    }, 
    on: function (event,callback) {
      self.events[event]=callback;
    },
    send: function (msg,to) {
      var buf,data,addr=to?url2addr(to):{};
      if (msg.agent) {
        data=msg.agent; // string of JSON+
        buf=Buf.Buffer();
        if (self.mode & Amp.AMMode.AMO_COMPRESS) data=Lz.compress(data);
        Buf.buf_put_string(buf,data); 
        // function request(cmd:integer,msg:Buffer,snd?:address)
        self.amp.request(Command.PS_MIGRATE, 
                         buf, 
                         self.amp.mode & Amp.AMMode.AMO_MULTICAST? addr:undefined);        
      } else if (msg.signal) {
        data=msg.signal;  // string of JSON
        // Signal propagation  
        buf=Buf.Buffer();
        if (self.mode & Amp.AMMode.AMO_COMPRESS) data=Lz.compress(data);
        Buf.buf_put_string(buf,data);   
        // function request(cmd:integer,msg:Buffer,snd?:address)
        self.amp.request(Command.PS_SIGNAL, 
                         buf, 
                         self.amp.mode & Amp.AMMode.AMO_MULTICAST? addr:undefined);        
      }
    },
    count: function () {return self.amp.count.rcv+self.amp.count.snd},
    status : function (to) {
      if (self.amp) {
        switch (to) {
          case '%':
            // P2P link?; return remote node/ip/link id
            return self.amp.status(to);
            break;
          default:
            if (to) to=url2addr(to);
            return to?self.amp.status(to.address,to.port):self.amp.status();
        }
      }
    },  // Linked?
    stats : function () {
      return {
        transferred:(self.amp.count.rcv+self.amp.count.snd),
        linked:self.linked
      }
    },
    ip:this.ip,
    mode:this.amp.mode
  }
  
  /** Connect to remote endpoint with optional capability key protection
   *  typeof @to = "<url>" | "<path>" | "<ip>:<ipport>" | "<ipport>"
   *  typeof @key = string "[<port>](<rights>)[<protport>]"
   */
  this.link.connect=function (to,key) {
    // allow url2addr DNS lookup
    url2addr(to,self.ip.address,function (addr) {
      self.amp.link(addr,true,key);
    })
  };

  // Disconnect remote endpoint
  this.link.disconnect=function (to) {
    var tokens;
    if (!to){
      if (self.amp.snd && self.amp.snd.address && self.amp.snd.port)
        self.amp.unlink(self.amp.snd);
    } else {
      var addr=url2addr(to,self.ip.address);
      self.amp.unlink(addr);
    }
  };
  this.link.init=function (cb) {
    if (self.state!=SLINK.INIT) return cb?cb():null;
    self.state=SLINK.INITED;
    return self.amp.init(cb);
  }
  this.link.start=function (cb) {
    if (self.state!=SLINK.INITED) return cb?cb():null;
    self.state=SLINK.RUNNING;
    return self.amp.start(cb);
  }
  this.link.stop=function (cb) {
    if (self.state!=SLINK.RUNNING) return cb?cb():null;
    self.state=SLINK.INITED;
    return self.amp.stop(cb); 
  }
  
  if (this.broker) this.link.lookup = function (path,callback) {
    if (self.amp.lookup) self.amp.lookup(path,callback);
    else if (callback) callback([]);
  }
  // Install route notification propagation to router (if installed)
  this.amp.on('route+',function (url,node,remote) {
    if (remote) self.ip.public=remote;
    if (self.router) self.router.add(url,self.link,node);
    self.emit('link+',url,node);
    Aios.emit('link+',url,node);
    self.linked++;
  });
  this.amp.on('route-',function (url) {
    if (self.router) self.router.delete(url,self.link);
    self.emit('link-',url);
    Aios.emit('link-',url);
    self.linked--;
  });
  this.amp.on('error',function (err,arg) {
    self.emit('error',err,arg);
  });
  if (options.on) {
    for(var p in options.on) this.on(p,options.on[p]);
  }
  // Register message receiver handler with STD/PS RPC 
  this.amp.receiver(function (handler) {
    var code,name,env,agentid,stat,obj,buf,status,tid;
    if (!handler) return;
    if (self.options.verbose>2) { 
      self.out('AMP: got request: '+ Io.inspect(handler),true);
    };
    switch (handler.cmd) {
      case Command.PS_MIGRATE:
        code = Buf.buf_get_string(handler.buf);
        // console.log(code);
        // console.log(myJam.amp.url(handler.remote))
        if (self.mode & Amp.AMMode.AMO_COMPRESS) code=Lz.decompress(code);
        if (self.events.agent) self.events.agent(code,false,handler.remote);
        break;
      case Command.PS_CREATE:
        code = Buf.buf_get_string(handler.buf);
        // console.log(code);
        // console.log(myJam.amp.url(handler.remote))
        if (self.mode & Amp.AMMode.AMO_COMPRESS) code=Lz.decompress(code);
        if (self.events.agent) self.events.agent(code,true);
        break;
      case Command.PS_WRITE:
        name = Buf.buf_get_string(handler.buf);
        code = Buf.buf_get_string(handler.buf);
        env = Buf.buf_get_string(handler.buf);
        // console.log(code);
        // console.log(myJam.amp.url(handler.remote))
        if (self.mode & Amp.AMMode.AMO_COMPRESS) code=Lz.decompress(code);
        obj={};
        try {eval("env = "+env)} catch (e) {};
        obj[name]={
          fun:code,
          env:env
        }
        if (self.events['class']) self.events['class'](obj);
        break;
      case Command.PS_SIGNAL:
        // TODO
        code = Buf.buf_get_string(handler.buf);
        // console.log(code);
        if (self.mode & Amp.AMMode.AMO_COMPRESS) code=Lz.decompress(code);
        if (self.events.signal) self.events.signal(code,handler.remote);
        break;
      case Command.PS_STUN:
        // Kill an agent (or all)
        code = Buf.buf_get_string(handler.buf);
        break;
        
      // Control Mesages
      case Command.STD_STATUS:
        // Send status of requested object (node: process table..)
        tid  = Buf.buf_get_int16(handler.buf);
        code = Buf.buf_get_string(handler.buf);
        code = JSON.parse(code);
        status = {}; 
        if (typeof code == 'string') {
          switch (code) {
            case 'node': 
            case 'links':
            case 'ports':
            case 'agents': 
              status=self.node.std_status(code); break;
          }
        }
        buf=Buf.Buffer();
        Buf.buf_put_int16(buf,tid);
        Buf.buf_put_string(buf,JSON.stringify(status));           
        self.amp.reply(Command.STD_MONITOR,  // Hack: Reply to STD_STATUS request
                       buf, 
                       self.amp.mode & Amp.AMMode.AMO_MULTICAST? handler.remote:undefined);        
        break;
      case Command.STD_INFO:
        // Send info about .. (agent ..)   
        tid  = Buf.buf_get_int16(handler.buf);
        code = JSON.parse(Buf.buf_get_string(handler.buf));
        status = {};
        if (typeof code == 'string') {
          switch (code) {
            case 'node' : status=self.node.std_info(); break;
          }
        } else if (typeof code == 'object') {
          if (code.node) {
            // get info of a specific node; probably not this but maybe attached?
            if (code.node==self.node.id) status=self.node.std_info();
            else {
              // one hop is possible if destination node is connected to this node, too
              var to = COM.lookupNode(self.node,code.node);
              console.log(to);
              // to.link.control({
              // cmd:COM.Command.STD_INFO,
              //   args:code,
              // },to.url, function (reply) {
              //    self.amp.reply(Command.STD_MONITOR()...
              // })
            }
          } 
          if (code.agent) {
            status=self.node.std_info(code); 
          }
        }
        buf=Buf.Buffer();
        Buf.buf_put_int16(buf,tid);
        Buf.buf_put_string(buf,JSON.stringify(status));

        self.amp.reply(Command.STD_MONITOR,  // Hack: Reply to STD_INFO request
                       buf, 
                       self.amp.mode & Amp.AMMode.AMO_MULTICAST? handler.remote:undefined);        
        break;
      case Command.STD_MONITOR:
        // Hack: Reply to STD_STATUS/INFO request
        tid  = Buf.buf_get_int16(handler.buf);
        code = Buf.buf_get_string(handler.buf);
        code = JSON.parse(code);
        if (self.callbacks[tid]) {
          self.callbacks[tid](code);
          delete self.callbacks[tid];
        }
        break;
    }

  });
};

physical.prototype.emit = function (event,arg,aux1,aux2) { if (this.events[event]) this.events[event](arg,aux1,aux2)};
physical.prototype.on = function (event,handler) {this.events[event]=handler};
physical.prototype.init = function (callback) { return this.link.init(callback)};
physical.prototype.start = function (callback) {return this.link.start(callback)};
physical.prototype.stop = function () {return this.link.stop()};

var Physical = function (node,dir,options) {
  var obj=new physical(node,dir,options);
  return obj;
}

module.exports.Physical=Physical;

/*************************
** IP UTILS
*************************/
var url2addr=Amp.url2addr;
var addr2url=Amp.addr2url;
var resolve=Amp.resolve;

/*  url = "<name>:<ipport>" | "<ip>:<ipport>" | "<ipport>"
 *  and ipport = (1-65535) | "*"
function url2addr(url,defaultIP) {
  var addr={address:defaultIP||'localhost',proto:'IP',port:undefined},
      parts = url.toString().split(':');
  if (parts.length==1) {
    if (Comp.string.isNumeric(parts[0])) addr.port=Number(parts[0]); // port number
    else if (parts[0].indexOf('-') != -1) addr.port=parts[0]; // port range p0-p1
    else if (parts[0]=='*') addr.port=undefined; // any port
    else addr.address=parts[0];  // ip/url
  } else return {address:parts[0],port:parts[1]=='*'?undefined:Number(parts[1])||parts[1]};
  return addr;
};

function addr2url(addr) {
  return (addr.proto?(addr.proto+'://'):'')+addr.address+':'+(addr.port?addr.port:'*')
};
function resolve (url,defaultIP) {
  var addr=url2addr(url,defaultIP);
  return addr2url(addr) 
}
 */

function addrequal(addr1,addr2) {
  return ipequal(addr1.address,addr2.address) && addr1.port==addr2.port;
}


function ipequal(ip1,ip2) {
  if (ip1==undefined || ip2==undefined) return false;
  else if ((Comp.string.equal(ip1,'localhost') || Comp.string.equal(ip1,'127.0.0.1')) &&
           (Comp.string.equal(ip2,'localhost') || Comp.string.equal(ip2,'127.0.0.1'))) return true;
  else return ip1==ip2;
}


/***********************************************
 * IP Router using AMP/UDP/TCP/HTTP links
 * Entry point for move and send operations DIR.IP
 ***********************************************
 */

function iprouter() {
  this.routingTable={};
  this.nodeTable={};
  this.links=[];
}
// Add route and link to be used for the route (and optional remote node id)
iprouter.prototype.add = function (to,link,node) {
  to=resolve(to);
  if (options.verbose) Aios.logAsync('[IP] iprouter: add route '+addr2url(link.ip)+' -> '+to+(node?'#'+node:''));
  this.routingTable[to]=link;
  this.nodeTable[to]=node;
}

// Add link device
iprouter.prototype.addLink = function (link) {
  if (!link.ip) link.ip='*';
  if (options.verbose) Aios.logAsync('[IP] iprouter: add link '+addr2url(link.ip));
  this.links.push(link);
}

// Connect to a remote endpoint
iprouter.prototype.connect = function (to,key) {
  var link,p,addr;
  to=resolve(to);
  addr=url2addr(to);
  // Search for an unconnected port!?
  for(p in this.links) {
    if (this.links[p].status(to)) return;
    if (!(this.links[p].mode&Amp.AMMode.AMO_MULTICAST) && this.links[p].status()) continue;
    if (addr.proto && this.links[p].ip && this.links[p].ip.proto != addr.proto) continue;
    link=this.links[p]; 
    break;
  }
  if (link && link.connect) {
    link.connect(to,key);
  }
}

//
iprouter.prototype.count = function (dest) {
  var res=0;
  for(var i in this.links) {
    res += this.links[i].count();
  }
  return res;
}

// Remove route
iprouter.prototype.delete = function (to) {
  to=resolve(to);
  if (this.routingTable[to]) {
    if (options.verbose) Aios.logAsync('[IP] iprouter: remove route '+addr2url(this.routingTable[to].ip)+ ' -> ' + to);
    delete this.routingTable[to];
    delete this.nodeTable[to];
  }
}

// Disconnect a remote endpoint
iprouter.prototype.disconnect = function (to) {
  // Search for a connected port!
  to=resolve(to);
  if (this.routingTable[to] && this.routingTable[to].status(to)) {
    this.routingTable[to].disconnect(to);
  }
}

/** Lookup a IP:PORT address pair of a nodeid OR contact a broker to get reachable 
 *  nodeid-IP address pairs 
 *
 */
iprouter.prototype.lookup = function (nodeid,callback) {
  var p,result=[],n=0;
  // Broker lookup with a pattern like /domain/*  (DIR.PATH)
  if (nodeid.indexOf('*')!=-1) {
    // TODO
    for (p in this.links) {
      if (this.links[p].lookup) {
        n++;
        this.links[p].lookup(nodeid,function (_result) {
          if (_result && _result.length) result=result.concat(_result);
          n--;
          if (n==0) callback(result);
        });
      }
    }
  } else for(p in this.nodeTable) { 
    if (this.nodeTable[p] == nodeid && this.routingTable[p]) return p; 
  }
} 


/** Try to find our local IP address. 
 *
 */
iprouter.prototype.ip = function () {
  for(var i in this.links) {
    if (this.links[i].ip) return this.links[i].ip;
  }
} 

/** Reverse lookup: Get the nodeid from an IP:PORT address
*   typeof @ip = string <ip:ipport>
*/
iprouter.prototype.reverse = function (ip) {
  return this.nodeTable[ip];
}



/** Send a message
*
*/

iprouter.prototype.send = function (msg) {
  msg.to=resolve(msg.to);
  if (this.routingTable[msg.to]) {
    this.routingTable[msg.to].send(msg,msg.to);
  } else {
    
  }
}

/** Start all attached devices
*
*/
iprouter.prototype.start = function (callback) {
  var cbl=CBL(callback||function(){});
  this.links.forEach(function (link) {
    cbl.push(function (next) {link.start(next)});
  });
  cbl.start();
}

iprouter.prototype.stats = function () {
  return {
    links:Object.keys(this.routingTable).length
  }
}

// Check status of link in given direction  (or any direction dest==undefined)
// OR return all current registered routes string []  (dest=='*')!
// OR return all current connected nodes   string []  (dest=='%')!
// OR return all current registered links (ip) string [] (dest=='$')!
iprouter.prototype.status = function (dest) {
  var res,p;
  if (dest==undefined) {
    // Any registered routes?
    for(p in this.routingTable) { if (this.routingTable[p]) return true }
  } else if (dest=='*') {
    res=[];
    for(p in this.routingTable) { if (this.routingTable[p]) res.push(p) }
    return res;
  } else if (dest=='%') {
    res=[];
    for(p in this.nodeTable) { 
      if (this.nodeTable[p] && this.routingTable[p]) res.push(this.nodeTable[p]); 
    }
    return res;
  } else {
    dest=resolve(dest);
    if (this.routingTable[dest])
      return this.routingTable[dest].status(dest);
    else
      return false;
  }
  return false;
}

// Stop all attached devices
iprouter.prototype.stop = function (callback) {
  var cbl=CBL(callback||function(){});
  this.links.forEach(function (link) {
    cbl.push(function (next) {link.stop(next)});
  });
  cbl.start();
}


module.exports.iprouter=iprouter;

module.exports.Command=Command
module.exports.Status=Status

module.exports.url2addr=url2addr;
module.exports.addr2url=addr2url;
};
BundleModuleCode['os/lz-string']=function (module,exports){
// Copyright (c) 2013 Pieroxy <pieroxy@pieroxy.net>
// This work is free. You can redistribute it and/or modify it
// under the terms of the WTFPL, Version 2
// For more information see LICENSE.txt or http://www.wtfpl.net/
//
// For more information, the home page:
// http://pieroxy.net/blog/pages/lz-string/testing.html
//
// LZ-based compression algorithm, version 1.4.4
var LZString = (function() {

// private property
var f = String.fromCharCode;
var keyStrBase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var keyStrUriSafe = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$";
var baseReverseDic = {};

function getBaseValue(alphabet, character) {
  if (!baseReverseDic[alphabet]) {
    baseReverseDic[alphabet] = {};
    for (var i=0 ; i<alphabet.length ; i++) {
      baseReverseDic[alphabet][alphabet.charAt(i)] = i;
    }
  }
  return baseReverseDic[alphabet][character];
}

var LZString = {
  compressToBase64 : function (input) {
    if (input == null) return "";
    var res = LZString._compress(input, 6, function(a){return keyStrBase64.charAt(a);});
    switch (res.length % 4) { // To produce valid Base64
    default: // When could this happen ?
    case 0 : return res;
    case 1 : return res+"===";
    case 2 : return res+"==";
    case 3 : return res+"=";
    }
  },

  decompressFromBase64 : function (input) {
    if (input == null) return "";
    if (input == "") return null;
    return LZString._decompress(input.length, 32, function(index) { return getBaseValue(keyStrBase64, input.charAt(index)); });
  },

  compressToUTF16 : function (input) {
    if (input == null) return "";
    return LZString._compress(input, 15, function(a){return f(a+32);}) + " ";
  },

  decompressFromUTF16: function (compressed) {
    if (compressed == null) return "";
    if (compressed == "") return null;
    return LZString._decompress(compressed.length, 16384, function(index) { return compressed.charCodeAt(index) - 32; });
  },

  //compress into uint8array (UCS-2 big endian format)
  compressToUint8Array: function (uncompressed) {
    var compressed = LZString.compress(uncompressed);
    var buf=new Uint8Array(compressed.length*2); // 2 bytes per character

    for (var i=0, TotalLen=compressed.length; i<TotalLen; i++) {
      var current_value = compressed.charCodeAt(i);
      buf[i*2] = current_value >>> 8;
      buf[i*2+1] = current_value % 256;
    }
    return buf;
  },

  //decompress from uint8array (UCS-2 big endian format)
  decompressFromUint8Array:function (compressed) {
    if (compressed===null || compressed===undefined){
        return LZString.decompress(compressed);
    } else {
        var buf=new Array(compressed.length/2); // 2 bytes per character
        for (var i=0, TotalLen=buf.length; i<TotalLen; i++) {
          buf[i]=compressed[i*2]*256+compressed[i*2+1];
        }

        var result = [];
        buf.forEach(function (c) {
          result.push(f(c));
        });
        return LZString.decompress(result.join(''));

    }

  },


  //compress into a string that is already URI encoded
  compressToEncodedURIComponent: function (input) {
    if (input == null) return "";
    return LZString._compress(input, 6, function(a){return keyStrUriSafe.charAt(a);});
  },

  //decompress from an output of compressToEncodedURIComponent
  decompressFromEncodedURIComponent:function (input) {
    if (input == null) return "";
    if (input == "") return null;
    input = input.replace(/ /g, "+");
    return LZString._decompress(input.length, 32, function(index) { return getBaseValue(keyStrUriSafe, input.charAt(index)); });
  },

  compress: function (uncompressed) {
    return LZString._compress(uncompressed, 16, function(a){return f(a);});
  },
  _compress: function (uncompressed, bitsPerChar, getCharFromInt) {
    if (uncompressed == null) return "";
    var i, value,
        context_dictionary= {},
        context_dictionaryToCreate= {},
        context_c="",
        context_wc="",
        context_w="",
        context_enlargeIn= 2, // Compensate for the first entry which should not count
        context_dictSize= 3,
        context_numBits= 2,
        context_data=[],
        context_data_val=0,
        context_data_position=0,
        ii;

    for (ii = 0; ii < uncompressed.length; ii += 1) {
      context_c = uncompressed.charAt(ii);
      if (!Object.prototype.hasOwnProperty.call(context_dictionary,context_c)) {
        context_dictionary[context_c] = context_dictSize++;
        context_dictionaryToCreate[context_c] = true;
      }

      context_wc = context_w + context_c;
      if (Object.prototype.hasOwnProperty.call(context_dictionary,context_wc)) {
        context_w = context_wc;
      } else {
        if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate,context_w)) {
          if (context_w.charCodeAt(0)<256) {
            for (i=0 ; i<context_numBits ; i++) {
              context_data_val = (context_data_val << 1);
              if (context_data_position == bitsPerChar-1) {
                context_data_position = 0;
                context_data.push(getCharFromInt(context_data_val));
                context_data_val = 0;
              } else {
                context_data_position++;
              }
            }
            value = context_w.charCodeAt(0);
            for (i=0 ; i<8 ; i++) {
              context_data_val = (context_data_val << 1) | (value&1);
              if (context_data_position == bitsPerChar-1) {
                context_data_position = 0;
                context_data.push(getCharFromInt(context_data_val));
                context_data_val = 0;
              } else {
                context_data_position++;
              }
              value = value >> 1;
            }
          } else {
            value = 1;
            for (i=0 ; i<context_numBits ; i++) {
              context_data_val = (context_data_val << 1) | value;
              if (context_data_position ==bitsPerChar-1) {
                context_data_position = 0;
                context_data.push(getCharFromInt(context_data_val));
                context_data_val = 0;
              } else {
                context_data_position++;
              }
              value = 0;
            }
            value = context_w.charCodeAt(0);
            for (i=0 ; i<16 ; i++) {
              context_data_val = (context_data_val << 1) | (value&1);
              if (context_data_position == bitsPerChar-1) {
                context_data_position = 0;
                context_data.push(getCharFromInt(context_data_val));
                context_data_val = 0;
              } else {
                context_data_position++;
              }
              value = value >> 1;
            }
          }
          context_enlargeIn--;
          if (context_enlargeIn == 0) {
            context_enlargeIn = Math.pow(2, context_numBits);
            context_numBits++;
          }
          delete context_dictionaryToCreate[context_w];
        } else {
          value = context_dictionary[context_w];
          for (i=0 ; i<context_numBits ; i++) {
            context_data_val = (context_data_val << 1) | (value&1);
            if (context_data_position == bitsPerChar-1) {
              context_data_position = 0;
              context_data.push(getCharFromInt(context_data_val));
              context_data_val = 0;
            } else {
              context_data_position++;
            }
            value = value >> 1;
          }


        }
        context_enlargeIn--;
        if (context_enlargeIn == 0) {
          context_enlargeIn = Math.pow(2, context_numBits);
          context_numBits++;
        }
        // Add wc to the dictionary.
        context_dictionary[context_wc] = context_dictSize++;
        context_w = String(context_c);
      }
    }

    // Output the code for w.
    if (context_w !== "") {
      if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate,context_w)) {
        if (context_w.charCodeAt(0)<256) {
          for (i=0 ; i<context_numBits ; i++) {
            context_data_val = (context_data_val << 1);
            if (context_data_position == bitsPerChar-1) {
              context_data_position = 0;
              context_data.push(getCharFromInt(context_data_val));
              context_data_val = 0;
            } else {
              context_data_position++;
            }
          }
          value = context_w.charCodeAt(0);
          for (i=0 ; i<8 ; i++) {
            context_data_val = (context_data_val << 1) | (value&1);
            if (context_data_position == bitsPerChar-1) {
              context_data_position = 0;
              context_data.push(getCharFromInt(context_data_val));
              context_data_val = 0;
            } else {
              context_data_position++;
            }
            value = value >> 1;
          }
        } else {
          value = 1;
          for (i=0 ; i<context_numBits ; i++) {
            context_data_val = (context_data_val << 1) | value;
            if (context_data_position == bitsPerChar-1) {
              context_data_position = 0;
              context_data.push(getCharFromInt(context_data_val));
              context_data_val = 0;
            } else {
              context_data_position++;
            }
            value = 0;
          }
          value = context_w.charCodeAt(0);
          for (i=0 ; i<16 ; i++) {
            context_data_val = (context_data_val << 1) | (value&1);
            if (context_data_position == bitsPerChar-1) {
              context_data_position = 0;
              context_data.push(getCharFromInt(context_data_val));
              context_data_val = 0;
            } else {
              context_data_position++;
            }
            value = value >> 1;
          }
        }
        context_enlargeIn--;
        if (context_enlargeIn == 0) {
          context_enlargeIn = Math.pow(2, context_numBits);
          context_numBits++;
        }
        delete context_dictionaryToCreate[context_w];
      } else {
        value = context_dictionary[context_w];
        for (i=0 ; i<context_numBits ; i++) {
          context_data_val = (context_data_val << 1) | (value&1);
          if (context_data_position == bitsPerChar-1) {
            context_data_position = 0;
            context_data.push(getCharFromInt(context_data_val));
            context_data_val = 0;
          } else {
            context_data_position++;
          }
          value = value >> 1;
        }


      }
      context_enlargeIn--;
      if (context_enlargeIn == 0) {
        context_enlargeIn = Math.pow(2, context_numBits);
        context_numBits++;
      }
    }

    // Mark the end of the stream
    value = 2;
    for (i=0 ; i<context_numBits ; i++) {
      context_data_val = (context_data_val << 1) | (value&1);
      if (context_data_position == bitsPerChar-1) {
        context_data_position = 0;
        context_data.push(getCharFromInt(context_data_val));
        context_data_val = 0;
      } else {
        context_data_position++;
      }
      value = value >> 1;
    }

    // Flush the last char
    while (true) {
      context_data_val = (context_data_val << 1);
      if (context_data_position == bitsPerChar-1) {
        context_data.push(getCharFromInt(context_data_val));
        break;
      }
      else context_data_position++;
    }
    return context_data.join('');
  },

  decompress: function (compressed) {
    if (compressed == null) return "";
    if (compressed == "") return null;
    return LZString._decompress(compressed.length, 32768, function(index) { return compressed.charCodeAt(index); });
  },

  _decompress: function (length, resetValue, getNextValue) {
    var dictionary = [],
        next,
        enlargeIn = 4,
        dictSize = 4,
        numBits = 3,
        entry = "",
        result = [],
        i,
        w,
        bits, resb, maxpower, power,
        c,
        data = {val:getNextValue(0), position:resetValue, index:1};

    for (i = 0; i < 3; i += 1) {
      dictionary[i] = i;
    }

    bits = 0;
    maxpower = Math.pow(2,2);
    power=1;
    while (power!=maxpower) {
      resb = data.val & data.position;
      data.position >>= 1;
      if (data.position == 0) {
        data.position = resetValue;
        data.val = getNextValue(data.index++);
      }
      bits |= (resb>0 ? 1 : 0) * power;
      power <<= 1;
    }

    switch (next = bits) {
      case 0:
          bits = 0;
          maxpower = Math.pow(2,8);
          power=1;
          while (power!=maxpower) {
            resb = data.val & data.position;
            data.position >>= 1;
            if (data.position == 0) {
              data.position = resetValue;
              data.val = getNextValue(data.index++);
            }
            bits |= (resb>0 ? 1 : 0) * power;
            power <<= 1;
          }
        c = f(bits);
        break;
      case 1:
          bits = 0;
          maxpower = Math.pow(2,16);
          power=1;
          while (power!=maxpower) {
            resb = data.val & data.position;
            data.position >>= 1;
            if (data.position == 0) {
              data.position = resetValue;
              data.val = getNextValue(data.index++);
            }
            bits |= (resb>0 ? 1 : 0) * power;
            power <<= 1;
          }
        c = f(bits);
        break;
      case 2:
        return "";
    }
    dictionary[3] = c;
    w = c;
    result.push(c);
    while (true) {
      if (data.index > length) {
        return "";
      }

      bits = 0;
      maxpower = Math.pow(2,numBits);
      power=1;
      while (power!=maxpower) {
        resb = data.val & data.position;
        data.position >>= 1;
        if (data.position == 0) {
          data.position = resetValue;
          data.val = getNextValue(data.index++);
        }
        bits |= (resb>0 ? 1 : 0) * power;
        power <<= 1;
      }

      switch (c = bits) {
        case 0:
          bits = 0;
          maxpower = Math.pow(2,8);
          power=1;
          while (power!=maxpower) {
            resb = data.val & data.position;
            data.position >>= 1;
            if (data.position == 0) {
              data.position = resetValue;
              data.val = getNextValue(data.index++);
            }
            bits |= (resb>0 ? 1 : 0) * power;
            power <<= 1;
          }

          dictionary[dictSize++] = f(bits);
          c = dictSize-1;
          enlargeIn--;
          break;
        case 1:
          bits = 0;
          maxpower = Math.pow(2,16);
          power=1;
          while (power!=maxpower) {
            resb = data.val & data.position;
            data.position >>= 1;
            if (data.position == 0) {
              data.position = resetValue;
              data.val = getNextValue(data.index++);
            }
            bits |= (resb>0 ? 1 : 0) * power;
            power <<= 1;
          }
          dictionary[dictSize++] = f(bits);
          c = dictSize-1;
          enlargeIn--;
          break;
        case 2:
          return result.join('');
      }

      if (enlargeIn == 0) {
        enlargeIn = Math.pow(2, numBits);
        numBits++;
      }

      if (dictionary[c]) {
        entry = dictionary[c];
      } else {
        if (c === dictSize) {
          entry = w + w.charAt(0);
        } else {
          return null;
        }
      }
      result.push(entry);

      // Add w+entry[0] to the dictionary.
      dictionary[dictSize++] = w + entry.charAt(0);
      enlargeIn--;

      w = entry;

      if (enlargeIn == 0) {
        enlargeIn = Math.pow(2, numBits);
        numBits++;
      }

    }
  }
};
  return LZString;
})();

if (typeof define === 'function' && define.amd) {
  define(function () { return LZString; });
} else if( typeof module !== 'undefined' && module != null ) {
  module.exports = LZString
}
};
BundleModuleCode['dos/buf']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2006-2018 bLAB
 **    $CREATED:     29-4-15 by sbosse.
 **    $RCS:         $Id$
 **    $VERSION:     1.1.5
 **
 **    $INFO:
 **
 **  DOS: Buffer Management
 **
 **    $ENDOFINFO
 */
"use strict";
var log = 0;

var util = Require('util');
var Io = Require('com/io');
var Comp = Require('com/compat');
var String = Comp.string;
var Array = Comp.array;
var Perv = Comp.pervasives;
var des48 = Require('dos/des48');
var Rand = Comp.random;
var Net = Require('dos/network');
var Status = Net.Status;
var Fs = Require('fs');


var SIZEOF_INT16 = 2;
var SIZEOF_INT32 = 4;
var PORT_SIZE = 6;
var PRIV_SIZE =  PORT_SIZE+SIZEOF_INT32;
var CAP_SIZE = PORT_SIZE+PRIV_SIZE;

/** Generic buffer, union with rpcio object
 ** Argument: optional, hex-ascci string or number (size), passed to Buffer instantiation
 *
 *
 * @param {number|string|Buffer} [data]
 * @constructor
 */
var buffer = function (data) {
    var size;
    this.pos=0;
    if (Comp.isNumber(data)) {
        this.data=new Buffer(data);
    } else if (Comp.isString(data)) {
        this.data=new Buffer('');
        buf_of_hex(this,data)
    } else if (Comp.isArray(data)) {
        this.data=new Buffer(data);
    } else if (typeof data == "object" && data.constructor === Buffer) {
        this.data=data;
    } else this.data=new Buffer('');
};

/** Extend a buffer to new size (buf.pos+off).
 *
 * @param buf
 * @param off
 */
function buf_extend(buf,off) {
    if (buf.data.length<(buf.pos+off)) {
        buf.data=Buffer.concat([buf.data,new Buffer(off-(buf.data.length-buf.pos))]);
    }
}

/** Expand buffer to new size.
 *
 * @param buf
 * @param size
 */
function buf_expand(buf,size) {
    if (buf.data.length<size) {
        buf.data=Buffer.concat([buf.data,new Buffer(size-buf.data.length)]);
    }
}

/** Shrink buffer to new size.
 *
 * @param buf
 * @param size
 */
function buf_shrink(buf,size) {
    if (buf.data.length>size) {
        buf.data=Buffer.slice(buf.data,size);
    }
}

/*
 ** BUFFER encoding and decoding of native data types
 ** Supported objects: rpcio, buffer.
 ** Supported native data types: int16, int32, string, float, port, private, capability, ...
 ** ALL buffer data is stored in byte buffers that extends automatically (buf_put_XX).
 */
function buf_put_string (buf,str) {
    buf_extend(buf,str.length+1);
    for(var i=0;i<str.length;i++) {
        buf.data[buf.pos]=Perv.int_of_char(String.get(str,i));
        buf.pos++;
    }
    buf.data[buf.pos]=0;
    buf.pos++;
}

function buf_get_string (buf) {
    var str='';
    var end=buf.data.length;
    var finished=false;

    while (!finished && buf.pos < end) {
        if (buf.data[buf.pos]==0) finished=true; else {
            str = str + Perv.char_of_int(buf.data[buf.pos]);
            buf.pos++;
        }
    }
    buf.pos++;
    return str;
}

/*
** Convert byte buffer to ASCII two-digit hexadecimal text representation and vice versa
 */
function buf_to_hex (buf) {
    /*
    var str='';
    var len=buf.data.length;
    for(var i=0;i<len;i++) {
        str=str+String.format_hex(buf.data[i],2);
    }
    return str;
    */
    return buf.data.toString('hex');
}

function buf_of_hex  (buf,str) {
    /*
    var len=str.length/2;
    var pos=0;
    buf.pos=0;
    buf_extend(buf,len);
    for(var i=0;i<len;i++) {
        buf.data[i]=String.parse_hex(str, pos, 2);
        pos=pos+2;
    }
    */
    buf.pos=0;
    buf.data= new Buffer(str,'hex');
}

/*
 ** Convert byte buffer to strings and vice versa
 */
function buf_to_str (buf) {
    var str=buf.data.toString('binary');
    return str;
}

function buf_of_str  (buf,str) {
    buf.pos=0;
    buf.data=new Buffer(str,'binary');
    return buf;
}

/** Put a string to a buffer w/o EOS
 *
 * @param buf
 * @param {string} str
 */
function buf_put_bytes (buf,str) {
    buf_extend(buf,str.length);
    for(var i=0;i<str.length;i++) {
        var n=Perv.int_of_char(String.get(str,i));
        buf.data[buf.pos]=n;
        buf.pos++;
    }
    // No final EOS marker!
}

/** Get number of bytes from buffer and store in string (w/o EOS)
 *
 * @param buf
 * @param size
 * @returns {string}
 */
function buf_get_bytes (buf,size) {
    var i=0;
    var str='';
    var end=buf.data.length;
    var finished=false;

    while (!finished && buf.pos < end) {
        if (i==size) finished=true; else {
            str = str + Perv.char_of_int(buf.data[buf.pos]);
            buf.pos++;i++;
        }
    }
    return str;
}

function buf_put_int16 (buf,n) {
    buf_extend(buf,2);
    buf.data[buf.pos]=n & 0xff;
    buf.data[buf.pos+1]=(n >> 8) & 0xff;
    buf.pos=buf.pos+2;
}

function buf_get_int16 (buf) {
    var n=0;
    var end=buf.data.length;
    if (buf.pos+2 <= end) {
        n = buf.data[buf.pos];
        n = n | (buf.data[buf.pos+1] << 8);
        buf.pos = buf.pos + 2;
        if (n&0x8000) return (n-0x10000); else return (n);
    } else throw Status.BUF_OVERFLOW;
}

function buf_put_int32 (buf,n) {
    buf_extend(buf,4);
    buf.data[buf.pos]=n & 0xff;
    buf.data[buf.pos+1]=(n >> 8) & 0xff;
    buf.data[buf.pos+2]=(n >> 16) & 0xff;
    buf.data[buf.pos+3]=(n >> 24) & 0xff;
    buf.pos=buf.pos+4;
}

function buf_get_int32 (buf) {
    var n=0;
    var end=buf.data.length;
    if (buf.pos+4 <= end) {
        n = buf.data[buf.pos];
        n = n | (buf.data[buf.pos+1] << 8);
        n = n | (buf.data[buf.pos+2] << 16);
        n = n | (buf.data[buf.pos+3] << 24);
        buf.pos = buf.pos + 4;
        // TBD: Sign check???
        return (n);
    } else throw Status.BUF_OVERFLOW;
}

function buf_put_port (buf,port) {
    buf_extend(buf,Net.PORT_SIZE);
    for(var i=0;i<Net.PORT_SIZE;i++) {
        var n=Perv.int_of_char(String.get(port,i));
        buf.data[buf.pos]=n;
        buf.pos++;
    }
}

function buf_get_port (buf) {
    var port='';
    var end=buf.data.length;
    if (buf.pos+Net.PORT_SIZE <= end) {
        for (var i = 0; i < Net.PORT_SIZE; i++) {
            port = port + Perv.char_of_int(buf.data[buf.pos]);
            buf.pos++;
        }
        return port;
    } else throw Status.BUF_OVERFLOW;
}

function buf_put_priv (buf,priv) {
    buf_extend(buf,Net.PRIV_SIZE);
    buf.data[buf.pos]=priv.prv_obj & 0xff;
    buf.data[buf.pos+1]=(priv.prv_obj >> 8) & 0xff;
    buf.data[buf.pos+2]=(priv.prv_obj >> 16) & 0xff;
    buf.data[buf.pos+3]=priv.prv_rights & 0xff;
    buf.pos=buf.pos+4;
    buf_put_port(buf,priv.prv_rand);
}

function buf_get_priv (buf,priv) {
    var n;
    var end=buf.data.length;
    if (buf.pos+(Net.PRIV_SIZE) <= end) {
        if (priv == undefined) priv = Net.Private();
        n = buf.data[buf.pos];
        n = n | (buf.data[buf.pos+1] << 8);
        n = n | (buf.data[buf.pos+2] << 16);
        priv.prv_obj=n;
        priv.prv_rights=buf.data[buf.pos+3];
        buf.pos=buf.pos+4;
        priv.prv_rand=buf_get_port(buf);
        return priv;
    } else throw Status.BUF_OVERFLOW;
}

function buf_put_cap (buf,cap) {
    buf_put_port(buf,cap.cap_port);
    buf_put_priv(buf,cap.cap_priv);
}

function buf_get_cap (buf,cap) {
    var end=buf.data.length;
    if (buf.pos+(Net.CAP_SIZE) <= end) {
        if (cap == undefined) cap = Net.Capability();
        cap.cap_port=buf_get_port(buf);
        buf_get_priv(buf,cap.cap_priv);
        return cap;
    } else throw Status.BUF_OVERFLOW;
}

function buf_put_hdr (buf,hdr) {
    buf_put_port(buf,hdr.h_port);
    buf_put_priv(buf,hdr.h_priv);
    buf_put_int32(buf,hdr.h_command);
    buf_put_int32(buf,hdr.h_status);
}

function buf_get_hdr (buf,hdr) {
    if (hdr==undefined) hdr=Net.Header();
    hdr.h_port=buf_get_port(buf);
    buf_get_priv(buf,hdr.h_priv);
    hdr.h_command=buf_get_int32(buf);
    hdr.h_status=buf_get_int32(buf);
    return hdr;
}

/** TODO: buf blit
 *
 * @param buf
 * @param bufsrc
 * @param [srcoff]
 * @param [len]
 */
function buf_put_buf (buf,bufsrc,srcoff,len) {
    if (srcoff==undefined) srcoff=0;
    if (len==undefined) len=bufsrc.data.length;
    buf_extend(buf,len);
    for(var i=0;i<len;i++) {
        buf.data[buf.pos]=bufsrc.data[srcoff+i];
        buf.pos++;
    }
}
/** TODO: buf blit
 *
 * @param buf
 * @param bufdst
 * @param dstoff
 * @param len
 */
function buf_get_buf (buf,bufdst,dstoff,len) {
    buf_extend(bufdst,dstoff+len);
    for(var i=0;i<len;i++) {
        bufdst.data[dstoff+i]=buf.data[buf.pos];
        buf.pos++;
    }
}

function buf_pad (buf,size,byte) {
    if (buf.data.length < size) buf_extend(buf,size-buf.data.length);
    if (byte!=undefined) {
        while (buf.pos < size) {
            buf.data[buf.pos] = byte;
            buf.pos++;
        }
    } else buf.pos=size-1;
}

function buf_set_pos (buf,off) {
    if (off >= buf.data.length) buf_expand(buf,off+1);
    buf.pos=off;
}
/**
 * @param {file} fd
 * @param {buffer} buf
 * @param {number} [off]        file offset
 * @param {number} [len]
 * @returns {number} n
 */
function buf_write (fd,buf,off,len) {
    var n;
    if (off==undefined) n=Io.write_buf(fd,buf.data,0,buf.data.length);
    else {
        if (len==undefined) len=buf.data.length;
        n=Io.write_buf(fd,buf.data,0,len,off);
    }
    return n;
}
/**
 * @param {file} fd
 * @param {buffer} buf
 * @param {number} off          file offset
 * @param {number} len
 * @returns {number} n
 */
function buf_read (fd,buf,off,len) {
    var n;
    buf_expand(buf,len);
    n=Io.read_buf(fd,buf.data,0,len,off);
    buf.pos=0;
    return n;
}

function buf_print(buf) {
    var str='[';
    for(var i=0;i<buf.data.length;i++) {
        if(i>0) str=str+','+buf.data[i];
        else str=str+buf.data[i];
    }
    return str+']'+buf.pos+':'+buf.data.length;
}

function buf_set (buf,off,byte) {
    if (off >= buf.data.length) buf_expand(buf,off+1);
    buf.data[off]=byte;
}

function buf_get (buf,off) {
    return buf.data[off];
}

/** Reset buffer
 *
 * @param buf
 */
function buf_init (buf) {
    buf.data=new Buffer('');
    buf.pos=0;
}

function buf_copy (dst,src) {
    dst.data=new Buffer(src.data);
    dst.pos=0;
}

function buf_blit (dst,dstoff,src,srcoff,len) {
    buf_expand(dst,dstoff+len);
    src.data.copy(dst.data,dstoff,srcoff,srcoff+len);
    dst.pos=0;
}


/**
 *
 * @type {{SIZEOF_INT16: number, SIZEOF_INT32: number, PORT_SIZE: number, PRIV_SIZE: number, CAP_SIZE: number, Buffer: Function, buf_put_string: buf_put_string, buf_put_int16: buf_put_int16, buf_put_int32: buf_put_int32, buf_put_port: buf_put_port, buf_put_priv: buf_put_priv, buf_put_cap: buf_put_cap, buf_put_hdr: buf_put_hdr, buf_put_buf: buf_put_buf, buf_put_bytes: buf_put_bytes, buf_get_string: buf_get_string, buf_get_int16: buf_get_int16, buf_get_int32: buf_get_int32, buf_get_port: buf_get_port, buf_get_priv: buf_get_priv, buf_get_cap: buf_get_cap, buf_get_hdr: buf_get_hdr, buf_get_buf: buf_get_buf, buf_get_bytes: buf_get_bytes, buf_pad: buf_pad, buf_set: buf_set, buf_get: buf_get, buf_set_pos: buf_set_pos, buf_init: buf_init, buf_blit: buf_blit, buf_copy: buf_copy, buf_extend: buf_extend, buf_expand: buf_expand, buf_shrink: buf_shrink, buf_read: buf_read, buf_write: buf_write, buf_print: buf_print, buf_to_hex: buf_to_hex, buf_of_hex: buf_of_hex, buf_to_str: buf_to_str, buf_of_str: buf_of_str}}
 */
module.exports = {
    SIZEOF_INT16: SIZEOF_INT16,
    SIZEOF_INT32: SIZEOF_INT32,
    PORT_SIZE: PORT_SIZE,
    PRIV_SIZE: PRIV_SIZE,
    CAP_SIZE: CAP_SIZE,
    /**
     *
     * @param {number|string|Buffer} [data]
     * @returns {buffer}
     */
    Buffer: function Buffer(data) {
        var obj = new buffer(data);
        Object.preventExtensions(obj);
        return obj;
    },
    // Buffer data operations
    buf_put_string:buf_put_string,
    buf_put_int16:buf_put_int16,
    buf_put_int32:buf_put_int32,
    buf_put_port:buf_put_port,
    buf_put_priv:buf_put_priv,
    buf_put_cap:buf_put_cap,
    buf_put_hdr:buf_put_hdr,
    buf_put_buf:buf_put_buf,
    buf_put_bytes:buf_put_bytes,
    buf_get_string:buf_get_string,
    buf_get_int16:buf_get_int16,
    buf_get_int32:buf_get_int32,
    buf_get_port:buf_get_port,
    buf_get_priv:buf_get_priv,
    buf_get_cap:buf_get_cap,
    buf_get_hdr:buf_get_hdr,
    buf_get_buf:buf_get_buf,
    buf_get_bytes:buf_get_bytes,
    buf_pad:buf_pad,
    buf_set:buf_set,
    buf_get:buf_get,
    buf_set_pos:buf_set_pos,
    buf_init:buf_init,
    buf_blit:buf_blit,
    buf_copy:buf_copy,
    buf_extend:buf_extend,
    buf_expand:buf_expand,
    buf_shrink:buf_shrink,
    // Buffer IO
    buf_read:buf_read,
    buf_write:buf_write,
    buf_print:buf_print,
    // Conversion
    buf_to_hex:buf_to_hex,
    buf_of_hex:buf_of_hex,
    buf_to_str:buf_to_str,
    buf_of_str:buf_of_str,

    length: function(buf) {
        if (buf.data==undefined) return 0;
        else return buf.data.length;
    }
};
};
BundleModuleCode['dos/des48']=function (module,exports){
/**
 **      ==================================
 **      OOOO   OOOO OOOO  O      O   OOOO
 **      O   O  O    O     O     O O  O   O
 **      O   O  O    O     O     O O  O   O
 **      OOOO   OOOO OOOO  O     OOO  OOOO
 **      O   O     O    O  O    O   O O   O
 **      O   O     O    O  O    O   O O   O
 **      OOOO   OOOO OOOO  OOOO O   O OOOO
 **      ==================================
 **      BSSLAB, Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR.
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2006-2022 BSSLAB
 **    $CREATED:     3/30/15 by sbosse.
 **    $VERSION:     1.1.5
 **
 **    $INFO:
 **
 **  DOS: Encryption 48bit
 **
 **    $ENDOFINFO
 */
var util = Require('util');
var Io = Require('com/io');
var Comp = Require('com/compat');
var Array = Comp.array;
var assert = Comp.assert;


const des_HBS = 24;
const des_BS = des_HBS * 2;


/*
** Initial permutation,
*/

var des_IP = [
    23, 27, 34, 44, 37, 17, 12, 42,
    3, 32, 41, 29, 20,  2,  1, 10,
    0, 28, 40,  6,  7, 11, 16,  8,
    25, 30, 14, 26, 47, 38, 19, 43,
    18,  5, 35, 39, 36, 21,  4, 45,
    24, 22, 13, 33, 31,  9, 15, 46 ];

/*
** Final permutation, FP = IP^(-1)
*/

var des_FP = [
    16, 14, 13,  8, 38, 33, 19, 20,
    23, 45, 15, 21,  6, 42, 26, 46,
    22,  5, 32, 30, 12, 37, 41,  0,
    40, 24, 27,  1, 17, 11, 25, 44,
    9, 43,  2, 34, 36,  4, 29, 35,
    18, 10,  7, 31,  3, 39, 47, 28 ];

/*
** Permuted-choice 1 from the key bits
** to yield C and D.
** Note that bits 8,16... are left out:
    ** They are intended for a parity check.
*/

var des_PC1_C = [
    57,49,41,33,25,17, 9,
    1,58,50,42,34,26,18,
    10, 2,59,51,43,35,27,
    19,11, 3,60,52,44,36 ];

var des_PC1_D = [
    63,55,47,39,31,23,15,
    7,62,54,46,38,30,22,
    14, 6,61,53,45,37,29,
    21,13, 5,28,20,12, 4 ];


/*
** Sequence of shifts used for the key schedule.
*/

var des_shifts = [
    1,1,2,2,2,2,2,2,1,2,2,2,2,2,2,1 ];



/*
** Permuted-choice 2, to pick out the bits from
** the CD array that generate the key schedule.
*/

var des_PC2_C = [
    14,17,11,24, 1, 5,
    3,28,15, 6,21,10,
    23,19,12, 4,26, 8,
    16, 7,27,20,13, 2 ];

var des_PC2_D = [
    41,52,31,37,47,55,
    30,40,51,45,33,48,
    44,49,39,56,34,53,
    46,42,50,36,29,32 ];

/*
** The C and D arrays used to calculate the key schedule.
*/


var des_C = Array.create(56,0);
// des_D = des_C[28]
var des_D_get = function (i) {return des_C[i+28]};
var des_D_set  =  function (i,sval) { des_C[i+28] = sval };

/*
** The key schedule.
** Generated from the key.
*/

var des_KS= Array.create_matrix(16,48,0);

var des_OWsetkey = function(key) {
    var ks = [];
    var t = 0;
    var i,j,k;
    /*
    ** First, generate C and D by permuting
    ** the key.  The low order bit of each
    ** 8-bit char is not used, so C and D are only 28
    ** bits apiece.
    */

    for(i = 0;i < 28;i++) {

        var index1 = des_PC1_C[i] - 1;
        var index2 = des_PC1_D[i] - 1;

        des_C[i] = key[index1];
        des_D_set(i,key[index2]);
    }

    /*
    ** To generate Ki, rotate C and D according
    ** to schedule and pick up a permutation
    ** using PC2.
    */


    for (i = 0 ;i< 16;i++) {

        ks = des_KS[i];

        // rotate
        for (k = 0; k < des_shifts[i]; k++) {
            t = des_C[0];

            for (j = 0; j < 27; j++) {
                des_C[j] = des_C[j + 1];
            }

            des_C[27] = t;
            t = des_D_get(0);

            for (j = 0; j < 27; j++) {
                des_D_set(j, des_D_get(j + 1));
            }
            des_D_set(27, t);
        }

        /*
         ** get Ki. Note C and D are concatenated.
         */

        for (j = 0; j < 24; j++) {
            ks[j] = des_C[des_PC2_C[j] - 1];
            ks[j + 24] = des_D_get(des_PC2_D[j] - 28 - 1);
        }

    }
    return { C:des_C, KS:des_KS }
};


/*
** The E bit-selection table.
*/

var des_E = [
    22, 15, 12,  3,  8,  2, 23, 16,
    14, 13,  9, 10,  0,  1, 21, 19,
    18,  6, 11,  7, 17,  4, 20,  5,
    5, 17, 11, 13, 12, 14,  8,  7,
    19, 22, 18,  9,  3,  4,  1,  6,
    16,  2, 20, 15, 10, 23,  0, 21 ];


/*
** The 8 selection functions.
** For some reason, they give a 0-origin
** index, unlike everything else.
*/

var des_S = [
    [ 14, 4,13, 1, 2,15,11, 8, 3,10, 6,12, 5, 9, 0, 7,
    0,15, 7, 4,14, 2,13, 1,10, 6,12,11, 9, 5, 3, 8,
    4, 1,14, 8,13, 6, 2,11,15,12, 9, 7, 3,10, 5, 0,
    15,12, 8, 2, 4, 9, 1, 7, 5,11, 3,14,10, 0, 6,13 ],

    [ 15, 1, 8,14, 6,11, 3, 4, 9, 7, 2,13,12, 0, 5,10,
    3,13, 4, 7,15, 2, 8,14,12, 0, 1,10, 6, 9,11, 5,
    0,14, 7,11,10, 4,13, 1, 5, 8,12, 6, 9, 3, 2,15,
    13, 8,10, 1, 3,15, 4, 2,11, 6, 7,12, 0, 5,14, 9 ],

    [ 10, 0, 9,14, 6, 3,15, 5, 1,13,12, 7,11, 4, 2, 8,
    13, 7, 0, 9, 3, 4, 6,10, 2, 8, 5,14,12,11,15, 1,
    13, 6, 4, 9, 8,15, 3, 0,11, 1, 2,12, 5,10,14, 7,
    1,10,13, 0, 6, 9, 8, 7, 4,15,14, 3,11, 5, 2,12 ],

    [ 7,13,14, 3, 0, 6, 9,10, 1, 2, 8, 5,11,12, 4,15,
    13, 8,11, 5, 6,15, 0, 3, 4, 7, 2,12, 1,10,14, 9,
    10, 6, 9, 0,12,11, 7,13,15, 1, 3,14, 5, 2, 8, 4,
    3,15, 0, 6,10, 1,13, 8, 9, 4, 5,11,12, 7, 2,14 ],

    [ 2,12, 4, 1, 7,10,11, 6, 8, 5, 3,15,13, 0,14, 9,
    14,11, 2,12, 4, 7,13, 1, 5, 0,15,10, 3, 9, 8, 6,
    4, 2, 1,11,10,13, 7, 8,15, 9,12, 5, 6, 3, 0,14,
    11, 8,12, 7, 1,14, 2,13, 6,15, 0, 9,10, 4, 5, 3 ],

    [ 12, 1,10,15, 9, 2, 6, 8, 0,13, 3, 4,14, 7, 5,11,
    10,15, 4, 2, 7,12, 9, 5, 6, 1,13,14, 0,11, 3, 8,
    9,14,15, 5, 2, 8,12, 3, 7, 0, 4,10, 1,13,11, 6,
    4, 3, 2,12, 9, 5,15,10,11,14, 1, 7, 6, 0, 8,13 ],

    [ 4,11, 2,14,15, 0, 8,13, 3,12, 9, 7, 5,10, 6, 1,
    13, 0,11, 7, 4, 9, 1,10,14, 3, 5,12, 2,15, 8, 6,
    1, 4,11,13,12, 3, 7,14,10,15, 6, 8, 0, 5, 9, 2,
    6,11,13, 8, 1, 4,10, 7, 9, 5, 0,15,14, 2, 3,12 ],

    [ 13, 2, 8, 4, 6,15,11, 1,10, 9, 3,14, 5, 0,12, 7,
    1,15,13, 8,10, 3, 7, 4,12, 5, 6,11, 0,14, 9, 2,
    7,11, 4, 1, 9,12,14, 2, 0, 6,10,13,15, 3, 5, 8,
    2, 1,14, 7, 4,10, 8,13,15,12, 9, 0, 3, 5, 6,11 ]
    ];


/*
** P is a permutation on the selected combination
** of the current L and key.
*/

var des_P = [
    3, 13,  9, 12,  8, 20, 21,  7,
    5, 23, 16,  1, 14, 18,  4, 15,
    22, 10,  2,  0, 11, 19, 17,  6 ];

var des_L = Array.create(des_BS,0);
var des_R_get = function (i) { return des_L[(i+des_HBS)]};
var des_R_set = function (i,sval) { des_L[i+des_HBS]= sval};
var des_tempL = Array.create(des_HBS,0);
var des_f = Array.create (32,0);

/*
** Warning!!
**
** f[] used to be HBS for some years.
** 21/6/1990 cbo and sater discovered that inside the loop where f is computed
** indices are used from 0 to 31. These overlapped the preS array which is
** declared hereafter on all compilers upto that point, but only those
** values that were not used anymore. But the values of f are only used
** upto HBS. Makes you wonder about the one-way property.
** Then came ACK, and reversed the order of the arrays in the image.
**
** As a short term solution f[] was increased to 32, but in the long run
** someone should have a good look at our "oneway" function
*/

/*
** The combination of the key and the input, before selection.
*/
var des_preS = Array.create (48,0);

/*
** The payoff: encrypt a block. (Now 48 bytes, 1 bit/byte)
*/

var des_OWcrypt48 = function(block) {
    var ks = [];
    var t1 = 0;
    var t2 = 0;
    var i, j, k;
    /*
     ** First, permute the bits in the input
     */

    for (j = 0; j <= (des_BS - 1); j++) {
        des_L[j] = block[des_IP[j]];
    }
    /*
     ** Perform an encryption operation 16 times.
     */

    for (i = 0; i <= 15; i++) {
        ks = des_KS[i];

        /*
         ** Save the R array,
         ** which will be the new L.
         */

        for (j = 0; j < (des_HBS - 1); j++) {
            des_tempL[j] = des_R_get(j);
        }
        /*
         ** Expand R to 48 bits using the E selector;
         ** exclusive-or with the current key bits.
         */

        for (j = 0; j <= 47; j++) {
            des_preS[j] = (des_R_get(des_E[j])) ^ ks[j];
        }

        /*
         ** The pre-select bits are now considered
         ** in 8 groups of 6 bits each.
         ** The 8 selection functions map these
         ** 6-bit quantities into 4-bit quantities
         ** and the results permuted
         ** to make an f(R, K).
         ** The indexing into the selection functions
         ** is peculiar; it could be simplified by
         ** rewriting the tables.
         */

        t1 = 0;
        t2 = 0;

        for (j = 0; j <= 7; j++) {
            /*
            C: for (j=0,t1=0,t2=0; j<8; j++,t1+=6,t2+=4) {
			          k = S[j][(preS[t1+0]<<5)+
				          (preS[t1+1]<<3)+
				          (preS[t1+2]<<2)+
				          (preS[t1+3]<<1)+
				          (preS[t1+4]<<0)+
				          (preS[t1+5]<<4)];
			          f[t2+0] = (k>>3)&01;
			          f[t2+1] = (k>>2)&01;
			          f[t2+2] = (k>>1)&01;
			          f[t2+3] = (k>>0)&01;	
		        }            
            */
            var sind2 =
                ((des_preS[t1 + 0] << 5) & 0xff) +
                ((des_preS[t1 + 1] << 3) & 0xff) +
                ((des_preS[t1 + 2] << 2) & 0xff) +
                ((des_preS[t1 + 3] << 1) & 0xff) +
                ((des_preS[t1 + 4] << 0) & 0xff) +
                ((des_preS[t1 + 5] << 4) & 0xff);

            k = des_S[j][sind2];

            des_f[t2 + 0] = (k >> 3) & 0x1;
            des_f[t2 + 1] = (k >> 2) & 0x1;
            des_f[t2 + 2] = (k >> 1) & 0x1;
            des_f[t2 + 3] = (k >> 0) & 0x1;    // 3 .. 31 !!!

            t1 = t1 + 6;
            t2 = t2 + 4;
        }

        /*
         ** The new R is L ^ f(R, K).
         ** The f here has to be permuted first, though.
         */

        for (j = 0; j < des_HBS; j++) {
            des_R_set(j, (des_L[j] ^ des_f[des_P[j]]));
        }

        /*
         ** Finally, the new L (the original R)
         ** is copied back.
         */

        for (j = 0; j < des_HBS; j++) {
            des_L[j] = des_tempL[j];
        }

    }


    /*
     ** The output L and R are reversed.
     */

    for (j = 0; j < des_HBS; j++) {
        t1 = des_L[j];
        des_L[j] = des_R_get(j);
        des_R_set(j, t1);
    }

    /*
     ** The final output
     ** gets the inverse permutation of the very original.
     */

    for (j = 0; j < des_BS; j++) {
        block[j] = des_L[des_FP[j]];
    }
    return block;
};

module.exports = {
    des_OWsetkey:des_OWsetkey,
    des_OWcrypt48:des_OWcrypt48
};
};
BundleModuleCode['dos/network']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR.
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2006-2018 bLAB
 **    $CREATED:     3-5-15 by sbosse.
 **    $VERSION:     1.2.6
 **
 **    $INFO:
 **
 **  DOS: Networking, Commands, Status/Error codes, ..
 **
 **    $ENDOFINFO
 */
"use strict";
var log = 0;

var util = Require('util');

var Io = Require('com/io');
var Comp = Require('com/compat');
var String = Comp.string;
var Array = Comp.array;
var Perv =Comp.pervasives;
var Des48 = Require('dos/des48');
var Base64 = Require('os/base64');
var Rand = Comp.random;
var Fs = Require('fs');

//var xmldoc = Require('xmldoc');


function pad(str,size) {
    while (str.length < (size || 2)) {str = "0" + str;}
    return str;
}


/** Direction
 *
var Direction = {
    NORTH:1,
    WEST:2,
    EAST:3,
    SOUTH:4,
    ORIGIN:5,
    tostring:function (i) {
        switch (i) {
            case 1: return 'NORTH';
            case 2: return 'WEST';
            case 3: return 'EAST';
            case 4: return 'SOUTH';
            case 5: return 'ORIGIN';
            default: return 'Direction?';
        }
    }
};
*/


// Standard Object Service
var STD_FIRST_COM = 1000;
var STD_LAST_COM = 1999;
var STD_FIRST_ERR = (-STD_FIRST_COM);
var STD_LAST_ERR = (-STD_LAST_COM);

// File Server
var AFS_FIRST_COM = 2000;
var AFS_LAST_COM = 2099;
var AFS_FIRST_ERR = (-AFS_FIRST_COM);
var AFS_LAST_ERR = (-AFS_LAST_COM);
var AFS_REQBUFSZ = 1024*32;


// Directory and Name Server
var DNS_FIRST_COM = 2100;
var DNS_LAST_COM = 2199;
var DNS_FIRST_ERR = (-DNS_FIRST_COM);
var DNS_LAST_ERR = (-DNS_LAST_COM);
var DNS_MAXCOLUMNS = 4;

// System Process Server (incl. Agent Platform Manager)
var PS_FIRST_COM = 2200;
var PS_LAST_COM = 2299;
var PS_FIRST_ERR = (-PS_FIRST_COM);
var PS_LAST_ERR = (-PS_LAST_COM);

// Broker Server
var BR_FIRST_COM = 2300;
var BR_LAST_COM = 2399;
var BR_FIRST_ERR = (-BR_FIRST_COM);
var BR_LAST_ERR = (-BR_LAST_COM);
/** RPC Status
 *
 * @enum {number}
 */
var Status = {
    STD_OK:0,
    STD_CAPBAD      :   STD_FIRST_ERR,
    STD_COMBAD      :  (STD_FIRST_ERR-1),
    STD_ARGBAD      :  (STD_FIRST_ERR-2),
    STD_NOTNOW      :  (STD_FIRST_ERR-3),
    STD_NOSPACE     :  (STD_FIRST_ERR-4),
    STD_DENIED      :  (STD_FIRST_ERR-5),
    STD_NOMEM       :  (STD_FIRST_ERR-6),
    STD_EXISTS      :  (STD_FIRST_ERR-7),
    STD_NOTFOUND    :  (STD_FIRST_ERR-8),
    STD_SYSERR      :  (STD_FIRST_ERR-9),
    STD_INTR        :  (STD_FIRST_ERR-10),
    STD_OVERFLOW    :  (STD_FIRST_ERR-11),
    STD_WRITEPROT   :  (STD_FIRST_ERR-12),
    STD_NOMEDIUM    :  (STD_FIRST_ERR-13),
    STD_IOERR       :  (STD_FIRST_ERR-14),
    STD_WRONGSRV    :  (STD_FIRST_ERR-15),
    STD_OBJBAD      :  (STD_FIRST_ERR-16),
    STD_UNKNOWN     :  (STD_FIRST_ERR-17),
    DNS_UNAVAIL      : (DNS_FIRST_ERR -1),
    DNS_NOTEMPTY     : (DNS_FIRST_ERR -2),
    DNS_UNREACH      : (DNS_FIRST_ERR -3),
    DNS_CLASH        : (DNS_FIRST_ERR -4),
    RPC_FAILURE     : -1,
    BUF_OVERFLOW    : -2,
    print: function(stat) {
        switch(stat) {
            case Status.STD_OK          :  return 'STD_OK';
            case Status.STD_CAPBAD      :  return 'STD_CAPBAD';
            case Status.STD_COMBAD      :  return 'STD_COMBAD';
            case Status.STD_ARGBAD      :  return 'STD_ARGBAD';
            case Status.STD_NOTNOW      :  return 'STD_NOTNOW';
            case Status.STD_NOSPACE     :  return 'STD_NOSPACE';
            case Status.STD_DENIED      :  return 'STD_DENIED';
            case Status.STD_NOMEM       :  return 'STD_NOMEM';
            case Status.STD_EXISTS      :  return 'STD_EXISTS';
            case Status.STD_NOTFOUND    :  return 'STD_NOTFOUND';
            case Status.STD_SYSERR      :  return 'STD_SYSERR';
            case Status.STD_INTR        :  return 'STD_INTR';
            case Status.STD_OVERFLOW    :  return 'STD_OVERFLOW';
            case Status.STD_WRITEPROT   :  return 'STD_WRITEPROT';
            case Status.STD_NOMEDIUM    :  return 'STD_NOMEDIUM';
            case Status.STD_IOERR       :  return 'STD_IOERR';
            case Status.STD_WRONGSRV    :  return 'STD_WRONGSRV';
            case Status.STD_OBJBAD      :  return 'STD_OBJBAD';
            case Status.STD_UNKNOWN     :  return 'STD_UNKNOWN';
            case Status.DNS_UNAVAIL     :  return 'DNS_UNAVAIL';
            case Status.DNS_NOTEMPTY    :  return 'DNS_NOTEMPTY';
            case Status.DNS_UNREACH     :  return 'DNS_UNREACH';
            case Status.DNS_CLASH       :  return 'DNS_CLASH';
            case Status.RPC_FAILURE     :  return 'RPC_FAILURE';
            case Status.BUF_OVERFLOW     :  return 'BUF_OVERFLOW';
            default          :     return '"'+stat+'"';
        }
    }
};

/** RPC Command
 *
 * @enum {number}
 */

var Command = {
    /*
    ** Standard Commands
     */
    STD_MONITOR     : STD_FIRST_COM,
    STD_AGE         : (STD_FIRST_COM+1),
    STD_COPY        : (STD_FIRST_COM + 2),
    STD_DESTROY     : (STD_FIRST_COM + 3),
    STD_INFO        : (STD_FIRST_COM + 4),
    STD_RESTRICT    : (STD_FIRST_COM + 5),
    STD_STATUS      : (STD_FIRST_COM + 6),
    STD_TOUCH       : (STD_FIRST_COM + 7),
    STD_GETPARAMS   : (STD_FIRST_COM + 8),
    STD_SETPARAMS   : (STD_FIRST_COM + 9),
    STD_NTOUCH      : (STD_FIRST_COM + 10),
    STD_EXIT        : (STD_FIRST_COM + 11),
    STD_RIGHTS      : (STD_FIRST_COM + 12),
    STD_EXEC        : (STD_FIRST_COM + 13),
    STD_LOCATION    : (STD_FIRST_COM + 20),
    STD_LABEL       : (STD_FIRST_COM + 21),

    /*
    ** AFC Commands
     */
    AFS_CREATE          : (AFS_FIRST_COM + 1),
    AFS_DELETE          : (AFS_FIRST_COM + 2),
    AFS_FSCK            : (AFS_FIRST_COM + 3),
    AFS_INSERT          : (AFS_FIRST_COM + 4),
    AFS_MODIFY          : (AFS_FIRST_COM + 5),
    AFS_READ            : (AFS_FIRST_COM + 6),
    AFS_SIZE            : (AFS_FIRST_COM + 7),
    AFS_DISK_COMPACT    : (AFS_FIRST_COM + 8),
    AFS_SYNC            : (AFS_FIRST_COM + 9),
    AFS_DESTROY         : (AFS_FIRST_COM + 10),

    /*
    ** DNS Commands
     */

    DNS_CREATE       : (DNS_FIRST_COM),
    DNS_DISCARD      : (DNS_FIRST_COM + 1),
    DNS_LIST         : (DNS_FIRST_COM + 2),
    DNS_APPEND       : (DNS_FIRST_COM + 3),
    DNS_CHMOD        : (DNS_FIRST_COM + 4),
    DNS_DELETE       : (DNS_FIRST_COM + 5),
    DNS_LOOKUP       : (DNS_FIRST_COM + 6),
    DNS_SETLOOKUP    : (DNS_FIRST_COM + 7),
    DNS_INSTALL      : (DNS_FIRST_COM + 8),
    DNS_REPLACE      : (DNS_FIRST_COM + 10),
    DNS_GETMASKS     : (DNS_FIRST_COM + 11),
    DNS_GETSEQNR     : (DNS_FIRST_COM + 12),
    DNS_RENAME       : (DNS_FIRST_COM + 13),
    DNS_GETROOT      : (DNS_FIRST_COM + 14),
    DNS_GETDEFAFS    : (DNS_FIRST_COM + 15),

    PS_STUN         : (PS_FIRST_COM),     // Kill a process/ create a snapshot
    PS_MIGRATE      : (PS_FIRST_COM+1),   // Execute a process from a snapshot after migration (->next+)
    PS_EXEC         : (PS_FIRST_COM+2),   // Execute a process from a snapshot (->next)
    PS_WRITE        : (PS_FIRST_COM+4),   // Store a process class template
    PS_READ         : (PS_FIRST_COM+5),   // Get a process class template
    PS_CREATE       : (PS_FIRST_COM+6),   // Create a process from a template and execute
    PS_FORK         : (PS_FIRST_COM+7),   // Fork a process from a running process
    PS_SIGNAL       : (PS_FIRST_COM+8),   // Send a signal to a process

    BR_CONNECT      : (BR_FIRST_COM),
    BR_DISCONN      : (BR_FIRST_COM+1),

    print: function(cmd) {
        switch(cmd) {
            case Command.STD_MONITOR     : return 'STD_MONITOR';
            case Command.STD_AGE         : return 'STD_AGE';
            case Command.STD_COPY        : return 'STD_COPY';
            case Command.STD_DESTROY     : return 'STD_DESTROY';
            case Command.STD_INFO        : return 'STD_INFO';
            case Command.STD_RESTRICT    : return 'STD_RESTRICT';
            case Command.STD_STATUS      : return 'STD_STATUS';
            case Command.STD_TOUCH       : return 'STD_TOUCH';
            case Command.STD_GETPARAMS   : return 'STD_GETPARAMS';
            case Command.STD_SETPARAMS   : return 'STD_SETPARAMS';
            case Command.STD_NTOUCH      : return 'STD_NTOUCH';
            case Command.STD_EXIT        : return 'STD_EXIT';
            case Command.STD_RIGHTS      : return 'STD_RIGHTS';
            case Command.STD_EXEC        : return 'STD_EXEC';
            case Command.STD_LOCATION    : return 'STD_LOCATION';
            case Command.STD_LABEL       : return 'STD_LABEL';
            case Command.AFS_CREATE      : return 'AFS_CREATE';
            case Command.AFS_DELETE      : return 'AFS_DELETE';
            case Command.AFS_FSCK        : return 'AFS_FSCK';
            case Command.AFS_INSERT      : return 'AFS_INSERT';
            case Command.AFS_MODIFY      : return 'AFS_MODIFY';
            case Command.AFS_READ        : return 'AFS_READ';
            case Command.AFS_SIZE        : return 'AFS_SIZE';
            case Command.AFS_DISK_COMPACT : return 'AFS_DISK_COMPACT';
            case Command.AFS_SYNC        : return 'AFS_SYNC';
            case Command.AFS_DESTROY     : return 'AFS_DESTROY';
            case Command.DNS_CREATE      : return 'DNS_CREATE';
            case Command.DNS_DISCARD     : return 'DNS_DISCARD';
            case Command.DNS_LIST        : return 'DNS_LIST';
            case Command.DNS_APPEND      : return 'DNS_APPEND';
            case Command.DNS_CHMOD       : return 'DNS_CHMOD';
            case Command.DNS_DELETE      : return 'DNS_DELETE';
            case Command.DNS_LOOKUP      : return 'DNS_LOOKUP';
            case Command.DNS_SETLOOKUP   : return 'DNS_SETLOOKUP';
            case Command.DNS_INSTALL     : return 'DNS_INSTALL';
            case Command.DNS_REPLACE     : return 'DNS_REPLACE';
            case Command.DNS_GETMASKS    : return 'DNS_GETMASKS';
            case Command.DNS_GETSEQNR    : return 'DNS_GETSEQNR';
            case Command.DNS_RENAME      : return 'DNS_RENAME';
            case Command.DNS_GETROOT     : return 'DNS_GETRROT';
            case Command.DNS_GETDEFAFS   : return 'DNS_GETDEFAFS';
            case Command.PS_STUN         : return 'PS_STUN';
            case Command.PS_EXEC         : return 'PS_EXEC';
            case Command.PS_MIGRATE      : return 'PS_MIGRATE';
            case Command.PS_READ         : return 'PS_READ';
            case Command.PS_WRITE        : return 'PS_WRITE';
            case Command.PS_CREATE       : return 'PS_CREATE';
            case Command.PS_FORK         : return 'PS_FORK';
            case Command.PS_SIGNAL       : return 'PS_SIGNAL';
            case Command.BR_CONNECT      : return 'BR_CONNECT';
            case Command.BR_DISCONN      : return 'BR_DISCONN';
            default: return '"'+cmd+'"';
        }


    }
};

var PORT_SIZE = 6;
var PRIV_SIZE = 4+PORT_SIZE;
var CAP_SIZE = 16;

/** Object Rights
 *
 * @enum {number}
 */
var Rights = {
    AFS_RGT_READ        : 0x1,
    AFS_RGT_CREATE      : 0x2,
    AFS_RGT_MODIFY      : 0x4,
    AFS_RGT_DESTROY     : 0x8,
    AFS_RGT_ADMIN       : 0x80,
    
    DNS_COLMASK     : ((1 << DNS_MAXCOLUMNS) - 1),  // Rights to access specific columns of a directory row, one bit, one column.
    DNS_RGT_COLALL  : ((1 << DNS_MAXCOLUMNS) - 1),
    DNS_RGT_COL1    : 0x01,
    DNS_RGT_OWNER   : 0x01,
    DNS_RGT_COL2    : 0x02,
    DNS_RGT_GROUP   : 0x02,
    DNS_RGT_COL3    : 0x04,
    DNS_RGT_OTHERS  : 0x04,
    DNS_RGT_COL4    : 0x08,
    DNS_RGT_READ    : 0x10,
    DNS_RGT_CREATE  : 0x20,
    DNS_RGT_MODIFY  : 0x40,
    DNS_RGT_DELETE  : 0x80,

    HOST_INFO       : 0x01,
    HOST_READ       : 0x02,
    HOST_WRITE      : 0x04,
    HOST_EXEC       : 0x08,

    PSR_READ        : 0x01,
    PSR_WRITE       : 0x02,
    PSR_CREATE      : 0x04,
    PSR_DELETE      : 0x08,
    PSR_EXEC        : 0x10,
    PSR_KILL        : 0x20,
    PSR_ALL         : 0xff,

    NEG_SCHED       : 0x08,
    NEG_CPU         : 0x10,
    NEG_RES         : 0x20,
    NEG_LEVEL       : 0x40,
    
    PRV_ALL_RIGHTS  : 0xff

};



var DEF_RPC_MAX_HOP = 4;

var priv2pub_cache = [];

/**
 *
 * @param {number []} [port_vals]
 * @returns {string}
 */
var port = function (port_vals) {
    if (port_vals==undefined) port_vals=[0,0,0,0,0,0];
    var port='';
    for(var i = 0; i< PORT_SIZE;i++) {
        port=port+Perv.char_of_int(port_vals[i]);
    }
    return port;

};
/**
 *
 * @param {number} [obj]
 * @param {number} [rights]
 * @param {port} [rand]
 * @constructor
 */
var privat = function (obj,rights,rand) {
    if (obj==undefined) {
        // Create empty private field
        this.prv_obj=0;
        this.prv_rights=0;
        this.prv_rand=port();
    } else {
        this.prv_obj = obj;               // Integer
        this.prv_rights = rights;         // Integer
        this.prv_rand = rand;             // Port=string
    }
};

/**
 *
 * @param {port} [cap_port]
 * @param {privat} [cap_priv]
 * @constructor
 */
var capability = function(cap_port, cap_priv) {
    if (cap_port==undefined) {
        // Create empty capability
        this.cap_port = port();
        this.cap_priv = new privat();
    } else {
        this.cap_port = cap_port;       // Port=string
        if (cap_priv==undefined)
            this.cap_priv = new privat();
        else
            this.cap_priv = cap_priv;    // Private
    }
};

/*
 ** RPC communication is XML based using the HTTP interface.
 ** RPC communication is synchronous, hence a callback
 ** function is used to handle the reply (acknowledge).
 */
/**
 *
 * @param {port} [h_port]
 * @param {privat} [h_priv]
 * @param {Command} [h_command]
 * @param {(Status.STD_OK|*)} [h_status]
 * @constructor
 */
var header = function(h_port,h_priv,h_command,h_status) {
    if (h_port==undefined) {
        // Create empty header
        this.h_port = port();
        this.h_priv = new privat();
        this.h_command = undefined;
        this.h_status = undefined;
    } else {
        this.h_port = h_port;
        this.h_priv = h_priv;
        this.h_command = h_command;
        this.h_status = h_status;
    }
};

/**
 *
 * @param {number} [obj]
 * @param {number} [rights]
 * @param {port} [rand]
 * @returns {privat}
 */
function Private(obj,rights,rand) {
    var _obj = new privat(obj,rights,rand);
    Object.preventExtensions(_obj);
    return _obj;
}
/**
 *
 * @param {port} [cap_port]
 * @param {privat} [cap_priv]
 * @returns {capability}
 */
function Capability (cap_port, cap_priv) {
    var obj = new capability(cap_port, cap_priv);
    Object.preventExtensions(obj);
    return obj;
}
/**
 *
 * @param {port} [h_port]
 * @param {privat} [h_priv]
 * @param {Command} [h_command]
 * @param {(Status.STD_OK|*)} [h_status]
 * @returns {header}
 */

function Header(h_port,h_priv,h_command,h_status) {
    var obj = new header(h_port,h_priv,h_command,h_status);
    Object.preventExtensions(obj);
    return obj;
}

/*
** Hash table of all locally created unique ports.
 */
var uniqports=[];


/**
 *
 */
var Net = {
    // Direction:Direction,
    PORT_SIZE:PORT_SIZE,
    PRIV_SIZE:PRIV_SIZE,

    AFS_REQBUFSZ:AFS_REQBUFSZ,
    CAP_SIZE:CAP_SIZE,
    DNS_MAXCOLUMNS:DNS_MAXCOLUMNS,
    TIMEOUT:5000,
    DEF_RPC_MAX_HOP:DEF_RPC_MAX_HOP,

    Status:Status,
    Command:Command,
    Rights:Rights,

    Private:Private,
    Capability: Capability,
    Header: Header,
    Port: port,

    /**
     * @type {port}
     */
    nilport: port(),
    nilpriv: Private(0,0,this.nilport),
    nilcap: Capability(this.nilport,this.nilpriv),

    /*
     ** Utils to get and set single bytes of a port
     */
    get_portbyte: function(port,i) {
        return Perv.int_of_char(String.get(port,i))
    },
    set_portbyte: function(port,i,byte) {
        return String.set(port, i, (Perv.char_of_int(byte)));
    },
    /*
     * Return a unique key of a capability that can be used for hash tables
     */
    key: function (cap) {
        return cap.cap_port+
         cap.cap_priv.prv_obj+
         cap.cap_priv.prv_rights+
         cap.cap_priv.prv_rand;
    },

    /*
     ** Encryption function
     */
    one_way: function (port) {
        var key = Array.create(64,0);
        var block = Array.create(48,0);
        var pubport = String.make (PORT_SIZE,'\0');
        var i, j, k;

        /*
        ** We actually need 64 bit key.
        ** Throw some zeroes in at bits 6 and 7 mod 8
        ** The bits at 7 mod 8 etc are not used by the algorithm
        */
        j=0;
        for (i = 0; i< 64; i++) {
            if ((i & 7) > 5)
                key[i] = 0;
            else {
                if ((this.get_portbyte(port, (j >> 3)) & (1 << (j & 7))) != 0)
                    key[i] = 1;
                else
                    key[i] = 0;
                j++;
            }
        }

        Des48.des_OWsetkey(key);
        /*
        ** Now go encrypt constant 0
        */
        block=Des48.des_OWcrypt48(block);


        /*
        ** and put the bits in the destination port
        */
        var pb = 0;

        for (i = 0; i < PORT_SIZE;i++) {
            var pbyte = 0;
            for (j = 0; j < 8; j++) {
                pbyte = pbyte | (block[pb] << j);
                pb++;
            }
            pubport=this.set_portbyte(pubport, i, pbyte);
        }

        return pubport;
    },
    /*
     ** Check whether the required rights [R1;R2;..] are
     ** present in the rights field rg. Return a boolean value.
     */
    rights_req : function(rights,required) {
        var all=true;
        Array.iter(required,function(rq) {
            if (rq & rights == 0) all = false;
        });
        return all;
    },
    port_cmp: function(port1,port2) {
        var i;
        var eq=true;
        for(i=0;i<PORT_SIZE;i++) { if (String.get(port1,i)!=String.get(port2,i)) eq=false;}
        return eq;},
    port_copy: function(port) {
        return String.copy(port);
    },
    /*
     ** Derive a port from a string.
     */
    port_name: function(name){
        var p = String.make(PORT_SIZE,'\0');
        var i;
        var n = name.length;

        for (i = 0; i < n;i++) {
            var k = i % PORT_SIZE;
            p = String.set(p, k, Perv.char_of_int(
                (Perv.int_of_char(String.get(p, k)) +
                 Perv.int_of_char(String.get(name, i)))
                                 & 0xff));
        }
        return p;
    },
    port_to_str: function(port,compact) {
        var i,str='';
        if (port) {
            for (i = 0; i < PORT_SIZE; i++) {
                var num = Perv.int_of_char(String.get(port, i));
                if (!compact && i > 0) str = str + ':';
                str = str + pad(num.toString(16).toUpperCase(), 2);
            }
        } else str='undefined';
        return str;
    },
    port_of_str: function (str,compact) {
        var tokens=str.split(':'),i,port='';
        for (i=0;i<PORT_SIZE;i++) {
            var num='0x'+tokens[i];
            port=port+Perv.char_of_int(parseInt(num,16));
        }
        return port;
    },
    /** String parameter to port conversion including "undefined" case.
     *
     * @param str
     * @returns {string}
     */
    port_of_param: function (str) {
        if (str==undefined ||
            String.equal(str,'undefined')) return undefined;
        var tokens=str.split(':');
        var i;
        var port='';
        for (i=0;i<PORT_SIZE;i++) {
            var num='0x'+tokens[i];
            port=port+Perv.char_of_int(parseInt(num,16));
        }
        return port;
    },
    prv2pub: function(port) {
        var putport;
        if (priv2pub_cache[port] == undefined) {
            putport=this.one_way(port);
            priv2pub_cache[port] = putport;
        } else putport = priv2pub_cache[port];
        return putport;
    },
    /**
     ** Decode a private structure
     *
     * @param {privat} prv
     * @param {port} rand
     * @returns {boolean}
     */
    prv_decode: function(prv,rand) {
        if (prv.prv_rights == Rights.PRV_ALL_RIGHTS)
            return this.port_cmp(prv.prv_rand,rand);
        else {
            var tmp_port = this.port_copy(rand),
                pt0 = this.get_portbyte(tmp_port, 0),
                pr0 = prv.prv_rights;
            tmp_port = this.set_portbyte(tmp_port, 0, (pt0 ^ pr0));
            tmp_port = this.one_way(tmp_port);
            return this.port_cmp(prv.prv_rand, tmp_port)
        }
    },
    /*
     ** Encode a private part from the object number, the rights field
     ** and the random port.
     ** Returns the created private structure.
     */
    prv_encode: function(obj,rights,rand) {
        var tmp_port = this.port_copy(rand);

        var r1 = rights;
        var rmask = Rights.PRV_ALL_RIGHTS;

        if (rights == Rights.PRV_ALL_RIGHTS)
            return this.Private(obj,r1 & rmask,tmp_port);
        else {
            var pt0 = this.get_portbyte(tmp_port,0);
            tmp_port = this.set_portbyte(tmp_port,0,pt0 ^ r1);
            tmp_port = this.one_way(tmp_port);
            return this.Private(obj,r1 & rmask,tmp_port)
        }
    },
    /*
     ** Return the private object number form a private structure
     */
    prv_number: function(prv) {
        return prv.prv_obj;
    },
    /*
     ** Return the private rights field.
     */
    prv_rights: function(prv) {
        return prv.prv_rights & Rights.PRV_ALL_RIGHTS;
    },

    /*
     ** Check the private rights field: 1. Validation, 2: Required rights.
     */
    prv_rights_check: function(prv,rand,required) {
      if (!Net.prv_decode(prv,rand)) return false;
      return (prv.prv_rights & required)==required;
    },

    /** Restrict a private field (rights&mask) of a capability.
     *
     * @param {privat} priv
     * @param {number} mask rights restriction mask
     * @param {port} random secret server random port
     */
    restrict: function(priv,mask,random) {
        var pr =
            this.prv_encode(priv.prv_obj,
                            priv.prv_rights & mask,
                            random);
        return pr;
    },
    /*
     * Return a new random port.
     *
     * Warning: the quality of the random ports are strongly
     * related to JSVMs underlying random generator. Be warned!
     *
     * @returns {port}
     */
    uniqport: function() {
        var port = String.create (PORT_SIZE);
        var exists = true;
        while (exists) {
            var i;
            for (i = 0; i <= (PORT_SIZE - 1); i++) {

                port = String.set(port, i, (Perv.char_of_int(Rand.int(256))));
            }
            if (uniqports[port]==undefined)
            {
                uniqports[port]=port;
                exists=false;
            }
        }
        return port;
    },
    /** Write a capability to a file.
     *
     * @param {capability} cap
     * @param {string} path
     */
    cap_to_file: function(cap,path) {
        try {
            Fs.writeFileSync(path, this.Print.capability(cap));
        } catch(e) {
        }
    },
    /** Read a capability from a file.
     *
     * @param {string} path
     * @returns {capability|undefined}
     */
    cap_of_file: function(path) {
        try {
            var cap=undefined;
            var data = Fs.readFileSync(path);
            var cp = this.Parse.capability(data.toString(), 0);
            cap = cp.cap;
            return cap;
        } catch(e) {
            return undefined;
        }
    },

    Position: function (x,y) {
        this.x = x;
        this.y = y;
    },
    Copy: {
        /**
         *
         * @param src
         * @returns {port}
         */
        port: function(src) {
            // !!!!
            return String.copy(src);
        },
        /**
         *
         * @param src
         * @param dst
         * @returns {privat}
         */
        private: function(src,dst) {
            if (dst!=undefined) {
                dst.prv_obj = src.prv_obj;
                dst.prv_rights = src.prv_rights;
                dst.prv_rand = this.port(src.prv_rand);
                return dst;
            } else {
                var dstnew=Private();
                dstnew.prv_obj = src.prv_obj;
                dstnew.prv_rights = src.prv_rights;
                dstnew.prv_rand = this.port(src.prv_rand);
                return dstnew;
            }
        },
        /**
         *
         * @param src
         * @param dst
         * @returns {capability}
         */
        capability: function(src,dst) {
            if (dst!=undefined) {
                dst.cap_port = this.port(src.cap_port);
                this.private(src.cap_priv, dst.cap_priv);
                return dst;
            }
            else {
                var dstnew=Capability();
                dstnew.cap_port = this.port(src.cap_port);
                this.private(src.cap_priv, dstnew.cap_priv);
                return dstnew;
            }
        },
        /**
         *
         * @param src
         * @param dst
         */
        header: function(src,dst) {
            dst.h_port=this.port(src.h_port);
            dst.h_status=src.h_status;
            dst.h_command=src.h_command;
            if (src.h_priv!=undefined) {
                if (dst.h_priv==undefined) {
                    var obj = new privat();
                    Object.preventExtensions(obj);
                    dst.h_priv=obj;
                }
                this.private(src.h_priv,dst.h_priv);
            } else dst.h_priv=undefined;
        }
    },
    Equal: {
        port: function (port1,port2) {
            if (port1==undefined || port2==undefined) return (port1==port2);
            else return String.equal(port1,port2);
        },
        private: function (prv1,prv2) {
            return  (prv1==undefined&&prv2==undefined) ||
                    (prv1.prv_obj==prv2.prv_obj &&
                     prv1.prv_rights==prv2.prv_rights &&
                     this.port(prv1.prv_rand,prv2.prv_rand))
        },
        capability: function(cap1,cap2) {
            return  (cap1==undefined&&cap2==undefined) ||
                    (this.private(cap1.cap_priv,cap2.cap_priv) &&
                     this.port(cap1.cap_port,cap2.cap_port))
        },
        header: function(hdr1,hdr2) {
            return  (hdr1==undefined&&hdr2==undefined) ||
                    (this.private(hdr1.h_priv,hdr1.h_priv) &&
                     this.port(hdr1.h_port,hdr2.h_port) &&
                     hdr1.h_status==hdr2.h_status &&
                     hdr1.h_command==hdr2.h_command)

        }
    },

    /**
     * @typedef {{
     * port:function(string,number):{port:port,pos:number}|undefined,
     * private:function(string,number):{priv:privat,pos:number}|undefined,
     * capability:function(string,number):{cap:capability,pos:number}|undefined
     * }} Parse
     */
    Parse: {
        /**
         *
         * @param str
         * @param pos
         * @returns {{port:port,pos:number}}
         */
        port: function(str,pos) {
            var port='';
            var len=str.length;
            if (pos==undefined) pos=0;
            if (len<(pos+17)) return undefined;
            if (str[pos]=='[') pos++;
            for(var i=0;i<6;i++) {
                var sv='0x'+str[pos]+str[pos+1];
                port=port+Perv.char_of_int(Perv.int_of_string(sv));
                pos=pos+2;
                if (str[pos]==':') pos++;
            }
            if (str[pos]==']') pos++;
            return {port:port,pos:pos};
        },
        /**
         *
         * @param str
         * @param pos
         * @returns {{priv:privat,pos:number}}
         */
        private: function(str,pos) {
            var priv=Private();
            var sv;
            var len=str.length;
            if (pos==undefined) pos=0;
            if (len<(pos+25)) return undefined;
            if (str[pos]=='(') pos++;
            sv='';
            while(str[pos]!='(') {
                sv=sv+str[pos];
                pos++;
            }
            priv.prv_obj=Perv.int_of_string(sv);
            sv='';
            if (str[pos]=='(') pos++;
            while(str[pos]!=')') {
                sv=sv+str[pos];
                pos++;
            }
            priv.prv_rights=Perv.int_of_string('0x'+sv);
            if (str[pos]==')') pos++;
            var pp=this.port(str,pos);
            if (pp==undefined) return undefined;
            priv.prv_rand=pp.port;
            pos=pp.pos;
            return {priv:priv,pos:pos};
        },
        /**
         *
         * @param str
         * @param pos
         * @returns {{cap:capability,pos:number}|undefined}
         */
        capability: function(str,pos) {
            var cap=Capability();
            if (pos==undefined) pos=0;
            var pp=this.port(str,pos);
            if (pp==undefined) return undefined;
            cap.cap_port=pp.port;
            pos=pp.pos;
            pp=this.private(str,pos);
            if (pp==undefined) return undefined;
            cap.cap_priv=pp.priv;
            pos=pp.pos;
            return {cap:cap,pos:pos};
        }
    },
    Print: {
        /**
         *
         * @param cap
         * @returns {string}
         */
        capability: function (cap) {
            var str='';
            if (cap==undefined) return 'undefined';
            if (cap.cap_port!=undefined) str='['+this.port(cap.cap_port)+']'; else str = '[]';
            if (cap.cap_priv!=undefined) str=str+'('+this.private(cap.cap_priv)+')'; else str=str+'()';
            return str;
        },
        command: Command.print,
        /**
         *
         * @param hdr
         * @returns {string}
         */
        header: function (hdr) {
            var str='';
            if (hdr==undefined) return 'undefined';
            if (hdr.h_port!=undefined) str='['+this.port(hdr.h_port)+']'; else str = '[]';
            if (hdr.h_command!=undefined) str=str+': '+Command.print(hdr.h_command);
            if (hdr.h_priv!=undefined) str=str+'('+this.private(hdr.h_priv)+')'; else str=str+'()';
            if (hdr.h_status!=undefined) str=str+' ?'+Status.print(hdr.h_status);
            return str;
        },
        /**
         *
         * @param port
         * @returns {string}
         */
        port: function(port) {
            var i;
            var str='';
            if (port!=undefined) {
                for (i = 0; i < PORT_SIZE; i++) {
                    var num = Perv.int_of_char(String.get(port, i));
                    if (i > 0) str = str + ':';
                    str = str + pad(num.toString(16).toUpperCase(), 2);
                }
            } else str='undefined';
            return str;
        },
        /**
         *
         * @param priv
         * @returns {string}
         */
        private: function(priv) {
            var str='';
            if (priv==undefined) return 'undefined';
            str=priv.prv_obj;
            str=str+'('+String.format_hex(priv.prv_rights,2).toUpperCase()+')[';
            str=str+this.port(priv.prv_rand)+']';
            return str;
        },
        status: Status.print
    }
};

module.exports = Net;
};
BundleModuleCode['com/cbl']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2006-2017 bLAB
 **    $CREATED:     27-11-17 by sbosse.
 **    $VERSION:     1.1.1
 **
 **    $INFO:
 **
 **  JavaScript Callback List
 **
 **  Assume there is a set of non-blocking IO operations with callbacks io1,io2,.., and there is a final
 **  callback function that should be called after all io operations have finished.
 **
 **    $ENDOFINFO
 */

function CBL(callback) {
  if (!(this instanceof CBL)) return new CBL(callback);
  this.schedules=[];
  this.callback=callback;
}

// Next schedule
CBL.prototype.next = function (status) {
  var f=this.schedules.shift();
  // if (f) console.log('next '+f.toString())
  if (f) {
    f(this.next.bind(this),status);
  } else if (this.callback) this.callback(status);
}

// Add next IO callback at the end of the list
CBL.prototype.push = function (f) {
  this.schedules.push(f);
}

// Execute CBL
CBL.prototype.start = function () {
  this.next();
}

// Add next IO callback at the top of the list
CBL.prototype.top = function (f) {
  this.schedules.unshift(f);
}

module.exports=CBL;
};
BundleModuleCode['jam/amp']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2006-2020 bLAB
 **    $CREATED:     09-02-16 by sbosse.
 **    $RCS:         $Id: amp.js,v 1.1 2020/02/03 09:45:01 sbosse Exp sbosse $
 **    $VERSION:     1.11.1
 **
 **    $INFO:
 **
 **  JAM Agent Management Port (AMP) over UDP/HTTP/devices/streams
 **
 **
 **  New: Fully negotiated IP Multicast Ports (P2N)
 **
 **    $ENDOFINFO
 */

var Io = Require('com/io');
var Lz = Require('os/lz-string');
var Comp = Require('com/compat');
var Buf = Require('dos/buf');
var Net = Require('dos/network');
var Command = Net.Command;
var Status = Net.Status;
var current=none;
var Aios=none;
var CBL = Require('com/cbl');

var COM = Require('jam/ampCOM'),
    AMMode=COM.AMMode,
    AMMessageType=COM.AMMessageType,
    AMState=COM.AMState,
    amp=COM.amp,
    options=COM.options,
    url2addr=COM.url2addr,
    addr2url=COM.addr2url,
    addrequal=COM.addrequal,
    resolve=COM.resolve,
    ipequal=COM.ipequal,
    getNetworkIP=COM.getNetworkIP,
    doUntilAck=COM.doUntilAck;

options.localhost='localhost';
options.version='1.11.1',
options.AMC_MAXLIVE=5,
options.TIMER=500,
options.TRIES=10;
options.REGTMO=1000;
options.pem={};

/***********************
** AMP
************************/

var ampMAN = Require('jam/ampMAN');
var ampRPC = Require('jam/ampRPC');

if (global.TARGET!= 'browser') {
  /******************************* AMP *************************************/

  var ampUDP = Require('jam/ampUDP');
  var ampTCP = Require('jam/ampTCP');
  var ampStream = Require('jam/ampStream');
}  

var ampHTTP = Require('jam/ampHTTP');
var ampHTTPS = Require('jam/ampHTTPS');

  
/** Main AMP constructor
 *  ====================
 *
 */
var Amp = function (options) {
  var obj;

  switch (options.proto) {
    case 'stream':
      obj=new amp.stream(options);
      break;
    case 'http':
      obj=new amp.http(options);
      break;
    case 'https':
      obj=new amp.https(options);
      break;
    case 'tcp':
      obj=new amp.tcp(options);
      break;
    case 'udp':
    default:
      obj=new amp.udp(options);
  }
  return obj;
}

module.exports.current=function (module) { 
  current=module.current; Aios=module; 
  if (ampMAN) ampMAN.current(module);
  if (ampUDP) ampUDP.current(module);
  if (ampHTTP) ampHTTP.current(module);
  if (ampHTTPS) ampHTTPS.current(module);
  if (ampTCP) ampTCP.current(module);
  if (ampStream) ampStream.current(module);
  if (ampRPC) ampRPC.current(module);
};

module.exports.Amp=Amp;
module.exports.AMMessageType=AMMessageType;
module.exports.AMState=AMState;
module.exports.AMMode=AMMode;
module.exports.Com=COM;
module.exports.Rpc=ampRPC;

module.exports.url2addr=url2addr;
module.exports.addr2url=addr2url;
module.exports.resolve=resolve;

module.exports.Command=Command
module.exports.Status=Status

module.exports.options=options;
};
BundleModuleCode['jam/ampCOM']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2006-2022 bLAB
 **    $CREATED:     09-02-16 by sbosse.
 **    $RCS:         $Id: ampCOM.js,v 1.1 2020/02/03 09:45:01 sbosse Exp sbosse $
 **    $VERSION:     1.16.1
 **
 **    $INFO:
 **
 **  JAM Agent Management Port (AMP) - Common Types and Utils
 **
 **
 **
 **    $ENDOFINFO
 */
 
var options = {
  debug:{},
  magic: 0x6dab,              // start of an AMP message (16bit)
  peekIP: '134.102.22.124',   // used by getnetworkip, must be an HTTP server
  localhost : 'localhost',    // default name for localhost
}

var Comp = Require('com/compat');
var Dns = Require('dns');

// Channel mode flags
var AMMode = {
    AMO_UNICAST: 1,       // P2P
    AMO_MULTICAST: 2,     // P2N
    AMO_STATIC: 4,        // 
    AMO_BUFFER: 8,        // Transfer buffer data
    AMO_OBJECT: 16,       // Transfer objects instead of buffer data
    AMO_COMPRESS: 32,     // Compress data
    AMO_SERVER: 64,       // This is HTTP server mode
    AMO_CLIENT: 128,      // This is HTTP client mode
    AMO_ONEWAY:256,       // Other side can be reache dw/o link negotiation
    print: function (m) {
      var s='',sep='';
      if (m & AMMode.AMO_UNICAST) s += (sep+'UNI'),sep='|';
      if (m & AMMode.AMO_MULTICAST) s += (sep+'MUL'),sep='|';
      if (m & AMMode.AMO_STATIC) s += (sep+'STA'),sep='|';
      if (m & AMMode.AMO_BUFFER) s += (sep+'BUF'),sep='|';
      if (m & AMMode.AMO_OBJECT) s += (sep+'OBJ'),sep='|';
      if (m & AMMode.AMO_COMPRESS) s += (sep+'ZIP'),sep='|';
      if (m & AMMode.AMO_CLIENT) s += (sep+'CLI'),sep='|';
      if (m & AMMode.AMO_SERVER) s += (sep+'SRV'),sep='|';
      if (m & AMMode.AMO_ONEWAY) s += (sep+'ONE'),sep='|';
      return s;
    }
}

// Message type
var AMMessageType = {
      AMMACK:0,
      AMMPING:1,
      AMMPONG:2,
      AMMLINK:3,
      AMMUNLINK:4,
      AMMRPCHEAD:6, // Header followed by multiple data requests
      AMMRPCDATA:7,
      // Broker Rendezvous support
      AMMCONTROL:8,
      AMMRPC:9,   // Header + data in one message
      AMMCOLLECT:10,   // Collect messages
      AMMRPCHEADDATA:11, // Header with embedded data
 
      // Port Scan for external Run Server - returns AMP info
      AMMSCAN : 12,
      AMMINFO : 13,

      print:function(op) {
          switch (op) {
              case AMMessageType.AMMACK: return "AMMACK";
              case AMMessageType.AMMPING: return "AMMPING";
              case AMMessageType.AMMPONG: return "AMMPONG";
              case AMMessageType.AMMLINK: return "AMMLINK";
              case AMMessageType.AMMUNLINK: return "AMMUNLINK";
              case AMMessageType.AMMRPCHEAD: return "AMMRPCHEAD";
              case AMMessageType.AMMRPCHEADDATA: return "AMMRPCHEADDATA";
              case AMMessageType.AMMRPCDATA: return "AMMRPCDATA";
              case AMMessageType.AMMRPC: return "AMMRPC";
              case AMMessageType.AMMCOLLECT: return "AMMCOLLECT";
              // Rendezvous Broker Message
              case AMMessageType.AMMCONTROL: return "AMMCONTROL";
              case AMMessageType.AMMSCAN: return "AMMSCAN";
              case AMMessageType.AMMINFO: return "AMMINFO";
              default: return "Chan.AMMessageType?";
          }
      }

};

// Channel state
var AMState = {
      AMS_NOTINIT:1,          // AMP Not initialized conenction
      AMS_INIT:2,             // AMP Server started, but not confirmed
      AMS_READY:3,            // AMP Server initialized and confirmed (other end point not connected)
      AMS_NEGOTIATE:4,        // AMP Server intiialized, in negotiation state (other end point not connected)
      AMS_CONNECTED:5,        // AMP Other side connected
      AMS_AWAIT:6,            // AMP waits for event (pairing)
      AMS_NOTCONNECTED:10,    // AMP Other side not connected
      // Optional IP broker service
      AMS_RENDEZVOUS:7,       // Broker IP P2P rendezvous; starting
      AMS_REGISTERED:8,       // Broker IP P2P rendezvous; registered; expecting pairing
      AMS_PAIRING:9,          // Broker IP P2P rendezvous; now pairing; send punches until paired
      AMS_PAIRED:10,          // Broker IP P2P rendezvous; acknowldeged and paired -> NOTCONNECTED
      print:function(op) {
          switch (op) {
              case AMState.AMS_NOTINIT: return "AMS_NOTINIT";
              case AMState.AMS_INIT: return "AMS_INIT";
              case AMState.AMS_READY: return "AMS_READY";
              case AMState.AMS_NEGOTIATE: return "AMS_NEGOTIATE";
              case AMState.AMS_CONNECTED: return "AMS_CONNECTED";
              case AMState.AMS_AWAIT: return "AMS_AWAIT";
              case AMState.AMS_NOTCONNECTED: return "AMS_NOTCONNECTED";
              case AMState.AMS_RENDEZVOUS: return "AMS_RENDEZVOUS";
              case AMState.AMS_REGISTERED: return "AMS_REGISTERED";
              case AMState.AMS_PAIRING: return "AMS_PAIRING";
              case AMState.AMS_PAIRED: return "AMS_PAIRED";
              default: return "Chan.AMState?";
          }
      }
  };

/** Used by AMP messages (msg.cmd,msg.status)
 *
 */

// Standard Object Service
var STD_FIRST_COM = 1000;
var STD_LAST_COM = 1999;
var STD_FIRST_ERR = (-STD_FIRST_COM);
var STD_LAST_ERR = (-STD_LAST_COM);

// System Process Server (incl. Agent Platform Manager)
var PS_FIRST_COM = 2200;
var PS_LAST_COM = 2299;
var PS_FIRST_ERR = (-PS_FIRST_COM);
var PS_LAST_ERR = (-PS_LAST_COM);

var Command = {
    /*
    ** Standard Commands
     */
    STD_MONITOR     : STD_FIRST_COM,
    STD_AGE         : (STD_FIRST_COM+1),
    STD_COPY        : (STD_FIRST_COM + 2),
    STD_DESTROY     : (STD_FIRST_COM + 3),
    // Get agent or node info
    STD_INFO        : (STD_FIRST_COM + 4),
    STD_RESTRICT    : (STD_FIRST_COM + 5),
    // Get agent or node status
    STD_STATUS      : (STD_FIRST_COM + 6),
    STD_TOUCH       : (STD_FIRST_COM + 7),
    STD_GETPARAMS   : (STD_FIRST_COM + 8),
    STD_SETPARAMS   : (STD_FIRST_COM + 9),
    STD_NTOUCH      : (STD_FIRST_COM + 10),
    STD_EXIT        : (STD_FIRST_COM + 11),
    STD_RIGHTS      : (STD_FIRST_COM + 12),
    STD_EXEC        : (STD_FIRST_COM + 13),
    STD_LOCATION    : (STD_FIRST_COM + 20),
    STD_LABEL       : (STD_FIRST_COM + 21),

    /*
    ** Agent Process Control
    */
    PS_STUN         : (PS_FIRST_COM),     // Kill a process/ create a snapshot
    PS_MIGRATE      : (PS_FIRST_COM+1),   // Execute a process from a snapshot after migration (->next+)
    PS_EXEC         : (PS_FIRST_COM+2),   // Execute a process from a snapshot (->next)
    PS_WRITE        : (PS_FIRST_COM+4),   // Store a process class template
    PS_READ         : (PS_FIRST_COM+5),   // Get a process class template
    PS_CREATE       : (PS_FIRST_COM+6),   // Create a process from a template and execute
    PS_FORK         : (PS_FIRST_COM+7),   // Fork a process from a running process
    PS_SIGNAL       : (PS_FIRST_COM+8),   // Send a signal to a process

};

var Status = {
    STD_OK:0,
    STD_CAPBAD      :   STD_FIRST_ERR,
    STD_COMBAD      :  (STD_FIRST_ERR-1),
    STD_ARGBAD      :  (STD_FIRST_ERR-2),
    STD_NOTNOW      :  (STD_FIRST_ERR-3),
    STD_NOSPACE     :  (STD_FIRST_ERR-4),
    STD_DENIED      :  (STD_FIRST_ERR-5),
    STD_NOMEM       :  (STD_FIRST_ERR-6),
    STD_EXISTS      :  (STD_FIRST_ERR-7),
    STD_NOTFOUND    :  (STD_FIRST_ERR-8),
    STD_SYSERR      :  (STD_FIRST_ERR-9),
    STD_INTR        :  (STD_FIRST_ERR-10),
    STD_OVERFLOW    :  (STD_FIRST_ERR-11),
    STD_WRITEPROT   :  (STD_FIRST_ERR-12),
    STD_NOMEDIUM    :  (STD_FIRST_ERR-13),
    STD_IOERR       :  (STD_FIRST_ERR-14),
    STD_WRONGSRV    :  (STD_FIRST_ERR-15),
    STD_OBJBAD      :  (STD_FIRST_ERR-16),
    STD_UNKNOWN     :  (STD_FIRST_ERR-17),
    RPC_FAILURE     : -1,
    BUF_OVERFLOW    : -2,
}

var amp={
  AMMessageType:AMMessageType,
  AMState:AMState
};

/** Search a channel that is connected to node 'destnode'
 *
 */
function lookupNode(node,destnode) {
  var chan,url;
  if (node.connections.ip && node.connections.ip.lookup) {
    url=node.connections.ip.lookup(destnode);
    if (url) return {
      chan:node.connections.ip,
      url:url,
      link:node.connections.ip.routingTable[url]
    };
  }
}


/*************************
** IP UTILS
*************************/
function isLocal(addr) {
  return addr=='localhost'||
         addr=='127.0.0.1'
}
function isIpAddr(addr) {
  return (/[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/.test(addr))
}
/*  typeof @url = "<proto>://<domain>:<ipport>" | "<domain>:<ipport>" | 
*                 "<name>:<ipport>" | "<ip>:<ipport>" | "<ip>:<portname>" | "<ipport>"
 *  and @ipport = (1-65535) | "*" 
 *  and @port = string 
 */
function parseUrl(url) {
  if (!isNaN(Number(url)) || url=='*') return {
    proto:  undefined,
    address:   undefined,
    port:   url,
    param:  undefined,
    value:  undefined    
  }
  var tokens = url.match(/((http|https|udp|tcp):\/\/)?([a-zA-Z0-9_\.\-]+):(\[?[a-zA-Z0-9]+\]?|\*)(\?([a-zA-z0-9]+)=([a-zA-Z0-9:]+))?/)
  if (!tokens)
    tokens   = url.match(/((http|https|udp|tcp):\/\/)?([a-zA-Z0-9_\.\-]+)/);
  return  {
    proto:  tokens[2],
    address:   tokens[3],
    port:   tokens[4],
    param:  tokens[6],
    value:  tokens[7]
  }
}

function url2addr(url,defaultIP,callback) {
  var addr={address:defaultIP||options.localhost,port:undefined},
      parts = parseUrl(url);
  if (parts.proto)   addr.proto=parts.proto;
  if (parts.address) addr.address=parts.address;
  if (parts.port && parts.port!='*')
    addr.port=!isNaN(Number(parts.port))?Number(parts.port):parts.port;
  if (parts.param) { 
    addr.parameter={};
    addr.parameter[parts.param]=parts.value;
  }

  if (!isLocal(parts.address) && !isIpAddr(parts.address)) {
    // <domain>:<ipport>
    // needs dns lookup with callback (async)
    if (Dns) 
      Dns.lookup(parts.address, function (err,_addr) {
        if (!err) addr.address=_addr;
        if (callback) callback(addr);
      }); 
    else if (callback) callback(addr);
    return addr;
  }
  if (callback) callback(addr);
  else return addr;
};

function params(po) {
  var s='?',sep='';
  for(var p in po) {
    s += (sep+p+'='+po[p]);
    sep='&';
  }
  return s;
}
function addr2url(addr,noproto) {
  return (!noproto && addr.proto?(addr.proto+'://'):'')+
         (isLocal(addr.address)?options.localhost:addr.address)+':'+
         (addr.port?addr.port:'*')+
         (!noproto && addr.parameter?params(addr.parameter):'')
};

function obj2url(obj) {
  if (!obj) return '*';
  if (obj.name && !obj.address) return obj.name+':*';
  if (!obj.address) return '*';
  return (isLocal(obj.address)?options.localhost:obj.address)+':'+(obj.port?obj.port:'*')
};

function addrequal(addr1,addr2) {
  return ipequal(addr1.address,addr2.address) && addr1.port==addr2.port;
}

function addrempty(addr) {
  return !(addr && addr.address && addr.port);
}

function resolve (url) {return addr2url(url2addr(url)) }

function ipequal(ip1,ip2) {
  if (ip1==undefined || ip2==undefined) return false;
  else if ((Comp.string.equal(ip1,'localhost') || Comp.string.equal(ip1,'127.0.0.1')) &&
           (Comp.string.equal(ip2,'localhost') || Comp.string.equal(ip2,'127.0.0.1'))) return true;
  else return ip1==ip2;
}

// Use remote TCP connection to get this host IP (private address if behind NAT) 
var ipnet = Require('net');
var myip;
function getNetworkInterfaces() {
  var results = null;
  try { 
    var networkInterfaces  = require('os').networkInterfaces;
    var nets = networkInterfaces();
    for (var name of Object.keys(nets)) {
        for (var net of nets[name]) {
            // Skip over non-IPv4 and internal (i.e. 127.0.0.1) addresses
            if (net.family === 'IPv4' && !net.internal) {
                results=results||{};
                if (!results[name]) {
                    results[name] = [];
                }
                results[name].push(net.address);
            }
        }
    }
  } catch (e) {};
  return results
}
function getNetworkIP(server,callback) {
  var socket;
  // 0. Use user defined environment variable
  try {
    if (typeof process != 'undefined' &&
        process.env &&
        process.env['HOSTIP'])
        return callback(undefined,process.env['HOSTIP']);
  } catch (e) {
  
  }
  // 1. Try to connect external HTTP server to get our public IP
  if (!ipnet) return callback('Not supported','error');
  if (myip) return callback(undefined,myip);
  if (!server) server={address:options.peekIP,port:80};
  socket = ipnet.createConnection(server.port, server.address);
  socket.on('connect', function() {
    myip=socket.address().address;
    callback(undefined, socket.address().address);
      socket.end();
  });
  socket.on('error', function(e) {
    // Try to get our (local) IP from network interface information
    var results = getNetworkInterfaces();
    if (!results)
      return callback(e, 'error');
    else {
      for(var i in results) return callback(undefined,results[i]);
    }
  });
}


function doUntilAck(interval, fn, ack, arg) {
  if (ack()) return;
  fn(arg);
  return setTimeout(function() {
    doUntilAck(interval, fn, ack, arg);
  }, interval);  
}


module.exports = {
  AMMode:AMMode,
  AMMessageType:AMMessageType,
  AMState:AMState,
  doUntilAck:doUntilAck,
  getNetworkIP:getNetworkIP,
  amp:amp,
  options:options,
  addrempty:addrempty,
  addrequal:addrequal,
  addr2url:addr2url,
  ipequal:ipequal,
  isLocal:isLocal,
  lookupNode:lookupNode,
  obj2url:obj2url,
  resolve:resolve,
  url2addr:url2addr,
  Command:Command,
  Status:Status,
}
};
BundleModuleCode['jam/ampMAN']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2006-2020 bLAB
 **    $CREATED:     30-01-18 by sbosse.
 **    $RCS:         $Id: ampMAN.js,v 1.1 2020/02/03 09:45:01 sbosse Exp sbosse $
 **    $VERSION:     1.14.9
 **
 **    $INFO:
 **
 **  JAM Agent Management Port (AMP) - General Management Operations
 **
 **
 **  New:
 **   - Single message transfers (HEADER+DATA)
 **
 **
 **    $ENDOFINFO
 */
var Io = Require('com/io');
var Lz = Require('os/lz-string');
var Comp = Require('com/compat');
var Buf = Require('dos/buf');
var Net = Require('dos/network');
var Sec = Require('jam/security');
var Command = Net.Command;
var Status = Net.Status;
var current=none;
var Aios=none;
var CBL = Require('com/cbl');

var COM = Require('jam/ampCOM'),
    AMMode=COM.AMMode,
    AMMessageType=COM.AMMessageType,
    AMState=COM.AMState,
    amp=COM.amp,
    options=COM.options,
    url2addr=COM.url2addr,
    addr2url=COM.addr2url,
    addrequal=COM.addrequal,
    resolve=COM.resolve,
    ipequal=COM.ipequal,
    addrempty=COM.addrempty,
    getNetworkIP=COM.getNetworkIP;

// Get data from message
function msgData(msg) {
  // typeof msg.data = Array | Buffer | { type: 'Buffer', data: Array }
  return msg.data && msg.data.data?msg.data.data:msg.data;
}

module.exports.current=function (module) { current=module.current; Aios=module; };


amp.man = function (options) {

}

// Message logger
amp.man.prototype.LOG = function (op,msg) {
  if (!this.logging) return;
  switch (op) {
    case 'print':
      for(var i in this.logs) {
        Aios.log(this.logs[i].op,this.logs[i].time,this.logs[i].msg,AMState.print(this.logs[i].state));
      }
      this.logs=[];
      break;
    case 'enable':
      this.logging=true;
      break;
    case 'disable':
      this.logging=false;
      break;
    default:
      var date = new Date();
      var time = Math.floor(date.getTime());
      this.logs.push({op:op,time:time,msg:msg,state:(this.url && this.links[this.url].state)});
  }
} 



/** Transation cache for receiving data fragments that can be out of order.
 *  typeof @data = [handler:{tid,remote,cmd,size,frags,buf},data:[],timeout:number]
 *
 */
amp.man.prototype.addTransaction = function (remote,tid,data) {
  if (this.mode & AMMode.AMO_MULTICAST)
    this.transactions[remote.address+remote.port+tid]=data; 
  else
    this.transactions[tid]=data; 
}
amp.man.prototype.deleteTransaction = function (remote,tid) {
  if (this.mode & AMMode.AMO_MULTICAST)
    delete this.transactions[remote.address+remote.port+tid];
  else 
    delete this.transactions[tid];
}
amp.man.prototype.findTransaction = function (remote,tid) {
  if (this.mode & AMMode.AMO_MULTICAST)
    return this.transactions[remote.address+remote.port+tid];
  else
    return this.transactions[tid];
}

/** Check the state of a link
  *
  */
amp.man.prototype.checkState = function (state,addr) {
  switch (state) {
    case AMState.AMS_CONNECTED:
      if (this.mode & AMMode.AMO_ONEWAY) return true;
      if (this.mode & AMMode.AMO_MULTICAST) return this.links[addr2url(addr,true)];
      if (this.url && this.links[this.url].state == AMState.AMS_CONNECTED) return true;
      break;
  }
  return false;
}

/** Update AMP object configuration
 *
 */
amp.man.prototype.config = function(options) { 
  for(var p in options) this[p]=options[p];
}
/** Handle events
 *
 */
amp.man.prototype.emit = function(event,arg,aux,aux2) { 
  if (this.events[event]) this.events[event](arg,aux,aux2);
}

/** Handler for incoming messages (proecssed by receiver)
 *
 */
amp.man.prototype.handle = function (msg,remote,response) {
  var handler,thisnum,ipport,cmsg,url,ack,info;
  if (this.verbose > 1) this.out('handle '+AMMessageType.print(msg.type)+' from '+addr2url(remote),true);
  switch (msg.type) {
    case AMMessageType.AMMRPCHEAD:
    case AMMessageType.AMMRPCHEADDATA:
      if (!this.checkState(AMState.AMS_CONNECTED,remote)) return;

      handler={};
      handler.tid=msg.tid; 
      // handler.remote=remote.address+':'+Buf.buf_get_int16(buf);
      handler.remote=remote;
      handler.cmd=msg.cmd;
      handler.size=msg.size;
      handler.frags=msg.frags;
      if (msg.size<0) this.err('Got invalid message (size<0) from '+addr2url(remote)); // in16 limit
      // console.log(handler)
      if (handler.size>0 && handler.frags>0) {
        // AMMRPCDATA messages are following (used by UDP)
        handler.buf=Buf.Buffer();
        dlist = Comp.array.range(0, handler.frags - 1);
        // Add transaction to cache for pending data
        this.addTransaction(remote, handler.tid, [handler,dlist,1000]);
      } else if (handler.size>0) {
        // Single message transfer; message contains all data (msg.data: Buf.buffer!, used by TCP)
        handler.buf=msg.data;
        this.callback(handler);        
      } else {
        // No data; control message
        handler.buf=Buf.Buffer();
        this.callback(handler);
      }
      break;

    case AMMessageType.AMMRPCDATA:
      if (!this.checkState(AMState.AMS_CONNECTED,remote)) return;
      thisnum = msg.off/this.dlimit;
      transaction = this.findTransaction(remote,msg.tid);
      if (transaction!=undefined) {
        handler=transaction[0];
        if (this.verbose>1)
          this.out('receiver: adding data num='+
                   thisnum+' off='+msg.off+' size='+msg.size+' dlist='+transaction[1],true);

        Buf.buf_get_buf(msg.data,handler.buf,msg.off,msg.size);
        transaction[1]=Comp.array.filter(transaction[1],function(num) {return (num!=thisnum)});
        if (Comp.array.empty(transaction[1])) {
            if (this.verbose>2) this.out('[AMP] receiver: finalize '+addr2url(remote),true);
            // Io.out(handler.data.toString());
            // Deliver
            this.callback(handler);
            this.deleteTransaction(remote,msg.tid);
        }
      }
      break;

    case AMMessageType.AMMRPC:
      // Single data transfer - used by HTTP/Browser
      if (!this.checkState(AMState.AMS_CONNECTED,remote)) return;
      // Complete RPC message
      handler={};
      handler.tid=msg.tid; 
      // handler.remote=remote.address+':'+Buf.buf_get_int16(buf);
      handler.remote=remote;
      handler.cmd=msg.cmd;
      handler.size=msg.size;
      handler.frags=msg.frags;
      handler.buf=Buf.Buffer(msgData(msg));
      this.callback(handler);
      if (this.ack && response) this.ack(response);
      break;
      
    case AMMessageType.AMMPING:
        url=addr2url(remote,true);
        ipport=remote.port;
        if (this.mode&AMMode.AMO_MULTICAST) {
          if (!this.links[url] || this.links[url].state!=AMState.AMS_CONNECTED) return;
        } else if (this.url) {
          if (this.links[this.url].state!=AMState.AMS_CONNECTED) return;
        }
        // Send back a PONG message only if we're connected
        this.pong({address:remote.address,port:ipport},response);
        break;

    case AMMessageType.AMMPONG:
        ipport=remote.port;
        if (this.mode&AMMode.AMO_MULTICAST) {
          url=addr2url(remote,true);
          if (this.links[url] && this.links[url].state==AMState.AMS_CONNECTED) {
            this.links[url].live = options.AMC_MAXLIVE;
          }
        } else if (this.url && this.links[this.url].state==AMState.AMS_CONNECTED) {
          this.links[this.url].live = options.AMC_MAXLIVE;
        }
        if (this.ack && response) this.ack(response);
        break;

    case AMMessageType.AMMACK:
        // TODO: check pending waiters (scan mode)
        if (msg.status=="ELINKED") {    
          if (this.mode&AMMode.AMO_MULTICAST) {
            // Multicast mode
            url=addr2url(remote,true);
            if (!this.links[url] || this.links[url].state==AMState.AMS_NOTCONNECTED) {
                // Ad-hoc remote connect
                if (!this.links[url]) this.links[url]={};
                this.links[url].snd=remote;
                this.links[url].live=options.AMC_MAXLIVE; 
                this.links[url].port=msg.port;
                this.links[url].ipport=remote.port; 
                this.links[url].state=AMState.AMS_CONNECTED;
                this.links[url].node=msg.node;
                this.emit('route+',url,msg.node);
                this.watchdog(true);
                if (this.verbose) 
                  this.out('Linked with ad-hoc '+this.proto+' '+url+', AMP '+
                            Net.Print.port(msg.port)+', Node '+msg.node,true);
            }           
          }
        }
        break;
        
    case AMMessageType.AMMLINK:
        ipport=remote.port;
        url=addr2url(remote,true);
        if (this.secure && (!msg.secure || !Sec.Port.equal(this.secure,msg.secure))) return; 
        if (this.mode&AMMode.AMO_MULTICAST) {
          // Multicast mode
          if (!this.links[url] || this.links[url].state==AMState.AMS_NOTCONNECTED) {
              // Ad-hoc remote connect
              if (!this.links[url]) this.links[url]={};
              this.links[url].snd=remote;
              this.links[url].live=options.AMC_MAXLIVE; 
              this.links[url].port=msg.port;
              this.links[url].ipport=remote.port; 
              // back link acknowledge
              this.link(this.links[url].snd,false,none,response);
              // no ack="EOK" -- ack send by link response!;
              this.links[url].state=AMState.AMS_CONNECTED;
              this.links[url].node=msg.node;
              // if (this.mode&AMMode.AMO_UNICAST) this.snd=remote,this.url=url;
              this.emit('route+',url,msg.node,msg.remote);
              this.watchdog(true);
              if (this.verbose) 
                this.out('Linked with ad-hoc '+this.proto+' '+url+', AMP '+
                          Net.Print.port(msg.port)+', Node '+msg.node,true);
          } else if (this.links[url].state==AMState.AMS_CONNECTED) {
            // Already linked! Just acknowledge
            ack="ELINKED";
          }
        } else {

          // Unicast mode; only one connection
          if (this.links[url] && !addrempty(this.links[url].snd) &&
              this.links[url].state==AMState.AMS_NOTCONNECTED &&
              ipequal(this.links[url].snd.address,remote.address) &&
              this.links[url].snd.port==ipport)    // ipport or remote.port??
          {
              // Preferred / expected remote connect
              this.links[url].snd=remote;
              this.links[url].port=msg.port;
              this.links[url].ipport=remote.port; 
              this.links[url].node=msg.node;
              this.links[url].live=options.AMC_MAXLIVE; 

              // back link acknowledge
              this.link(this.links[url].snd);

              this.links[url].state=AMState.AMS_CONNECTED;
              // Inform router
              this.emit('route+',url,msg.node,msg.remote);
              this.watchdog(true);
              if (this.verbose) 
                this.out('Linked with preferred '+this.proto+' '+ url +', '+
                         Net.Print.port(msg.port),true); 
          } else if ((!this.links[url] && !this.url) || 
                     (this.links[url] && this.links[url].state==AMState.AMS_NOTCONNECTED) ||
                     (this.broker && this.url && this.links[this.url].state==AMState.AMS_NOTCONNECTED)) {
              if (!this.links[url]) this.links[url]={};
              this.links[url].snd=remote;
              this.links[url].live=options.AMC_MAXLIVE; 
              this.links[url].port=msg.port;
              this.links[url].ipport=remote.port; 
              this.links[url].node=msg.node;

              // back link acknowledge
              this.link(this.links[url].snd,false,none,response);
              // no ack="EOK"; - ack was send with link message!

              this.links[url].state=AMState.AMS_CONNECTED;
              this.url=url;  // remember this link

              // Inform router
              this.emit('route+',url,msg.node);
              this.watchdog(true);
          
              if (this.verbose) 
                this.out('Linked with ad-hoc ' + this.proto +' '+ url +', '+
                          Net.Print.port(msg.port),true);
          } 
        }
        if (ack && this.ack && response) this.ack(response,ack);
        break;

    case AMMessageType.AMMUNLINK:
        ipport=remote.port;
        if (this.mode&AMMode.AMO_MULTICAST) {
          // Multicast mode
          url=addr2url(remote,true); // ipport or remote.port??
          if (this.links[url] && !addrempty(this.links[url].snd) && ipequal(this.links[url].snd.address,remote.address) &&
              this.links[url].snd.port==ipport && this.links[url].state==AMState.AMS_CONNECTED) {
              this.links[url].state=AMState.AMS_NOTCONNECTED;
              // Not negotiated. Just close the link!
              if (this.verbose) 
                this.out('Unlinked ' +url+', '+
                         Net.Print.port(msg.port),true);
              // Inform router
              this.emit('route-',url);
              if (!this.links[url].snd.connect) this.links[url].snd={};
              if (this.cleanup) this.cleanup(url);
          }
        } else {
          // Unicast mode
          if (this.url && !addrempty(this.links[this.url].snd) &&
              ipequal(this.links[this.url].snd.address,remote.address) &&
              this.links[this.url].snd.port==ipport &&
              this.links[this.url].state==AMState.AMS_CONNECTED) 
          {
              this.links[this.url].state=AMState.AMS_NOTCONNECTED;
              addr=this.links[this.url].snd;
              // Not negotiated. Just close the link!
              if (this.verbose) 
                this.out('Unlinked ' + this.url +', '+
                         Net.Print.port(msg.port),true);

              // Inform router
              this.emit('route-',addr2url(addr));
              if (!this.links[this.url].snd.connect) this.links[this.url].snd=null;
              if (this.cleanup) this.cleanup(url);
          }
        }
        if (this.ack && response) this.ack(response);
        break;

    // optional rendezvous brokerage ; remote is broker IP!!!
    case AMMessageType.AMMCONTROL:
        cmsg = JSON.parse(msgData(msg));
        if (this.verbose>1) this.out('# got message '+msgData(msg),true);
        this.LOG('rcv',cmsg);
        // All brokerage and pairing is handled by the root path '*'!
        if (this.control && this.links['*'])
          this.control(this.links['*'],cmsg,remote);    
        break;
        
    case AMMessageType.AMMSCAN:
        url=addr2url(remote,true);
        ipport=remote.port;
        info={
          world:(current.world&&current.world.id),
          stats:(current.world&&current.world.info()),
        };
        this.scan({address:remote.address,port:ipport,info:info},response);
        break;

    default:
      this.out('handle: Unknown message type '+msg.type,true);
  }
}

/** Install event handler
 *
 */
amp.man.prototype.on = function(event,handler) { 
  this.events[event]=handler;
}

// Status of link, optionally checking destination
amp.man.prototype.status = function (ip,ipport) {
  var p,url,sl=[];
  if (ip=='%') {
    // return all connected nodes
    for(p in this.links) if (this.links[p] && this.links[p].state==AMState.AMS_CONNECTED) 
      sl.push(this.links[p].node);
    return sl;
  }
  if (this.mode&AMMode.AMO_MULTICAST) {
    if (!ip) {
      for(p in this.links) if (this.links[p] && this.links[p].state==AMState.AMS_CONNECTED) return true;
      return false;
    } else {
      url=addr2url({address:ip,port:ipport});
      if (!this.links[url]) return false;
      return this.links[url].state==AMState.AMS_CONNECTED;
    }
  }
  if (!ip && this.url) return this.links[this.url].state==AMState.AMS_CONNECTED || 
                              (this.mode&AMMode.AMO_ONEWAY)==AMMode.AMO_ONEWAY;
 
  return (this.url && ipequal(this.links[this.url].snd.address,ip) && this.links[this.url].snd.port==ipport);
}
};
BundleModuleCode['jam/security']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2006-2019 bLAB
 **    $CREATED:     04-02-19 by sbosse.
 **    $RCS:         $Id: security.js,v 1.1 2020/02/03 09:45:01 sbosse Exp sbosse $
 **    $VERSION:     1.1.3
 **
 **    $INFO:
 **
 **  JAM Capability and Security Management. Derived from dos/net module.
 **
 **
 **
 **    $ENDOFINFO
 */

var Io      = Require('com/io');
var Des48   = Require('dos/des48');
var Base64  = Require('os/base64');
var Comp    = Require('com/compat');
var String  = Comp.string;
var Array   = Comp.array;
var Perv    = Comp.pervasives;
var current = none;
var Aios    = none;
var Rnd     = Require('com/pwgen');


var PORT_SIZE = 6;
var PRIV_SIZE = 4+PORT_SIZE;
var CAP_SIZE = 16;
var PRV_ALL_RIGHTS = 0xff;


var priv2pub_cache = [];
var uniquePorts = {};

var Rights = {
    HOST_INFO       : 0x01,
    HOST_READ       : 0x02,
    HOST_WRITE      : 0x04,
    HOST_EXEC       : 0x08,

    PSR_READ        : 0x01,
    PSR_WRITE       : 0x02,
    PSR_CREATE      : 0x04,
    PSR_DELETE      : 0x08,
    PSR_EXEC        : 0x10,
    PSR_KILL        : 0x20,
    PSR_ALL         : 0xff,

    NEG_SCHED       : 0x08,
    NEG_CPU         : 0x10,
    NEG_RES         : 0x20,
    NEG_LIFE        : 0x40,
    NEG_LEVEL       : 0x80,
    
    PRV_ALL_RIGHTS  : 0xff
};

/**
 *
 * typeof @port_valse = number [] 
 * typeof return = string
 */
var Port = function (port_vals) {
    if (port_vals==undefined) port_vals=[0,0,0,0,0,0];
    var port='';
    for(var i = 0; i< PORT_SIZE;i++) {
        port=port+Perv.char_of_int(port_vals[i]);
    }
    return port;

};
/**
 *
 * typeof @obj = number | undefined
 * typeof @rights = number | undefined
 * typeof @rand = port | undefined
 * typeof function = constructor
 */
var Private = function (obj,rights,rand) {
    if (obj==undefined) {
        // Create empty private field
      return {
          prv_obj : 0,
          prv_rights : 0,
          prv_rand : Port()
      }
    } else {
      return {
        prv_obj : obj,               // Integer
        prv_rights : rights,         // Integer
        prv_rand : rand              // Port=string
      }
    }
}

/**
 *
 * typeof @cap_port = port
 * typeof @cap_priv = privat
 * typeof function = @constructor
 */
var Capability = function(cap_port, cap_priv) {
    if (cap_port==undefined) {
        // Create empty capability
        return {
          cap_port : Port(),
          cap_priv : Private()
        }
    } else {
        return {
          cap_port : cap_port,      // Port=string
          cap_priv : cap_priv?cap_priv:Private()
        }
    }
}
function cap_parse(str,offset) {
    var cap=Capability(),
        pos=0;
    if (offset!=undefined) pos=offset;
    var pp=port_parse(str,pos);
    if (pp==undefined) return undefined;
    cap.cap_port=pp.port;
    pos=pp.pos;
    pp=prv_parse(str,pos);
    if (pp==undefined) return undefined;
    cap.cap_priv=pp.priv;
    pos=pp.pos;
    return {cap:cap,pos:pos};
}

function cap_of_string(str) { var pp = cap_parse(str,0); return pp?pp.cap:undefined }

function cap_to_string(cap) {
    var str='';
    if (cap==undefined) return 'undefined';
    if (cap.cap_port!=undefined) str='['+port_to_string(cap.cap_port)+']'; else str = '[]';
    if (cap.cap_priv!=undefined) str=str+'('+prv_to_string(cap.cap_priv)+')'; else str=str+'()';
    return str;
}

/*
 ** Utils to get and set single bytes of a port
 */
function get_portbyte(port,i) {
    return Perv.int_of_char(String.get(port,i))
}
function set_portbyte(port,i,byte) {
    return String.set(port, i, (Perv.char_of_int(byte)));
}

/*
 ** Encryption function
 */
function one_way(port) {
    var key = Array.create(64,0);
    var block = Array.create(48,0);
    var pubport = String.make (PORT_SIZE,'\0');
    var i, j, k;

    /*
    ** We actually need 64 bit key.
    ** Throw some zeroes in at bits 6 and 7 mod 8
    ** The bits at 7 mod 8 etc are not used by the algorithm
    */
    j=0;
    for (i = 0; i< 64; i++) {
        if ((i & 7) > 5)
            key[i] = 0;
        else {
            if ((get_portbyte(port, (j >> 3)) & (1 << (j & 7))) != 0)
                key[i] = 1;
            else
                key[i] = 0;
            j++;
        }
    }

    Des48.des_OWsetkey(key);
    /*
    ** Now go encrypt constant 0
    */
    block=Des48.des_OWcrypt48(block);


    /*
    ** and put the bits in the destination port
    */
    var pb = 0;

    for (i = 0; i < PORT_SIZE;i++) {
        var pbyte = 0;
        for (j = 0; j < 8; j++) {
            pbyte = pbyte | (block[pb] << j);
            pb++;
        }
        pubport=set_portbyte(pubport, i, pbyte);
    }
    return pubport;
}

function pad(str,size) {
    while (str.length < (size || 2)) {str = "0" + str;}
    return str;
}

function port_cmp(port1,port2) {
  if (port1==undefined || port2==undefined) return (port1==port2);
  else return String.equal(port1,port2);
}

function port_copy(port) {
    return String.copy(port);
}

// Expected format: XX:XX:XX:XX:XX
function port_of_string(str,compact) {
    var tokens=str.split(':'),i,port='';
    for (i=0;i<PORT_SIZE;i++) {
        var num='0x'+tokens[i];
        port=port+Perv.char_of_int(parseInt(num,16));
    }
    return port;
}

function port_parse(str,pos) {
    var port='';
    var len=str.length;
    if (pos==undefined) pos=0;
    if (len<(pos+17)) return undefined;
    if (str[pos]=='[') pos++;
    for(var i=0;i<6;i++) {
        var sv='0x'+str[pos]+str[pos+1];
        port=port+Perv.char_of_int(Perv.int_of_string(sv));
        pos=pos+2;
        if (str[pos]==':') pos++;
    }
    if (str[pos]==']') pos++;
    return {port:port,pos:pos};
}

function port_to_string(port,compact) {
    var i,str='';
    if (port) {
        for (i = 0; i < PORT_SIZE; i++) {
            var num = Perv.int_of_char(String.get(port, i));
            if (!compact && i > 0) str = str + ':';
            str = str + pad(num.toString(16).toUpperCase(), 2);
        }
    } else str='undefined';
    return str;
}


function prv2pub (port) {
    var putport;
    if (priv2pub_cache[port] == undefined) {
        putport=one_way(port);
        priv2pub_cache[port] = putport;
    } else putport = priv2pub_cache[port];
    return putport;
}

function prv_cmp(prv1,prv2) {
 return  (prv1==undefined&&prv2==undefined) ||
         (prv1.prv_obj==prv2.prv_obj &&
          prv1.prv_rights==prv2.prv_rights &&
          port_cmp(prv1.prv_rand,prv2.prv_rand))
}

/**
 ** Decode a private structure (check for a valid private field)
 *
 * typeof @prv =  privat
 * typeof @rand = port
 * returns boolean
 */
function prv_decode (prv,rand) {
    if (prv.prv_rights == PRV_ALL_RIGHTS)
        return port_cmp(prv.prv_rand,rand);
    else {
        var tmp_port = port_copy(rand),
            pt0 = get_portbyte(tmp_port, 0),
            pr0 = prv.prv_rights;
        tmp_port = set_portbyte(tmp_port, 0, (pt0 ^ pr0));
        tmp_port = one_way(tmp_port);
        return port_cmp(prv.prv_rand, tmp_port)
    }
}

/*
 ** Encode a private part from the object number, the rights field
 ** and the random port.
 ** Returns the created private structure.
 */
function prv_encode(obj,rights,rand) {
    var tmp_port = port_copy(rand),
        r1 = rights,
        rmask = PRV_ALL_RIGHTS;

    if (rights == PRV_ALL_RIGHTS)
        return Private(obj,r1 & rmask,tmp_port);
    else {
        var pt0 = get_portbyte(tmp_port,0);
        tmp_port = set_portbyte(tmp_port,0,pt0 ^ r1);
        tmp_port = one_way(tmp_port);
        return Private(obj,r1 & rmask,tmp_port)
    }
}

function prv_of_string(str) { var pp=prv_parse(str,0); return pp?pp.priv:undefined }

/*
 ** Return the private object number form a private structure
 */
function prv_number(prv) {
    return prv.prv_obj;
}

// Expected format: obj(right)[port]
function prv_parse(str,offset) {
    var priv=Private();
    var sv;
    var len=str.length,pos=offset;
    if (str[pos]=='(') pos++;
    sv='';
    while(str[pos]!='(') {
        sv=sv+str[pos];
        pos++;
    }
    priv.prv_obj=Perv.int_of_string(sv);
    sv='';
    if (str[pos]=='(') pos++;
    while(str[pos]!=')') {
        sv=sv+str[pos];
        pos++;
    }
    priv.prv_rights=Perv.int_of_string('0x'+sv);
    if (str[pos]==')') pos++;
    var pp=port_parse(str,pos);
    if (pp==undefined) return undefined;
    priv.prv_rand=pp.port;
    pos=pp.pos;
    return {priv:priv,pos:pos};
}


function prv_to_string(priv) {
    var str='';
    if (priv==undefined) return 'undefined';
    str=priv.prv_obj;
    str=str+'('+String.format_hex(priv.prv_rights,2).toUpperCase()+')[';
    str=str+port_to_string(priv.prv_rand)+']';
    return str;
}

/** Restrict a private field (rights&mask) of a capability.
 *
 * @param {privat} priv
 * @param {number} mask rights restriction mask
 * @param {port} random secret server random port
 */
function prv_restrict(priv,mask,random) {
    var pr = prv_encode(priv.prv_obj,
                        priv.prv_rights & mask,
                        random);
    return pr;
}
/*
 ** Return the private rights field.
 */
function prv_rights(prv) {
    return prv.prv_rights & Rights.PRV_ALL_RIGHTS;
}
/*
 ** Check the private rights field: 1. Validation, 2: Required rights.
 */
function prv_rights_check(prv,rand,required) {
  if (!prv_decode(prv,rand)) return false;
  return (prv.prv_rights & required)==required;
}

/*
 * Return a new random unique port.
 *
 * Warning: the quality of the random ports are strongly
 * related to JSVMs underlying random generator.
 *
 * typeof return = port
 */
function uniqport() {
    var port = String.create (PORT_SIZE);
    var i,values;
    
    do {
      values = Rnd.generate({number:true,length:PORT_SIZE});
      for (i = 0; i <= (PORT_SIZE - 1); i++) 
        port = String.set(port, i, (Perv.char_of_int(values[i])));
      if (uniquePorts[port]) uniquePorts[port]++;
      else uniquePorts[port]=1;
    } while (uniquePorts[port]>1);
    return port;
}

Port.equal = port_cmp
Port.toString = port_to_string
Port.ofString = port_of_string
Port.prv2pub = prv2pub
Port.random = uniqport
Port.unique = uniqport
Private.decode = prv_decode
Private.encode = prv_encode
Private.equal = prv_cmp
Private.number = prv_number
Private.ofString = prv_of_string
Private.restrict = prv_restrict
Private.rights = prv_rights
Private.rights_check = prv_rights_check
Private.toString = prv_to_string
Capability.toString = cap_to_string
Capability.ofString = cap_of_string

 
var Security = {
    current:function (module) { current=module.current; Aios=module; },

    PORT_SIZE:PORT_SIZE,
    PRIV_SIZE:PRIV_SIZE,
    Rights:Rights,
    Private:Private,
    Capability: Capability,
    Port: Port,
    nilport: Port(),
    nilpriv: Private(0,0,Port()),
    nilcap:  Capability(Port(),Private(0,0,Port())),
    one_way : one_way,
    prv2pub : prv2pub,
}

module.exports = Security;

};
BundleModuleCode['com/pwgen']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Bermi Ferrer, Stefan Bosse 
 **    $INITIAL:     (C) 2011-2015 Bermi Ferrer <bermi@bermilabs.com>, 2017-2018 Stefan Bosse
 **    $REVESIO:     1.3.1
 **
 **    $INFO:
 *
 * password-generator using crypto random number generation (slow,HQ)
 * !using built-in crypto random generators using either native crypto module or polyfill!
 * 
 * options = {length,memorable,lowercase,uppercase,pattern,number?:boolean,range?:[]}
 *
 * Using always twister random byte generator (not random byte array) 
 *
 *     $ENDINFO
 */

var Crypto = Require('os/crypto.rand'); // Require('crypto');

module.exports.generate = function (options) {
  
  function numgen (options) {
    // assuming byte number range 0-255
    var arr = new Uint8Array(options.length||8);
    getRandomValues(arr);
    return arr;
  }
  
  function pwgen (options) {
    var localName, consonant, letter, vowel, pattern = options.pattern,
        char = "", n, i, validChars = [], prefix=options.prefix;
    letter = /[a-zA-Z]$/;
    vowel = /[aeiouAEIOU]$/;
    consonant = /[bcdfghjklmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ]$/;
    if (options.length == null) {
      options.length = 10;
    }
    if (pattern == null) {
      pattern = /\w/;
    }
    if (prefix == null) {
      prefix = '';
    }

    // Non memorable passwords will pick characters from a pre-generated
    // list of characters
    if (!options.memorable) {
      for (i = 33; 126 > i; i += 1) {
        char = String.fromCharCode(i);
        if (char.match(pattern)) {
          validChars.push(char);
        }
      }

      if (!validChars.length) {
        throw new Error("Could not find characters that match the " +
          "password pattern " + pattern + ". Patterns must match individual " +
          "characters, not the password as a whole.");
      }
    }


    while (prefix.length < options.length) {
      if (options.memorable) {
        if (prefix.match(consonant)) {
          pattern = vowel;
        } else {
          pattern = consonant;
        }
        n = Crypto.randomByte(33,126); // rand(33, 126);
        char = String.fromCharCode(n);
      } else {
        char = validChars[rand(0, validChars.length)];
      }

      if (options.lowercase) char = char.toLowerCase();
      else if (options.uppercase) char = char.toUpperCase();
      
      if (char.match(pattern)) {
        prefix = "" + prefix + char;
      }
    }
    return prefix;
  };


  function rand(min, max) {
    var key, value, arr = new Uint8Array(max);
    getRandomValues(arr);
    for (key in arr) {
      if (arr.hasOwnProperty(key)) {
        value = arr[key];
        if (value > min && value < max) {
          return value;
        }
      }
    }
    return rand(min, max);
  }


  function getRandomValues(buf) {
    var bytes = Crypto.randomBytes(buf.length);
    buf.set(bytes);
  }
  if (options.number) 
    return numgen(options)
  else
    return pwgen(options);
};
};
BundleModuleCode['os/crypto.rand']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2006-2018 bLAB
 **    $CREATED:     15-1-16 by sbosse.
 **    $VERSION:     1.2.4
 **
 **    $INFO:
 **
 **  Crypto module with HQ random number generators (replacing not available crypto.getRandomValues
 **  if there is no global crypto module).
 **
 **    $ENDOFINFO
 */
var crypto = global.crypto || global.msCrypto;

if (!crypto && typeof require != 'undefined') try { crypto=global.crypto=require('require') } catch (e) {};

var twister;

var MersenneTwister = function(seed) {
	if (seed == undefined) {
        /**
        ** It is not sure that Math.random is seeded randomly
        ** Thus, a combination of current system time and Math.random 
        ** is used to seed and initialize this random generator
        */
		seed = new Date().getTime();
        seed *= Math.random()*91713;
        seed |= 0;
	}

	/* Period parameters */
	this.N = 624;
	this.M = 397;
	this.MATRIX_A = 0x9908b0df;   /* constant vector a */
	this.UPPER_MASK = 0x80000000; /* most significant w-r bits */
	this.LOWER_MASK = 0x7fffffff; /* least significant r bits */

	this.mt = new Array(this.N); /* the array for the state vector */
	this.mti=this.N+1; /* mti==N+1 means mt[N] is not initialized */

	if (seed.constructor == Array) {
		this.init_by_array(seed, seed.length);
	}
	else {
		this.init_seed(seed);
	}
}

/* initializes mt[N] with a seed */
/* origin name init_genrand */
MersenneTwister.prototype.init_seed = function(s) {
	this.mt[0] = s >>> 0;
	for (this.mti=1; this.mti<this.N; this.mti++) {
		var s = this.mt[this.mti-1] ^ (this.mt[this.mti-1] >>> 30);
		this.mt[this.mti] = (((((s & 0xffff0000) >>> 16) * 1812433253) << 16) + (s & 0x0000ffff) * 1812433253)
		+ this.mti;
		/* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */
		/* In the previous versions, MSBs of the seed affect   */
		/* only MSBs of the array mt[].                        */
		/* 2002/01/09 modified by Makoto Matsumoto             */
		this.mt[this.mti] >>>= 0;
		/* for >32 bit machines */
	}
}

/* initialize by an array with array-length */
/* init_key is the array for initializing keys */
/* key_length is its length */
/* slight change for C++, 2004/2/26 */
MersenneTwister.prototype.init_by_array = function(init_key, key_length) {
	var i, j, k;
	this.init_seed(19650218);
	i=1; j=0;
	k = (this.N>key_length ? this.N : key_length);
	for (; k; k--) {
		var s = this.mt[i-1] ^ (this.mt[i-1] >>> 30)
		this.mt[i] = (this.mt[i] ^ (((((s & 0xffff0000) >>> 16) * 1664525) << 16) + ((s & 0x0000ffff) * 1664525)))
		+ init_key[j] + j; /* non linear */
		this.mt[i] >>>= 0; /* for WORDSIZE > 32 machines */
		i++; j++;
		if (i>=this.N) { this.mt[0] = this.mt[this.N-1]; i=1; }
		if (j>=key_length) j=0;
	}
	for (k=this.N-1; k; k--) {
		var s = this.mt[i-1] ^ (this.mt[i-1] >>> 30);
		this.mt[i] = (this.mt[i] ^ (((((s & 0xffff0000) >>> 16) * 1566083941) << 16) + (s & 0x0000ffff) * 1566083941))
		- i; /* non linear */
		this.mt[i] >>>= 0; /* for WORDSIZE > 32 machines */
		i++;
		if (i>=this.N) { this.mt[0] = this.mt[this.N-1]; i=1; }
	}

	this.mt[0] = 0x80000000; /* MSB is 1; assuring non-zero initial array */
}

/* generates a random number on [0,0xffffffff]-interval */
/* origin name genrand_int32 */
MersenneTwister.prototype.random_int = function() {
	var y;
	var mag01 = new Array(0x0, this.MATRIX_A);
	/* mag01[x] = x * MATRIX_A  for x=0,1 */

	if (this.mti >= this.N) { /* generate N words at one time */
		var kk;

		if (this.mti == this.N+1)  /* if init_seed() has not been called, */
			this.init_seed(5489);  /* a default initial seed is used */

		for (kk=0;kk<this.N-this.M;kk++) {
			y = (this.mt[kk]&this.UPPER_MASK)|(this.mt[kk+1]&this.LOWER_MASK);
			this.mt[kk] = this.mt[kk+this.M] ^ (y >>> 1) ^ mag01[y & 0x1];
		}
		for (;kk<this.N-1;kk++) {
			y = (this.mt[kk]&this.UPPER_MASK)|(this.mt[kk+1]&this.LOWER_MASK);
			this.mt[kk] = this.mt[kk+(this.M-this.N)] ^ (y >>> 1) ^ mag01[y & 0x1];
		}
		y = (this.mt[this.N-1]&this.UPPER_MASK)|(this.mt[0]&this.LOWER_MASK);
		this.mt[this.N-1] = this.mt[this.M-1] ^ (y >>> 1) ^ mag01[y & 0x1];

		this.mti = 0;
	}

	y = this.mt[this.mti++];

	/* Tempering */
	y ^= (y >>> 11);
	y ^= (y << 7) & 0x9d2c5680;
	y ^= (y << 15) & 0xefc60000;
	y ^= (y >>> 18);

	return y >>> 0;
}

/* generates a random number on [0,0x7fffffff]-interval */
/* origin name genrand_int31 */
MersenneTwister.prototype.random_int31 = function() {
	return (this.random_int()>>>1);
}

/* generates a random number on [0,1]-real-interval */
/* origin name genrand_real1 */
MersenneTwister.prototype.random_incl = function() {
	return this.random_int()*(1.0/4294967295.0);
	/* divided by 2^32-1 */
}

/* generates a random number on [0,1)-real-interval */
MersenneTwister.prototype.random = function() {
	return this.random_int()*(1.0/4294967296.0);
	/* divided by 2^32 */
}

/* generates a random number on (0,1)-real-interval */
/* origin name genrand_real3 */
MersenneTwister.prototype.random_excl = function() {
	return (this.random_int() + 0.5)*(1.0/4294967296.0);
	/* divided by 2^32 */
}

/* generates a random number on [0,1) with 53-bit resolution*/
/* origin name genrand_res53 */
MersenneTwister.prototype.random_long = function() {
	var a=this.random_int()>>>5, b=this.random_int()>>>6;
	return(a*67108864.0+b)*(1.0/9007199254740992.0);
}

function polyfill () {
  twister = new MersenneTwister(); // (Math.random()*Number.MAX_SAFE_INTEGER)|0)
  if (!crypto) crypto=global.crypto={};
  crypto.getRandomValues = function getRandomValues (abv) {
    var l = abv.length
    while (l--) {
      abv[l] = Math.floor(twister.random() * 256)
    }
    return abv
  }
  if (!global.Uint8Array && !Uint8Array) throw new Error('crypto.rand: No Uint8Array found!');
  if (!global.Uint8Array) global.Uint8Array=Uint8Array;
}


function randomByte (min,max) {
  if (!twister) twister = new MersenneTwister();
  return Math.floor(twister.random() * (max-min))+min;
}

function randomBytes (size, cb) {
  // phantomjs needs to throw
  if (size > 65536) throw new Error('requested too many random bytes')
  if (!crypto || !crypto.getRandomValues) polyfill();

  // in case browserify  isn't using the Uint8Array version
  var rawBytes = new global.Uint8Array(size);
  // This will not work in older browsers.
  // See https://developer.mozilla.org/en-US/docs/Web/API/window.crypto.getRandomValues
  if (size > 0) {  // getRandomValues fails on IE if size == 0
    crypto.getRandomValues(rawBytes);
  }
  // phantomjs doesn't like a buffer being passed here
  var bytes = new Buffer(rawBytes);
  if (typeof cb === 'function') {
    cb(null, bytes)
  }

  return bytes
} 

module.exports = {
  randomByte:randomByte,
  randomBytes:randomBytes
}
};
BundleModuleCode['jam/ampRPC']=function (module,exports){
var Buf = Require('dos/buf');
var Net = Require('dos/network');
var Command = Net.Command;
var Status = Net.Status;
var COM = Require('jam/ampCOM');

var current=none;
var Aios=none;

// Current node must be set!
// E.g., by using jamlib.setCurrentNode(0)

// typeof @obj = 
// { agent : string } |
// { node  : string } |
// 'processes' |
// 'agents' |
// 'links' |
// 'ports' |
// 'node' |
// 'ts' |
// { prv: .., .. }
//

// Server side implementation is in chan.js

var Std = {

  info : function (node,obj,callback) {
    var node0=current.node;
    var to=COM.lookupNode(node0,node);
    if (to) {
      to.link.control({
        cmd:COM.Command.STD_INFO,
        args:obj,
      },to.url, function (reply) {
        callback(reply)
      })
    }
  },
  
  status : function  (node,obj,callback) {
    var node0=current.node;
    var to=COM.lookupNode(node0,node);
    if (to) {
      to.link.control({
        cmd:COM.Command.STD_STATUS,
        args:obj,
      },to.url, function (reply) {
        callback(reply)
      })
    }
    
  },
}

var Run = {  
  stun : function (node,agent) {
  
  },
}

module.exports = {
  current : function (module) { current=module.current; Aios=module; },
  Run : Run,
  Std : Std
};

};
BundleModuleCode['jam/ampUDP']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2006-2020 bLAB
 **    $CREATED:     09-02-16 by sbosse.
 **    $RCS:         $Id: ampUDP.js,v 1.1 2020/02/03 09:45:01 sbosse Exp sbosse $
 **    $VERSION:     1.17.6
 **
 **    $INFO:
 **
 **  JAM Agent Management Port (AMP) over UDP
 **
 **  Supports:
 **
 **   - Unicast link
 **   - Multicast link
 **   - Oneway link
 **   - Broker service with UDP punch holing and pairing, lookup of registered nodes
 **
 **
 **  Events out: 'error','route+','route-'
 **
 **  TODO: Garbage collection
 **
 **    $ENDOFINFO
 */
var Io = Require('com/io');
var Lz = Require('os/lz-string');
var Comp = Require('com/compat');
var Buf = Require('dos/buf');
var Net = Require('dos/network');
var Sec = Require('jam/security');
var Command = Net.Command;
var Status = Net.Status;
var current=none;
var Aios=none;
var CBL = Require('com/cbl');

var COM = Require('jam/ampCOM'),
    AMMode=COM.AMMode,
    AMMessageType=COM.AMMessageType,
    AMState=COM.AMState,
    amp=COM.amp,
    options=COM.options,
    url2addr=COM.url2addr,
    addr2url=COM.addr2url,
    obj2url=COM.obj2url,
    addrequal=COM.addrequal,
    resolve=COM.resolve,
    ipequal=COM.ipequal,
    doUntilAck=COM.doUntilAck,
    getNetworkIP=COM.getNetworkIP,
    magic=COM.options.magic;

module.exports.current=function (module) { current=module.current; Aios=module; };

var dgram = Require('dgram');



/** AMP port using UDP
 *  ==================
 *
 * type url = string
 * type amp.udp = function (options:{rcv:address,snd?:address,verbose?,logging?,out?:function,log?:number,broker?:address})
 * type address = {address:string,port:number}
 */
amp.udp = function (options) {
  var self=this;
  options=checkOptions(options,{});
  this.proto = 'udp';
  // Some sanity checks
  this.options=options;
  if (options.oneway && options.multicast) this.err('Invalid: Both ONEWAY and MULTICAST modes enabled!');
  this.verbose=checkOption(options.verbose,0);
  if (!options.rcv) options.rcv=url2addr('localhost:*');

  this.dir    = options.dir;                        // attached to JAM port
  this.rcv    = options.rcv;                        // IP (this side) address
  this.broker = options.broker;                     // IP (rendezvous broker) address
  this.node   = options.node;                       // Attached to this node
 
  this.port = options.port||Net.uniqport();               // This AMP Port
  this.secure = this.options.secure;
 
  if (this.broker && !this.broker.port) 
    Io.err('['+Io.Time()+' AMP] No broker port specified!');

  this.out = function (msg) {
    Aios.print('[AMP '+Net.Print.port(self.port)+
              (self.dir?(' '+Aios.DIR.print(self.dir)):'')+'] '+msg);
  }
  this.err = function (msg) {
    Aios.print('[AMP '+Net.Print.port(self.port)+
              (self.dir?(' '+Aios.DIR.print(self.dir)):'')+'] Error: '+msg);
    throw 'AMP';
  }
  

  // Virtual link table
  // MULTICAST (P2N) mode: link cache [{addr,live,state}] of all connected links
  // UNICAST (P2P): One link only, remebered by this.url!
  this.links={};
  
  if (options.snd) {
    url=addr2url(options.snd,true);
    this.links[url]={
        snd:options.snd,
        tries:0,
        state:this.broker?AMState.AMS_AWAIT:AMState.AMS_NOTCONNECTED,
        live:COM.AMC_MAXLIVE
      };
    if (this.verbose>0) this.out('Added destiantion route '+url+', '+Io.inspect(this.links[url]));
    if (!options.multicast) this.url=url;  // Remember this link
  } 
  if (this.broker) {
    // Create a root path that handles all brokerag and pairing
    url='*';
    this.links[url]={
        tries:0,
        state:AMState.AMS_RENDEZVOUS,
        live:COM.options.AMC_MAXLIVE,
        queue:{pairing:[],lookup:[]}
      };
    if (this.verbose>0) this.out('Added default registration route '+url);    
  }                                     
  this.rcv.name=options.name;                             // Optional name of this port



  this.mode = options.multicast?AMMode.AMO_MULTICAST:AMMode.AMO_UNICAST;
  if (options.oneway) this.mode |= AMMode.AMO_ONEWAY;       // Oneway: No link negotation; no ack.

  this.sock = dgram.createSocket("udp4");                   // Receiver and sender socket

  this.dlimit = options.dlimit||512;

  this.count={rcv:0,snd:0,lnk:0,png:0};

  this.timer=undefined;
  this.inwatchdog=false;

  this.events = [];
  this.transactions = Comp.hashtbl.create();
  
  this.logs=[];
  this.logging=options.logging||false;
  if (this.logging) {
    setInterval(function () { self.LOG('print') },5000);
  }
};

amp.udp.prototype.LOG = amp.man.prototype.LOG;  
amp.udp.prototype.addTransaction = amp.man.prototype.addTransaction;
amp.udp.prototype.checkState = amp.man.prototype.checkState;
amp.udp.prototype.deleteTransaction = amp.man.prototype.deleteTransaction;
amp.udp.prototype.emit = amp.man.prototype.emit;
amp.udp.prototype.findTransaction = amp.man.prototype.findTransaction;
amp.udp.prototype.handle = amp.man.prototype.handle;
amp.udp.prototype.on = amp.man.prototype.on;
amp.udp.prototype.status = amp.man.prototype.status;


/** Handle AMP control messages (used by ampbroker and UDP hole punching sub-system) 
 *  for P2P network pairing before linking is performed (NAT traversal)!
 *
 */
amp.udp.prototype.control = function(link, msg, remote) { 
  var self=this;
  
  switch (msg.type) {
    case 'lookup':
      // Lookup reply
      if (link.queue.lookup[msg.path]) {
        link.queue.lookup[msg.path](msg.data);
        delete link.queue.lookup[msg.path];
      }
      break;
      
    case 'registered':
      if (link.state==AMState.AMS_RENDEZVOUS) {
        link.state=AMState.AMS_REGISTERED;
        if (this.verbose) self.out('Registered on broker with name '+this.rcv.name);
      }
      break;

    case 'pairing':
      if (link.state == AMState.AMS_REGISTERED) {
        // pairing of remote endpoint sent by broker; punch now
        var punch = { type: 'punch', from: this.rcv.name, to: msg.client.name},
            counter = options.TRIES;
        link.state=AMState.AMS_PAIRING; // stops sending pair requests
        for (var con in msg.client.connections) {
          doUntilAck(options.TIMER, 
            // DO
            function(i) {
              counter--;
              self.send(punch, msg.client.connections[i]);
            },
            // UNTIL ACK = true
            function () {
              return counter==0 || link.state!=AMState.AMS_PAIRING;
            },
            con);
        } 
      }
      break; 

    case 'ack':
      if (link.state == AMState.AMS_PAIRING) {
        if (this.verbose) self.out('Paired with '+msg.from+'('+addr2url(remote)+')');
        // Find waiting virtual link ...
        self.links.forEach(function (link2,url) {
          if (url=='*') return;
          if (link2 && link2.state==AMState.AMS_AWAIT && link2.snd.name==link.snd.name) {
            var newurl=addr2url(remote,true);
            // Move link to new url
            self.links[url]=undefined;
            self.links[newurl]=link2;
            link2.state=AMState.AMS_NOTCONNECTED;
            link2.snd.address=remote.address;
            link2.snd.port=remote.port;      // Correct address/port???
            if (self.mode&AMMode.AMO_UNICAST) self.url=newurl,self;
            // console.log('wakeup ',link);
          }
        });
        
          // this.watchdog(true,true);
          // This is the root link '*' performed the pairing on this destination side. 
          // Wait for next pairing...
        link.state=AMState.AMS_RENDEZVOUS;
        if (link.queue.pairing.length)
          link.snd = {name:link.queue.pairing.shift().snd.name};
        else
          link.snd = undefined;
        self.watchdog(true);
      }
      break;

    case 'punch':
      if (msg.to == this.rcv.name) {
        // send ACK
        this.send({type:'ack',from:this.rcv.name},remote);
      }
      break;
  }

}

/** Initialize AMP
 *
 */
amp.udp.prototype.init = function(callback) { 
  if (callback) callback();
};

/** Negotiate (create) a virtual communication link (peer-to-peer).
 *
 *  The link operation can be called externally establishing a connection or internally.
 *
 *  In oneway mode only a destination endpoint is set and it is assumed the endpoint can receive messages a-priori!
 *
 * typeof @snd = address
 * typeof @callback = function
 * typeof @connect = boolean is indicating an initial connect request and not an acknowledge
 * typeof @key = string
 * +------------+
 * VCMessageType (int16)
 * Connection Port (port)
 * Node ID (string)
 * //Receiver IP Port (int32)
 * +------------+
 *
 */
 
amp.udp.prototype.link=function(snd,connect,key) {
    var self = this,
        url, 
        buf = Buf.Buffer(),
        sock = this.sock; // snd_sock;

    if (this.verbose>1) this.out('amp.link: to '+(snd && snd.address) + ':' + ((snd && snd.port) || '*'));
    snd=this.updateLinkTable(snd||(this.url && this.links[this.url].snd) ,connect,key);

    if (snd && snd.parameter && snd.parameter.secure) key=snd.parameter.secure;

    if (!snd) return this.watchdog(true);

    Buf.buf_put_int16(buf, magic);
    Buf.buf_put_int16(buf, AMMessageType.AMMLINK);
    Buf.buf_put_port(buf,this.port);          // This AMP id
    Buf.buf_put_string(buf,this.node?this.node.id:'*');
    Buf.buf_put_string(buf,key?key:'');
    
    // Buf.buf_put_int32(buf, this.rcv.port);
    this.count.snd += Buf.length(buf);
    this.count.lnk++;

    sock.send(buf.data,0,Buf.length(buf),snd.port,snd.address,function (err) {
        if (err) {
            sock.close();
            self.emit('error',err);
        } 
    });
};

// Ask broker for registered hosts/nodes
amp.udp.prototype.lookup = function(path,callback) {
  var link=this.links['*'];
  if (!link && callback) return callback([]);
  if (callback) link.queue.lookup[path]=callback;
  this.send(
    {type:'lookup',name: this.rcv.name, linfo: this.rcv, data:path },
    this.broker,
    function () {}
  );

}

// Return link for destination
amp.udp.prototype.lookupLinkTable=function(snd) {
  if (this.url) return this.links[this.url]; // Unicast mode
  if (!snd) return;
  var url = obj2url(snd);
  return this.links[url];
}


// Initiate a pairing of two nodes via broker handled by the '*' root link
amp.udp.prototype.pairing = function(link) {
  if (!this.links['*'].snd) {
    // Root link will perform pairing ...
    this.links['*'].snd={name:link.snd.name};
    this.watchdog(true);
  } else {
    this.links['*'].queue.pairing.push(link);
  }
}

/**
 *
 * typeof @snd = address
 * typeof @callback = function
 *
 * +------------+
 * AMMessageType (int16)
 * Connection Port (port)
 * Receiver IP Port (int32)
 * +------------+
 */
amp.udp.prototype.ping=function(snd) {
    var self = this,
        buf = Buf.Buffer(),
        sock = this.sock; // snd_sock;

    Buf.buf_put_int16(buf, magic);
    Buf.buf_put_int16(buf, AMMessageType.AMMPING);
    Buf.buf_put_port(buf,this.port);

    if (this.mode&AMMode.AMO_UNICAST) {
      if (snd==undefined || snd.address==undefined) snd={},snd.address=this.links[this.url].snd.address;
      if (snd.port==undefined) snd.port=this.links[this.url].snd.port;
    } 
    if (snd == undefined) this.err('amp.udp.ping: no destinataion set (snd==null)');
    
    // Buf.buf_put_int32(buf, self.rcv.port);

    if (this.verbose>1) this.out('amp.ping: to '+addr2url(snd));
    this.count.snd += Buf.length(buf);
    this.count.png++;
    
    sock.send(buf.data,0,Buf.length(buf),snd.port,snd.address,function (err) {
        if (err) {
            sock.close();
            self.emit('error',err);
        } 
    });
};

/**
 *
 * typeof @snd = address
 * typeof @callback = function
 * +------------+
 * AMMessageType (int16)
 * Connection Port (port)
 * Receiver IP Port (int32)
 * +------------+
 */
amp.udp.prototype.pong=function(snd) {
    var self = this,
        buf = Buf.Buffer(),
        sock = this.sock; // snd_sock;

    Buf.buf_put_int16(buf, magic);
    Buf.buf_put_int16(buf, AMMessageType.AMMPONG);
    Buf.buf_put_port(buf,this.port);

    if (this.mode&AMMode.AMO_UNICAST) {
      if (snd==undefined || snd.address==undefined) snd={},snd.address=this.links[this.url].snd.address;
      if (snd.port==undefined) snd.port=this.links[this.url].snd.port;
    } 
    if (snd == undefined) this.err('amp.udp.pong: no destinataion set (snd==null)');

    // Buf.buf_put_int32(buf, this.rcv.port);

    if (this.verbose>1) this.out('amp.pong: to '+addr2url(snd));
    this.count.snd += Buf.length(buf);
    this.count.png++;

    sock.send(buf.data,0,Buf.length(buf),snd.port,snd.address,function (err) {
        if (err) {
            sock.close();
            self.emit('error',err);
        } 
    });
};


/*
** Set-up a message receiver.
**
** Message structure
**
**  msgtyp            msgtyp            2
**  =AMMRPCHEAD       =AMMRPCDATA
**  tid               tid               2
**  remoteport                          2
**  cmd                                 2
**  size                                2
**  frags                               2
**                    off               4
**                    size              2
**                    more              2
**                    buf               *
**  
**  
*/
/** Message receiver and handler.
 *  typeof @rcv=address|undefined
 */
amp.udp.prototype.receiver = function (callback,rcv) {
  var self = this;

  if (rcv == undefined || rcv.address==undefined) rcv={},rcv.address=this.rcv.address;
  if (rcv.port==undefined) rcv.port=this.rcv.port;
  if (callback) this.callback=callback;

  var buf = Buf.Buffer();
  var sock = this.sock; // rcv_sock;

  sock.on('listening', function () {
    var address = sock.address();
    if (!rcv.port) self.rcv.port=rcv.port=address.port;
    if (self.verbose>1) self.out('UDP receiver listening on ' + addr2url(rcv));
    if (self.dir.ip=='*') self.dir=Aios.DIR.IP(self.rcv.port); 
    // Try to get network IP address of this host 
    getNetworkIP(undefined,function (err,ip) {
      if (!err) self.rcv.address=ip;
      if (self.verbose) self.out('IP port '+addr2url(self.rcv)+ ' (proto '+self.options.proto+')');
      if (err) return self.out("! Unable to obtain network connection information: "+err);
    });
  });
  sock.on('error', function (err) {
    Io.out('[AMP] UDP error: '+err);
    self.sock.close();
  });    
  sock.on('message', function (message, remote) {
    var handler,dfrags,dlist,msgtyp,tid,ipport,discard,off,size,thisnum,transaction,more,port,addr,url,data,msg;
    remote.address=remote.address.replace(/^::ffff:/,'')
    Buf.buf_init(buf);
    Buf.buf_of_str(buf,message);
    self.count.rcv += message.length;
    msg={};
    
    if (message.length >= 10) {
      msg.magic=Buf.buf_get_int16(buf);
      // consistency check
      if (msg.magic!=magic) return;

      msg.type=Buf.buf_get_int16(buf);
      

      discard=false;
      if (self.verbose>1) {
        url=addr2url(remote,true);
        self.out('receiver: Receiving Message from '+ url + ' [' + message.length+'] '+
                 AMMessageType.print(msg.type)+' in state '+
                 (self.mode&AMMode.AMO_MULTICAST?(self.links[url] && AMState.print(self.links[url].state)):
                                                 (self.links[self.url] && AMState.print(self.links[self.url].state))));
      }
      switch (msg.type) {

        case AMMessageType.AMMRPCHEAD:
          if (!self.checkState(AMState.AMS_CONNECTED,remote)) return;
          msg.tid = Buf.buf_get_int16(buf);
          msg.port = Buf.buf_get_port(buf);
          msg.cmd=Buf.buf_get_int16(buf);
          msg.size=Buf.buf_get_int32(buf);    // total data size
          msg.frags=Buf.buf_get_int16(buf);
          msg.data=Buf.Buffer();
          self.handle(msg,remote);
          break;

        case AMMessageType.AMMRPCDATA:
          if (!self.checkState(AMState.AMS_CONNECTED,remote)) return;
          msg.tid = Buf.buf_get_int16(buf);
          msg.port = Buf.buf_get_port(buf);
          msg.off = Buf.buf_get_int32(buf);
          msg.size = Buf.buf_get_int16(buf);    // fragment size
          msg.more = Buf.buf_get_int16(buf);
          msg.data = buf;
          self.handle(msg,remote);
          break;

        case AMMessageType.AMMPING:
          msg.port = Buf.buf_get_port(buf);
          self.handle(msg,remote);
          break;

        case AMMessageType.AMMPONG:
          msg.port = Buf.buf_get_port(buf);
          self.handle(msg,remote);
          break;

        case AMMessageType.AMMLINK:
          msg.port = Buf.buf_get_port(buf);
          msg.node = Buf.buf_get_string(buf);
          msg.secure  = Buf.buf_get_string(buf);
          if (msg.secure!='') msg.secure=Sec.Port.ofString(msg.secure); 
          self.handle(msg,remote);
          break;

        case AMMessageType.AMMUNLINK:
          msg.port = Buf.buf_get_port(buf);
          self.handle(msg,remote);
          break;

        // optional rendezvous brokerage 
        case AMMessageType.AMMCONTROL:
          // Control message; 
          msg.port = Buf.buf_get_port(buf);
          msg.data = Buf.buf_get_string(buf);
          self.handle(msg,remote);
          break;
      }
    }
  });
};



/** Send a request message to a remote node endpoint
 *
 * function (cmd:integer,msg:Buffer,snd?:address)
 */

amp.udp.prototype.request = function (cmd,msg,snd) {
  var self=this,
      buf = Buf.Buffer(),
      sock = this.sock, // snd_sock;
      size = msg.data.length,
      frags = div((size+self.dlimit-1),self.dlimit),
      tid = msg.tid||Comp.random.int(65536/2);

  if (this.mode&AMMode.AMO_UNICAST) {
    if (snd==undefined || snd.address==undefined) snd={},snd.address=this.links[this.url].snd.address;
    if (snd.port==undefined) snd.port=this.links[this.url].snd.port;
  } 
  if (snd == undefined) this.err('amp.udp.request: no destinataion set(snd==null)');

  Buf.buf_put_int16(buf,magic);
  Buf.buf_put_int16(buf,AMMessageType.AMMRPCHEAD);
  Buf.buf_put_int16(buf,tid);                   // Transaction Message ID
  Buf.buf_put_port(buf,this.port);
  // Buf.buf_put_int16(buf,self.rcv.port);         // For reply
  Buf.buf_put_int16(buf,cmd);
  Buf.buf_put_int32(buf,size);
  Buf.buf_put_int16(buf,frags);

  if (self.verbose>1) self.out('Send AMMRPCHEAD tid='+tid+' @'+Comp.pervasives.mtime());
  this.count.snd += Buf.length(buf);
  
  sock.send(buf.data,0,Buf.length(buf),snd.port,snd.address,function (err) {
    if (self.verbose>1) self.out('Send AMMRPCHEAD tid='+tid+'. Done @'+Comp.pervasives.mtime());
    if (err) {
      if (self.verbose>1) self.out('AMMRPCHEAD Error: '+err);
      sock.close();
      if (callback) callback(Status.STD_IOERR,err);
    } else {
      if (size >0) {
          var dsend = function (n, off) {
              var fsize,more;
              if (frags == 1) fsize = size;
              else if (n < frags) fsize = self.dlimit;
              else fsize = size - off;
              if (n==frags) more=0; else more=1;
              Buf.buf_init(buf);
              Buf.buf_put_int16(buf, magic);
              Buf.buf_put_int16(buf, AMMessageType.AMMRPCDATA);
              Buf.buf_put_int16(buf, tid);      // Transaction Message number
              Buf.buf_put_port(buf,self.port);
              Buf.buf_put_int32(buf, off);      // Data fragment offset
              Buf.buf_put_int16(buf, fsize);    // Data fragment size
              Buf.buf_put_int16(buf, more);     // More data?
              Buf.buf_put_buf(buf, msg, off, fsize);
              if (self.verbose>1) self.out('Send AMMRPCDATA tid='+tid+'. Start #'+n+'/'+frags+'  @'+Comp.pervasives.mtime());
              self.count.snd += Buf.length(buf);

              sock.send(buf.data, 0, Buf.length(buf), snd.port, snd.address, function (err) {
                  if (self.verbose>1) self.out('Send AMMRPCDATA tid='+tid+'. Done #'+n+'/'+frags+' @'+Comp.pervasives.mtime());
                  if (err) {
                    if (self.verbose>1) self.out('AMMRPCDATA Error: '+err);
                    sock.close();
                    self.emit('error',err);
                  }
                  else if (n < frags) dsend(n + 1, off + fsize);
               });
          };
          dsend(1,0);
      } 
    }
  });    
};

/** Reply to a request (msg.tid contains request tid)
 */
amp.udp.prototype.reply = function (cmd,msg,snd) {
  this.request(cmd,msg,snd);
}

// typeof @snd : {address,port}
amp.udp.prototype.scan=function(snd,response,callback) {
    var self = this,msg={magic:magic};

    msg.type=response?AMMessageType.AMMACK:AMMessageType.AMMSCAN;
    
    if (this.verbose>1) this.out('amp.scan: to '+addr2url(snd));
    // TODO: callback!? Must be handled by the receiver/pkt. manager
       
    // TODO ??
    // this.send(msg,snd);
}

// Send a short control message
// typeof @msg : {type:string,..}
// typeof @snd : {address,port}
amp.udp.prototype.send = function (msg, snd) {
  var buf = Buf.Buffer(),
      sock = this.sock, // snd_sock;
      data = JSON.stringify(msg);
  this.LOG('snd',msg);
  if (this.mode&AMMode.AMO_UNICAST) {
    if (snd==undefined || snd.address==undefined) snd={},snd.address=this.links[this.url].snd.address;
    if (snd.port==undefined) snd.port=this.links[this.url].snd.port;
  } 
  if (snd == undefined) this.err('amp.udp.send: no destinataion set (snd==null)');
  
  if (this.verbose>1) this.out('amp.send: to '+addr2url(snd)+': '+data);
  Buf.buf_put_int16(buf,magic);
  Buf.buf_put_int16(buf,AMMessageType.AMMCONTROL);
  Buf.buf_put_port(buf,this.port);
  Buf.buf_put_string(buf,data);
  this.count.snd += Buf.length(buf);

  sock.send(buf.data,0,Buf.length(buf),snd.port,snd.address,function (err) {
    if (err) self.emit('error',err);
  });
};



// Start AMP watchdog and receiver
amp.udp.prototype.start = function(callback) {
  var self=this,link,startwatch=false
       s=this.secure?' (security port '+Sec.Port.toString(this.secure)+')':'';
       
  if (this.verbose>0) this.out('Starting ' + (this.rcv.name?(this.rcv.name+' '):'')+ addr2url(this.rcv)+
                               (this.mode&AMMode.AMO_UNICAST && this.url?(' -> '+this.url):'')+
                               ' ['+AMMode.print(this.mode)+'] (proto '+this.proto+')'+s);
  if (this.mode&AMMode.AMO_UNICAST) {
    if (this.url) {
      link=this.links[this.url];
      link.state = this.broker?AMState.AMS_AWAIT:AMState.AMS_NOTCONNECTED;
      if (link.snd && !(this.mode&AMMode.AMO_ONEWAY))
        startwatch=true;
      if (link.snd && (this.mode&AMMode.AMO_ONEWAY)) 
        this.emit('route+',addr2url(link.snd,true));
      if (this.broker) this.pairing(link);
    }
  }
  
  if (this.broker) {
    startwatch=true;
    // TODO init link['*'].snd / state ..
  }
  if (startwatch) this.watchdog(true);
    
  if (!this.sock) {
    // after stop?
    this.sock=dgram.createSocket("udp4");
    // restart listener
    this.receiver();
  } 
  this.sock/*rcv_sock*/.bind(this.rcv.port, undefined /*this.rcv.address*/, function (arg) {
    if (callback) callback();
  });    
}

// Stop AMP
amp.udp.prototype.stop = function(callback) {
  if (this.mode&AMMode.AMO_MULTICAST) 
    for(var p in this.links) {
      if (this.links[p]) {
        // Try to unlink remote endpoint
        this.unlink(this.links[p].snd);
        this.links[p].state=AMState.AMS_NOTCONNECTED;
      }
    }
  else
    this.links.state = AMState.AMS_NOTCONNECTED;
  if (this.timer) clearTimeout(this.timer),this.timer=undefined;
  if (this.sock) this.sock.close(),this.sock=undefined;
  if (callback) callback();
}



// Unlink remote endpoint
amp.udp.prototype.unlink=function(snd) {
  var self = this,
      buf = Buf.Buffer(),
      url = snd?addr2url(snd,true):null,
      sock = this.sock; //snd_sock;

  if (!this.links[url||this.url] || this.links[url||this.url].state!=AMState.AMS_CONNECTED) return;
  this.emit('route-',addr2url(snd,true));
  if (this.mode&AMMode.AMO_ONEWAY) return;

  Buf.buf_put_int16(buf, magic);
  Buf.buf_put_int16(buf, AMMessageType.AMMUNLINK);
  Buf.buf_put_port(buf,this.port);

  if (this.mode&AMMode.AMO_UNICAST) {
    if (snd==undefined || snd.address==undefined) snd={},snd.address=this.links[this.url].snd.address;
    if (snd.port==undefined) snd.port=this.links[this.url].snd.port;
    url=this.url;
  } 
  if (snd == undefined) this.err('amp.udp.unlink: no destination (snd==null)');

  // Buf.buf_put_int32(buf, this.rcv.port);

  if (this.verbose>1) this.out('amp.unlink: to '+addr2url(snd));
  this.count.snd += Buf.length(buf);

  sock.send(buf.data,0,Buf.length(buf),snd.port,snd.address,function (err) {
      if (err) {
          sock.close();
          self.emit('error',err)
      } 
  });
  this.links[url].state=AMState.AMS_NOTCONNECTED;
  if (!this.links[url].snd.connect) this.links[url].snd={};   // Invalidate link - or remove it from table?
  if (this.broker) {
    // Special case: brokerage! Remove link entry entirely!?
    this.links[url]=undefined;
    if (this.url) this.url=undefined;
  }
  if (this.verbose) this.out('Unlinked ' + addr2url(snd));
};


// Update link table, add new entry, and return snd address (or none if the watchdog should handle the messaging)
amp.udp.prototype.updateLinkTable=function(snd,connect) {
  var link;
  if (!snd) this.err('amp.udp.link: no destinataion set (snd==null)'); 
  url=addr2url(snd,true);

  // Add new link to link table if not already existing
  if (this.broker && !snd.port && !this.links[url]) {
    // Initial broker rendezvous delivering endpoint ip address and port
    link=this.links[url]={
      state:AMState.AMS_AWAIT,
      tries:0,
      connect:connect,
      live:options.AMC_MAXLIVE,
      snd:{name:snd.address}      // Watchdog will send link messages initially to broker if address is resolved
    };
    if (connect) link.snd.connect=true;
    if (this.mode&AMMode.AMO_UNICAST) this.url=url;   // Remember this link
    
    this.pairing(link);
    
    // Let watchdog handle rendezvous and connect request messages
    return;
  } else if (this.mode&AMMode.AMO_UNICAST) {
    // UNICAST mode
    if (!this.links[url]) link=this.links[url]={state:AMState.AMS_NOTCONNECTED};
    else link=this.links[url];
    
    if (snd != undefined && snd.address!=undefined && snd.port!=undefined && !link.snd)
      link.snd=snd;

    if (snd != undefined && snd.address!=undefined && snd.port!=undefined && snd.port!='*' && link.snd.address==undefined) 
      link.snd.address=snd.address;
      
    if (snd != undefined && snd.port!=undefined && link.snd.port==undefined) 
      link.snd.port=snd.port;

    if (connect) link.snd.connect=true;  

    // Nothing to do or let watchdog handle link messages?
    if ((link.state && link.state!=AMState.AMS_NOTCONNECTED && link.state!=AMState.AMS_PAIRED) || 
         this.mode&AMMode.AMO_ONEWAY) return;

    // Random port range p0-p1? Let watchdog do the work
    if (typeof link.snd.port == 'string') return;

    // Send link message
    if (snd==undefined || snd.address==undefined) snd={},snd.address=link.snd.address;
    if (snd.port==undefined) snd.port=link.snd.port;
    
    this.url=url;   // Remember this link
  } else {
    // MULTICAST mode
    if (!this.links[url] || !this.links[url].snd.address) 
      link=this.links[url]={
        snd:snd,
        state:AMState.AMS_NOTCONNECTED,
        tries:0,
        connect:connect,
        live:options.AMC_MAXLIVE
      };
    // Let watchdog handle connect request link messages
    if (!this.inwatchdog && connect) {
      this.watchdog(true);
      return;
    }
    // if (this.verbose>1) this.out('send link '+Io.inspect(snd));
  }
  return snd;
}



/** Install a watchdog timer.
 *
 * 1. If link state is AMS_NOTCONNECTED, retry link request if this.links[].snd is set.
 * 2. If link state is AMS_CONNECTED, check link end point.
 * 3, If link state is AMS_RENDEZVOUS, get remote endpoint connectivity via broker
 *
 * @param run
 */
amp.udp.prototype.watchdog = function(run,immed) {
    var self=this;
    if (this.timer) clearTimeout(self.timer),this.timer=undefined;
    if (run) this.timer=setTimeout(function () {
        var con,to,tokens;
        if (!self.timer || !self.sock || self.inwatchdog) return; // stopped or busy?
        self.timer = undefined;
        self.inwatchdog=true;

        function handle(obj,url) {
          if (self.verbose>1) self.out('Watchdog: handle link '+
                                        url+(obj.snd?('('+obj2url(obj.snd)+')'):'')+' in state '+AMState.print(obj.state)+
                                        '['+obj.live+'] '+
                                        (obj.tries!=undefined?('[#'+obj.tries+']'):''));
          switch (obj.state) {

            case AMState.AMS_CONNECTED:
                if (obj.live == 0) {
                    // No PING received, disconnect...
                    if (self.verbose>0) 
                      self.out('Endpoint ' + addr2url(obj.snd) +
                               ' not responding, propably dead. Unlinking...');
                    // self.emit('route-',addr2url(obj.snd)) .. done in unlink
                    if (self.mode&AMMode.AMO_MULTICAST) self.unlink(obj.snd); 
                    else self.unlink();
                    obj.state = AMState.AMS_NOTCONNECTED;
                    if (!obj.snd.connect) obj.snd={};
                    if (self.broker)  {
                      // Re-register on broker for rendezvous ...
                      self.watchdog(true); 
                      if (self.links['*']) {
                        self.links['*'].state=AMState.AMS_RENDEZVOUS;
                      }
                    }
                } else {
                    obj.tries=0;
                    obj.live--;
                    self.watchdog(true);
                    if (self.mode&AMMode.AMO_MULTICAST) self.ping(obj.snd);
                    else self.ping();
                }
                break;
                
            case AMState.AMS_NOTCONNECTED:
                if (!obj.snd) return;
                if (obj.snd.port && typeof obj.snd.port == 'string') {
                  // Random port connection from a port range p0-p1; save it and start with first
                  // random selection
                  tokens=obj.snd.port.split('-');
                  if (tokens.length==2) obj.range=[Number(tokens[0]),Number(tokens[1])];
                } 
                if (obj.range) {
                  // Get a random port from range
                  obj.snd.port=Comp.random.interval(obj.range[0],obj.range[1]);
                  if (self.verbose>0) 
                    self.out('Trying link to ' + addr2url(obj.snd));
                  if (self.mode&AMMode.AMO_MULTICAST) self.link(obj.snd); 
                  else self.link();
                  obj.tries++;
                  if (obj.tries < options.TRIES) self.watchdog(true);
                  else {
                    obj.snd={},obj.tries=0,obj.range=undefined;   
                  }                 
                } else if (obj.snd.port && typeof obj.snd.port == 'number') {
                  // Try link to specified remote endpoint obj.snd
                  if (self.verbose>0 && obj.tries==0) 
                    self.out('Trying link to ' + addr2url(obj.snd));
                  if (self.mode&AMMode.AMO_MULTICAST) self.link(obj.snd); 
                  else self.link();
                  obj.tries++;
                  if (obj.tries < options.TRIES) self.watchdog(true);
                  else {
                    self.out('Giving up to link '+addr2url(obj.snd));
                    self.emit('error','link',addr2url(obj.snd,true));
                    obj.snd={},obj.tries=0;
                  }
                }
                break;
                
            // AMP Broker P2P Control and Management
            case AMState.AMS_RENDEZVOUS:
                obj.next=Aios.time()+options.REGTMO;
                obj.interval=options.REGTMO;
                self.send(
                  {type:'register',name: self.rcv.name, linfo: self.rcv},
                  self.broker,
                  function () {}
                );
                self.watchdog(true);
                break;
                
            case AMState.AMS_REGISTERED:
                if (obj.snd && obj.snd.name && obj.tries < options.TRIES) {
                  obj.tries++;
                  self.send(
                    {type:'pair', from:self.rcv.name, to: obj.snd.name},
                    self.broker,
                    function () {}
                  );
                  // self.watchdog(true);
                } else if (options.REGTMO && Aios.time() > obj.next) {
                  // Update registration periodically; messages can be lost
                  obj.interval *= 2;
                  obj.interval = Math.min(obj.interval,options.REGTMO*8);
                  obj.next=Aios.time()+obj.interval;
                  self.send(
                    {type:'register',name: self.rcv.name, linfo: self.rcv},
                    self.broker,
                    function () {}
                  );
                }
                self.watchdog(true);
                break;
          }          
        }
        for(var p in self.links) if (self.links[p]) handle(self.links[p],p);
        self.inwatchdog=false;
    },immed?0:options.TIMER);
};
};
BundleModuleCode['jam/ampTCP']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2006-2022 bLAB
 **    $CREATED:     02-11-18 by sbosse.
 **    $RCS:         $Id: ampTCP.js,v 1.1 2020/02/03 09:45:01 sbosse Exp sbosse $
 **    $VERSION:     1.19.1
 **
 **    $INFO:
 **
 **  JAM Agent Management Port (AMP) over TCP
 **  Three modes: 
 **    (A) Pair of ad-hoc connections for each message
 **    (B) Pair of permanent connections with message stream; keepAlive:true
 **    (C) Single permanent connection from "client" to "server", i.e., an AMP handler for each socket, sharedSocket:true
 **        => for client behind NATs?
 **
 **  Supports:
 **
 **   - Unicast link
 **   - Multicast link
 **  
 **  Default mode A: A TCP connection is opened each time a message has to be sent and closed after transfer is finished.
 **  (Workaround for wine+wineserver - WIN32 node.js / nw.js bug, probably a libuv wine bug)
 **
 **  Simplified data transfer: No requirement for data fragmentation!
 **
 **  Events out: 'error','route+','route-'
 **
 **  TODO:  
 **        - Garbage collection
 **
 **  New:
 **     - No size limit of data transfers
 **
 **    $ENDOFINFO
 */
var Io = Require('com/io');
var Lz = Require('os/lz-string');
var Comp = Require('com/compat');
var Buf = Require('dos/buf');
var Net = Require('dos/network');
var Sec = Require('jam/security');
var Command = Net.Command;
var Status = Net.Status;
var current=none;
var Aios=none;
var CBL = Require('com/cbl');

var COM = Require('jam/ampCOM'),
    AMMode=COM.AMMode,
    AMMessageType=COM.AMMessageType,
    AMState=COM.AMState,
    amp=COM.amp,
    options=COM.options,
    url2addr=COM.url2addr,
    addr2url=COM.addr2url,
    obj2url=COM.obj2url,
    addrequal=COM.addrequal,
    resolve=COM.resolve,
    ipequal=COM.ipequal,
    doUntilAck=COM.doUntilAck,
    getNetworkIP=COM.getNetworkIP,
    magic=COM.options.magic;

module.exports.current=function (module) { current=module.current; Aios=module; };

var net = require('net');

var dgram = Require('dgram');



/** AMP port using TCP
 *  ==================
 *
 * type url = string
 * type amp.tcp = function (options:{rcv:address,snd?:address,verbose?,logging?,out?:function,log?:number,broker?:address})
 * type address = {address:string,port:number}
 */
amp.tcp = function (options) {
  var self=this;
  options=checkOptions(options,{});
  this.options=options;
  this.proto = 'tcp';
  // Some sanity checks
  if (options.oneway && options.multicast) this.err('Invalid: Both ONEWAY and MULTICAST modes enabled!');

  this.verbose=checkOption(options.verbose,0);
  if (!options.rcv) options.rcv=url2addr('localhost:*');

  if (this.verbose>2) console.log(options);

  this.dir    = options.dir;                        // attached to JAM port
  this.rcv    = options.rcv;                        // IP (this side) address
  this.broker = options.broker;                     // IP (rendezvous broker) address
  this.node   = options.node;                       // Attached to this node
 
  this.port = options.port||Net.uniqport();               // This AMP Port
  this.secure = this.options.secure;
  this.client = {};

  this.keepAlive    = checkOption(options.keepAlive,false);
  this.sharedSocket = checkOption(options.sharedSocket,false);

  if (this.broker && !this.broker.port) 
    Io.err('[AMP] No broker port specified!');

  this.out = function (msg) {
    Aios.print('[AMP '+Net.Print.port(self.port)+
              (self.dir?(' '+Aios.DIR.print(self.dir)):'')+'] '+msg);
  }
  this.err = function (msg) {
    Aios.print('[AMP '+Net.Print.port(self.port)+
              (self.dir?(' '+Aios.DIR.print(self.dir)):'')+'] Error: '+msg);
    throw 'AMP';
  }
  

  // Virtual link table
  // MULTICAST (P2N) mode: link cache [{addr,live,state}] of all connected links
  // UNICAST (P2P): One link only, remebered by this.url!
  this.links={};
  
  if (options.snd) {
    url=addr2url(options.snd,true);
    this.links[url]={
        snd:options.snd,
        tries:0,
        state:this.broker?AMState.AMS_AWAIT:AMState.AMS_NOTCONNECTED,
        live:COM.AMC_MAXLIVE
      };
    if (this.verbose>0) this.out('Added destiantion route '+url+', '+Io.inspect(this.links[url]));
    if (!options.multicast) this.url=url;  // Remember this link
  } 
  if (this.broker) {
    // Create a root path that handles all brokerag and pairing
    url='*';
    this.links[url]={
        tries:0,
        state:AMState.AMS_RENDEZVOUS,
        live:COM.options.AMC_MAXLIVE,
        queue:{pairing:[],lookup:[]}
      };
    if (this.verbose>0) this.out('Added default registration route '+url);    
  }                                     
  this.rcv.name=options.name;                             // Optional name of this port

  this.mode = options.multicast?AMMode.AMO_MULTICAST:AMMode.AMO_UNICAST;
  if (options.oneway) this.mode |= AMMode.AMO_ONEWAY;       // Oneway: No link negotation; no ack.

  this.sock = undefined;                   // Receiver socket

  this.count={rcv:0,snd:0,lnk:0,png:0};

  this.timer=undefined;
  this.inwatchdog=false;

  this.events = [];
  this.transactions = Comp.hashtbl.create();
  
  this.logs=[];
  this.logging=options.logging||false;
  if (this.logging) {
    setInterval(function () { self.LOG('print') },5000);
  }
};

amp.tcp.prototype.LOG = amp.man.prototype.LOG;  
amp.tcp.prototype.addTransaction = amp.man.prototype.addTransaction;
amp.tcp.prototype.checkState = amp.man.prototype.checkState;
amp.tcp.prototype.deleteTransaction = amp.man.prototype.deleteTransaction;
amp.tcp.prototype.emit = amp.man.prototype.emit;
amp.tcp.prototype.findTransaction = amp.man.prototype.findTransaction;
amp.tcp.prototype.handle = amp.man.prototype.handle;
amp.tcp.prototype.on = amp.man.prototype.on;
amp.tcp.prototype.status = amp.man.prototype.status;



/** Initialize AMP
 *
 */
amp.tcp.prototype.init = function(callback) { 
  if (callback) callback();
};

/** Negotiate (create) a virtual communication link (peer-to-peer).
 *
 *  The link operation can be called externally establishing a connection or internally.
 *
 *  In oneway mode only a destination endpoint is set and it is assumed the endpoint can receive messages a-priori!
 *
 * typeof @snd = address
 * typeof @callback = function
 * typeof @connect = boolean is indicating an initial connect request and not an acknowledge
 * typeof @key = string 
 * +------------+
 * VCMessageType (int16)
 * Connection Port (port)
 * Node ID (string)
 * Receiver IP Port (int32)
 * +------------+
 *
 */
 
amp.tcp.prototype.link=function(snd,connect,key) {
  var self = this,
      url  = addr2url(snd,true), 
      buf = Buf.Buffer();

  if (this.verbose>1) this.out('amp.link: to '+(snd && snd.address) + ':' + ((snd && snd.port) || '*'));
  snd=this.updateLinkTable(snd||(this.url && this.links[this.url].snd) ,connect,key);

  if (snd && snd.parameter && snd.parameter.secure) key=snd.parameter.secure;

  if (!snd) return this.watchdog(true);

  // Create sockets here for permanent TCP sessions
  if ((this.keepAlive || this.sharedSocket) && 
      (!this.client[url] || !this.client[url].socket)) {
    if (!this.client[url]) this.client[url]={busy:false,id:Math.random(),connected:false,queue:[]}
    if (!this.client[url].socket) {
      if (self.verbose>1) console.log('amp.link Creating keepAlive socket for client '+url);
      this.client[url].socket=new net.Socket();
      this.client[url].connected=false;
      this.client[url].socket.on('close', function () {
        console.log('close',url);
        delete self.client[url];
      });
      this.client[url].socket.on('error', function (err) {
        if (self.verbose>1) console.log('error',url,err)
        delete self.client[url];
      });        
    }
    if (this.sharedSocket && !this.rcv.port) {
      // TODO bind receiver
      if (self.verbose>1) console.log('amp.link Creating sharedSocket receiver for client '+url);
      this.receiverSocket(this.client[url].socket);
      if (!this.inwatchdog) {
        this.watchdog(true);
      }
    }
    this.client[url].busy=true;
    console.log('Connecting to client '+url);
    this.client[url].socket.connect(snd.port,snd.address, function () {
      var rcv=self.sharedSocket?self.client[url].socket.address():self.rcv;
      if (self.verbose>1) console.log('amp.link.connect:',url,rcv)
      self.client[url].rcv=rcv;
      self.client[url].busy=false;
      send(self.client[url].rcv.port);
    });  
    this.client[url].connected=true;
    return;
  }
  function send(rcvport) {
    if (self.verbose>1) self.out('amp.link.send: '+url+' rcvport='+rcvport);
    Buf.buf_put_int16(buf, magic);
    Buf.buf_put_int16(buf, AMMessageType.AMMLINK);
    Buf.buf_put_port(buf,self.port);          // This AMP id
    Buf.buf_put_string(buf,self.node?self.node.id:'*');
    Buf.buf_put_string(buf,key?key:'');
    Buf.buf_put_int32(buf, rcvport);
    // Buf.buf_put_int32(buf, this.rcv.port);
    self.count.snd += Buf.length(buf);
    self.count.lnk++;

    self.write(buf.data,0,Buf.length(buf),snd.port,snd.address,function (err) {
        if (err) {
            // self.close();
            self.emit('error',err);
        } 
    });
  }
  send(this.sharedSocket && this.client[url] && this.client[url].rcv?this.client[url].rcv.port:this.rcv.port);
};

// Ask broker for registered hosts/nodes
amp.tcp.prototype.lookup = function(path,callback) {
  var link=this.links['*'];
  if (!link && callback) return callback([]);
  if (callback) link.queue.lookup[path]=callback;
  this.send(
    {type:'lookup',name: this.rcv.name, linfo: this.rcv, data:path },
    this.broker,
    function () {}
  );

}

// Return link for destination
amp.tcp.prototype.lookupLinkTable=function(snd) {
  if (this.url) return this.links[this.url]; // Unicast mode
  if (!snd) return;
  var url = obj2url(snd);
  return this.links[url];
}



/**
 *
 * typeof @snd = address
 * typeof @callback = function
 *
 * +------------+
 * AMMessageType (int16)
 * Connection Port (port)
 * Receiver IP Port (int32)
 * +------------+
 */
amp.tcp.prototype.ping=function(snd) {
  var self = this,
      buf = Buf.Buffer();

  Buf.buf_put_int16(buf, magic);
  Buf.buf_put_int16(buf, AMMessageType.AMMPING);
  Buf.buf_put_port(buf,this.port);
  if (!this.sharedSocket)
    Buf.buf_put_int32(buf,this.rcv.port);         // For reply
  else // from this.client[url]
  Buf.buf_put_int32(buf,this.client[url] && this.client[url].rcv?this.client[url].rcv.port:this.rcv.port);         // For reply  

  if (this.mode&AMMode.AMO_UNICAST) {
    if (snd==undefined || snd.address==undefined) snd={},snd.address=this.links[this.url].snd.address;
    if (snd.port==undefined) snd.port=this.links[this.url].snd.port;
  } 
  if (snd == undefined) this.err('ping: snd=null');
  
  // Buf.buf_put_int32(buf, self.rcv.port);

  if (this.verbose>1) this.out('amp.ping: to '+addr2url(snd));
  this.count.snd += Buf.length(buf);
  this.count.png++;
  
  this.write(buf.data,0,Buf.length(buf),snd.port,snd.address,function (err) {
      if (err) {
          // self.close();
          self.emit('error',err);
      } 
  });
};

/**
 *
 * typeof @snd = address
 * typeof @callback = function
 * +------------+
 * AMMessageType (int16)
 * Connection Port (port)
 * Receiver IP Port (int32)
 * +------------+
 */
amp.tcp.prototype.pong=function(snd) {
  var self = this,
      buf = Buf.Buffer();

  Buf.buf_put_int16(buf, magic);
  Buf.buf_put_int16(buf, AMMessageType.AMMPONG);
  Buf.buf_put_port(buf,this.port);
  if (!this.sharedSocket)
    Buf.buf_put_int32(buf,this.rcv.port);         // For reply
  else // from this.client[url]
    Buf.buf_put_int32(buf,this.client[url] && this.client[url].rcv?this.client[url].rcv.port:this.rcv.port);         // For reply  

  if (this.mode&AMMode.AMO_UNICAST) {
    if (snd==undefined || snd.address==undefined) snd={},snd.address=this.links[this.url].snd.address;
    if (snd.port==undefined) snd.port=this.links[this.url].snd.port;
  } 
  if (snd == undefined) this.err('pong: snd=null');

  // Buf.buf_put_int32(buf, this.rcv.port);

  if (this.verbose>1) this.out('amp.pong: to '+addr2url(snd));
  this.count.snd += Buf.length(buf);
  this.count.png++;

  this.write(buf.data,0,Buf.length(buf),snd.port,snd.address,function (err) {
      if (err) {
          // self.close();
          self.emit('error',err);
      } 
  });
};


/*
** Set-up a message receiver.
**
** Message structure
**
**  msgtyp            msgtyp            2
**  =AMMRPCHEAD       =AMMRPCDATA
**  tid               tid               2
**  remoteport                          2
**  cmd                                 2
**  size                                2
**  frags                               2
**                    off               4
**                    size              2
**                    more              2
**                    buf               *
**  
**  
*/
/** Message receiver and handler.
 *  typeof @rcv=address|undefined
 */
amp.tcp.prototype.receiverHandler = function handler (message,remote) {
  var self=this,handler,dfrags,dlist,msgtyp,tid,ipport,discard,off,size,
      thisnum,transaction,more,port,addr,url,data,msg,
      url0=addr2url(remote,true);
  // console.log('handler',message.length);
  var buf = Buf.Buffer();
  Buf.buf_init(buf);
  Buf.buf_of_str(buf,message);
  self.count.rcv += message.length;
  msg={};

  if (message.length >= 10) {
    msg.magic=Buf.buf_get_int16(buf);
    // consistency check
    if (msg.magic!=magic) return;
    
    msg.type=Buf.buf_get_int16(buf);
    discard=false;
    if (self.verbose>1) {
      url=addr2url(remote,true);
      self.out('receiver: Receiving Message from '+ url + ' [' + message.length+'] '+
               AMMessageType.print(msg.type));
    }
    switch (msg.type) {

      case AMMessageType.AMMRPCHEADDATA:      // single message transfers only
        msg.tid = Buf.buf_get_int16(buf);
        msg.port = Buf.buf_get_port(buf);
        msg.sndport = Buf.buf_get_int32(buf);
        remote.port=msg.sndport;   // reply to this port!
        url=addr2url(remote,true);
        if (self.verbose>1)self.out('receiver: Receiving Message in state '+
                                     (self.mode&AMMode.AMO_MULTICAST?(self.links[url] && AMState.print(self.links[url].state)):
                                     (self.links[self.url] && AMState.print(self.links[self.url].state))));
        if (!self.checkState(AMState.AMS_CONNECTED,remote)) return;
        msg.cmd=Buf.buf_get_int16(buf);
        msg.size=Buf.buf_get_int32(buf);
        msg.data = buf;
        msg.frags=0;
        msg.more=false;
        self.handle(msg,remote);
        break;

      case AMMessageType.AMMPING:
        msg.port = Buf.buf_get_port(buf);
        msg.sndport = Buf.buf_get_int32(buf);
        remote.port=msg.sndport;   // reply to this port!
        url=addr2url(remote,true);
        if (self.verbose>1)self.out('receiver: Receiving Message in state '+
                                     (self.mode&AMMode.AMO_MULTICAST?(self.links[url] && AMState.print(self.links[url].state)):
                                     (self.links[self.url] && AMState.print(self.links[self.url].state))));
        self.handle(msg,remote);
        break;

      case AMMessageType.AMMPONG:
        msg.port = Buf.buf_get_port(buf);
        msg.sndport = Buf.buf_get_int32(buf);
        remote.port=msg.sndport;   // reply to this port!
        url=addr2url(remote,true);
        if (self.verbose>1)self.out('receiver: Receiving Message in state '+
                                     (self.mode&AMMode.AMO_MULTICAST?(self.links[url] && AMState.print(self.links[url].state)):
                                     (self.links[self.url] && AMState.print(self.links[self.url].state))));
        self.handle(msg,remote);
        break;

      case AMMessageType.AMMLINK:
        msg.port = Buf.buf_get_port(buf);
        msg.node = Buf.buf_get_string(buf);
        msg.secure = Buf.buf_get_string(buf);
        if (msg.secure!='') msg.secure=Sec.Port.ofString(msg.secure); 
        msg.sndport = Buf.buf_get_int32(buf);
        
        var oldport = remote.port;
        remote.port=msg.sndport;   // reply to this port!
        url=addr2url(remote,true);

        if (remote.port!=oldport) {
          // client behind NAT? IP port changed
          if (self.verbose>1) console.log('LINK NAT fix',remote.port,oldport,url,url0,typeof self.client[url0])
          if (self.client[url0]) {
            // migrate client entry to new url
            self.client[url]={address:remote.address,port:remote.port,
                              busy:self.client[url0].busy,
                              connected:self.client[url0].connected,
                              queue:self.client[url0].queue,
                              socket:self.client[url0].socket};
          }
        }

        if (self.verbose>1)self.out('receiver: Receiving Message in state '+
                                     (self.mode&AMMode.AMO_MULTICAST?(self.links[url] && AMState.print(self.links[url].state)):
                                     (self.links[self.url] && AMState.print(self.links[self.url].state))));
        self.handle(msg,remote);
        break;

      case AMMessageType.AMMUNLINK:
        msg.port = Buf.buf_get_port(buf);
        msg.sndport = Buf.buf_get_int32(buf);
        remote.port=msg.sndport;   // reply to this port!
        url=addr2url(remote,true);
        self.handle(msg,remote);
        break;

      // optional rendezvous brokerage 
      case AMMessageType.AMMCONTROL:
        // Control message; 
        msg.port = Buf.buf_get_port(buf);
        msg.data = Buf.buf_get_string(buf);
        self.handle(msg,remote);
        break;
    }
  }
};

amp.tcp.prototype.receiverSocket = function (sock) {
  var self=this,chunks,remote,expect=0,url;
  // Remote connect and request
  sock.on('data', function (data) {
    var pending;
    // console.log(data);
    if (!remote) {
      remote = {address:sock.remoteAddress.replace(/^::ffff:/,''),port:sock.remotePort};
      url = addr2url(remote);
      // console.log(remote,url)
      if (self.sharedSocket && !self.client[url]) {
        self.client[url]={busy:false,connected:true,socket:sock,queue:[]};
      }
    }
    if (self.keepAlive || self.sharedSocket) {
      // still broken
      do {
        if (pending) { data=pending; pending=null };
        if (data.length==0) process.exit();
        // message stream connectioN; first 4 bytes: length of message
        if (data.length==4) { expect=data.readInt32LE(0); return; }
        else if (expect==0) {
          if (data.length<4) return console.log('uff',data.length);
          var dataLength = data.slice(0,4);
          data=data.slice(4);
          expect=dataLength.readInt32LE(0);
        }
        if (expect) {
          if (chunks && (data.length+chunks.length) > expect ||
              data.length > expect) {
            var diff = (data.length+(chunks?chunks.length:0)) - expect,
                need = expect-(chunks?chunks.length:0);
            // console.log('mess',expect,diff,need,chunks && chunks.length,data.length)
            pending=data.slice(need);
            data=data.slice(0,need);
            // console.log(data.length,pending.length);
          }
        }
         
        if (!chunks) chunks=data;
        else chunks=Buffer.concat([chunks,data]);
        if (expect && chunks.length == expect) {
          // console.log(chunks)
          self.receiverHandler(Buffer(chunks),remote);
          expect=0;
          chunks=null;
        }
      } while (pending && pending.length)
    } else {
      if (!chunks) chunks=data;
      else chunks=Buffer.concat([chunks,data]);      
    }
   });
  sock.on('end', function () {
    if (chunks) self.receiverHandler(Buffer(chunks),remote);
  });
  if (!this.sharedSocket) {
    // Add a 'close' event handler to this instance of socket
    sock.on('close', function(data) {
      // console.log('CLOSED: ' + sock.remoteAddress +' '+ sock.remotePort);
      if (self.sharedSocket && self.client[url]) delete self.client[url];
    });
    sock.on('error', function(data) {
      // console.log('CLOSED: ' + sock.remoteAddress +' '+ sock.remotePort);
      if (self.sharedSocket && self.client[url]) delete self.client[url];
    });
  }
}
amp.tcp.prototype.receiver = function (callback,rcv) {
  var self = this;

  if (rcv == undefined || rcv.address==undefined) rcv={},rcv.address=this.rcv.address;
  if (rcv.port==undefined) rcv.port=this.rcv.port;
  if (callback) this.callback=callback;

  if (this.sharedSocket && rcv.port==undefined) {
    if (this.verbose) this.out('IP port * (proto '+this.options.proto+') SS');
    return; // client side socket receiver, installed later on each link request
  }
  this.sock=net.createServer({keepAlive:this.keepAlive,noDelay:true},this.receiverSocket.bind(this));

  this.sock.on('listening', function () {
    var address = self.sock.address();
    if (!rcv.port) self.rcv.port=rcv.port=address.port;
    if (self.verbose>1) self.out('TCP receiver listening on ' + addr2url(rcv));
    if (self.dir.ip=='*') self.dir=Aios.DIR.IP(self.rcv.port); 
    // Try to get network IP address of this host 
    getNetworkIP(undefined,function (err,ip) {
      if (!err) self.rcv.address=ip;
      if (self.verbose) self.out('IP port '+addr2url(self.rcv)+ ' (proto '+self.options.proto+') '+
                                 (self.keepAlive?'KA ':'')+
                                 (self.sharedSocket?'SS':''));
      if (err) return self.out("! Unable to obtain network connection information: "+err);
    });
  });
  this.sock.on('error', function (err) {
    Io.out('[AMP] TCP error: '+err);
    self.sock.close();
  });    
};



/** Send a request message to a remote node endpoint
 *
 * function (cmd:integer,msg:Buffer,snd?:address)
 */

amp.tcp.prototype.request = function (cmd,msg,snd) {
  var self=this,
      buf = Buf.Buffer(),
      size = msg.data.length,
      url = snd?addr2url(snd,true):'',
      tid = msg.tid||Comp.random.int(65536/2);

  if (this.mode&AMMode.AMO_UNICAST) {
    if (snd==undefined || snd.address==undefined) snd={},snd.address=this.links[this.url].snd.address;
    if (snd.port==undefined) snd.port=this.links[this.url].snd.port;
  } 
  if (snd == undefined) this.err('request: request=null');

  Buf.buf_put_int16(buf,magic);
  Buf.buf_put_int16(buf,AMMessageType.AMMRPCHEADDATA);
  Buf.buf_put_int16(buf,tid);                   // Transaction Message ID
  Buf.buf_put_port(buf,this.port);
  if (!this.sharedSocket)
    Buf.buf_put_int32(buf,this.rcv.port);         // For reply
  else // from this.client[url]
    Buf.buf_put_int32(buf,this.client[url] && this.client[url].rcv?this.client[url].rcv.port:this.rcv.port);         // For reply  
  Buf.buf_put_int16(buf,cmd);
  Buf.buf_put_int32(buf,size);
  Buf.buf_put_buf(buf, msg, 0, size);

  if (self.verbose>1) self.out('Send AMMRPCHEAD tid='+tid+' @'+Comp.pervasives.mtime());
  this.count.snd += Buf.length(buf);
  
  this.write(buf.data,0,Buf.length(buf),snd.port,snd.address,function (err) {
    if (self.verbose>1) self.out('Send AMMRPCHEADDATA tid='+tid+'. Done @'+Comp.pervasives.mtime());
    if (err) {
      if (self.verbose>1) self.out('AMMRPCHEADDATA Error: '+err);
      if (callback) callback(Status.STD_IOERR,err);
    } 
  });
};
/** Reply to a request (msg.tid contains request tid)
 */
amp.tcp.prototype.reply = function (cmd,msg,snd) {
  this.request(cmd,msg,snd);
}

// Send a short control message
// typeof @msg : {type:string,..}
// typeof @snd : address
amp.tcp.prototype.send = function (msg, snd) {
  var buf = Buf.Buffer(),
      data = JSON.stringify(msg);
  this.LOG('snd',msg);
  if (this.mode&AMMode.AMO_UNICAST) {
    if (snd==undefined || snd.address==undefined) snd={},snd.address=this.links[this.url].snd.address;
    if (snd.port==undefined) snd.port=this.links[this.url].snd.port;
  } 
  if (snd == undefined) this.err('send: snd=null');
  
  if (this.verbose>1) this.out('amp.send: to '+addr2url(snd)+': '+data);
  Buf.buf_put_int16(buf,magic);
  Buf.buf_put_int16(buf,AMMessageType.AMMCONTROL);
  Buf.buf_put_port(buf,this.port);
  Buf.buf_put_string(buf,data);
  this.count.snd += Buf.length(buf);

  this.write(buf.data,0,Buf.length(buf),snd.port,snd.address,function (err) {
    if (err) self.emit('error',err);
  });
};



// Start AMP watchdog and receiver
amp.tcp.prototype.start = function(callback) {
  var self=this,link,startwatch=false,
       s=this.secure?' (security port '+Sec.Port.toString(this.secure)+')':'';

  if (this.verbose>0) this.out('Starting ' + (this.rcv.name?(this.rcv.name+' '):'')+ addr2url(this.rcv)+
                               (this.mode&AMMode.AMO_UNICAST && this.url?(' -> '+this.url):'')+
                               ' ['+AMMode.print(this.mode)+'] (proto '+this.proto+') '+s);
  if (this.mode&AMMode.AMO_UNICAST) {
    if (this.url) {
      link=this.links[this.url];
      link.state = this.broker?AMState.AMS_AWAIT:AMState.AMS_NOTCONNECTED;
      if (link.snd && !(this.mode&AMMode.AMO_ONEWAY))
        startwatch=true;
      if (link.snd && (this.mode&AMMode.AMO_ONEWAY)) 
        this.emit('route+',addr2url(link.snd,true));
      if (this.broker) this.pairing(link);
    }
  }
  
  if (startwatch) this.watchdog(true);
    
  if (!this.sock && !this.sharedSocket && this.rcv.port) {
    // restart listener
    this.receiver();
  } 
  if (this.sock) {
    this.sock.listen(this.rcv.port, undefined /*this.rcv.address*/);
  }
  if (callback) callback();
}

// Stop AMP
amp.tcp.prototype.stop = function(callback) {
  if (this.mode&AMMode.AMO_MULTICAST) 
    for(var p in this.links) {
      if (this.links[p]) {
        // Try to unlink remote endpoint
        this.unlink(this.links[p].snd);
        this.links[p].state=AMState.AMS_NOTCONNECTED;
      }
    }
  else
    this.links.state = AMState.AMS_NOTCONNECTED;
  if (this.timer) clearTimeout(this.timer),this.timer=undefined;
  // TODO
  if (this.sock) this.sock.close(),this.sock=undefined;
  for (var p in this.client) {
    if (this.client[p] && this.client[p].socket) {
      this.client[p].socket.destroy();
      delete this.client[p];
    }
  }

  if (callback) callback();
}



// Unlink remote endpoint
amp.tcp.prototype.unlink=function(snd) {
  var self = this,
      buf = Buf.Buffer(),
      url = snd?addr2url(snd,true):null;

  if (!this.links[url||this.url] || this.links[url||this.url].state!=AMState.AMS_CONNECTED) return;
  this.emit('route-',addr2url(snd,true));
  if (this.mode&AMMode.AMO_ONEWAY) return;

  Buf.buf_put_int16(buf, magic);
  Buf.buf_put_int16(buf, AMMessageType.AMMUNLINK);
  Buf.buf_put_port(buf,this.port);
  if (!this.sharedSocket)
    Buf.buf_put_int32(buf,this.rcv.port);         // For reply
  else // from this.client[url]
    Buf.buf_put_int32(buf,this.client[url] && this.client[url].rcv?this.client[url].rcv.port:this.rcv.port);         // For reply  

  if (this.mode&AMMode.AMO_UNICAST) {
    if (snd==undefined || snd.address==undefined) snd={},snd.address=this.links[this.url].snd.address;
    if (snd.port==undefined) snd.port=this.links[this.url].snd.port;
    url=this.url;
  } 
  if (snd == undefined) this.err('unlink: no destination');

  // Buf.buf_put_int32(buf, this.rcv.port);

  if (this.verbose>1) this.out('amp.unlink: to '+addr2url(snd));
  this.count.snd += Buf.length(buf);

  this.write(buf.data,0,Buf.length(buf),snd.port,snd.address,function (err) {
      if (err) {
          self.emit('error',err)
      } 
  });
  this.links[url].state=AMState.AMS_NOTCONNECTED;

  if (!this.links[url].snd.connect) this.links[url].snd={};   // Invalidate link - or remove it from table?
  if (this.broker) {
    // Special case: brokerage! Remove link entry entirely!?
    this.links[url]=undefined;
    if (this.url) this.url=undefined;
  }
  if (this.client[url]) { this.client[url].socket.destroy(); delete this.client[url] };
  if (this.verbose) this.out('Unlinked ' + url);
};


// Update link table, add new entry, and return snd address (or none if the watchdog should handle the messaging)
amp.tcp.prototype.updateLinkTable=function(snd,connect) {
  var link;
  if (!snd) this.err('link: no destinataion set'); 
  url=addr2url(snd,true);

  // Add new link to link table if not already existing
  if (this.broker && !snd.port && !this.links[url]) {
    // Initial broker rendezvous delivering endpoint ip address and port
    link=this.links[url]={
      state:AMState.AMS_AWAIT,
      tries:0,
      connect:connect,
      live:options.AMC_MAXLIVE,
      snd:{name:snd.address}      // Watchdog will send link messages initially to broker if address is resolved
    };
    if (connect) link.snd.connect=true;
    if (this.mode&AMMode.AMO_UNICAST) this.url=url;   // Remember this link
    
    this.pairing(link);
    
    // Let watchdog handle rendezvous and connect request messages
    return;
  } else if (this.mode&AMMode.AMO_UNICAST) {
    // UNICAST mode
    if (!this.links[url]) link=this.links[url]={state:AMState.AMS_NOTCONNECTED};
    else link=this.links[url];
    
    if (snd != undefined && snd.address!=undefined && snd.port!=undefined && !link.snd)
      link.snd=snd;

    if (snd != undefined && snd.address!=undefined && snd.port!=undefined && snd.port!='*' && link.snd.address==undefined) 
      link.snd.address=snd.address;
      
    if (snd != undefined && snd.port!=undefined && link.snd.port==undefined) 
      link.snd.port=snd.port;

    if (connect) link.snd.connect=true;  

    // Nothing to do or let watchdog handle link messages?
    if ((link.state && link.state!=AMState.AMS_NOTCONNECTED && link.state!=AMState.AMS_PAIRED) || 
         this.mode&AMMode.AMO_ONEWAY) return;

    // Random port range p0-p1? Let watchdog do the work
    if (typeof link.snd.port == 'string') return;

    // Send link message
    if (snd==undefined || snd.address==undefined) snd={},snd.address=link.snd.address;
    if (snd.port==undefined) snd.port=link.snd.port;
    
    this.url=url;   // Remember this link
  } else {
    // MULTICAST mode
    url=addr2url(snd,true);
    if (!this.links[url] || !this.links[url].snd.address) 
      this.links[url]={
        snd:snd,
        state:AMState.AMS_NOTCONNECTED,
        tries:0,
        connect:connect,
        live:options.AMC_MAXLIVE
      };
    // Let watchdog handle connect request link messages
    if (!this.inwatchdog && connect && !this.sharedSocket) {
      this.watchdog(true);
      return;
    }
    // if (this.verbose>1) this.out('send link '+Io.inspect(snd));
  }
  return snd;
}



/** Install a watchdog timer.
 *
 * 1. If link state is AMS_NOTCONNECTED, retry link request if this.links[].snd is set.
 * 2. If link state is AMS_CONNECTED, check link end point.
 * 3, If link state is AMS_RENDEZVOUS, get remote endpoint connectivity via broker
 *
 * @param run
 */
amp.tcp.prototype.watchdog = function(run,immed) {
    var self=this;
    if (this.timer) clearTimeout(self.timer),this.timer=undefined;
    if (this.verbose>1) this.out('Starting watchdog run='+run+' immed='+immed);
    if (run) this.timer=setTimeout(function () {
        var con,to,tokens;
        if (!self.timer || (!self.sock && !self.sharedSocket) || self.inwatchdog) return; // stopped or busy?
        self.timer = undefined;
        self.inwatchdog=true;

        function handle(obj,url) {
          if (self.verbose>1) self.out('Watchdog: handle link '+
                                        url+(obj.snd?('('+obj2url(obj.snd)+')'):'')+' in state '+AMState.print(obj.state)+
                                        (obj.tries!=undefined?('[#'+obj.tries+']'):''));
          switch (obj.state) {

            case AMState.AMS_CONNECTED:
                if (obj.live == 0) {
                    // No PING received, disconnect...
                    if (self.verbose>0) 
                      self.out('(TCP) Endpoint ' + addr2url(obj.snd) +
                               ' not responding, propably dead. Unlinking...');
                    // self.emit('route-',addr2url(obj.snd)) .. done in unlink
                    if (self.mode&AMMode.AMO_MULTICAST) self.unlink(obj.snd); 
                    else self.unlink();
                    obj.state = AMState.AMS_NOTCONNECTED;
                    if (!obj.snd.connect) obj.snd={};
                    if (self.broker)  {
                      // Re-register on broker for rendezvous ...
                      self.watchdog(true); 
                      if (self.links['*']) {
                        self.links['*'].state=AMState.AMS_RENDEZVOUS;
                      }
                    }
                } else {
                    obj.tries=0;
                    obj.live--;
                    self.watchdog(true);
                    if (self.mode&AMMode.AMO_MULTICAST) self.ping(obj.snd);
                    else self.ping();
                }
                break;
                
            case AMState.AMS_NOTCONNECTED:
                if (!obj.snd) return;
                if (obj.snd.port && typeof obj.snd.port == 'string') {
                  // Random port connection from a port range p0-p1; save it and start with first
                  // random selection
                  tokens=obj.snd.port.split('-');
                  if (tokens.length==2) obj.range=[Number(tokens[0]),Number(tokens[1])];
                } 
                if (obj.range) {
                  // Get a random port from range
                  obj.snd.port=Comp.random.interval(obj.range[0],obj.range[1]);
                  if (self.verbose>0) 
                    self.out('Trying link to ' + addr2url(obj.snd));
                  if (self.mode&AMMode.AMO_MULTICAST) self.link(obj.snd); 
                  else self.link();
                  obj.tries++;
                  if (obj.tries < options.TRIES) self.watchdog(true);
                  else {
                    obj.snd={},obj.tries=0,obj.range=undefined;   
                  }                 
                } else if (obj.snd.port && typeof obj.snd.port == 'number') {
                  // Try link to specified remote endpoint obj.snd
                  if (self.verbose>0 && obj.tries==0) 
                    self.out('(TCP) Trying link to ' + addr2url(obj.snd));
                  if (self.mode&AMMode.AMO_MULTICAST) self.link(obj.snd); 
                  else self.link();
                  obj.tries++;
                  if (obj.tries < options.TRIES) self.watchdog(true);
                  else {
                    self.out('(TCP) Giving up to link '+addr2url(obj.snd));
                    self.emit('error','link',addr2url(obj.snd,true));
                    obj.snd={},obj.tries=0;
                  }
                }
                break;
                
            // AMP Broker P2P Control and Management
            case AMState.AMS_RENDEZVOUS:
                obj.next=Aios.time()+options.REGTMO;
                obj.interval=options.REGTMO;
                self.send(
                  {type:'register',name: self.rcv.name, linfo: self.rcv },
                  self.broker,
                  function () {}
                );
                self.watchdog(true);
                break;
                
            case AMState.AMS_REGISTERED:
                if (obj.snd && obj.snd.name && obj.tries < options.TRIES) {
                  obj.tries++;
                  self.send(
                    {type:'pair', from:self.rcv.name, to: obj.snd.name },
                    self.broker,
                    function () {}
                  );
                  // self.watchdog(true);
                } else if (options.REGTMO && Aios.time() > obj.next) {
                  // Update registration periodically; messages can be lost
                  obj.interval *= 2;
                  obj.interval = Math.min(obj.interval,options.REGTMO*8);
                  obj.next=Aios.time()+obj.interval;
                  self.send(
                    {type:'register',name: self.rcv.name, linfo: self.rcv },
                    self.broker,
                    function () {}
                  );
                }
                self.watchdog(true);
                break;
          }          
        }
        for(var p in self.links) if (self.links[p]) handle(self.links[p],p);
        self.inwatchdog=false;
    },immed?0:options.TIMER);
};

/** Write message data to remote endpoint address:port over a temporary TCP connection
 *
 */
amp.tcp.prototype.write = function(data,off,len,port,address,cb) {
  var self=this,
      url=addr2url({address:address,port:port});
  if (off!=0) this.err('tpc.socket.write: buffer offset <> 0');
  if (this.keepAlive || this.sharedSocket) {
    // reuse TCP session and connection
    if (!this.client[url]) return; // closed?
    // keep tcp connection alive and send messages as a stream
    // if (!this.client[url]) this.client[url]={busy:false,connected:false,queue:[]}
    if (this.client[url].busy) {
      // console.log('enqueue');
      this.client[address+':'+port].queue.push([data,off,len,port,address,cb]);
      return;
    }
    this.client[url].busy=true;

    function send() {
      if (!self.client[url]) return; // closed?
      // console.log('send',data.length);
      // 1. send data length
      var dataLength = Buffer(4);
      dataLength.writeInt32LE(data.length,0);
      try {
        self.client[url].socket.write(dataLength, function () {
          if (!self.client[url]) return; // closed?
          // 2. send data payload
          self.client[url].socket.write(data, function () {
            if (!self.client[url]) return;
            // console.log('write done',self.client[address+':'+port].queue.length)
            if (cb) cb();
            self.client[url].busy=false;
            if (self.client[url].queue.length) {
              // console.log('dequeue');
              self.client[url].socket.write.apply(self,self.client[url].shift());
            }
          })    
        });
      } catch (e) { if (self.verbose) self.out(url+': '+e); delete self.client[url]; }
    }
    /*
    if (!this.client[url].socket) {
      this.client[url].socket=new net.Socket();
      console.log('write.connect',url);
      this.client[url].socket.connect(port,address, send);
      this.client[url].connected=true;
      this.client[url].socket.on('close', function () {
        console.log('close',url);
        delete self.client[url];
      });
      this.client[url].socket.on('error', function (err) {
        console.log('error',url,err)
        delete self.client[url];
      });
    } else*/
    if (!this.client[url].connected) {
      console.log('write.connect',url);
      this.client[url].busy=true;
      this.client[url].socket.connect(port,address, function () {
        self.client[url].busy=false;
        send();
      });  
      this.client[url].connected=true;   
    } else {
      send();
    }
  } else {
    // for each message a new ad-hoc connection
    var client = new net.Socket();
    client.on('error',function (e) { if (cb) cb(e.toString()) })
    client.connect(port,address, function () {
        client.write(data,function () {
          client.destroy();
          // console.log(len)
          if (cb) cb();      
        });
    });
  }
}
};
BundleModuleCode['jam/ampStream']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2006-2018 bLAB
 **    $CREATED:     09-02-16 by sbosse.
 **    $RCS:         $Id: ampStream.js,v 1.1 2020/02/03 09:45:01 sbosse Exp sbosse $
 **    $VERSION:     1.11.2
 **
 **    $INFO:
 **
 **  JAM Agent Management Port (AMP) over streams
 **
 **
 **
 **    $ENDOFINFO
 */
var Io = Require('com/io');
var Lz = Require('os/lz-string');
var Comp = Require('com/compat');
var Buf = Require('dos/buf');
var Net = Require('dos/network');
var Command = Net.Command;
var Status = Net.Status;
var current=none;
var Aios=none;
var CBL = Require('com/cbl');

var COM = Require('jam/ampCOM'),
    AMMode=COM.AMMode,
    AMMessageType=COM.AMMessageType,
    AMState=COM.AMState,
    amp=COM.amp,
    options=COM.options,
    url2addr=COM.url2addr,
    addr2url=COM.addr2url,
    addrequal=COM.addrequal,
    resolve=COM.resolve,
    ipequal=COM.ipequal,
    getNetworkIP=COM.getNetworkIP;

module.exports.current=function (module) { current=module.current; Aios=module; };

/** AMP port using streams
 *  ======================
 *
 *  Note: Process streams transfer objects not data!
 *  No negotiation is performed. Data transfer can be fragmented.
 * 
 * type amp.stream = function (options:{sock:stream,verbose?,logging?,out?:function,log?,mode?:'buffer'|'object'})
 */
amp.stream = function (options) {
  var self=this;
  options=checkOptions(options,{});
  this.options=options;
  this.verbose=checkOption(options.verbose,0);

  this.dir=options.dir;                                   // Logical direction

  this.mode = AMMode.AMO_UNICAST | 
              AMMode.AMO_STATIC |                         // No link change
              (options.mode=='object'? AMMode.AMO_OBJECT:AMMode.AMO_BUFFER);                   // Transfer data buffers or objects? 

  this.port = options.port||Net.uniqport();     // Connection Link Port (this side)
  this.id = Net.Print.port(this.port);
  
  this.links={state:AMState.AMS_NOTCONNECTED};
  
  // Stream socket; can be a process object!
  this.sock = options.sock;

  this.dlimit = options.dlimit||512;

  this.out = function (msg) {
    Aios.print('[AMP '+Net.Print.port(self.port)+
              (self.dir?(' '+Aios.DIR.print(self.dir)):'')+'] '+msg);
  }
  this.state = AMState.AMS_INIT;

  this.events = [];

  this.logs=[];
  this.logging=options.logging||false;
  if (this.logging) {
    setInterval(function () { self.LOG('print') },5000);
  }

  if (this.mode & AMMode.AMO_OBJECT) 
    this.receiver=this.receiverObj,
    this.request=this.requestObj;
};

amp.stream.prototype.LOG = amp.udp.prototype.LOG;  
amp.stream.prototype.emit = amp.udp.prototype.emit;
amp.stream.prototype.init = amp.udp.prototype.init;
amp.stream.prototype.on = amp.udp.prototype.on;

amp.stream.prototype.receiver=function (callback,rcv) {
  var self = this;

  if (rcv == undefined || rcv.address==undefined) rcv={},rcv.address=this.rcv.address;
  if (rcv.port==undefined) rcv,port=this.rcv.port;

  var cache = Comp.hashtbl.create();
  var buf = Buf.Buffer();
  var sock = this.sock; // rcv_sock;

  sock.on('message', function (message, remote) {
    var handler,dfrags,dlist,msgtyp,tid,ipport,discard,off,size,thisnum,transaction,more,port,ip,data,msg;
    handler={};

    Buf.buf_init(buf);
    Buf.buf_of_str(buf,message);
    self.count.rcv += message.length;

    if (message.length >= 12) {
      msgtyp=Buf.buf_get_int16(buf);
      discard=false;
      if (self.verbose>1)
        self.out('receiver: Receiving Message  [' + message.length+'] '+AMMessageType.print(msgtyp));

      switch (msgtyp) {

        case AMMessageType.AMMRPCHEAD:
          tid = Buf.buf_get_int16(buf);
          port = Buf.buf_get_port(buf);
          handler.tid=tid;
          handler.remote=remote.address+':'+Buf.buf_get_int16(buf);
          handler.cmd=Buf.buf_get_int16(buf);
          handler.size=Buf.buf_get_int16(buf);
          handler.frags=Buf.buf_get_int16(buf);
          handler.buf=Buf.Buffer();
          // console.log(handler)
          if (handler.size>0) {
            dlist = Comp.array.range(0, handler.frags - 1);
            // Add transaction to cache for pending data 
            Comp.hashtbl.add(cache, handler.tid, [handler,dlist,1000]); 
          } else {
            callback(handler);
          }
          break;

        case AMMessageType.AMMRPCDATA:
          tid = Buf.buf_get_int16(buf);
          port = Buf.buf_get_port(buf);
          off = Buf.buf_get_int32(buf);
          size = Buf.buf_get_int16(buf);
          more = Buf.buf_get_int16(buf);
          thisnum = off/self.dlimit;
          transaction = Comp.hashtbl.find(cache,tid);
          if (transaction!=undefined) {
            handler=transaction[0];
            if (self.verbose>1)
              self.out('receiver: adding data num='+
                       thisnum+' off='+off+' size='+size+' dlist='+transaction[1]);

            Buf.buf_get_buf(buf,handler.buf,off,size);
            transaction[1]=Comp.array.filter(transaction[1],function(num) {return (num!=thisnum)});
            if (Comp.array.empty(transaction[1])) {
                if (self.verbose>1) self.out('[AMP] receiver: finalize '+addr2url(remote));
                // Io.out(handler.data.toString());
                // Deliver
                callback(handler);
                Comp.hashtbl.remove(cache,tid);
            }
            handler=undefined;
          }
          break;
      }
    }
  });
};

// Object transfer mode (process streams)
// Message format: {cmd:*,msg:buffer}
amp.stream.prototype.receiverObj=function (callback,rcv) {
  this.sock.on('message', function (obj) {
    var handler={cmd:obj.cmd,buf:Buf.Buffer(obj.msg)};
    self.count.rcv += obj.msg.length;
    if (callback) callback(handler);
  });
}

amp.stream.prototype.request=amp.udp.prototype.request;

// Object transfer mode (process streams)
// function (cmd:integer,msg:{pos:number,data:buffer},callback:function)
amp.stream.prototype.requestObj=function (cmd,msg,callback) {
  // Sync. operation
  this.count.snd += msg.data.length;
  this.sock.send({cmd:cmd,msg:msg.data}); 
  if (callback) callback()
}
amp.stream.prototype.start = function(callback) { 
  if (this.verbose) this.out('Stream link '+Aios.DIR.print(this.dir)+' started.');
  this.links.state=AMState.AMS_CONNECTED;
  if (callback) callback()
};
amp.stream.prototype.stop = function(callback) { if (callback) callback()};
amp.stream.prototype.status=amp.udp.prototype.status;

};
BundleModuleCode['jam/ampHTTP']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2006-2020 bLAB
 **    $CREATED:     09-02-16 by sbosse.
 **    $RCS:         $Id: ampHTTP.js,v 1.1 2020/02/03 09:45:01 sbosse Exp sbosse $
 **    $VERSION:     1.14.7
 **
 **    $INFO:
 **
 **  JAM Agent Management Port (AMP) over HTTP
 **  Only Mulitcast IP(*) mode is supported!
 **
 **  Events out: 'error','route-'
 **
 **  TODO: Garbage collection
 **
 **    $ENDOFINFO
 */
var Io = Require('com/io');
var Lz = Require('os/lz-string');
var Comp = Require('com/compat');
var Buf = Require('dos/buf');
var Net = Require('dos/network');
var Command = Net.Command;
var Status = Net.Status;
var current=none;
var Aios=none;
var CBL = Require('com/cbl');
var Bas64 = Require('os/base64');
var Sec = Require('jam/security')
var JSONfn = Require('jam/jsonfn')

var options = {
  version:"1.14.7"
}

var COM = Require('jam/ampCOM'),
    AMMode=COM.AMMode,
    AMMessageType=COM.AMMessageType,
    AMState=COM.AMState,
    amp=COM.amp,
    options=COM.options,
    url2addr=COM.url2addr,
    addr2url=COM.addr2url,
    addrequal=COM.addrequal,
    resolve=COM.resolve,
    ipequal=COM.ipequal,
    isLocal=COM.isLocal,
    getNetworkIP=COM.getNetworkIP
    magic=COM.options.magic;

var debug = false;

module.exports.current=function (module) { current=module.current; Aios=module; };

/*
** Parse query string '?attr=val&attr=val... and return parameter record
*/
function parseQueryString( url ) {
    var queryString = url.substring( url.indexOf('?') + 1 );
    if (queryString == url) return [];
    var params = {}, queries, temp, i, l;

    // Split into key/value pairs
    queries = queryString.split("&");

    // Convert the array of strings into an object
    for ( i = 0, l = queries.length; i < l; i++ ) {
        temp = queries[i].split('=');
        if (temp[1]==undefined) temp[1]='true';
        params[temp[0]] = temp[1].replace('%20',' ');
    }

    return params;
}
/*
** Format a query string from a parameter record
*/
function formatQueryString (msg) {
  var path= '/?';
  path += "magic="+msg.magic;
  path += "&type="+AMMessageType.print(msg.type);
  if (msg.cmd) path += '&cmd='+msg.cmd;
  if (msg.tid) path += '&tid='+msg.tid;
  if (msg.port) path += '&port='+Net.port_to_str(msg.port);
  if (msg.timeout) path += '&timeout='+msg.timeout;
  if (msg.node) path += '&node='+msg.node.replace(' ','%20');
  if (msg.index) path += '&index='+msg.index;
  if (msg.secure) path += '&secure='+(msg.secure.length==8?Net.port_to_str(msg.secure):msg.secure);
  return path;
}

function msg2JSON(msg) {
  if (msg.port) msg.port=Net.port_to_str(msg.port);
  if (msg.msg && msg.msg.length) Comp.array.iter(msg.msg,function (msg) {
    if (msg.port) msg.port=Net.port_to_str(msg.port);
  });
  return JSONfn.stringify(msg);
}
function JSON2msg(data) {
  var msg=JSONfn.parse(data);
  if (msg.port) msg.port=Net.port_of_str(msg.port);
  if (msg.msg && msg.msg.length) Comp.array.iter(msg.msg,function (msg) {
    if (msg.port) msg.port=Net.port_of_str(msg.port);
  });
  return msg;
}

/** Get XML data
 *
 */
function getData(data) {
  if (data==undefined) return undefined;
  else if (data.val!='') return data.val;
  else return data.children.toString();
}

function is_error(data,err) {
  if (data==undefined) return true;
  if (err==undefined)
    return (data.length > 0 && Comp.string.get(data,0)=='E');
  else
    return (Comp.string.equal(data,err));
};

/** AMP port using HTTP
 *  ===================
 *
 *  No negotiation is performed. Data transfer can be fragmented.
 *  Each time a remote endpoint sends a GET/PUT request, we stall the request until
 *  a timeout occurs or we have to send data to the remote endpoint. A link is established. 
 *  The routing table is refreshed each time the same client send a
 *  GET/PUT request again. If the client do not send requests anymore after a timeout, it is considered to be 
 *  unlinked and the route is removed.
 * 
 * type amp.http = function (options:{rcv:address,snd?:address,verbose?,logging?,out?:function,log?})
 */
var http = Require('http');

amp.http = function (options) {
  var self=this;
  this.proto = 'http';
  this.options=checkOptions(options,{});
  this.verbose=checkOption(this.options.verbose,0);

  this.dir  = options.dir;                          // attached to JAM port
  this.rcv  = options.rcv;                          // Local  HTTP Server Port; Server Mode 
  this.mode = AMMode.AMO_MULTICAST;                 // We can handle multiple links at once 
  this.node   = options.node;                       // Attached to this node
  if (options.nodeid) this.node={id:options.nodeid};  // Different public node id 
  
  if (options.rcv && options.rcv.address!='*' && options.rcv.port) this.mode |= AMMode.AMO_SERVER;
  else this.mode |= AMMode.AMO_CLIENT;
  
  this.options.keepalive=checkOption(options.keepAlive,true);
  this.secure = this.options.secure;
  
  this.port = options.port||Net.uniqport();     // Connection Link Port (this side)
  this.id = Net.Print.port(this.port);
  // Stream socket; can be a process object!
  this.out = function (msg,async) {
    (async?Aios.logAsync:Aios.log)
      ('[AMP '+Net.Print.port(self.port)+
       (self.dir?(' '+Aios.DIR.print(self.dir)):'')+'] '+msg);
  }
  this.debug = function (msg) {
    Aios.logAsync
      ('[AMP '+Net.Print.port(self.port)+
       (self.dir?(' '+Aios.DIR.print(self.dir)):'')+'] '+msg);
  }
  this.err = function (msg,async) {
    (async?Aios.logAsync:Aios.log)
      ('[AMP '+Net.Print.port(self.port)+
        (self.dir?(' '+Aios.DIR.print(self.dir)):'')+'] Error: '+msg);
    throw 'AMP';
  }

  this.events = [];
  // typeof linkentry = {snd:address,tries:number,state:amstate,collect?,collecting?,msgqueue?:{} []} 
  this.links = {};
  this.count={rcv:0,snd:0,lnk:0,png:0};
  if (options.snd) {
    url=addr2url(options.snd,true);
    this.links[url]={snd:options.snd,tries:0,state:AMState.AMS_NOTCONNECTED,live:options.AMC_MAXLIVE};
    //this.out(url)
  }
  // Collector thread collecting messages from server (AMO_CLIENT mode)
  this.collector=undefined;
  
  this.logs=[];
  this.logging=options.logging||false;
  if (this.logging) {
    setInterval(function () { self.LOG('print') },5000);
  }
  this.index=0;
};

amp.http.prototype.LOG = amp.man.prototype.LOG;  
amp.http.prototype.checkState = amp.man.prototype.checkState;
amp.http.prototype.config = amp.man.prototype.config;
amp.http.prototype.emit = amp.man.prototype.emit;
amp.http.prototype.on = amp.man.prototype.on;
amp.http.prototype.handle = amp.man.prototype.handle;
amp.http.prototype.status = amp.man.prototype.status;

/** Acknowledge reply
 *
 */
amp.http.prototype.ack=function(snd,status) {
  this.response(snd,{type:AMMessageType.AMMACK,status:status||"EOK",
                     port:this.port,node:this.node?this.node.id:'*'});
}

/** Callback from ampMAN handler to inform about remote unlink event
 *
 */
amp.http.prototype.cleanup=function(url,keep) {
  // Cleanup link
  var obj=this.links[url];
  if (!obj) return;
  obj.state=AMState.AMS_NOTCONNECTED
  if (obj.collect) clearTimeout(obj.collect), obj.collect=undefined;
  if (obj.collecting) this.response(obj.collecting,{status:'ENOENTRY'}),obj.collecting=undefined;
  // Link was initiated on remote side
  // Remove link!
  if (!keep) {
    obj.snd={};
    this.links[url]=undefined;
  }
}

/** Collect request
 *
 */
amp.http.prototype.collect=function(snd) {
  var self=this,
      url=addr2url(snd,true),
      msg={type:AMMessageType.AMMCOLLECT,port:this.port,index:this.index++,magic:magic};
  if (this.links[url] && this.links[url].state==AMState.AMS_CONNECTED) 
    this.send(snd,msg,function (reply) {
      var err=is_error(reply);
      if (err) return; //  self.cleanup(url,true);
      if (reply.msg) Comp.array.iter(reply.msg,function (msg) {
        self.handle(msg,snd);
      });
      if (!self.links[url]) return; // unlinked?
      self.links[url].collect=setTimeout(function () {
        self.collect(snd); 
      },0);
    });
}
/** Service collect request
 *
 */
amp.http.prototype.collecting=function(msg,remote,response) {
  var url;
  if (this.verbose>2) this.debug('handle AMMCOLLECT from '+addr2url(remote));
  url=addr2url(remote,true); // ipport or remote.port??
  if (this.links[url]  && this.links[url].msgqueue && this.links[url].msgqueue.length) {
    this.response(response,{msg:this.links[url].msgqueue});
    this.links[url].msgqueue=[];
  } 
  else if (this.links[url]) this.links[url].collecting=response;
  else this.response(response,{status:'ENOENTRY'});
}

/** HTTP GET request to send a messageto the server broker returning data on reply.
 *
 * @param path
 * @param callback
 */
 
amp.http.prototype.get = function (snd,path,callback) {
    var body,req,
        self=this;
  
    if (this.verbose>2) this.debug('get '+addr2url(snd)+ path); 
    this.count.snd = this.count.snd + path.length;
    if (!http.xhr) {
      req = http.request({
        host: snd.address,
        port: snd.port,
        path: path,
        method: 'GET',
        keepAlive: this.options.keepalive,
        headers: {
        }
      } , function(res) {
        if (self.verbose>2) self.debug('got '+addr2url(snd)+ path); 
        if (res.setEncoding != null) res.setEncoding('utf8');
        body = '';
        res.on('data', function (chunk) {
          body = body + chunk;
        });
        res.once('end', function () {
          self.count.rcv += body.length;
          if (callback) callback(body);
        });
      });
      req.once('error', function(err) {
        if (self.verbose) self.out('Warning: request to '+addr2url(snd)+' '+path+' failed: '+err,true);
        self.emit('error',err);
        if (callback) callback();
      });
      req.end();
    } else {
      // XHR Browser
      http.request({
        host: snd.address,
        port: snd.port,
        path:path,
        proto:'http',
        keepAlive: this.options.keepalive,
        method: 'GET',
        headers: {
        }
      } , function(err,xhr,body) {
        if (err) {
          if (self.verbose) self.out('Warning: request to '+addr2url(snd)+' '+path+' failed: '+err,true);
          self.emit('error',err);
          if (callback) callback();
        } else {
          self.count.rcv += body.length;
          if (callback) callback(body);
        }
    });    
  }
};

/** Initialize AMP
 *
 */
amp.http.prototype.init = function(callback) { 
  if (callback) callback();
};

/** Negotiate a virtual communication link (peer-to-peer).
 *  In oneway mode only a destination endpoint is set and it is assumed the endpoint can receive messages a-priori!
 *
 * typeof @snd = address
 * typeof @callback = function
 * typeof @connect = boolean is indicating an initial connect request and not an acknowledge
 * typeof @key = private
 * typeof @response = object
 *
 * +------------+
 * VCMessageType (int16)
 * Connection Port (port)
 * Node ID (string)
 * // Receiver IP Port (int32)
 * +------------+
 *
 */
amp.http.prototype.link=function(snd,connect,key,response) {
    var self = this,
        msg,
        url;
    if (this.verbose>1) this.out('amp.link: to '+addr2url(snd),true);
    
    // MULTICAST mode
    // Add new link to cache of links
    if (!snd) this.err(true,'link: no destinataion set in MULTICAST mode');
    if (snd.parameter && snd.parameter.secure) key=snd.parameter.secure;
    url=addr2url(snd,true);
    if (!this.links[url] || !this.links[url].snd.address) {
      if (connect) snd.connect=true;
      this.links[url]={
        snd:snd,
        state:AMState.AMS_NOTCONNECTED,
        tries:0,
        connect:connect,
        live:options.AMC_MAXLIVE};
    }
    // Let watchdog handle connect request link messages
    if (!this.inwatchdog && connect)
        return this.watchdog(true);
    // if (this.verbose>1) this.debug('send link '+Io.inspect(snd));
    msg={
      type:AMMessageType.AMMLINK,
      port:this.port,
      node:this.node?this.node.id:'*',
      index:this.index++,
      magic:magic,
      remote:snd.address,
    };
    if (key) msg.secure=key;

    this.count.lnk++;
    
    if (response)
      this.response(response,msg); 
    else this.send(snd,msg,function (reply) {
      if (is_error(reply)) return; // error
      // start message collector thread after first link reply!
      if ((self.mode & AMMode.AMO_CLIENT) && !self.links[url].collect) {
        self.links[url].collect=setTimeout(function () {
          self.collect(snd); 
        },0);
      }
      // handle reply
      self.handle(reply,snd);
    });
};

amp.http.prototype.ping=function(snd,response) {
    var self = this,msg={};

   
    msg.type=AMMessageType.AMMPING;
    msg.port=this.port;
    msg.index=this.index++;
    msg.magic=magic;
     
    if (this.verbose>1) this.debug('amp.ping'+(response?'in response':'')+': to '+addr2url(snd));

    this.count.png++;

    if (response)
      this.response(response,msg); 
    else this.send(snd,msg,function (reply) {
      if (is_error(reply)) return;   // error
      // handle reply
      self.handle(reply,snd);
    });
}

amp.http.prototype.pong=function(snd,response) {
    var self = this,msg={};

    msg.type=AMMessageType.AMMPONG;
    msg.port=this.port;
    msg.index=this.index++;
    msg.magic=magic;

    if (this.verbose>1) this.debug('amp.pong '+(response?'in response':'')+': to '+addr2url(snd));

    this.count.png++;

    if (response)
      this.response(response,msg); 
    else this.send(snd,msg,function (reply) {
        if (is_error(reply)) {
          self.emit('error',reply);
        }
    });
}

/** HTTP PUT request to send a message and data to the AMP HTTP server.
 *
 * @param path
 * @param data
 */
amp.http.prototype.put = function (snd,path,data) {
    var self=this,
        req,body;
    this.count.snd = this.count.snd + path.length + data.length;
    if (!http.xhr) {
      req = http.request({
        host: snd.address,
        port: snd.port,
        path: path,
        method: 'POST',
        keepAlive: this.options.keepalive,
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
            'Content-Length': data.length
        }
      } , function(res) {
        if (res.setEncoding != null) res.setEncoding('utf8');
        // TODO body=+chunk, res.on('end') ..??
        res.once('data', function (chunk) {
          // TODO
        });
      });
      req.once('error', function(err) {
        self.out('Warning: request to '+addr2url(snd)+' failed: '+err,true);
        self.emit('error',err);
      });

      // write data to request body
      req.write(data);
      req.end();
    } else {
      // XHR Browser
      http.request({
        host: snd.address,
        port: snd.port,
        path: path,
        method: 'POST',
        body:data,
        keepAlive: this.options.keepalive,
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
            'Content-Length': data.length
        }
      } , function(err,xhr,body) {
        if (err) {
          if (self.verbose) self.out('Warning: request to '+addr2url(snd)+' failed: '+err,true);
          self.emit('error',err);
        }
        // TODO
      })
    }
};

amp.http.prototype.receiver = function (callback,rcv) {
  var self = this;

  if (callback) this.callback=callback;
  
  if (this.mode & AMMode.AMO_SERVER) {
    // Only if this is a public or locally visible network node this node 
    // should provide a server port!
    if (rcv == undefined || rcv.address==undefined) rcv={},rcv.address=this.rcv.address;
    if (rcv.port==undefined) rcv.port=this.rcv.port;
    
    this.server=http.createServer(function (request,response) {
      if(parseQueryString(request.url).length==0) return response.end('EINVALID'); // accidental access by WEB browser
      // console.log(request.connection.remoteAddress);
      var i,body,
          msg = parseQueryString(request.url),
          remote = {address:request.connection.remoteAddress.replace(/^::ffff:/,'').replace(/^::1/,'localhost'),
                    port:'['+msg.port.replace(/:/g,'')+']' /* unique remote identifier */};

      if (self.verbose>2) 
        console.log(request.method,request.url,msg,addr2url(remote),url2addr(addr2url(remote)));
        
      // consistency check
      if (msg.magic!=magic) return;
      
      self.count.rcv += 1;
      msg.type=AMMessageType[msg.type];

      if (msg.secure) msg.secure=Net.port_of_str(msg.secure);
      
      if (debug) console.log(Io.Time(),msg)

      response.origin=request.headers.origin||request.headers.Origin;
      Comp.string.match(request.method,[
          ['GET',function() {
            if (msg.type==AMMessageType.AMMCOLLECT)
              self.collecting(msg,remote,response);
            else
              self.handle(msg,remote,response);
          }],
          ['POST',function() {
            body = '';
            request.on('data', function (chunk) {
              body = body + chunk;
            });
            request.on('end', function () {
              msg.data=Buffer(body,'hex');
              self.count.rcv += msg.data.length;
              if (msg.cmd) msg.cmd=Number(msg.cmd);
              self.handle(msg,remote,response);
            });
          }]
      ])
    });

    this.server.on("connection", function (socket) {
        socket.setNoDelay(true);
    });

    this.server.on("error", function (err) {
      self.out('Warning: receiver failed: '+err,true);
      if (err) self.err(true,err);
    });

    this.server.listen(rcv.port,function (err) {
      // Try to get network IP address of this host 
      if (!err) getNetworkIP(undefined,function (err,ip) {
        if (!err) self.rcv.address=isLocal(ip)?options.localhost:ip;
        if (self.verbose) self.out('IP port '+addr2url(self.rcv)+ ' (proto '+self.options.proto+')',true);
        if (err) return self.out("! Unable to obtain network connection information: "+err,true);
      });
      if (callback) callback(err);
    });
  }
  if (this.mode & AMMode.AMO_CLIENT) {

    // If this is a hidden node (e.g., inside a WEB browser), we have to connect to a remote public server
    // by using stalled GET requests.
    if (callback) this.callback=callback;
  }
}

/** Reply to a request (msg.tid contains request tid)
 */
amp.http.prototype.reply = function (cmd,msg,snd) {
  this.request(cmd,msg,snd);
}

/** Send a response reply for a pending HTTP GET/PUT request (AMO_SERVER)
 *
 */ 
amp.http.prototype.response = function (response,msg) {
  var data=msg2JSON(msg), header;

  if (response.origin!=undefined)
      header={'Access-Control-Allow-Origin': response.origin,
              'Access-Control-Allow-Credentials': 'true',
              'Content-Type': 'text/plain'};
  else
      header={'Content-Type': 'text/plain'};
  if (this.options.keepalive) header["Connection"]="keep-alive";
  
  response.writeHead(200,header);
  response.write(data);
  if (debug) console.log(Io.Time(),msg)
  response.end();
}

/** Send a request message to a remote node endpoint
 *
 * function (cmd:integer,msg:Buffer,snd:address)
 */

amp.http.prototype.request = function (cmd,msg,snd) {
  var self=this,req={},
      size = msg.data.length,
      tid = msg.tid||Comp.random.int(65536/2);

  if (snd==undefined) this.err(true,'request: snd=null');

  req.type=AMMessageType.AMMRPC;
  req.tid=tid;                   // Transaction Message ID
  req.port=this.port;            // This AMP id
  req.cmd=cmd;
  req.size=size;
  req.magic=magic;
  req.data=msg.data;
  this.send(snd,req);

}


amp.http.prototype.scan=function(snd,response,callback) {
    var self = this,msg={};
    
    msg.type=response?AMMessageType.AMMACK:AMMessageType.AMMSCAN;
    msg.port=this.port;
    msg.magic=magic;

    if (response) msg.info=snd.info;
    
    if (this.verbose>1 && snd) this.debug('amp.scan: to '+addr2url(snd));

    if (response) 
      this.response(response,msg);
    else
      this.send(snd,msg,function (reply) {
        callback(reply)
      });
}

/** Main entry for requests with JSON interface. Multiplexer for HTTP GET/PUT.
 *
 *  msg: JSON 
 *  callback : function (reply:object)
 */
amp.http.prototype.send = function (snd,msg,callback) {
  var path,
      url,
      body,
      self=this;
  // Create query selector
  path = formatQueryString(msg);
    
  if (typeof snd.port == 'string') {
    url=addr2url(snd,true);
    // If Pending get from client
    
    // Else queue message, client will collect them later (or never)
    if (this.links[url]) {
      if (!this.links[url].msgqueue) this.links[url].msgqueue=[];
      if (this.links[url].collecting) {// pending AMMCOLLECT request
        if (this.verbose>1) this.debug('REPLY msg '+AMMessageType.print(msg.type)+' to '+url);
        this.response(this.links[url].collecting,{msg:[msg]});
        this.links[url].collecting=undefined;
      } else {
        if (this.verbose>1) this.debug('QUEUE msg '+AMMessageType.print(msg.type)+' for '+url);
        this.links[url].msgqueue.push(msg);
      }
    }
  } else if (msg.data!=undefined) { 
    // Convert buffer data to hex formatted string
    body=msg.data.toString('hex');
    
    this.put(snd,path,body,function (body) {
      if (is_error(body)) self.emit('error',body);
      else if (!is_status(body)) self.emit('error','EINVALID');
      // No reply expected!
    }); 
  } else {
    this.get(snd,path,function (body) {
      var xml,i,
          reply;
      if (!body || is_error(body)) {
        self.emit('error','EINVALID');
      } else {
        reply=JSON2msg(body);
        // { status:string,reply:*,msg?:{}[],..} 
      }
      if (callback) callback(reply);
    });
  } 
}


// Start AMP watchdog and receiver
amp.http.prototype.start = function(callback) {
  var self=this,
      s=this.secure?' (security port '+Sec.Port.toString(this.secure)+')':'';
  if (this.verbose>0 && this.mode & AMMode.AMO_SERVER) 
    this.out('Starting ' + addr2url(this.rcv)+' ['+AMMode.print(this.mode)+'] (proto '+this.proto+')'+s);
  if (this.verbose>0 && this.mode & AMMode.AMO_CLIENT) 
    this.out('Starting ['+AMMode.print(this.mode)+'] (proto http)');
  this.watchdog(true);
  if (!this.server && this.mode & AMMode.AMO_SERVER) {
    // After stop? Restart receiver.
    this.receiver();
  } 
  if (callback) callback();
}

// Stop AMP
amp.http.prototype.stop = function(callback) {
  if (this.links) for(var p in this.links) {
    if (this.links[p]) {
      // Try to unlink remote endpoint
      if (this.links[p].collect) clearTimeout(this.links[p].collect),this.links[p].collect=undefined;
      this.unlink(this.links[p].snd);
      if (this.links[p]) this.links[p].state=AMState.AMS_NOTCONNECTED;
    }
  }
  if (this.verbose>0 && this.mode & AMMode.AMO_SERVER) 
    this.out('Stopping ' + addr2url(this.rcv)+' ['+AMMode.print(this.mode)+'] (proto '+this.proto+')'+s);
  if (this.verbose>0 && this.mode & AMMode.AMO_CLIENT) 
    this.out('Stopping ['+AMMode.print(this.mode)+'] (proto http)');
  if (this.timer) clearTimeout(this.timer),this.timer=undefined;
  if (this.server) this.server.close(),this.server=undefined;
  
  if (callback) callback();
}

// Unlink remote endpoint
amp.http.prototype.unlink=function(snd) {
  var self = this,msg,
      url = snd?addr2url(snd,true):null;
  if (this.mode&AMMode.AMO_MULTICAST) {
    if (!this.links[url] || this.links[url].state!=AMState.AMS_CONNECTED) return;
  } else {
    if (this.links.state!=AMState.AMS_CONNECTED) return;
  }
  msg={type:AMMessageType.AMMUNLINK,port:this.port,node:this.node?this.node.id:'*',index:this.index++,magic:magic};
    
  this.send(snd,msg,function (reply) {
    // handle reply
    if (reply) {}
  });
  this.emit('route-',addr2url(snd,true));
  if (this.mode&AMMode.AMO_MULTICAST) {
    this.links[url].state=AMState.AMS_NOTCONNECTED;
    if (!this.links[url].snd.connect) this.links[url].snd={};
  } else {
    this.links.state=AMState.AMS_NOTCONNECTED;
    if (!this.links.snd.connect) this.links.snd={};
  }
  this.cleanup(url);
}


/** Install a watchdog timer.
 *
 * 1. If link state is AMS_NOTCONNECTED, retry link request if this.links[].snd is set.
 * 2. If link state is AMS_CONNECTED, check link end point.
 * 3, If link state is AMS_RENDEZVOUS, get remote endpoint connectivity via broker
 *
 * @param run
 */
amp.http.prototype.watchdog = function(run,immedOrDelay) {
    var self=this;
    if (this.timer) clearTimeout(self.timer),this.timer=undefined;
    if (run) self.timer=setTimeout(function () {
        if (!self.timer ||  self.inwatchdog) return; // stopped or busy?
        self.timer = undefined;
        self.inwatchdog=true;
        
        function handle(obj,url) {
          if (self.verbose>1) self.debug('Watchdog: handle link ('+url+') '+
                                         (obj.snd?addr2url(obj.snd):'')+' in state '+
                                         AMState.print(obj.state)+' live '+obj.live,true);
          switch (obj.state) {
            case AMState.AMS_CONNECTED:
                if (obj.live == 0) {
                    // No PING received, disconnect...
                    if (self.verbose>0) 
                      self.out('Endpoint ' + addr2url(obj.snd) +
                               ' not responding, propably dead. Unlinking...',true);
                    obj.state = AMState.AMS_NOTCONNECTED;
                    self.emit('route-',addr2url(obj.snd,true));
                    self.cleanup(url,obj.snd.connect);
                    if (obj.snd.connect) self.watchdog(true,2000);
                } else {
                    obj.tries=0;
                    obj.live--;
                    self.watchdog(true);
                    if (self.mode&AMMode.AMO_MULTICAST) self.ping(obj.snd);
                    else self.ping();
                }
                break;
            case AMState.AMS_NOTCONNECTED:
            case AMState.AMS_PAIRED:
                if (obj.snd.port && typeof obj.snd.port == 'number') {
                  // Try link to specified remote endpoint obj.snd
                  if (self.verbose>0 && obj.tries==0) 
                    self.out('Trying link to ' + addr2url(obj.snd),true);
                  self.link(obj.snd); 
                  obj.tries++;
                  if (obj.tries < options.TRIES) self.watchdog(true);
                  else {
                    self.out('Giving up to link '+addr2url(obj.snd),true);
                    self.emit('error','link',addr2url(obj.snd));
                    obj.snd={},obj.tries=0;
                  }
                }
                break;
            // AMP P2P Control
            case AMState.AMS_RENDEZVOUS:
                obj.send(
                  {type:'register',name: self.rcv.name, linfo: self.rcv},
                  self.broker,
                  function () {}
                );
                self.watchdog(true);
                break;
            case AMState.AMS_REGISTERED:
                if (obj.tries < options.TRIES && obj.snd.name) {
                  obj.tries++;
                  self.send(
                    {type:'pair', from:self.rcv.name, to: obj.snd.name},
                    self.broker,
                    function () {
                    }
                  );
                }
                if (obj.tries < options.TRIES) self.watchdog(true);
                break;
          }          
        }
        for(var p in self.links) if (self.links[p]) handle(self.links[p],p);
        self.inwatchdog=false;
    },immedOrDelay==true?0:immedOrDelay||options.TIMER);
};
    
};
BundleModuleCode['jam/jsonfn']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Vadim Kiryukhin, Stefan Bosse
 **    $INITIAL:     (C) 2006-2017 Vadim Kiryukhin
 **    $MODIFIED:    by sbosse.
 **    $RCS:         $Id: jsonfn.js,v 1.1 2017/05/20 15:56:53 sbosse Exp $
 **    $VERSION:     1.3.3X
 **
 **    $INFO:
 **
 ** JSONfn - javascript (both node.js and browser) plugin to stringify, 
 **          parse and clone objects with embedded functions in an optional  masked context (mask).
 **        - supported data types: number, boolean, string, array, buffer, typedarray, function, regex
 **
 **     browser:
 **         JSONfn.stringify(obj);
 **         JSONfn.parse(str[, date2obj]);
 **         JSONfn.clone(obj[, date2obj]);
 **
 **     nodejs:
 **       var JSONfn = require('path/to/json-fn');
 **       JSONfn.stringify(obj);
 **       JSONfn.parse(str[, date2obj]);
 **       JSONfn.clone(obj[, date2obj]);
 **
 **
 **     @obj      -  Object;
 **     @str      -  String, which is returned by JSONfn.stringify() function; 
 **     @mask     -  Environment Mask (optional)
 **
 **    $ENDOFINFO
 */

var current=null;


function typedarrayTObase64(ta,ftyp) {
  var b,i;
  if (ta.buffer instanceof ArrayBuffer) {
    b=Buffer(ta.buffer);
    if (b.length>0) return b.toString('base64');
  }
  // Fall-back conversion
  switch (ftyp) {
    case Float32Array: 
      b = Buffer(ta.length*4);
      for(i=0;i<ta.length;i++) b.writeFloatLE(ta[i],i*4);
      return b.toString('base64');
    case Float64Array: 
      b = Buffer(ta.length*8);
      for(i=0;i<ta.length;i++) b.writeDoubleLE(ta[i],i*8);
      return b.toString('base64');
    case Int16Array: 
      b = Buffer(ta.length*2);
      for(i=0;i<ta.length;i++) b.writeInt16LE(ta[i],i*2);
      return b.toString('base64');
    case Int32Array: 
      b = Buffer(ta.length*4);
      for(i=0;i<ta.length;i++) b.writeInt32LE(ta[i],i*4);
      return b.toString('base64');
  }
  return ta.toString();
}
function base64TOtypedarray(buff,ftyp) {
  var i,ta;
  if (buff.buffer instanceof ArrayBuffer) {
    switch (ftyp) {
      case Float32Array: return new Float32Array((new Uint8Array(buff)).buffer);
      case Float64Array: return new Float64Array((new Uint8Array(buff)).buffer);
      case Int16Array:   return new Int16Array((new Uint8Array(buff)).buffer);
      case Int32Array:   return new Int32Array((new Uint8Array(buff)).buffer);
    }
  } else if (typeof Uint8Array.from != 'undefined') {
    switch (ftyp) {
      case Float32Array: return new Float32Array(Uint8Array.from(buff).buffer);
      case Float64Array: return new Float64Array(Uint8Array.from(buff).buffer);
      case Int16Array:   return new Int16Array(Uint8Array.from(buff).buffer);
      case Int32Array:   return new Int32Array(Uint8Array.from(buff).buffer);
    }
  } else {
    // Fall-back conversion
    switch (ftyp) {
      case Float32Array: 
        ta=new Float32Array(buff.length/4);
        for(i=0;i<ta.length;i++) 
          ta[i]=buff.readFloatLE(i*4);
        return ta;
      case Float64Array: 
        ta=new Float64Array(buff.length/8);
        for(i=0;i<ta.length;i++) 
          ta[i]=buff.readDoubleLE(i*8);
        return ta;
      case Int16Array: 
        ta=new Int16Array(buff.length/2);
        for(i=0;i<ta.length;i++) 
          ta[i]=buff.readInt16LE(i*2);
        return ta;
      case Int32Array: 
        ta=new Int32Array(buff.length/4);
        for(i=0;i<ta.length;i++) 
          ta[i]=buff.readInt32LE(i*4);
        return ta;
    }
  }
}
(function (exports) {

  function stringify (obj) {

    return JSON.stringify(obj, function (key, value) {
      if (value instanceof Function || typeof value == 'function')
        return '_PxEnUf_' +Buffer(value.toString(true)).toString('base64');  // try minification (true) if supported
      if (value instanceof Buffer)
        return '_PxEfUb_' +value.toString('base64');
      if (typeof Float64Array != 'undefined' && value instanceof Float64Array)
        return '_PxE6Lf_' + typedarrayTObase64(value,Float64Array);
      if (typeof Float32Array != 'undefined' && value instanceof Float32Array)
        return '_PxE3Lf_' + typedarrayTObase64(value,Float32Array);
      if (typeof Int16Array != 'undefined' && value instanceof Int16Array)
        return '_PxE1Ni_' + typedarrayTObase64(value,Int16Array);
      if (typeof Int32Array != 'undefined' && value instanceof Int32Array)
        return '_PxE3Ni_' + typedarrayTObase64(value,Int32Array);
      if (value instanceof RegExp)
        return '_PxEgEr_' + value;
      
      return value;
    });
  };

  function parse(str, mask) {
    var code;
    try {
      with (mask||{}) {
        code= JSON.parse(str, function (key, value) {
          var prefix;

          try {
            if (typeof value != 'string') {
              return value;
            }
            if (value.length < 8) {
              return value;
            }
            prefix = value.substring(0, 8);

            if (prefix === '_PxEnUf_') {
              var code = value.slice(8);
              // TODO: arrow function support (missing own this object fix)
              // must be addressed in higher-level code (code.js)
              if (code.indexOf('function')==0)  // Backward comp.
                return eval('(' + code + ')');
              else
                return eval('(' + Buffer(code,'base64').toString() + ')');
            }
            if (prefix === '_PxEfUb_')
              return Buffer(value.slice(8),'base64');
            if (prefix === '_PxE6Lf_')
              return base64TOtypedarray(Buffer(value.slice(8),'base64'),Float64Array);
            if (prefix === '_PxE3Lf_')
              return base64TOtypedarray(Buffer(value.slice(8),'base64'),Float32Array);
            if (prefix === '_PxE1Ni_')
              return base64TOtypedarray(Buffer(value.slice(8),'base64'),Int16Array);
            if (prefix === '_PxE3Ni_')
              return base64TOtypedarray(Buffer(value.slice(8),'base64'),Int32Array);
            if (prefix === '_PxEgEr_')
              return eval(value.slice(8));
           
            return value;
          } catch (e) {
            throw {error:e,value:value};
          }
        });
     };
    } catch (e) {
      throw e.error||e;
    }
   return code;
  };

  exports.clone = function (obj, date2obj) {
    return exports.parse(exports.stringify(obj), date2obj);
  };
  exports.current         = function (module) { current=module.current; };
  exports.serialize       = stringify;
  exports.stringify       = stringify;
  exports.deserialize     = parse;
  exports.parse           = parse;

  /* Remove any buffer toJSON bindings */
  if (typeof Buffer != 'undefined' && Buffer.prototype.toJSON) delete Buffer.prototype.toJSON;
  if (typeof buffer == 'object' && buffer.Buffer) delete buffer.Buffer.prototype.toJSON;

}(typeof exports === 'undefined' ? (window.JSONfn = {}) : exports));


};
BundleModuleCode['jam/ampHTTPS']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2006-2020 bLAB
 **    $CREATED:     31-05-20 by sbosse.
 **    $RCS:         $Id: ampHTTPS.js,v 1.1 2020/02/03 09:45:01 sbosse Exp sbosse $
 **    $VERSION:     1.14.7
 **
 **    $INFO:
 **
 **  JAM Agent Management Port (AMP) over HTTPS
 **  Only Mulitcast IP(*) mode is supported!
 **
 **  Events out: 'error','route-'
 **
 **  TODO: Garbage collection
 **
 **  Requires cert.pem and key.pem strings (options.pem.key/cert) and builtin https/crypto!
 **  Letsencrypt files:
 **      SSLCertificateFile        /etc/letsencrypt/live/<domain>/fullchain.pem
 **      SSLCertificateKeyFile     /etc/letsencrypt/live/<domain>/privkey.pem
 **
 **
 **    $ENDOFINFO
 */
var Io = Require('com/io');
var Lz = Require('os/lz-string');
var Comp = Require('com/compat');
var Buf = Require('dos/buf');
var Net = Require('dos/network');
var Command = Net.Command;
var Status = Net.Status;
var current=none;
var Aios=none;
var CBL = Require('com/cbl');
var Bas64 = Require('os/base64');
var Sec = Require('jam/security')
var JSONfn = Require('jam/jsonfn')

var options = {
  version:"1.14.7",
}

var COM = Require('jam/ampCOM'),
    AMMode=COM.AMMode,
    AMMessageType=COM.AMMessageType,
    AMState=COM.AMState,
    amp=COM.amp,
    options=COM.options,
    url2addr=COM.url2addr,
    addr2url=COM.addr2url,
    addrequal=COM.addrequal,
    resolve=COM.resolve,
    ipequal=COM.ipequal,
    isLocal=COM.isLocal,
    getNetworkIP=COM.getNetworkIP,
    pem=COM.options.pem,
    magic=COM.options.magic;

var debug = false;

module.exports.current=function (module) { current=module.current; Aios=module; };

/*
** Parse query string '?attr=val&attr=val... and return parameter record
*/
function parseQueryString( url ) {
    var queryString = url.substring( url.indexOf('?') + 1 );
    if (queryString == url) return [];
    var params = {}, queries, temp, i, l;

    // Split into key/value pairs
    queries = queryString.split("&");

    // Convert the array of strings into an object
    for ( i = 0, l = queries.length; i < l; i++ ) {
        temp = queries[i].split('=');
        if (temp[1]==undefined) temp[1]='true';
        params[temp[0]] = temp[1].replace('%20',' ');
    }

    return params;
}
/*
** Format a query string from a parameter record
*/
function formatQueryString (msg) {
  var path= '/?';
  path += "magic="+msg.magic;
  path += "&type="+AMMessageType.print(msg.type);
  if (msg.cmd) path += '&cmd='+msg.cmd;
  if (msg.tid) path += '&tid='+msg.tid;
  if (msg.port) path += '&port='+Net.port_to_str(msg.port);
  if (msg.timeout) path += '&timeout='+msg.timeout;
  if (msg.node) path += '&node='+msg.node.replace(' ','%20');
  if (msg.index) path += '&index='+msg.index;
  if (msg.secure) path += '&secure='+(msg.secure.length==8?Net.port_to_str(msg.secure):msg.secure);
  return path;
}

function msg2JSON(msg) {
  if (msg.port) msg.port=Net.port_to_str(msg.port);
  if (msg.msg && msg.msg.length) Comp.array.iter(msg.msg,function (msg) {
    if (msg.port) msg.port=Net.port_to_str(msg.port);
  });
  return JSONfn.stringify(msg);
}
function JSON2msg(data) {
  var msg=JSONfn.parse(data);
  if (msg.port) msg.port=Net.port_of_str(msg.port);
  if (msg.msg && msg.msg.length) Comp.array.iter(msg.msg,function (msg) {
    if (msg.port) msg.port=Net.port_of_str(msg.port);
  });
  return msg;
}

/** Get XML data
 *
 */
function getData(data) {
  if (data==undefined) return undefined;
  else if (data.val!='') return data.val;
  else return data.children.toString();
}

function is_error(data,err) {
  if (data==undefined) return true;
  if (err==undefined)
    return (data.length > 0 && Comp.string.get(data,0)=='E');
  else
    return (Comp.string.equal(data,err));
};

/** AMP port using HTTP
 *  ===================
 *
 *  No negotiation is performed. Data transfer can be fragmented.
 *  Each time a remote endpoint sends a GET/PUT request, we stall the request until
 *  a timeout occurs or we have to send data to the remote endpoint. A link is established. 
 *  The routing table is refreshed each time the same client send a
 *  GET/PUT request again. If the client do not send requests anymore after a timeout, it is considered to be 
 *  unlinked and the route is removed.
 * 
 * type amp.https = function (options:{pem?:{key,cert}, rcv:address,snd?:address,verbose?,logging?,out?:function,log?})
 */
var https;
var http = Require('http');

amp.https = function (options) {
  var self=this;
  this.proto = 'http';
  this.options = checkOptions(options,{});
  this.verbose = checkOption(this.options.verbose,0);


  if (global.TARGET!= 'browser' && !https) try {
    https=require('https');
  } catch (e) {
    throw 'amp.https: no https/crypto support ('+e+')';
  }
  
  this.dir  = options.dir;                          // attached to JAM port
  this.rcv  = options.rcv;                          // Local  HTTP Server Port; Server Mode 
  this.mode = AMMode.AMO_MULTICAST;                 // We can handle multiple links at once 
  this.node   = options.node;                       // Attached to this node

  if (options.rcv && options.rcv.address!='*' && options.rcv.port) this.mode |= AMMode.AMO_SERVER;
  else this.mode |= AMMode.AMO_CLIENT;

  if (!options.pem) this.options.pem=pem;

  if ((this.mode & AMMode.AMO_CLIENT)==0 && 
      (!this.options.pem || !this.options.pem.key || !this.options.pem.cert)) 
    throw "amp.https: no pem certificate and key provided like pem:{key,cert}";


  this.options.keepalive=checkOption(options.keepAlive,true);
  this.secure = this.options.secure;
  
  this.port = options.port||Net.uniqport();     // Connection Link Port (this side)
  this.id = Net.Print.port(this.port);
  // Stream socket; can be a process object!
  this.out = function (msg,async) {
    (async?Aios.logAsync:Aios.log)
      ('[AMP '+Net.Print.port(self.port)+
       (self.dir?(' '+Aios.DIR.print(self.dir)):'')+'] '+msg);
  }
  this.debug = function (msg) {
    Aios.logAsync
      ('[AMP '+Net.Print.port(self.port)+
       (self.dir?(' '+Aios.DIR.print(self.dir)):'')+'] '+msg);
  }
  this.err = function (msg,async) {
    (async?Aios.logAsync:Aios.log)
      ('[AMP '+Net.Print.port(self.port)+
        (self.dir?(' '+Aios.DIR.print(self.dir)):'')+'] Error: '+msg);
    throw 'AMP';
  }

  this.events = [];
  // typeof linkentry = {snd:address,tries:number,state:amstate,collect?,collecting?,msgqueue?:{} []} 
  this.links = {};
  this.count={rcv:0,snd:0,lnk:0,png:0};
  if (options.snd) {
    url=addr2url(options.snd,true);
    this.links[url]={snd:options.snd,tries:0,state:AMState.AMS_NOTCONNECTED,live:options.AMC_MAXLIVE};
    //this.out(url)
  }
  // Collector thread collecting messages from server (AMO_CLIENT mode)
  this.collector=undefined;
  
  this.logs=[];
  this.logging=options.logging||false;
  if (this.logging) {
    setInterval(function () { self.LOG('print') },5000);
  }
  this.index=0;
};

amp.https.prototype.LOG = amp.man.prototype.LOG;  
amp.https.prototype.checkState = amp.man.prototype.checkState;
amp.https.prototype.config = amp.man.prototype.config;
amp.https.prototype.emit = amp.man.prototype.emit;
amp.https.prototype.on = amp.man.prototype.on;
amp.https.prototype.handle = amp.man.prototype.handle;
amp.https.prototype.status = amp.man.prototype.status;

/** Acknowledge reply
 *
 */
amp.https.prototype.ack=function(snd,status) {
  this.response(snd,{type:AMMessageType.AMMACK,status:status||"EOK",
                  port:this.port,node:this.node?this.node.id:'*'});
}

/** Callback from ampMAN handler to inform about remote unlink event
 *
 */
amp.https.prototype.cleanup=function(url,keep) {
  // Cleanup link
  var obj=this.links[url];
  if (!obj) return;
  obj.state=AMState.AMS_NOTCONNECTED
  if (obj.collect) clearTimeout(obj.collect), obj.collect=undefined;
  if (obj.collecting) this.response(obj.collecting,{status:'ENOENTRY'}),obj.collecting=undefined;
  // Link was initiated on remote side
  // Remove link!
  if (!keep) {
    obj.snd={};
    this.links[url]=undefined;
  }
}

/** Collect request
 *
 */
amp.https.prototype.collect=function(snd) {
  var self=this,
      url=addr2url(snd,true),
      msg={type:AMMessageType.AMMCOLLECT,port:this.port,index:this.index++,magic:magic};
  if (this.links[url] && this.links[url].state==AMState.AMS_CONNECTED) 
    this.send(snd,msg,function (reply) {
      var err=is_error(reply);
      if (err) return; //  self.cleanup(url,true);
      if (reply.msg) Comp.array.iter(reply.msg,function (msg) {
        self.handle(msg,snd);
      });
      if (!self.links[url]) return; // unlinked?
      self.links[url].collect=setTimeout(function () {
        self.collect(snd); 
      },0);
    });
}
/** Service collect request
 *
 */
amp.https.prototype.collecting=function(msg,remote,response) {
  var url;
  if (this.verbose>2) this.debug('handle AMMCOLLECT from '+addr2url(remote));
  url=addr2url(remote,true); // ipport or remote.port??
  if (this.links[url]  && this.links[url].msgqueue && this.links[url].msgqueue.length) {
    this.response(response,{msg:this.links[url].msgqueue});
    this.links[url].msgqueue=[];
  } 
  else if (this.links[url]) this.links[url].collecting=response;
  else this.response(response,{status:'ENOENTRY'});
}

/** HTTP GET request to send a messageto the server broker returning data on reply.
 *
 * @param path
 * @param callback
 */
 
amp.https.prototype.get = function (snd,path,callback) {
    var body,req,
        self=this;
  
    if (this.verbose>2) this.debug('get '+addr2url(snd)+ path); 
    this.count.snd = this.count.snd + path.length;
    if (https) {
      req = https.request({
        host: snd.address,
        port: snd.port,
        path: path,
        method: 'GET',
        keepAlive: this.options.keepalive,
        headers: {
        }
      } , function(res) {
        if (self.verbose>2) self.debug('got '+addr2url(snd)+ path); 
        if (res.setEncoding != null) res.setEncoding('utf8');
        body = '';
        res.on('data', function (chunk) {
          body = body + chunk;
        });
        res.once('end', function () {
          self.count.rcv += body.length;
          if (callback) callback(body);
        });
      });
      req.once('error', function(err) {
        if (self.verbose) self.out('Warning: request to '+addr2url(snd)+' '+path+' failed: '+err,true);
        self.emit('error',err);
        if (callback) callback();
      });
      req.end();
    } else {
      // XHR Browser
      http.request({
        host: snd.address,
        port: snd.port,
        path: path,
        proto:'https',
        method: 'GET',
        headers: {
        }
      } , function(err,xhr,body) {
        if (err) {
          if (self.verbose) self.out('Warning: request to '+addr2url(snd)+' '+path+' failed: '+err,true);
          self.emit('error',err);
          if (callback) callback();
        } else {
          self.count.rcv += body.length;
          if (callback) callback(body);
        }
    });    
  }
};

/** Initialize AMP
 *
 */
amp.https.prototype.init = function(callback) { 
  if (callback) callback();
};

/** Negotiate a virtual communication link (peer-to-peer).
 *  In oneway mode only a destination endpoint is set and it is assumed the endpoint can receive messages a-priori!
 *
 * typeof @snd = address
 * typeof @callback = function
 * typeof @connect = boolean is indicating an initial connect request and not an acknowledge
 * typeof @key = private
 * typeof @response = object
 *
 * +------------+
 * VCMessageType (int16)
 * Connection Port (port)
 * Node ID (string)
 * // Receiver IP Port (int32)
 * +------------+
 *
 */
amp.https.prototype.link=function(snd,connect,key,response) {
    var self = this,
        msg,
        url;
    if (this.verbose>1) this.debug('amp.link: to '+addr2url(snd));
    
    // MULTICAST mode
    // Add new link to cache of links
    if (!snd) this.err('link: no destinataion set in MULTICAST mode');
    if (snd.parameter && snd.parameter.secure) key=snd.parameter.secure;
    url=addr2url(snd,true);
    if (!this.links[url] || !this.links[url].snd.address) {
      if (connect) snd.connect=true;
      this.links[url]={
        snd:snd,
        state:AMState.AMS_NOTCONNECTED,
        tries:0,
        connect:connect,
        live:options.AMC_MAXLIVE};
    }
    // Let watchdog handle connect request link messages
    if (!this.inwatchdog && connect)
        return this.watchdog(true);
    // if (this.verbose>1) this.out('send link '+Io.inspect(snd));
    msg={
      type:AMMessageType.AMMLINK,
      port:this.port,
      node:this.node?this.node.id:'*',
      index:this.index++,
      magic:magic,
      remote:snd.address,
    };
    if (key) msg.secure=key;

    this.count.lnk++;
    
    if (response)
      this.response(response,msg); 
    else this.send(snd,msg,function (reply) {
      if (is_error(reply)) return; // error
      // start message collector thread after first link reply!
      if ((self.mode & AMMode.AMO_CLIENT) && !self.links[url].collect) {
        self.links[url].collect=setTimeout(function () {
          self.collect(snd); 
        },0);
      }
      // handle reply
      self.handle(reply,snd);
    });
};

amp.https.prototype.ping=function(snd,response) {
    var self = this,msg={};

   
    msg.type=AMMessageType.AMMPING;
    msg.port=this.port;
    msg.index=this.index++;
    msg.magic=magic;
     
    if (this.verbose>1) this.debug('amp.ping'+(response?'in response':'')+': to '+addr2url(snd));

    this.count.png++;

    if (response)
      this.response(response,msg); 
    else this.send(snd,msg,function (reply) {
      if (is_error(reply)) return;   // error
      // handle reply
      self.handle(reply,snd);
    });
}

amp.https.prototype.pong=function(snd,response) {
    var self = this,msg={};

    msg.type=AMMessageType.AMMPONG;
    msg.port=this.port;
    msg.index=this.index++;
    msg.magic=magic;

    if (this.verbose>1) this.debug('amp.pong '+(response?'in response':'')+': to '+addr2url(snd));

    this.count.png++;

    if (response)
      this.response(response,msg); 
    else this.send(snd,msg,function (reply) {
        if (is_error(reply)) {
          self.emit('error',reply);
        }
    });
}

/** HTTP PUT request to send a message and data to the AMP HTTP server.
 *
 * @param path
 * @param data
 */
amp.https.prototype.put = function (snd,path,data) {
    var self=this,
        req,body;
    this.count.snd = this.count.snd + path.length + data.length;
    if (https) {
      req = https.request({
        host: snd.address,
        port: snd.port,
        path: path,
        method: 'POST',
        keepAlive: this.options.keepalive,
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
            'Content-Length': data.length
        }
      } , function(res) {
        if (res.setEncoding != null) res.setEncoding('utf8');
        // TODO body=+chunk, res.on('end') ..??
        res.once('data', function (chunk) {
          // TODO
        });
      });
      req.once('error', function(err) {
        self.out('Warning: request to '+addr2url(snd)+' failed: '+err,true);
        self.emit('error',err);
      });

      // write data to request body
      req.write(data);
      req.end();
    } else {
      // XHR Browser
      http.request({
        host: snd.address,
        port: snd.port,
        path: path,
        proto: 'https',
        method: 'POST',
        body:data,
        keepAlive: this.options.keepalive,
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
            'Content-Length': data.length
        }
      } , function(err,xhr,body) {
        if (err) {
          if (self.verbose) self.out('Warning: request to '+addr2url(snd)+' failed: '+err,true);
          self.emit('error',err);
        }
        // TODO
      })
    }
};

amp.https.prototype.receiver = function (callback,rcv) {
  var self = this;

  if (callback) this.callback=callback;
  
  if (this.mode & AMMode.AMO_SERVER) {
    // Only if this is a public or locally visible network node this node 
    // should provide a server port!
    if (rcv == undefined || rcv.address==undefined) rcv={},rcv.address=this.rcv.address;
    if (rcv.port==undefined) rcv.port=this.rcv.port;
    
    var _options = {
      key : this.options.pem.key,
      cert: this.options.pem.cert,
    };
    this.server=https.createServer(_options, function (request,response) {
      if(parseQueryString(request.url).length==0) return response.end('EINVALID'); // accidental access by WEB browser
      // console.log(request.connection.remoteAddress);
      var i,body,
          msg = parseQueryString(request.url),
          remote = {address:request.connection.remoteAddress.replace(/^::ffff:/,'').replace(/^::1/,'localhost'),
                    port:'['+msg.port.replace(/:/g,'')+']' /* unique remote identifier */};

      if (self.verbose>2) 
        console.log(request.method,request.url,msg,addr2url(remote),url2addr(addr2url(remote)));
        
      // consistency check
      if (msg.magic!=magic) return;
      
      self.count.rcv += msg.length;
      msg.type=AMMessageType[msg.type];

      if (msg.secure) msg.secure=Net.port_of_str(msg.secure);
      
      if (debug) console.log(Io.Time(),msg)

      response.origin=request.headers.origin||request.headers.Origin;
      Comp.string.match(request.method,[
          ['GET',function() {
            if (msg.type==AMMessageType.AMMCOLLECT)
              self.collecting(msg,remote,response);
            else
              self.handle(msg,remote,response);
          }],
          ['POST',function() {
            body = '';
            request.on('data', function (chunk) {
              body = body + chunk;
            });
            request.on('end', function () {
              msg.data=Buffer(body,'hex');
              self.count.rcv += msg.data.length;
              if (msg.cmd) msg.cmd=Number(msg.cmd);
              self.handle(msg,remote,response);
            });
          }]
      ])
    });

    this.server.on("connection", function (socket) {
        socket.setNoDelay(true);
    });

    this.server.on("error", function (err) {
      self.out('Warning: receiver failed: '+err,true);
      if (err) self.err(err);
    });

    this.server.listen(rcv.port,function (err) {
      // Try to get network IP address of this host 
      if (!err) getNetworkIP(undefined,function (err,ip) {
        if (!err) self.rcv.address=isLocal(ip)?options.localhost:ip;
        if (self.verbose) self.out('IP port '+addr2url(self.rcv)+ ' (proto '+self.options.proto+')',true);
        if (err) return self.out("! Unable to obtain network connection information: "+err,true);
      });
      if (callback) callback(err);
    });
  }
  if (this.mode & AMMode.AMO_CLIENT) {

    // If this is a hidden node (e.g., inside a WEB browser), we have to connect to a remote public server
    // by using stalled GET requests.
    if (callback) this.callback=callback;
  }
}

/** Reply to a request (msg.tid contains request tid)
 */
amp.http.prototype.reply = function (cmd,msg,snd) {
  this.request(cmd,msg,snd);
}

/** Send a response reply for a pending HTTP GET/PUT request (AMO_SERVER)
 *
 */ 
amp.https.prototype.response = function (response,msg) {
  var data=msg2JSON(msg), header;

  if (response.origin!=undefined)
      header={'Access-Control-Allow-Origin': response.origin,
              'Access-Control-Allow-Credentials': 'true',
              'Content-Type': 'text/plain'};
  else
      header={'Content-Type': 'text/plain'};
  if (this.options.keepalive) header["Connection"]="keep-alive";
  
  response.writeHead(200,header);
  response.write(data);
  if (debug) console.log(Io.Time(),msg)
  response.end();
}

/** Send a request message to a remote node endpoint
 *
 * function (cmd:integer,msg:Buffer,snd:address)
 */

amp.https.prototype.request = function (cmd,msg,snd) {
  var self=this,req={},
      size = msg.data.length,
      tid = msg.tid||Comp.random.int(65536/2);

  if (snd==undefined) this.err('request: snd=null');

  req.type=AMMessageType.AMMRPC;
  req.tid=tid;                   // Transaction Message ID
  req.port=this.port;            // This AMP id
  req.cmd=cmd;
  req.size=size;
  req.magic=magic;
  req.data=msg.data;
  this.send(snd,req);

}


amp.https.prototype.scan=function(snd,response,callback) {
    var self = this,msg={};
    
    msg.type=response?AMMessageType.AMMACK:AMMessageType.AMMSCAN;
    msg.port=this.port;
    msg.magic=magic;

    if (response) msg.info=snd.info;
   
    if (this.verbose>1 && snd) this.debug('amp.scan: to '+addr2url(snd)+' '+(response?'R':''));

    if (response) 
      this.response(response,msg);
    else
      this.send(snd,msg,function (reply) {
        callback(reply)
      });
}

/** Main entry for requests with JSON interface. Multiplexer for HTTP GET/PUT.
 *
 *  msg: JSON 
 *  callback : function (reply:object)
 */
amp.https.prototype.send = function (snd,msg,callback) {
  var path,
      url,
      body,
      self=this;
  // Create query selector
  path = formatQueryString(msg);
    
  if (typeof snd.port == 'string') {
    url=addr2url(snd,true);
    // If Pending get from client
    
    // Else queue message, client will collect them later (or never)
    if (this.links[url]) {
      if (!this.links[url].msgqueue) this.links[url].msgqueue=[];
      if (this.links[url].collecting) {// pending AMMCOLLECT request
        if (this.verbose>1) this.debug('REPLY msg '+AMMessageType.print(msg.type)+' to '+url);
        this.response(this.links[url].collecting,{msg:[msg]});
        this.links[url].collecting=undefined;
      } else {
        if (this.verbose>1) this.debug('QUEUE msg '+AMMessageType.print(msg.type)+' for '+url);
        this.links[url].msgqueue.push(msg);
      }
    }
  } else if (msg.data!=undefined) { 
    // Convert buffer data to hex formatted string
    body=msg.data.toString('hex');
    
    this.put(snd,path,body,function (body) {
      if (is_error(body)) self.emit('error',body);
      else if (!is_status(body)) self.emit('error','EINVALID');
      // No reply expected!
    }); 
  } else {
    this.get(snd,path,function (body) {
      var xml,i,
          reply;
      if (!body || is_error(body)) {
        self.emit('error','EINVALID');
      } else {
        reply=JSON2msg(body);
        // { status:string,reply:*,msg?:{}[],..} 
      }
      if (callback) callback(reply);
    });
  } 
}


// Start AMP watchdog and receiver
amp.https.prototype.start = function(callback) {
  var self=this,
      s=this.secure?' (security port '+Sec.Port.toString(this.secure)+')':'';
  if (this.verbose>0 && this.mode & AMMode.AMO_SERVER) 
    this.out('Starting ' + addr2url(this.rcv)+' ['+AMMode.print(this.mode)+'] (proto '+this.proto+')'+s);
  if (this.verbose>0 && this.mode & AMMode.AMO_CLIENT) 
    this.out('Starting ['+AMMode.print(this.mode)+'] (proto http)');
  this.watchdog(true);
  if (!this.server && this.mode & AMMode.AMO_SERVER) {
    // After stop? Restart receiver.
    this.receiver();
  } 
  if (callback) callback();
}

// Stop AMP
amp.https.prototype.stop = function(callback) {
  if (this.verbose>0 && this.mode & AMMode.AMO_SERVER) 
    this.out('Stopping ' + addr2url(this.rcv)+' ['+AMMode.print(this.mode)+'] (proto '+this.proto+')'+s);
  if (this.verbose>0 && this.mode & AMMode.AMO_CLIENT) 
    this.out('Stopping ['+AMMode.print(this.mode)+'] (proto http)');
  if (this.links) for(var p in this.links) {
    if (this.links[p]) {
      // Try to unlink remote endpoint
      this.unlink(this.links[p].snd);
      this.links[p].state=AMState.AMS_NOTCONNECTED;
      if (this.links[p].collect) clearTimeout(this.links[p].collect),this.links[p].collect=undefined;
    }
  }
  if (this.timer) clearTimeout(this.timer),this.timer=undefined;
  if (this.server) this.server.close(),this.server=undefined;
  
  if (callback) callback();
}

// Unlink remote endpoint
amp.https.prototype.unlink=function(snd) {
  var self = this,msg,
      url = snd?addr2url(snd,true):null;
  if (this.mode&AMMode.AMO_MULTICAST) {
    if (!this.links[url] || this.links[url].state!=AMState.AMS_CONNECTED) return;
  } else {
    if (this.links.state!=AMState.AMS_CONNECTED) return;
  }
  msg={type:AMMessageType.AMMUNLINK,port:this.port,node:this.node?this.node.id:'*',index:this.index++,magic:magic};
    
  this.send(snd,msg,function (reply) {
    // handle reply
    if (reply) {}
  });
  this.emit('route-',addr2url(snd,true));
  if (this.mode&AMMode.AMO_MULTICAST) {
    this.links[url].state=AMState.AMS_NOTCONNECTED;
    if (!this.links[url].snd.connect) this.links[url].snd={};
  } else {
    this.links.state=AMState.AMS_NOTCONNECTED;
    if (!this.links.snd.connect) this.links.snd={};
  }
  this.cleanup(url);
}


/** Install a watchdog timer.
 *
 * 1. If link state is AMS_NOTCONNECTED, retry link request if this.links[].snd is set.
 * 2. If link state is AMS_CONNECTED, check link end point.
 * 3, If link state is AMS_RENDEZVOUS, get remote endpoint connectivity via broker
 *
 * @param run
 */
amp.https.prototype.watchdog = function(run,immedOrDelay) {
    var self=this;
    if (this.timer) clearTimeout(self.timer),this.timer=undefined;
    if (run) self.timer=setTimeout(function () {
        if (!self.timer ||  self.inwatchdog) return; // stopped or busy?
        self.timer = undefined;
        self.inwatchdog=true;
        
        function handle(obj,url) {
          if (self.verbose>1) self.debug('Watchdog: handle link ('+url+') '+
                                        (obj.snd?addr2url(obj.snd):'')+' in state '+
                                        AMState.print(obj.state)+' live '+obj.live);
          switch (obj.state) {
            case AMState.AMS_CONNECTED:
                if (obj.live == 0) {
                    // No PING received, disconnect...
                    if (self.verbose>0) 
                      self.out('Endpoint ' + addr2url(obj.snd) +
                               ' not responding, propably dead. Unlinking...',true);
                    obj.state = AMState.AMS_NOTCONNECTED;
                    self.emit('route-',addr2url(obj.snd,true));
                    self.cleanup(url,obj.snd.connect);
                    if (obj.snd.connect) self.watchdog(true,2000);
                } else {
                    obj.tries=0;
                    obj.live--;
                    self.watchdog(true);
                    if (self.mode&AMMode.AMO_MULTICAST) self.ping(obj.snd);
                    else self.ping();
                }
                break;
            case AMState.AMS_NOTCONNECTED:
            case AMState.AMS_PAIRED:
                if (obj.snd.port && typeof obj.snd.port == 'number') {
                  // Try link to specified remote endpoint obj.snd
                  if (self.verbose>0 && obj.tries==0) 
                    self.out('Trying link to ' + addr2url(obj.snd),true);
                  self.link(obj.snd); 
                  obj.tries++;
                  if (obj.tries < options.TRIES) self.watchdog(true);
                  else {
                    self.out('Giving up to link '+addr2url(obj.snd),true);
                    self.emit('error','link',addr2url(obj.snd));
                    obj.snd={},obj.tries=0;
                  }
                }
                break;
            // AMP P2P Control
            case AMState.AMS_RENDEZVOUS:
                obj.send(
                  {type:'register',name: self.rcv.name, linfo: self.rcv},
                  self.broker,
                  function () {}
                );
                self.watchdog(true);
                break;
            case AMState.AMS_REGISTERED:
                if (obj.tries < options.TRIES && obj.snd.name) {
                  obj.tries++;
                  self.send(
                    {type:'pair', from:self.rcv.name, to: obj.snd.name},
                    self.broker,
                    function () {
                    }
                  );
                }
                if (obj.tries < options.TRIES) self.watchdog(true);
                break;
          }          
        }
        for(var p in self.links) if (self.links[p]) handle(self.links[p],p);
        self.inwatchdog=false;
    },immedOrDelay==true?0:immedOrDelay||options.TIMER);
};
    
};
BundleModuleCode['db/db']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2006-2018 bLAB
 **    $CREATED:     09/02/16 by sbosse.
 **    $VERSION:     1.6.3
 **
 **    $INFO:
 **
 **  JavaScript AIOS: SQL Service API 
 **     
 **   Sqlc: SQLD Database Client Module using named FIFO pipes OR built-in SQL server (see below)
 **
 **     path_in: the write to SQLD channel (SQLD input)
 **     path_out: the read from SQLD channel (SQLD output)
 **
 **
 **   Sqld: SQL Database Server Module (and client interface) 
 **   -------------------------------------------------------
 **
 **     Using built-in sqlite3 module accessing a database file (jx: embedded, node.js: native module)
 **
 **   Example:
 **
 **   db = sql('/var/db/sensors.sql',{mode:'r+'}); // or in memory
 **   db = sql(':memory:',{mode:'r+'})
 **
 **   db.init();
 **   db.createTable('sensors',{name:'',value:0, unit:''});
 **   db.insertTable('sensors',{name:'current',value:1.0, unit:'A'});
 **   db.insertTable('sensors',{name:'voltage',value:12.1, unit:'V'});
 **   db.readTable('sensors',function (res) {
 **     print('callback',res);
 **   });
 **   print(db.readTable('sensors'));
 **   print(db.select('sensors','*'));
 **   print('finished')
 **
 **   Synchronous Version (using blocking IO, readFileSync)
 **
 ** TODO: Merge with dbS!!!!!
 ** Options: Sync/Async/Server ...
 **
 **    $ENDOFINFO
 */
var Io = Require('com/io');
var Comp = Require('com/compat');
var Fs = Require('fs');
var current={};
var Aios=none;
var NL='\n';

var options = {
  version:'1.6.3'
}

function await() {
  if (jxcore) jxcore.utils.jump();
}

function wakeup() {
  if (jxcore) jxcore.utils.continue();
}

function exists(path) {
  try {
    var fd=Fs.openSync(path,'r');
    Fs.close(fd);
    return true;
  } catch (e) { return false }
}

/* Some hacks for wine/win32 node.js/nw.js */
var Buffer = require('buffer').Buffer;

function tryReadSync(fd, buffer, pos, len) {
  var threw = true;
  var bytesRead;
  try {
    bytesRead = Fs.readSync(fd, buffer, pos, len);
    threw = false;
  } finally {
    if (threw) fs.closeSync(fd);
  }
  return bytesRead;
}

function readFileSync(path,encoding) {
  var fd = Fs.openSync(path, 'r', 666),
      bytesRead,buffer,pos=0,
      buffers=[];
  
  do {
    buffer = Buffer(8192);
    bytesRead = tryReadSync(fd, buffer, 0, 8192);
    if (bytesRead !== 0) {
      buffers.push(buffer.slice(0, bytesRead));
    }
    pos += bytesRead;
  } while (bytesRead !== 0);
  
  Fs.closeSync(fd);
  buffer = Buffer.concat(buffers, pos);
  
  if (encoding) buffer = buffer.toString(encoding);
  return buffer;
}

function sleep(time) {
  var stop = new Date().getTime();
  while(new Date().getTime() < stop + time) {
      ;
  }
}

function writeSync(path,str) {
  var n=0;
  var fd = Fs.openSync(path,'r+');
  n=Fs.writeSync(fd, str);
  Fs.closeSync(fd);
  return n;
}

/******************* SQLC ************************/

var sqlc = function (path,chan) {
  this.path=path;
  // Client -> Server
  this.input=none;
  // Server -> Client
  this.output=none;
  this.chan=chan;
  this.id='SQLC';
  this.log=function (msg) {
    ((Aios && Aios.print)||Io.log)('[SQLC] '+msg);
  }
  this.todo=[];
}

/** Return string list of comma seperated value list
 * + Strip string delimeters ''
 *
 */
sqlc.prototype.args = function (msg) {
  var args=Comp.string.split(',',msg);
  return Comp.array.filtermap(args, function (arg) {
    if (arg=='') return none;
    else if (Comp.string.get(arg,0)=="'") return Comp.string.trim(arg,1,1);
    else return arg;
  });
};

/** Create a numeric matrix
 * 
 *  typeof @header = * [] is type interface provider for all rows;
 *
 */

sqlc.prototype.createMatrix = function (matname,header,callback) {
  var repl,
      intf='', line='', sep='', self=this;
  if (!this.connected) return callback?callback(false):false;
  if (header.length==0) return false;
  Comp.array.iter(header,function (col,i) {
    intf += (sep+'c'+(i+1)+(Comp.obj.isNumber(col)?' integer':' varchar(32)')); sep=',';
  });
  sep='';
  function done(_repl) {
    repl=_repl[0];
    if (!repl || self.err(repl)) {current.error=repl;return 0;} 
    else return 1;
  } 
  return this.seq([
    ['exec','drop table if exists '+matname, done],
    ['exec','create table  '+matname+' ('+intf+')', done]
  ],callback);

}

/** Create a generic table
 * 
 *  typeof @header = {} is type and name interface provider for all rows;
 *
 */
sqlc.prototype.createTable = function (tabname,header,callback) {
  var repl,
      intf='', sep='', self=this;
  if (!this.connected) return callback?callback(false):false;
  // if (header.length==0) return false;
  Comp.obj.iter(header,function (attr,key) {
    intf += (sep+key+(Comp.obj.isNumber(attr)?' integer':' varchar(32)')); sep=',';
  });
  function done(_repl) {
    repl=_repl[0];
    if (!repl || self.err(repl)) {current.error=repl;return 0;} 
    else return 1;
  } 
  return this.seq([
    ['exec','drop table if exists '+tabname, done],
    ['exec','create table  '+tabname+' ('+intf+')', done],
  ],callback);  
}


/** Check end message
 *
 */
sqlc.prototype.end = function (msg) { return msg.indexOf('END') == 0 }
/** Check error message
 *
 */
sqlc.prototype.err = function (msg) {
  if (!msg) return false; 
  if (Comp.obj.isObject(msg)) return msg.errno != undefined;
  if (Comp.obj.isString(msg)) return msg.indexOf('ERR') == 0 || msg.indexOf('Error') == 0;
  return false;
}


/** Execute: send a request and wait for reply.
 */
sqlc.prototype.exec = function (cmd,callback) {
  var n,str='',fd;
  if (!this.connected) return callback?callback(none):none;
  n = Fs.writeSync(this.input,cmd+NL);
  if (n<=0) {
    if (callback) { callback(none); return;}
    else return none;
  }
  str = readFileSync(this.output,'utf8');
  if (callback) callback(str?Comp.array.filter(Comp.string.split('\n',str),function(line) {return line!=''}):none);
  else return str?Comp.array.filter(Comp.string.split('\n',str),function(line) {return line!=''}):none;
};

/** GET ROW operation
 * @fmt: {}|none 
 * @callback: function () -> string [] | * [] | none
 *
 */
sqlc.prototype.get = function (fmt,callback) {
  var row,self=this,repl;
  if (!this.connected) return callback?callback(none):none;
  function done(_repl) {
    repl=_repl?_repl[0]:none;
    if (!repl || self.err(repl)) {current.error=repl; row=none; return 0;} 
    else return 1;
  } 
  this.seq([
    ['exec','get row',function (_repl) {
      var cols,i,p;
      if (!row) row=[];
      if (done(_repl) && !self.end(repl)) {
          repl=Comp.string.replace_all('\n','',repl);
          cols=Comp.string.split(',',repl);
          if (fmt) {
            i=0;row=[];
            for(p in fmt) {
              switch (fmt[p]) {
                case 'string':
                  row.push(cols[i]);
                  break;
                case 'number':
                  row.push(Number(cols[i]));
                  break;
                default:
                  row.push(cols[i]);                                    
              }
              i++;
            }
          } else row=cols;
      } 
      return 1;
    }]
  ], callback?function (res) { if (res) callback(row); else callback(none);}:wakeup);
  if (callback) return; else if (row) return row; else { await(); return row }
 
}

sqlc.prototype.getError = function () { var err=current.error; current.error=undefined; return current.error || 'OK'}


/** Setup client-server connection.
 *  Only the input stream is opened (used for sending data to the SQLD server).
 *
 */
sqlc.prototype.init = function (callback) {
  var path_in  = Comp.printf.sprintf("%sI%s%d",this.path,this.chan<10?'0':'',this.chan),
      path_out = Comp.printf.sprintf("%sO%s%d",this.path,this.chan<10?'0':'',this.chan),
      stat,repl,self=this;
  this.id = Comp.printf.sprintf("[SQLC%s%d]",this.chan<10?'0':'',this.chan); 
  this.log (Comp.printf.sprintf("%s Connecting to server channel %s...",
                                this.id,path_in));
  if (!exists(path_in) || !exists(path_out)) {
    this.log (Comp.printf.sprintf("%s Connecting to server channel %s failed: %s",
                                    this.id,path_in,'No such file(s)'));
    if (callback) {callback(false); return;}
    else return false;
  }
  
  try {
    this.input  = Fs.openSync(path_in,'r+');
  } catch (e) {
    this.log (Comp.printf.sprintf("%s Connecting to server channel %s failed: %s",
                                  this.id,path_in,e));
    if (callback) {callback(false); return;} else return false;
  }
  
  // this.input  = path_in;
  this.output = path_out; 
  this.connected = true;
  return this.seq([
      ['set','nl',function (repl) {return (repl && !self.err(repl[0]))}],
      ['set','csv',function (repl) {return (repl && !self.err(repl[0]))}]      
    ], callback);
}

/** Insert operation
 *
 */
sqlc.prototype.insert = function (tbl,row,callback) {
  var repl,
      line='', 
      sep='',
      self=this;
  if (!this.connected) return callback?callback(false):false;
  if (Comp.obj.isArray(row))
    Comp.array.iter(row,function (col,i) {line += sep+(Comp.obj.isNumber(col)?int(col):"'"+col+"'"); sep=',';});
  else if (Comp.obj.isObject(row))
    Comp.obj.iter(row,function (attr,key) {line += sep+(Comp.obj.isNumber(attr)?int(attr):"'"+attr+"'"); sep=',';});
  else 
    throw 'sql.insert: row neither array nor object!';

  function done(_repl) {
    repl=_repl[0];
    if (!repl || self.err(repl)) {current.error=repl;return 0;} 
    else return 1;
  } 
  return this.seq([
    ['exec','insert into  '+tbl+' values ('+line+')', done]
  ], callback);
}

/** Insert a matrix row 
 *
 */
sqlc.prototype.insertMatrix = function (matname,row,callback) {
  var repl,
      line='', 
      sep='',
      self=this;
  if (this.connected) return callback?callback(false):false;
  Comp.array.iter(row,function (col,i) {line += sep+(Comp.obj.isNumber(col)?int(col):"'"+col+"'"); sep=',';});
  function done(_repl) {
    repl=_repl[0];
    if (!repl || self.err(repl)) {current.error=repl;return 0;} 
    else return 1;
  } 
  return this.seq([
    ['exec','insert into  '+matname+' values ('+line+')', done]
  ], callback);
}

sqlc.prototype.insertTable = sqlc.prototype.insert;

/** Read a matrix; return [][]
 *
 */
sqlc.prototype.readMatrix = function (matname,callback) {
  var mat,repl,cols, self=this;
  if (!this.connected) return callback?callback(none):none;
  
  function done(_repl) {
    repl=_repl[0];
    if (!repl || self.err(repl)) {current.error=repl; mat=none; return 0;} 
    else return 1;
  } 
  function doneSelect(_repl,_rows) {
    repl=_repl[0];
    if (_rows) { mat=_rows; return 2 };   // Maybe we got already all rows?
    if (!repl || self.err(repl)) {current.error=repl; tbl=none; return 0;} 
    else return 1;
  } 
  
  this.seq([
    ['exec','select * from '+matname,doneSelect],
    ['exec','get row',function (_repl) {
      if (done(_repl)) {
        if (!mat) mat=[];
        if (rows) { print('done'); mat=rows; return true /* all done */ };
        if (!self.end(repl)) {
          cols=_;
          if (typeof repl == 'string') {
            repl=Comp.string.replace_all('\n','',repl);
            var cols=Comp.array.map(Comp.string.split(',',repl),function (col) {
              return Number(col);
            });
          } else if (Comp.obj.isArray(repl)) {
            // Array
            cols=repl; // !!! 
          }
          mat.push(cols);
          return -1;
        } else return 1;
      } else return 0;
    }]
  ], callback?function (res) { if (res) callback(mat); else callback(none);}:wakeup);
  if (callback) return; else if (mat) return mat; else { await(); return mat };
}


/** Read a generic table; return {}[]
 *
 */
sqlc.prototype.readTable = function (tblname,callback) {
  var tbl,intf=[],repl,cols, self=this;
  if (!this.connected) return callback?callback(none):none;
  
  function done(_repl) {
    repl=_repl[0];
    if (!repl || self.err(repl)) {current.error=repl; tbl=none; return false;} 
    else return true;
  } 
  function doneSelect(_repl,_rows) {
    repl=_repl[0];
    // Maybe we got already all rows?
    if (_rows) { tbl=_rows; return 2}; 
    if (!repl || self.err(repl)) {current.error=repl; tbl=none; return 0;} 
    else return 1;
  } 
  
  this.seq([
    // TODO: Check SQLC API; this is only valid for SQLD!
    ['get',"select * from sqlite_master where type='table' and name='"+tblname+"'",function (_repl) {
      var tokens;
      if (done(_repl)) {
        if (repl.sql) {
          tokens=repl.sql.match(/\((.+)\)/)[1].split(',');
          tokens.forEach(function (token) { 
            var cols=token.split(' ');
            if (cols.length==2) {
              intf.push({tag:cols[0],type:self.sqlType2native(cols[1])});
            } else intf.push(null);
          });
          return !Comp.obj.isEmpty(intf)?1:0;
        }
      } else return 0;
    }],
    ['exec','select * from '+tblname,doneSelect],
    ['exec','get row',function (_repl) {
      var o={};
      if (!tbl) tbl=[];
      if (done(_repl)) {
        if (!self.end(repl)) {
          cols=_;
          if (typeof repl == 'string') {
            repl=Comp.string.replace_all('\n','',repl);
            repl.split(',').forEach(function (e,i) {
              var io=intf[i];
              if (io) o[io.tag]=io.type=='number'?Number(e):e;
            });
          } else if (Comp.obj.isArray(repl)) {
            // Array
            repl.forEach(function (e,i) {
              var io=intf[i];
              if (io) o(io.tag)=io.type=='number'?Number(e):e;
            });
          }
          
          tbl.push(o);
          return -1;
        } else return 1;
      } else return 0;
    }]
  ], callback?function (res) { if (res) callback(tbl); else callback(none);}:wakeup);
  if (callback) return; else if (tbl) return tbl; else { await(); return tbl }
}


/** SELECT operation
 * tbl: string
 * vars?: string | string []
 * cond: string
 * callback: function () -> status string
 *
 */
sqlc.prototype.select = function (tbl,vars,cond,callback) {
  var self=this,repl,stat;
  function done(_repl) {
    repl=_repl?_repl[0]:none;
    if (!repl || self.err(repl)) {current.error=repl; return 0;} 
    else return 1;
  } 
  if (vars==undefined) vars='*';
  stat = this.seq([
    ['exec',Comp.printf.sprintf('select %s from %s%s',
                                Comp.obj.isArray(vars)?Comp.printf.list(vars):vars,
                                tbl,
                                cond?(' '+cond):''),done]
    ],callback?callback:wakeup);
  if (!callback) await();
  return stat;
}

/** Execute a SQL operation sequence 
 ** todo format: [op: string,
 **               args?: string,
 **               result: function returning boolean (false: break (error), true: next, _:loop)]
 ** callback: function () -> status 
 */
sqlc.prototype.seq = function (todo,callback) {
  var l=todo,self=this,status,res,
      next;
  if (callback) { // Async non.block.
    function Todo() {
      if (self.todo.length>0) {
        var f = Comp.array.head(self.todo);
        self.error=undefined;
        f(_,function () {
          self.todo = Comp.array.tail(self.todo);
          Todo();    
        });
      }    
    }
    next=function (loop,finalize) {
      if (l.length==0 && !loop) { callback(status); if (finalize) finalize() }
      else {
        var hd;
        if (!loop) { 
          hd= Comp.array.head(l);
          l = Comp.array.tail(l);
        } else hd=loop;
        switch (hd[0]) {
          case 'set':
            self.set(hd[1],function (repl) {
              status=hd[2](repl); 
              if (status==1) next(_,finalize); else callback(status);
            });
            break;
          case 'exec':
            self.exec(hd[1],function (repl) {
              status=hd[2](repl); 
              if (status==1) next(_,finalize); 
              else if (status==-1) next(hd,finalize); 
              else callback(status);
            });
            break;
        }
      }
    }
    self.todo.push(next);
    if (self.todo.length==1) Todo();
    return;
  } else { // Sync block.
    next=function (loop) {
      if (l.length==0 && !loop) return status;
      else {
        if (!loop) { 
          hd= Comp.array.head(l);
          l = Comp.array.tail(l);
        } else hd=loop;
        switch (hd[0]) {
          case 'set':
            status=self.set(hd[1]);
            if (status==1) next(); else return status;  
            break;
          case 'exec':
            res=self.exec(hd[1]);
            status=hd[2](res);
            if (status==1) next(); else if (status==-1) next(hd); else return status;
            break;
        }
      }
    }
    return next();  
  }
};

/** Set a SQLD flag (nl,csv,..). 
 *
 */
sqlc.prototype.set = function (flag,callback) {
  var n,fd,str='';
  if (!this.connected) return callback?callback(false):false;
  n=Fs.writeSync(this.input, 'set '+flag);
  if (n<=0) {
    if (callback) { callback(none); return;}
    else return none;
  }
  str = readFileSync(this.output,'utf8');
  if (callback) callback(str?Comp.array.filter(Comp.string.split('\n',str),function(line) {return line!=''}):none);
  else return str?Comp.array.filter(Comp.string.split('\n',str),function(line) {return line!=''}):none;
};

sqlc.prototype.sqlType2native = function (str) {
  if (str=='integer') return 'number';
  if (str.indexOf('varchar')==0) return 'string';
} 

/** Write a matrix [][] (create + insert values)
 *
 */
sqlc.prototype.writeMatrix = function (matname,matrix,callback) {
  var repl, line='', self=this,
      intf='', sep='', i=0, row;
  if (!this.connected) return callback?callback(false):false;
  if (matrix.length==0) return false;
  Comp.array.iter(matrix[0],function (col,i) {
    intf += sep+'c'+(i+1)+(Comp.obj.isNumber(col)?' integer':' varchar(32)'); sep=',';
  });
  row=matrix[0];
  Comp.array.iter(row,function (col,i) {
    line += sep+(Comp.obj.isNumber(col)?int(col):"'"+col+"'"); sep=',';
  });
  function done(_repl) {
    repl=_repl[0];
    if (!repl || self.err(repl)) {current.error=repl;return 0;} 
    else return 1;
  } 
  seq=[
      ['exec','drop table if exists '+matname,done],
      ['exec','create table  '+matname+' ('+intf+')',done]
  ];
  for(i=0;i<matrix.length;i++) {
    row=matrix[i];
    line='',sep='';
    Comp.array.iter(row,function (col,i) {
      line += sep+(Comp.obj.isNumber(col)?int(col):"'"+col+"'"); sep=',';
    });
    seq.push(['exec','insert into  '+matname+' values ('+line+')',done]);
  }
  return this.seq(seq,callback);
}

/** Write a table {}[] (create + insert values)
 *
 */
sqlc.prototype.writeTable = function (tblname,tbl,callback) {
  var repl, line='', self=this,
      intf='', sep='', i=0, row;
  if (!this.connected) return callback?callback(false):false;
  if (matrix.length==0) return false;
  Comp.obj.iter(tbl[0],function (attr,key) {
    intf += sep+key+(Comp.obj.isNumber(attr)?' integer':' varchar(32)'); sep=',';
  });
  row=matrix[0];
  Comp.obj.iter(row,function (attr,key) {
    line += sep+(Comp.obj.isNumber(attr)?int(attr):"'"+attr+"'"); sep=',';
  });
  function done(_repl) {
    repl=_repl[0];
    if (!repl || self.err(repl)) {current.error=repl;return 0;} 
    else return 1;
  } 
  seq=[
      ['exec','drop table if exists '+tblname,done],
      ['exec','create table  '+tblname+' ('+intf+')',done]
  ];
  for(i=0;i<matrix.length;i++) {
    row=tbl[i];
    line='',sep='';
    Comp.array.iter(row,function (col,i) {
      line += sep+(Comp.obj.isNumber(col)?int(col):"'"+col+"'"); sep=',';
    });
    seq.push(['exec','insert into  '+tblname+' values ('+line+')',done]);
  }
  return this.seq(seq,callback);
}


sqlc.prototype.setLog = function (f) {
  this.log=f;
}

var Sqlc = function (path,chan) {
  var obj = new sqlc(path,chan);  
  return obj;
};


/******************* SQLD ************************/
var sqld = function (file,options) {
  this.file=file||':memory:';
  this.options=options||{};
  this.id='SQLD';
  this.todo=[];
  
  this.log=function (msg) {
    ((Aios && Aios.print)||Io.log)('[SQLD] '+msg);
  }
  try {
    // Bulit-in SQL3 server module?
    this.sqlite3=require('sqlite3');
  } catch (e) {
    throw e;
  }
  if (this.options.mode == 'r+')
    this.mode = this.sqlite3.OPEN_READWRITE;
  else if (this.options.mode == 'w+')
    this.mode = this.sqlite3.OPEN_READWRITE | this.sqlite3.OPEN_CREATE;
  else 
    this.mode = this.sqlite3.OPEN_READONLY
}

/** Create a generic table
 * 
 *  typeof @header = {} is type and name interface for all rows;
 *
 */
sqld.prototype.createTable = sqlc.prototype.createTable;

/** Check end message
 *
 */
sqld.prototype.end = sqlc.prototype.end;

/** Check error message
 *
 */
sqld.prototype.err = sqlc.prototype.err;

sqld.prototype.getError = sqlc.prototype.getError;

/** Initialize and create DB instance
 *
 */
sqld.prototype.init = function () {
  this.db = new this.sqlite3.Database(this.file,this.mode);
  this.db.serialize();
  this.log('Opened DB '+this.file+' in mode '+(this.mode & this.sqlite3.OPEN_READWRITE?'RW':'R')+
                                              (this.mode & this.sqlite3.OPEN_CREATE?'C':''));
  this.connected = true;
}

sqld.prototype.insertMatrix = sqlc.prototype.insertMatrix;
sqld.prototype.insertTable = sqlc.prototype.insert;

/** Read a matrix; return number [][]
 *
 * method (matname:string,callback:function)
 *
 */
sqld.prototype.readMatrix = sqlc.prototype.readMatrix;
/** Read a generic table; return {}[]
 *
 */

sqld.prototype.readTable = sqlc.prototype.readTable;

/** Execute a SQL operation sequence 
 ** todo format: [op: string,
 **               args?: string,
 **               result: function returning boolean (false: break (error), true: next, _:loop)]
 ** callback: function () -> status 
 */
sqld.prototype.seq = function (todo,callback) {
  var l=todo,self=this,status,res,row,rows,cols,
      next;
  if (callback) { // Async non.block.
    function Todo() {
      if (self.todo.length>0) {
        var f = Comp.array.head(self.todo);
        f(_,function () {
          self.todo = Comp.array.tail(self.todo);
          Todo();    
        });
      }    
    }
    next=function (loop,finalize) {
      var p;
      if (l.length==0 && !loop) { callback(status); if (finalize) finalize()}
      else {
        var hd;
        if (!loop) { 
          hd= l.shift();
        } else hd=loop;
        switch (hd[0]) {
          case 'exec':
            if (hd[1]=='get row') {
              if (rows && rows.length) {
                row=rows.shift();
                cols=[];
                for(p in row) cols.push(row[p]);
                status=hd[2]([cols.join(',')]);
              } else status=hd[2](['END']);
              if (status==1) next(_,finalize); 
              else if (status==-1) next(hd,finalize);
              else callback(status);
            } else if (hd[1].indexOf('select') == 0) {
              self.db.all(hd[1],[],function (repl,_rows) {
                rows=_rows;
                if (repl!=null && !Comp.obj.isArray(repl)) repl=[repl];
                if (repl==null) repl=['OK'];
                status=hd[2](repl,_rows);
                if (status==1) next(_,finalize); 
                else if (status==-1) next(hd,finalize);
                else if (status==2) { l=[]; next(_,finalize) }
                else callback(status);
              });
            
            } else {
              self.db.run(hd[1],[],function (repl) {
                rows=_rows;
                if (repl!=null && !Comp.obj.isArray(repl)) repl=[repl];
                if (repl==null) repl=['OK'];
                status=hd[2](repl); 
                if (status==1) next(_,finalize); 
                else if (status==-1) next(hd,finalize); 
                else if (status==2) { l=[]; next(_,finalize) }
                else callback(status);
              });
            }
            break;
          case 'get':
            self.db.get(hd[1], function (err, table) {
                var repl;
                if (err) repl=[err];
                else repl=[table];
                status=hd[2](repl); 
                if (status==1) next(_,finalize); 
                else if (status==-1) next(hd,finalize); 
                else callback(status);
              
            });
            break;
        }
      }
    }
    self.todo.push(next);
    if (self.todo.length==1) Todo();
    return;
  } else { // Sync block., limited usability
    next=function (loop) {
      if (l.length==0 && !loop) return status;
      else {
        if (!loop) { 
          hd=l.shift();
        } else hd=loop;
        switch (hd[0]) {
          case 'exec':
            // w/o callback we get no status immediately! 
            res='OK';
            self.db.run(hd[1], function (err) {
              if (err) self.log(err)
            });
            status=hd[2](res);
            if (status==1) return next(); 
            else if (status==-1) return next(hd); 
            else if (status==2) { l=[]; next() }
            else return status;
            break;
        }
      }
    }
    return next();  
  }
};

/** SELECT operation returning rows (implicit GET ROW)!!!!
 * tbl: string
 * vars?: string | string []
 * cond: string
 * callback: function () -> status string
 *
 */
sqld.prototype.select = function (tbl,vars,cond,callback) {
  var self=this,repl,stat,rows;
  function done(_repl,_rows) {
    repl=_repl?_repl[0]:none;
    rows=_rows;
    if (!repl || self.err(repl)) {current.error=repl; return 0;} 
    else return 1;
  } 
  if (vars==undefined) vars='*';
  stat = this.seq([
    ['exec',Comp.printf.sprintf('select %s from %s%s',
                                Comp.obj.isArray(vars)?Comp.printf.list(vars):vars,
                                tbl,
                                cond?(' '+cond):''),done]
    ],callback?callback:wakeup);
  if (!callback) await();
  return rows||stat;
}


sqld.prototype.sqlType2native = sqlc.prototype.sqlType2native;

/** Write a matrix [][] (create + insert values)
 *
 */
sqld.prototype.writeMatrix = sqlc.prototype.writeMatrix;

var Sqld = function (file,options) {
  var obj = new sqld(file,options);  
  return obj;
};


module.exports = {
  current:function (module) { current=module.current; Aios=module; },
  Sqlc:Sqlc,
  Sqld:Sqld
};
};
BundleModuleCode['shell/shell']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2006-2022 bLAB
 **    $CREATED:     1-3-18 by sbosse.
 **    $VERSION:     1.36.1
 **
 **    $INFO:
 **
 **  JAM Shell Interpreter (Back end)
 **
 **  Highly customizable command shell interpreter for JAMLIB
 **
 **
 ** typeof @options= {
 **    echo?: boolean,
 **    modules? : {
 **      csp? :     Require('csp/csp'),
 **      csv? :     Require('parser/papaparse')
 **      db?  :
 **      des48? : 
 **      doc? :     Require('doc/doc'),
 **      http? :    Require('http'),
 **      https? :   Require('https'),
 **      httpserv? :   Require('http/https'),
 **      logic?:    Require('logic/prolog'),
 **      ml?:       Require('ml/ml'),
 **      nn?:       Require('nn/nn'),
 **      numerics?:       Require('numerics/numerics'),
 **      sat?:    Require('sat/sat'),
 **      readline? : Require('com/readline'),
 **      readlineSync? : Require('term/readlineSync'),
 **      sip? :     Require('top/rendezvous'),
 **      sql? :     Require('db/db'),
 **    },
 **    nameopts? : {length:8, memorable:true, lowercase:true},
 **    Nameopts? : {length:8, memorable:true, uppercase:true},
 **    output? : function,        // AIOS output (for all, except if defined ..)
 **    outputAgent? : function,   // Agent output
 **    outputAsync? : function,   // AIOS/generic async output
 **    outputPrint? : function,   // jamsh print output
 **    renderer? : renderer,
 **    server? : boolean,
 **  }
 **
 **    $ENDOFINFO
 */

Require('os/polyfill');

var JamLib  = Require('top/jamlib');
var util    = Require('util');
var Comp    = Require('com/compat');
var Io      = Require('com/io');
var Sat     = Require('dos/ext/satelize');
var Cluster = Require('shell/cluster');
var Sec     = Require('jam/security');
var Rpc     = Require('rpc/rpc');
var Esprima = Require("parser/esprima");
var Json    = Require('jam/jsonfn');

var options = {
  verbose : JamLib.environment.verbose||1,
  version : '1.36.1',
}

Cluster.current(JamLib.Aios);

// Utils
if (typeof print == 'undefined') print=console.log;
DIR = JamLib.Aios.DIR;

var NET = Require('jam/ampCOM'),
    url2addr=NET.url2addr,
    addr2url=NET.addr2url;

function format(line) {
  var msg;
  switch (typeof line) {
    case 'boolean':   msg=line.toString(); break;
    case 'string':    msg=line; break;
    case 'number':    msg=line.toString(); break;
    case 'function':  msg=line.toString(); break;
    case 'object':    msg=Io.inspect(line); break;
    default: msg='';
  }
  return msg;
}



/** Shell Interpreter Object
*
*/
function Shell (_options) {
  if (!(this instanceof Shell)) return new Shell(_options);
  this.options=Comp.obj.extend(options,_options);
  this.modules=options.modules||{};
  this.events = {};
  this.env = {};
  this.modules.forEach(function (mod,name) {
    switch (name) {
      case 'des48':
        JamLib.Aios[name]=mod;
        break;
      case 'ml': 
      case 'nn': 
      case 'csp': 
      case 'sat': 
      case 'numerics': 
        mod.current(JamLib.Aios);
        JamLib.Aios[name]=mod.agent;
        JamLib.Aios.aios1[name]=mod.agent;
        JamLib.Aios.aios2[name]=mod.agent;
        JamLib.Aios.aios3[name]=mod.agent;
        break;
      case 'nlp': 
      case 'logic': 
        JamLib.Aios[name]=mod;
        JamLib.Aios.aios1[name]=mod;
        JamLib.Aios.aios2[name]=mod;
        JamLib.Aios.aios3[name]=mod;
        break;      
    }
  })
  if (!this.options.renderer) {
    if (this.modules.doc) this.options.renderer=this.modules.doc.Renderer({lazy:true}); 
    else 
      this.options.renderer = function (text) {
        return text.replace(/\n:/g,'\n  ');
      }
  }
}

Shell.prototype.cmd = function () { return this.env }


Shell.prototype.emit = function (ev,arg1,arg2,arg3,arg4) {
  if (this.events[ev]) this.events[ev](arg1,arg2,arg3,arg4);
}


Shell.prototype.help = function() {
return this.options.renderer([
'# Usage',
' jamsh [-v] [script.js] [-- <script args>]',
'# Shell Commands',
'The following shell commands are avaiable:',
'',
'add({x,y})\n: Add a new logical (virtual) node',
'agent(id,proc?:boolean)\n: Returns the agent object (or process)',
'agents\n: Get all agents (id list) of current node',
'args\n: Script arguments',
'ask(question:string,choices: string [])\n: Ask a question and read answer. Available only in script mode.',
'array(n,init)\n: Creates initialised array',
'assign(src,dst)\n: Copy elements of objects',
'broker(ip)\n: Start a UDP rendezvous broker server',
'Capability\n: Create a security capability object',
'clock(ms)\n: Returns system time (ms or hh:mm:ss format)',
'cluster(desc)\n:Create a worker process cluster',
'config(options)\n: Configure JAM.  Options: _print_, _printAgent_,_TSTMO_',
'configs\n: Get configuration of JAM AIOS',
'connect({x,y},{x,y})\n: Connect two logical nodes (DIR.NORTH...)',
'connect({to:dir)\n: Connect to physical node',
'connected(to:dir)\n: Check connection between two nodes',
'compile(function)\n: Compile an agent class constructor function',
'concat(a,b)->c\n: Concatenate two values',
'contains(a,v)->boolean\n: Check if array or object contains a value or oen in an array of values',
'copy(o)\n: Returns copy of record or array',
'create(ac:string|function,args:*[]|{},level?:number,node?)\n: Create an agent from class @ac with given arguments @args and @level',
'csp?\n: Constraint Solving Programming',
'csv?\n: CSV file reader and writer (read,write)',
'des48?\n: DES48 Encryption',
'disconnect({x,y},{x,y})\n: Disconnect two logical nodes',
'disconnect({to:dir)\n: Diconnect remote endpoint',
'env\n: Shell environment including command line arguments a:v',
'empty(a)->boolean\n: Test empty string, array, or object',
'exec(cnd:string)\n: Execute a jam shell command',
'exit\n: Exit shell',
'extend(level:number|number[],name:string,function,argn?:number|number[])\n: Extend AIOS',
'filter(a,f)->b\n: Filter array or object',
'http.get(url:string,path:string,callback?:function)\n: Serve HTTP get request',
'http.put(url:string,path:string,data,callback?:function)\n: Serve HTTP put request',
'http.GET(url:string,params:{},callback?:function)\n: Serve HTTP JSON get request',
'http.PUT(url:string,params:{},data,data,callback?:function)\n: Serve HTTP JSON put request',
'http.server(ip:string,dir:string,index?:string)\n: Create and start a HTTP file server',
'info(kind:"node"|"version"|"host",id?:string)->info {}\n: Return information (node)', 
'inp(pattern:[],all?:boolean)\n: Read and remove (a) tuple(s) from the tuple space', 
'Json\n:JSON+ serializer',
'kill(id:string|number)\n: Kill an agent (id="*": kill all) or a task (started by later)',
'last(object|array)\n: Return last element of array, string, or object',
'later(ms:number,callback:function(id,counter)->booleabn)\n: Execute a function later. If fucntion returns true, next cycle is started.',
'load(path:string,mimetype?)\n: Load a JSON or CSV file (autodetect) or custom',
'lookup(pattern:string,callback:function (string [])\n: Ask broker for registered nodes',
'locate(callback?:function)\n: Try to estimate node location (geo,IP,..)',
'log(msg)\n: Agent logger function',
'logic??\n: Generic predicate (pro)logic framework module',
'mark(tuple:[],millisec)\n: Store a tuple with timeout in the tuple space', 
'merge\n: Add a column (array) to a matrix (array array)',
'ml?\n: Generic machine Learning framework module',
'name("node"|"world")\n: Return name of current node or wolrd',
'nlp?\n:  Natural language processing framework module',
'node\n: Get or set current vJAM node (default: root) either by index or id name',
'nodes\n: Get all vJAM nodes',
'numerics?\n: Numerics module (fft, vector, matrix, ..)',
'nn?\n: Neural Network framework module',
'neg(v)->v\n: Negate number, array or object of numebrs',
'object(s)\n: Convert string to object',
'ofJSON(s)\n: Convert JSON to object including functions',
'on(event:string,handler:function)\n: Install an event handler. Events: "agent+","agent-","signal+","signal","link+","link-","exit"',
'open(file:string,verbose?:number)\n: Open an agent class file',
'out(tuple:[])\n: Store a tuple in the tuple space', 
'os?\n: OS utils module', 
'pluck(table,column)\n:Extracts a column of a table (array array or object array)',
'port(dir,options:{proto,secure},node)\n: Create a new physical communication port',
'Port\n: Create a security port',
'Private\n: Create a security private object',
'provider(function)\n: Register a tuple provider function',
'random(a,b)\n: Returns random number or element of array/object', 
'rd(pattern:[],all?:boolean)\n: Read (a) tuple(s) from the tuple space', 
'reverse(a)->b\n: Reverse array or string',
'rm(pattern:[],all?:boolean)\n: Remove (a) tuple(s) from the tuple space', 
'sat?\n: Logic (SAT) Solver module',
'save(path:string,data:string,csv?:boolean)\n: Save a JSON or CSV file',
'script(file:string)\n: Load and execute a jam shell script',
'select(arr,a,b)\n: Split matrix (array array) by columns [a,b] or [a]',
'setlog(<flag>,<on>)\n: Enable/disable logging attributes',
'signal(to:aid,sig:string|number,arg?:*)\n: Send a signal to specifid agent',
'sleep(millisec)?\n:Suspend entire shell for seconds',
'start()\n: start JAM',
'stats(kind:"process"|"node"|"vm"|"conn")\n: Return statistics',
'stop()\n: stop JAM',
'sql(filename:string)\n: Open or create an SQL database. A memory DB can be created with _filename_=":memory:". Requires native sqlite3 plug-in.',
'Std.info/status/age/\n: Standard RPC AMP API',
'table?\n: Terminal table formatter',
'test(pattern:[])\n: Test existence of tuple', 
'ts(pattern:[],callback:function(tuple)->tuple)\n: Update a tuple in the space (atomic action) - non-blocking', 
'time()\n: print AIOS time',
'toJSON(o)\n: Convert object including functions to JSON',
'verbose(level:number)\n: Set verbosity level',
'versions()\n: Returns JAM shell and library version',
'uniqid\n: Returns a random name',
'utime()\n: Returns system time in nanoseconds',
'UI?\n: User Interface Toolkit',
'without(a,b)\n: Returns "a" without "b"',
'world\n: Returns world object',
].join('\n'));
}

/* Set-up the Interpreter
*
*/

Shell.prototype.init = function(callback) {
  var self=this;

  if (!this.options.server && this.modules.readline) {
    this.rl = this.modules.readline.createInterface({
      input: process.stdin,
      output: process.stdout,
      completer : function (cmdline) {
          var args = Array.filter(String.split(' ', cmdline), function (str) {
              return str != '';
          });
          var completed=cmdline;
          var choices=[];
          return [choices,completed];
      }
    });

    this.rl.on('line', function (line) {
      self.cmdline = line;
      self.process(line)
      self.rl.prompt();
    });

    this.rl.on('close', function () {
    });
    if (this.modules.doc)
      this.output(this.modules.doc.Colors.bold('JAM Shell. Version '+this.options.version+' (C) Dr. Stefan Bosse'));
    else
      this.output('JAM Shell. Version '+this.options.version+' (C) Dr. Stefan Bosse');    
    this.rl.setPrompt('> ');
    this.rl.prompt();
  } else if (this.options.verbose) this.output('JAM Shell. Version '+this.options.version+' (c) Dr. Stefan Bosse');
  
  
  this.jam=JamLib.Jam({
      print:      this.output.bind(this),
      printAgent: this.outputAgent.bind(this),
      printAsync: this.outputAsync.bind(this),
      nameopts:   this.options.nameopts,
      Nameopts:   this.options.Nameopts,
      verbose:    this.options.verbose,
      type:       this.options.type||'shell'   // tyoe of root node
  });
  
  this.jam.init();
  
  function error(msg) {
    self.output('Error: '+msg);
  }

  self.log=self.output;

  this.tasks = [];
    
  this.env = {
    Aios:   this.jam.Aios,
    DIR:    this.jam.Aios.DIR,
    add:    this.jam.addNode.bind(this.jam),
    agent: function (id,proc) {
      var node = self.jam.world.nodes[self.jam.getCurrentNode()];
      if (node) {
        return proc?node.getAgentProcess(id):node.getAgent(id);
      }
    },
    get agents () {
      var node = self.jam.world.nodes[self.jam.getCurrentNode()];
      if (node) {
        return node.processes.table.map( function (pro) { return pro.agent.id });
      }
    }, 
    angle:  JamLib.Aios.aios0.angle,
    args:   options.args,
    array : JamLib.Aios.aios0.array,
    assign:  JamLib.Aios.aios0.assign,
    broker: function (ip) {
      if (!self.modules.sip) return;
      if (self.broker) self.broker.stop();
      if (!ip) ip='localhost';
      var ipport=10001,tokens = ip.toString().split(':');
      if (tokens.length==2) ip=tokens[0],ipport=Number(tokens[1]);
      self.broker=sip.Broker({ip:{address:ip,port:ipport},log:self.output.bind(self)});
      self.broker.init();
      self.broker.start();
    },
    Capability:     JamLib.Aios.Sec.Capability,
    clock: this.jam.clock.bind(this.jam),
    cluster : function (options) {
      return new Cluster(options,self.env);
    },
    compile: function (constr,name,options) {
      try {
        if (typeof name == 'object') // options?
          return self.jam.compileClass(undefined,constr,name)
        else
          return self.jam.compileClass(name,constr,options||{verbose:self.options.verbose})
      } catch (e) {
        error(e)
      }
    },
    config: function (options) {
      JamLib.Aios.config(options);
    },
    get configs () {
      return JamLib.Aios.configGet();      
    },
    connect:  function (n1,n2) { 
      if (n1 && n2)
        return self.jam.connectNodes([n1,n2]) 
      else
        return self.jam.connectTo(n1)
    },
    connected:   this.jam.connected.bind(this.jam),
    concat: JamLib.Aios.aios0.concat,
    contains: JamLib.Aios.aios0.contains,
    copy:     JamLib.Aios.aios0.copy,
    create: this.jam.createAgent.bind(this.jam),
    csp:     JamLib.Aios.csp,
    get current () { return JamLib.Aios.current },
    delta:  JamLib.Aios.aios0.delta,
    des48: JamLib.Aios.des48,
    disconnect:  function (n1,n2) { 
      if (n1 && n2) {}
         // TODO  
      else
        return self.jam.disconnect(n1)
    },
    distance : JamLib.Aios.aios0.distance,
    env: this.jam.environment,
    empty: JamLib.Aios.aios0.empty,
    extend: this.jam.extend.bind(this.jam),
    exec: this.process.bind(this),
    get exit () {   process.exit() },
    get help () {  return self.help() },
    filter:function (a,f) {
      var res=[],len,len2,i,j,found;
      if (Comp.obj.isArray(a) && Comp.obj.isFunction(f)) {
          res=[];
          len=a.length;
          for(i=0;i<len;i++) {
              var element=a[i];
              if (f(element,i)) res.push(element);
          }
          return res;
      } else if (Comp.obj.isArray(a) && Comp.obj.isArray(f)) {
          res=[];
          len=a.length;
          len2=f.length;
          for(i=0;i<len;i++) {
              var element=a[i];
              found=false;
              for (j=0;j<len2;j++) if(element==f[j]){found=true; break;}
              if (!found) res.push(element);
          }
          return res;      
      } else return undefined;   
    },
    flatten: JamLib.Aios.aios0.flatten,
    geoip : self.modules.geoip,
    Json:Json,
    ignore: function () { }, 
    info: this.jam.info.bind(this.jam),
    inspect: util.inspect,
    inp:    this.jam.inp.bind(this.jam),
    kill:   function (id) { 
      if (typeof id == 'string') self.jam.kill(id);
      else if (typeof id == 'number' && id >= 0) {
        if (self.tasks[id]) clearInterval(self.tasks[id]);
        self.tasks[id]=null;
      } 
    },
    last : JamLib.Aios.aios0.last,
    later:  function (timeout,callback) {
      var counter=0,id=self.tasks.length;
      var timer=setInterval(function () {
        try {
          var res=callback(id,counter);
        } catch (e) {
          error(e);
          res=0;
        }
        counter++;
        if (!res) {
          clearInterval(timer);
          self.tasks[id]=null;
        }
      },timeout)
      self.tasks[id]=timer;
      return id;
    },
    load:   function (file,mimetype) {
      var obj,text = Io.read_file(file);
      if (!text) return;
      if (!mimetype && file.match(/\.js$/)) mimetype='JS';
      if (!mimetype && file.match(/\.json$/)) mimetype='JSON';
      switch (mimetype && mimetype.replace(/application\//,'')) {
        case 'text': 
          return text;
      };
      var scanner = text.replace(/\/\/[^\n]*/g,'');
      if (scanner.match(/^\s*{/)||scanner.match(/^\s*\[\s*{/)||scanner.match(/^\s*\[\s*\[/)||scanner.match(/^\s*\[\s*\{/)) {
        switch (mimetype && mimetype.replace(/application\//,'')) {
          case 'JS': 
          case 'JSOB': 
            eval('"use strict"; obj = '+text);
            break; 
          case 'JSON':
          default:
            obj=self.env.ofJSON(text);
        };
      } else if (self.env.csv && self.env.csv.detect(text)) 
        obj=self.env.csv.read(text,false,true);
      return obj;
    },
    locate :  this.jam.locate.bind(this.jam),
    lookup:   this.jam.lookup.bind(this.jam),
    log:      this.jam.log.bind(this.jam),
    logic:    JamLib.Aios.logic,
    map : function (a,f) {
      var res,i,p;
      if (Comp.obj.isArray(a) && Comp.obj.isFunction(f)) {
        res=[];
        for (i in a) {
          v=f(a[i],i);
          if (v!=undefined) res.push(v);
        }
        return res;
      } else if (Comp.obj.isObject(a) && Comp.obj.isFunction(f)) {
        // Objects can be filtered (on first level), too!
        res={};
        for(p in a) {
          v=f(a[p],p);
          if (v != undefined) res[p]=v;
        }
        return res;
      } else return undefined;   
    },
    mark:    this.jam.mark.bind(this.jam),
    merge : function (a,b) {
      if (Comp.obj.isMatrix(a) && Comp.obj.isArray(b)) {
        a=a.map(function (row,i) { var _row=row.slice(); _row.push(b[i]); return _row })
      }
      return a
    },
    ml:     JamLib.Aios.ml,
    name:   function (of,arg) {
      switch (of) {
        case 'node': return self.jam.getNodeName(arg);
        case 'world': return self.jam.world.id;
      }
    },
    neg: JamLib.Aios.aios0.neg,
    get node ()   { return self.jam.getCurrentNode(true) },
    set node (n)  { return self.jam.setCurrentNode(n) },
    get nodes ()  { return self.jam.world.nodes.map(function (node) { return node.id }) },
    nlp:     JamLib.Aios.nlp,
    nn:     JamLib.Aios.nn,
    numerics:     JamLib.Aios.numerics,
    object: JamLib.Aios.aios0.object,
    ofJSON:  function (s) {
      return self.jam.Aios.Code.Jsonf.parse(s,{})
    },
    on:     function (ev,handler) {
      switch (ev) {
        case 'exit':
          process.on('exit',handler); process.on('SIGINT',function () { process.exit() });
          break;
        default:
          self.jam.on(ev,handler);
      }
    },
    open:   function (file,verbose) { 
      if (verbose==undefined) verbose=1; 
      return self.jam.readClass(file,{verbose:verbose}) },
    os:     self.modules.os,
    out:    this.jam.out.bind(this.jam),
    pluck: function (table,column) {
      var res=[];
      for(var i in table) {
        res.push(table[i][column]);
      }
      return res;
    },
    port:   function (dir,options,node) {
      if (typeof options == 'string') options={proto:options};
      else options=options||{};
      if (options.verbose==undefined) options.verbose=self.options.verbose;
      if (options.multicast == undefined) options.multicast=!options.broker;
      if (options.secure && options.secure.length!=Sec.PORT_SIZE) options.secure=Sec.Port.ofString(options.secure);
      var port=self.jam.createPort(dir,options,node);
      self.emit('port',dir,options);
      return port;
    },
    Port:     JamLib.Aios.Sec.Port,
    print : function () {
      if (arguments.length>1)
        self.outputPrint(Array.prototype.slice.call(arguments).map(Io.inspect).join(' '));
      else
        self.outputPrint(arguments[0])
    },
    Private:  JamLib.Aios.Sec.Private,
    provider: function (provider) {
      self.jam.world.nodes[0].ts.register(provider)
    },
    random: JamLib.Aios.aios.random,
    rd:     this.jam.rd.bind(this.jam),
    reduce: JamLib.Aios.aios0.reduce,
    reverse: JamLib.Aios.aios0.reverse,
    Rights: JamLib.Aios.Sec.Rights,
    rm:     this.jam.rm.bind(this.jam),
    Rpc:    Rpc,
    sat:     JamLib.Aios.sat,
    save : function (file,o,csv) {
      if (csv && self.env.csv) {
        self.env.csv.write(file,o[0],o.slice(1));
      } else Io.write_file(file,self.env.toJSON(o))
    },
    schedule : this.jam.schedule.bind(this.jam),
    script: function (file) {
      var text=Io.read_file(file);
      if (typeof text != 'string') text=text.toString();
      self.process(text);
    },
    select : function (data,a,b) {
      if (b==undefined) {
        return data.map(function(object) {
          return object[a];
        });
      } else {
        return data.map(function(object) {
          return object.slice(a,b+1);
        });      
      }
    },
    setlog: function (attr,on) { self.jam.Aios.config(on?{'log+':attr}:{'log-':attr}) },
    signal: this.jam.signal.bind(this.jam),
    start:  this.jam.start.bind(this.jam),
    start0:  this.jam.start0.bind(this.jam),
    stats:  this.jam.stats.bind(this.jam),
    Std :   JamLib.Aios.Amp.Rpc.Std,
    step:   this.jam.step.bind(this.jam),
    stop:   this.jam.stop.bind(this.jam),
    time:   this.jam.time.bind(this.jam),
    Time:   Io.Time,
    test:   this.jam.test.bind(this.jam),
    toJSON:  function (o) {
      // return self.jam.Aios.Code.minimize(
      return self.jam.Aios.Code.Jsonf.stringify(o)
    },
    ts:     this.jam.ts.bind(this.jam),
    uniqid: function (options) { return self.jam.Aios.aidgen(options) },
    UI:   self.modules.UI,
    url: {
      toAddr:   self.jam.Aios.Amp.url2addr,
      fromAddr: self.jam.Aios.Amp.addr2url,      
    },
    utime : function () {  hr=process.hrtime(); return hr[0]*1E9+hr[1] },
    verbose: function (l) { self.jam.Aios.config({verbose:l}); self.options.verbose=l; },
    versions: function () { return {shell:options.version,lib:JamLib.options.version, aios:JamLib.Aios.options.version} },
    without: JamLib.Aios.aios0.without,
    get world ()  { return self.jam.world },
  }

  if (this.options.extensions) {
    for(var p in this.options.extensions) this.env[p]=this.options.extensions[p];
  }
  if (this.modules.table) this.env.Table = this.modules.table;
  
  // Module dependent commands
  // HTTP
  if (this.modules.http) this.env.http = {
    get: function (url,path,callback) {
      var snd=url2addr(url),
          proto = snd.proto || 'http';
      if (proto == 'https' && !self.modules.https) throw ('http.get: unsupported HTTPS'); 
      if (!snd.port) snd.port=proto=='http'?80:443;
      if (!path) path='';
      else if (path.charAt(0)!='/') path = '/'+path;
      if (!self.modules.http.xhr) {
        req = (proto=='https'?self.modules.https.request:self.modules.http.request)({
          host: snd.address,
          port: snd.port,
          path: path,
          method: 'GET',
          keepAlive: true,
          headers: {
            'User-Agent': 'Mozilla/5.0 (X11; SunOS i86pc; rv:45.0) Gecko/20100101 Firefox/45.0',
          }
        } , function(res) {
          if (res.setEncoding != null) res.setEncoding('utf8');
          var body = '';
          res.on('data', function (chunk) {
            body = body + chunk;
          });
          res.once('end', function () {
            if (callback) callback(body);
          });
        });
        req.once('error', function(err) {
          print('Warning: request to '+addr2url(snd)+' failed: '+err);
          if (callback) callback(null,err);
        });
        req.end();
      } else {
        // XHR Browser
        self.modules.http.request({
          host: snd.address,
          port: snd.port,
          path:path,
          proto: proto,
          method: 'GET',
          keepAlive: true,
          headers: {
          }
        } , function(err,xhr,body) {
          if (err) {
            print('Warning: request to '+addr2url(snd)+' failed: '+err);
            if (callback) return callback(null,err);
          } 
          if (callback) callback(body);
        });    
      }
    },
    GET : function (url,params,callback) {
      var tokens=url.match(/(http[s]*)?([:\/\/]*)?([^\/]+)\/?(.+)?/);
      if (!tokens) throw "http.GET: Invalid URL";
      var proto = tokens[1]||'http',
          ip = tokens[3],
          path = tokens[4]||'',
          sep='';
      if (params) {
        path += '?';
        Object.keys(params).forEach(function (param) {
          path += (sep+param+'='+escape(params[param]));
          sep = '&';
        });
      }
      return self.env.http.get(proto+'://'+ip,path,function (result,err) {
        if (err || Comp.obj.isError(result)) {
          if (callback) callback(err || result);
          return;
        }
        try {
          result=JSON.parse(result);
          callback(result);
        } catch (e) {
          if (e.toString().indexOf('SyntaxError')!=-1 && callback)
             callback(e.toString()+'\n'+result);
          else callback(e);
        }
      });
    },
    put: function (url,path,data,callback) {
      var snd=url2addr(url),
          proto = snd.proto || 'http';
      if (proto == 'https' && !self.modules.https) throw ('http.put: unsupported HTTPS'); 
      if (!snd.port) snd.port=80;
      if (!path) path='';
      else if (path.charAt(0)!='/') path = '/'+path;
      if (!self.modules.http.xhr) {
        req = (proto=='https'?self.modules.https.request:self.modules.http.request)({
          host: snd.address,
          port: snd.port,
          path: path,
          method: 'POST',
          keepAlive: self.env.http.options.keepalive,
          headers: {
              'User-Agent': 'Mozilla/5.0 (X11; SunOS i86pc; rv:45.0) Gecko/20100101 Firefox/45.0',
              'Content-Type': 'application/x-www-form-urlencoded',
              'Content-Length': data.length
          }
        } , function(res) {
          if (res.setEncoding != null) res.setEncoding('utf8');
          var body = '';
          res.on('data', function (chunk) {
            body = body + chunk;
          });
          res.once('end', function () {
            if (callback) callback(body);
          });
        });
        req.once('error', function(err) {
          print('Warning: request to '+addr2url(snd)+' failed: '+err);
          if (callback) callback(err);
        });

        // write data to request body
        req.write(data);
        req.end();
      } else {
        // XHR Browser
        self.modules.http.request({
          host: snd.address,
          port: snd.port,
          path: path,
          method: 'POST',
          body:data,
          keepAlive: self.env.http.options.keepalive,
          headers: {
              'Content-Type': 'application/x-www-form-urlencoded',
              'Content-Length': data.length
          }
        } , function(err,xhr,body) {
          if (err) {
            print('Warning: request to '+addr2url(snd)+' failed: '+err);
            if (callback) callback(err);
            return;
          }
          if (callback) callback(body);
        })
      }
    },
    POST : function (url,data,callback,params) {
      var tokens=url.match(/(http[s]*)?([:\/\/]*)?([^\/]+)\/?(.+)?/);
      if (!tokens) throw "http.GET: Invalid URL";
      var proto = tokens[1]||'http',
          ip = tokens[3],
          path = tokens[4]||'',
          sep='';
      if (params) {
        path += '?';
        Object.keys(params).forEach(function (param) {
          path += (sep+param+'='+escape(params[param]));
          sep = '&';
        });
      }
      try {
        data=JSON.stringify(data);
      } catch (e) {
        if (callback) callback(e);
        return;     
      }
      // console.log(ip,path,data)
      return self.env.http.put(proto+'://'+ip,path,data,function (result,err) {
        if (err || Comp.obj.isError(result)) {
          if (callback) callback(err || result);
          return;
        }
        try {
          result=JSON.parse(result);
          if (callback) callback(result);
        } catch (e) {
          if (e.toString().indexOf('SyntaxError')!=-1 && callback)
             callback(e.toString()+'\n'+result);
          else if (callback) callback(e);
        }
      });
    },
    options : {
      keepalive:true,
    }
  }
  if (this.modules.httpserv) {
    if (!this.env.http) this.env.http = {};
    this.env.http.server =  function (ip,dir,index) {
      if (!self.modules.httpserv) return;
      if (!dir) return;
      if (ip==undefined) ip="localhost:8080";
      if (self.httpSrv) self.httpSrv.stop();
      var ipport=8080,tokens = ip.toString().split(':');
      if (tokens.length==2) ip=tokens[0],ipport=Number(tokens[1]);
      else if (typeof ip == 'number') ipport=ip;
      
      self.httpSrv=self.modules.httpserv.HTTPSrv({ip:ip,ipport:ipport,dir:dir,index:index,log:self.output.bind(self)});
      self.httpSrv.init();
      self.httpSrv.start();
    }

    if (self.modules.https) {
      if (!this.env.https) this.env.https = {};
      this.env.https.server =  function (ip,dir,index) {
        if (!self.modules.httpserv) return;
        if (!dir) return;
        if (ip==undefined) ip="localhost:8080";
        if (self.httpSrv) self.httpSrv.stop();
        var ipport=8080,tokens = ip.toString().split(':');
        if (tokens.length==2) ip=tokens[0],ipport=Number(tokens[1]);
        else if (typeof ip == 'number') ipport=ip;
        
        self.httpSrv=self.modules.httpserv.HTTPSrv({ip:ip,ipport:ipport,proto:'https',dir:dir,index:index,log:self.output.bind(self)});
        self.httpSrv.init();
        self.httpSrv.start();
      }
    }
  }
  if (this.modules.nlp)  this.env.nlp = this.modules.nlp;
  if (this.modules.sql)  this.env.sql =  function (filename,options) {
      return self.modules.sql.Sqld(filename,options);
  }
  if (this.modules.csv)  {
    this.env.csv =  {
      detect : function (text) {
        return self.modules.csv.detect(text);
      },
      read: function (file,convert,isString) {
        var data,text;      
        if (self.options.verbose) self.log('CSV: Reading from '+(isString?'string':file));
        try {
          text=isString?file:Io.read_file(file);
          if (!text) throw 'CSV File read error: '+file;          
          if (self.options.verbose) self.log('CSV: Parsing '+(isString?'string':file));
          self.modules.csv.parse(text,{
            skipEmptyLines: true,
            dynamicTyping: true,
            complete: function(results) {
              if (self.options.verbose) 
                self.log('CSV parsed with DEL="'+results.meta.delimiter+
                         '" TRUNC='+results.meta.truncated+
                         ' ABORT='+results.meta.aborted);
              data=results.data;
              if (convert) { // first line must be header
                header=data.shift();
                data=data.map(function (row) {
                  var r={};
                  header.forEach(function (col,i) { r[col]=row[i] });
                  return r; 
                }) 
              }
            }
          });
          if (data && data[0].length==1) data=data.map(function (row) { return row[0] });
          return data;
        } catch (e) {
          return e;
        }
      },
      write: function (file,header,data,sep) {
        var d1=false,fd,i,convert=!Comp.obj.isArray(data[0])&&Comp.obj.isObj(data[0]);
        if (!sep) sep=',';
        d1 = typeof data[0] != 'object';
        if (!header || header.length==0) {
          if (!convert)
            header=d1?['0']:data[0].map(function (x,i) { return String(i) });
          else {
            header=[];
            for (var p in data[0]) {
              header.push(p);
            }
          }
        }
        try {
          if (self.options.verbose) self.log('CSV: Wrting to '+file);
          fd=Io.open(file,'w+');
          Io.write_line(fd,header.join(sep));
          if (!d1) 
            for(i in data) {
              if (!convert)
                Io.write_line(fd,data[i].join(sep));
              else
                Io.write_line(fd,header.map(function (col) { return data[i][col]}).join(sep));
            }
          else
            for(i in data) {
              if (!convert)
                Io.write_line(fd,data[i]);
              else
                Io.write_line(fd,data[i][header[0]]);
            };
            
          Io.close(fd);
          return data.length
        } catch (e) {
          return e;
        }
      }
    }
  }
  if (!this.rl && this.modules.readlineSync) {
    // we can implement ask
    this.env.ask = function (msg,choices) {
      var answer;
      while (choices.indexOf(answer)==-1)
        answer = self.modules.readlineSync.question(msg+'? ['+choices.join(',')+'] ');
      return answer;
    }
  }
  if (this.options.script)  this.env.script(this.options.script);
  if (this.options.exec)    this.process(this.options.exec);
  
  return this;
}


Shell.prototype.on = function (event,handler) {
  var self=this;
  if (this.events[event]) {
    // Implement callback function chain
    var funorig=events[event];
    this.events[event]=function () {
      funorig.apply(this,arguments);
      handler.apply(this,arguments);    
    };
  } else {
    this.events[event]=handler;
    this.jam.Aios.on(event,function (arg1,arg2,arg3,arg4) { self.emit(event,arg1,arg2,arg3,arg4)});
  }
}

// Generic output
Shell.prototype.output = function (line) {
  var msg=format(line);
  if (this.options.output && msg.length) this.options.output(msg);
  if (this.rl && msg.length) this.rl.insertOutput(msg);
  if (msg.length) this.emit('output',msg);
}

// Agent output
Shell.prototype.outputAgent = function (line) {
  var msg=format(line);
  if (this.options.outputAgent && msg.length) this.options.outputAgent(msg);
  else if (this.options.output && msg.length) this.options.output(msg);
  if (this.rl && msg.length) this.rl.insertOutput(msg);
  if (msg.length) this.emit('output',msg);
}

// Shell output
Shell.prototype.outputPrint = function (line) {
  var msg=format(line);
  if (this.options.outputPrint && msg.length) this.options.outputPrint(msg);
  else if (this.options.output && msg.length) this.options.output(msg);
  if (this.rl && msg.length) this.rl.insertOutput(msg);
  if (msg.length) this.emit('output',msg);
}

// Async AIOS/generic output (from callbacks)
Shell.prototype.outputAsync = function (line) {
  var msg=format(line);
  if (this.options.outputAsync && msg.length) this.options.outputAsync(msg);
  else if (this.options.output && msg.length) this.options.output(msg);
  if (this.rl && msg.length) this.rl.insertOutput(msg);
  if (msg.length) this.emit('output',msg);
}

Shell.prototype.process = function (line) {
  var self=this;
  with(this.env) {
    try { 
      if (line.match(/;[ \n]*$/))
        eval(line);
      else
        self.output(eval(line)); 
    } catch (e) {
      var more='';
      if (e.name==="SyntaxError"||e.name==="TypeError") {
        try {
          var ast = Esprima.parse(line, { tolerant: true, loc:true });
          if (ast.errors && ast.errors.length>0) more = ", "+ast.errors[0];
        } catch (_e) {
          e=_e;
        }
        self.output(e.toString()+more)
      } else if (e.stack) {
        var line = e.stack.toString().match(/<anonymous>:([0-9]+):([0-9]+)\)/)
        self.output(e.toString()+(line?', at line '+line[1]:''));
      } else {
        self.output(e.toString())
      }
      if (self.options.verbose>1) self.output(Io.sprintstack(e)); }
  }
}


module.exports = Shell;

};
BundleModuleCode['top/jamlib']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2006-2022 bLAB
 **    $CREATED:     25-12-16 by sbosse.
 **    $RCS:         $Id: jamlib.js,v 1.5 2020/02/03 09:45:01 sbosse Exp sbosse $
 **    $VERSION:     1.35.1
 **
 **    $INFO:
 **
 **  JAM library API that can be embedded in any host application.
 **
 **
 ** New: Embedded auto setup (e.g., for clusters) using command line arguments
 ** 
 **      jamlib autosetup:"{options}"
 **
 **
 **    $ENDOFINFO
 */
var onexit=false;
var start=false;
var options = {
  geo:undefined,
  verbose:0,
  version:'1.35.1'  // public version
};

global.config={simulation:false,nonetwork:false};

var Io = Require('com/io');
var Comp = Require('com/compat');
var Aios = Require('jam/aios');
var Esprima = Require('parser/esprima');
var Json = Require('jam/jsonfn');
var fs = Require('fs');
var Sat = Require('dos/ext/satelize');
var GPS5 = Require('geoip/gps5');
var GeoLoc5 = Require('geoip/geoloc5');
var CBL = Require('com/cbl');
var platform = Require('os/platform');

var DIR = Aios.DIR;

// Parse command line arguments; extract a:v attributes
var environment = process.env; process.argv.slice(2).forEach(function (arg) { 
  var tokens=arg.match(/([a-zA-Z]+):(['"0-9a-zA-Z_:\->\.\{\},;]+)/);
  if (tokens && tokens.length==3) environment[tokens[1]]=tokens[2];
});

function locationEvalError(e) {
  return (e.lineNumber?(' at line '+e.lineNumber+
                       (e.columnNumber?(' column '+e.columnNumber):'')):'')
}

if (typeof setImmediate == 'undefined') {
  function setImmediate(callback) {return setTimeout(callback,0)};
}

// Extend DIR with IP capabilities of NORTH, ..
DIR.North= function (ip) { return {tag:DIR.NORTH,ip:ip}}
DIR.South= function (ip) { return {tag:DIR.SOUTH,ip:ip}}
DIR.West = function (ip) { return {tag:DIR.WEST ,ip:ip}}
DIR.East = function (ip) { return {tag:DIR.EAST ,ip:ip}}
DIR.Up   = function (ip) { return {tag:DIR.UP ,ip:ip}}
DIR.Down = function (ip) { return {tag:DIR.DOWN ,ip:ip}}

/**
 *  typeof options = { 
 *                     connections?, 
 *                     print? is agent and control message output function,
 *                     printAgent? is agent message only output function,
 *                     printAsync? async (callback) output function,
 *                     fork?,
 *                     provider?, consumer?, 
 *                     classes?, 
 *                     id?:string is JAM and JAM root node id,
 *                     world?:string is JAM world id,
 *                     position?:{x,y}, 
 *                     cluster?:boolean|[] is an attached cluster node,
 *                     nowatch:boolean is a disable flag for agent watchdog checking,
 *                     checkpoint:boolean is a flag forcing code checkpointing (even if watchdog is available),
 *                     nolimits:boolean is a disable flag for agent resource monitoring,
 *                     log?:{class?:boolean,node?,agent?,parent?,host?,time?,Time?,pid?},
 *                     logJam?:{host?,time?,pid?,node?,world?},
 *                     scheduler?:scheduler is an external scheduler, singlestep?,
 *                     network?:{cluster?,rows,columns,connect?:function},
 *                     verbose?, TMO? }
 *  with typeof connections = { 
 *    @kind : {from:string,to?:string,proto:string='udp'|'tcp'|'http'|'stream',num?:number,on?,range?:number[]},
 *    @kind : {send:function, status:function, register?:function(@link)} , 
 *    @kind : .. }
 *  with @kind = {north,south,west,east,ip, ..}
 *
 * Connecting JAM nodes (IP)
 * -------------------------
 *   
 *  .. Jam({
 *    connections: {
 *      // Generic, P2PN
 *      ip?: {from:string,to?:string,proto:string='udp'|'tcp'|'http',num?:number} // AMP link (UDP) or set of AMP links (num>1)
 *      // Assigned to a logical direction, P2P
 *      north?: {                                                             
 *        from:string,to?:string,proto?='udp'|'tcp'|'http'|'device',device?:string // device is a hardware P2P stream device 
 *      }, ..
 *
 * Integration of host program streams
 * ------------------------------------
 *
 *  var chan = Some Stream Channel Object;
 *  
 *  .. Jam({
 *    connections: {
 *      north?: {
 *        register: function (link) {
 *          // register channel data handler with link handler 
 *          chan.on('data',function (data) {
 *            // process raw data, extract msg={agent:string,to?,from?,..} or {signal:string,to?,from?,..}
 *            if (msg.agent) link.emit('agent',msg.agent);
 *            if (msg.signal) link.emit('signal',msg.signal);
 *          });
 *        }
 *        send: function (msg) {
 *          chan.send(msg);
 *        },
 *        status: function (to) {
 *          return true;
 *        }
 *      }
 *    }, ..
 *  } 
 *  
 * Cluster
 * --------
 *
 * A forked cluster consists of a master node (0) and up to 8 child ndoes connected around the root node
 * by streams in directions {E,S,SE,W,SW,N,NW,NE}. Each node is executed physically in a different JAM process. 
 * Ex. network: {cluster:true, rows:2, columns:2},
 *
 */
 
var jam = function (options) {
  var self=this,
      p,conn,node;
  this.options = options||{};
  this.environment=environment;
  if (this.setup)           this.setup(); // overwrite options
  if (this.options.world && !this.options.id) this.options.id=this.options.world;
  if (!this.options.id)     this.options.id=Aios.aidgen();
  if (!this.options.log)    this.options.log={};
  if (!this.options.logJam) this.options.logJam={pid:false,host:false,time:false};
  this.verbose =  this.options.verbose || 0;
  this.Aios =     Aios;
  this.DIR =      Aios.aios.DIR;

  Aios.options.verbose=this.verbose;
  if (options.scheduler) Aios.current.scheduler=scheduler;
  if (options.nolimits||options.nowatch||options.checkpoint) 
    Aios.config({nolimits:options.nolimits,nowatch:options.nowatch,checkpoint:options.checkpoint});

  // out=function (msg) { Io.print('[JAM '+self.options.id+'] '+msg)};
  if (this.options.print)  Aios.print=Aios.printAgent=this.options.print;
  if (this.options.print2) Aios.printAgent=this.options.print2;
  if (this.options.printAgent) Aios.printAgent=this.options.printAgent;
  if (this.options.printAsync) Aios.printAsync=this.options.printAsync;
  
  // JAM messages
  this.log=function (msg) { 
    var s='[JAM',sep=' ';
    if (self.options.logJam.pid && process) s += (' '+process.pid),sep=':';
    if (self.options.logJam.world && Aios.current.world) s += (sep+Aios.current.world.id),sep=':';
    if (self.options.logJam.node && Aios.current.node) s += (sep+Aios.current.node.id),sep=':';
    if (self.options.logJam.time) s += (sep+Aios.time());
    Aios.print(s+'] '+msg);
  };
  
  
  this.err=function (msg,err) {
    self.log('Error: '+msg);
    throw (err||'JAMLIB');
  }
  this.warn=function (msg) {
    self.log('Warning: '+msg);
  }
  
  this.error=undefined;
  
  // Create a world
  this.world = Aios.World.World([],{
    id:this.options.world||this.options.id.toUpperCase(),
    classes:options.classes||[],
    scheduler:options.scheduler,
    verbose:options.verbose
  });
  if (this.verbose) this.log('Created world '+this.world.id+'.');
  
  this.node=none;
  this.run=false;
  
  
  // Service loop executing the AIOS scheduler
  // NOT USED if there is an external scheduler supplied (world will create JAM scheduling loop)
  

  this.ticks=0;       // schedule loop execution counter!
  this.steps=0;       // Number of schedule loop execution steps
  this.loop=none;     // Schedule loop function
  this.looping=none;  // Current schedule loop run (or none); can be waiting for a timeout
      
  Aios.config({fastcopy:this.options.fastcopy,
               verbose:this.options.verbose});
  
  if (this.options.log) 
    for(p in this.options.log) Aios.config(this.options.log[p]?{"log+":p}:{"log-":p});

  this.process = Aios.Proc.Proc();
  this.process.agent={id:'jamlib'};
    
  this.events={};
}

// Import analyzer class...
var JamAnal = Require('jam/analyzer');
JamAnal.current(Aios);
jam.prototype.analyzeSyntax=JamAnal.jamc.prototype.analyze;
jam.prototype.syntax=JamAnal.jamc.prototype.syntax;



/** Add agent class to the JAM world and create sandboxed constructors.
 *  type constructor = function|string
 */
jam.prototype.addClass = function (name,constructor,env) {
  this.world.addClass(name,constructor,env);
  if (this.verbose) this.log('Agent class '+name+' added to world library.');
};

/** Add a new node to the world.
 *  Assumption: 2d meshgrid network with (x,y) coordinates.
 *  The root node has position {x=0,y=0}.
 *  type of nodeDesc = {x:number,y:number,id?}
 *
 */
jam.prototype.addNode = function (nodeDesc) {
  var node,x,y;
  x=nodeDesc.x;
  y=nodeDesc.y;
  if (Comp.array.find(this.world.nodes,function (node) {
    return node.position.x==x && node.position.y==y;
  })) {
    this.err('addNodes: Node at positition ('+x+','+y+') exists already.');
    return;
  }
  node=Aios.Node.Node({id:nodeDesc.id||Aios.aidgen(),position:{x:x,y:y}},true);
  if (this.verbose) this.log('Created node '+node.id+' ('+x+','+y+').');
  // Add node to world
  this.world.addNode(node);    
  return node.id;
}

/** Add logical nodes.
 *  The root node has position {x=0,y=0}.
 *  type of nodes = [{x:number,y:number,id?},..]
 */
jam.prototype.addNodes = function (nodes) {  
  var n,node,x,y,nodeids=[];
  for(n in nodes) {
    nodeids.push(this.addNode(nodes[n]));
  }
  return nodeids;
}

/** Analyze agent class template in text or object form
 ** typeof @options = {..,classname?:string}
 *  Returns {report:string,interface}
 */
jam.prototype.analyze = function (ac,options) {
  var source,name,syntax,content,report,interface;
  if (Comp.obj.isString(ac)) {
    // TODO
  } else if (Comp.obj.isObject(ac)) {
    // TODO
  } else if (Comp.obj.isFunction(ac)) {
    source = ac.toString();
    if (!options.classname) { 
      name=source.match(/^ *function *([^\s\(]*)\(/);
      if (name && name[1]!='') options.classname=name[1];
    }
    content = 'var ac ='+source;
    syntax = Esprima.parse(content, { tolerant: true, loc:true });
    try {
      interface=this.analyzeSyntax(syntax,{
        classname:options.classname||'anonymous',
        level:options.level==undefined?2:options.level,
        verbose:options.verbose,
        err:function (msg){throw msg},
        out:function (msg){if (!report) report=msg; else report=report+'\n'+msg;},
        warn:function (msg){if (!report) report=msg; else report=report+'\n'+msg;}
      });
      return {report:report||'OK',interface:interface};
    } catch (e) {
      return {report:e,interface:interface};
    }
  }
}
jam.prototype.clock = Aios.clock;

/** Compile (analyze) an agent class constructor function and add it to the world class library.
 ** Can be used after an open statement.
 ** Usage: compileClass(name,constructor,options?)
 **        compileClass(constructor,options?)
 **
 **  typeof @name=string|undefined
 **  typeof @constructor=function|string
 **  typeof @options={verbose:number|boolean)|verbose:number|undefined
*/ 
jam.prototype.compileClass = function (name,constructor,options) {
  var ac,p,verbose,content,syntax,report,text,env={ac:undefined},self=this,ac;

  if (typeof name == 'function') constructor=name,name=undefined,options=constructor;
  if (typeof options == 'object') verbose=options.verbose||0; 
  else if (options!=undefined) verbose=options; else verbose=this.verbose;
  // if (typeof constructor != 'function') throw 'compileClass: second constructor argument not a function';

  if (typeof constructor == 'function') text = constructor.toString();
  else text = constructor;
  
  if (!name) {
    // try to find name in function definition
    name=text.match(/[\s]*function[\s]*([A-Za-z0-9_]+)[\s]*\(/);
    if (!name) throw ('compileClass: No class name provided and no name found in constructor '+
                      text.substring(0,80));
    name=name[1];
    
  }
  content = 'var ac = '+text;
  try { syntax = Esprima.parse(content, { tolerant: true, loc:true }) }
  catch (e) { throw 'compileClass('+name+'): Parsing failed with '+e }
  report = this.analyzeSyntax(syntax,
    {
      classname:name,
      level:2,
      verbose:verbose||0,
      err:  function (msg){self.log(msg)},
      out:  function (msg){self.log(msg)},
      warn: function (msg){self.log(msg)}
    });
  if (report.errors.length) { throw 'compileClass('+name+'): failed with '+report.errors.join('; ')};
  for (p in report.activities) env[p]=p;
  try { with (env) { eval(content) } }
  catch (e) { throw ('compileClass('+name+'): failed with '+e+locationEvalError(e)) };
  ac=env.ac; env.ac=undefined;
  this.addClass(name,ac,env);
  return name;
}

/** Connect logical nodes (virtual link).
 *  The root node has position {x=0,y=0}.
 *  type of links = [{x1:number,y1:number,x2:number,x2:number},..]|[{x,y},{x,y}]
 */
jam.prototype.connectNodes = function (connections) {  
  var c,node1,node2,x1,y1,x2,y2,dir;
  if (connections[0].x != undefined && connections[0].y != undefined) {
    if (connections.length!=2) throw 'INVALID'; // invalid
    // simple style
    connections=[{x1:connections[0].x,x2:connections[1].x,
                  y1:connections[0].y,y2:connections[1].y}];
  }
  for(c in connections) {
    x1=connections[c].x1;
    y1=connections[c].y1;
    x2=connections[c].x2;
    y2=connections[c].y2;
    if (this.verbose) this.log('Connecting ('+x1+','+y1+') -> ('+x2+','+y2+')');
    node1=Comp.array.find(this.world.nodes,function (node) {
      return node.position.x==x1 && node.position.y==y1;
    });
    node2=Comp.array.find(this.world.nodes,function (node) {
      return node.position.x==x2 && node.position.y==y2;
    });
    if (!node1) this.err('connectNodes: Node at positition ('+x1+','+y1+') does not exist.');
    if (!node2) this.err('connectNodes: Node at positition ('+x2+','+y2+') does not exist.');
    if ((x2-x1)==0) {
      if ((y2-y1) > 0) dir=Aios.DIR.SOUTH;
      else dir=Aios.DIR.NORTH;
    } else if ((x2-x1)>0) dir=Aios.DIR.EAST;
    else dir=Aios.DIR.WEST;
    this.world.connect(dir,node1,node2);
    this.world.connect(Aios.DIR.opposite(dir),node2,node1);
  }
}

/** Dynamically connect remote endpoint at run-time
  * typeof @to = string <dir->url>|<url>
  */
jam.prototype.connectTo = function (to,nodeid) {
  var node=this.getNode(nodeid),
      tokens=(typeof to=='string')?to.split('->'):null,
      dir;
  // console.log(tokens)
  if (!node) return;
  if (to.tag) dir=to;
  else if (tokens.length==2) {
    dir=Aios.DIR.from(tokens[0]);
    if (dir) dir.ip=tokens[1];
  } else dir={tag:'DIR.IP',ip:to};
  if (dir) this.world.connectTo(dir,node);
}

/** Check connection status of a link
 *
 */
jam.prototype.connected = function (dir,nodeid) {
  var node=this.getNode(nodeid);
  if (!node) return;
  return this.world.connected(dir,node);
}

/** Create and start an agent from class ac with arguments. 
 *  Ac is either already loaded (i.e., ac specifies the class name) or 
 *  AC is supplied as a constructor function (ac), a class name, or a sandboxed constructor
 *  {fun:function,mask:{}} object for a specific level.
 *
 *  type of ac = string|object|function
 *  type of args = * []
 *  level = {0,1,2,3}
 *
 */
jam.prototype.createAgent = function (ac,args,level,className,parent) {
  var node=this.world.nodes[this.node],
      process=none,sac;
  if (level==undefined) level=Aios.options.LEVEL;
  if (!className && typeof ac == 'string') className=ac;
  if (!className && typeof ac == 'function') className=Aios.Code.className(ac);
  if (Comp.obj.isFunction(ac) || Comp.obj.isObject(ac)) {
    // Create an agent process from a constructor function or sandboxed constructor object
    process = Aios.Code.createOn(node,ac,args,level,className);
    if (process && !process.agent.parent) process.agent.parent=parent;
    if (process) return process.agent.id;   
  } else {
    // It is a class name. Find an already sandboxed constructor from world classes pool
    if (this.world.classes[ac])
      process = Aios.Code.createOn(node,this.world.classes[ac][level],args,level,className);
    else {
      this.error='createAgent: Cannot find agent class '+ac;
      this.log(this.error);
      return;
    }
    if (process) {
      if (!process.agent.parent) process.agent.parent=parent;
      process.agent.ac=ac;
      return process.agent.id; 
    } else return none;
  }
}

/** Create agent on specified (logical or physical) node.
 *  typeof node = number|string|{x,y}
 */
jam.prototype.createAgentOn = function (node,ac,args,level,className,parent) {
  var res,_currentNode=this.node,found=this.getNode(node);

  if (found) {
    this.setCurrentNode();
    res=this.createAgent(ac,args,level,className,parent);
    this.setCurrentNode(_currentNode);
  }
  return res;
}

/** Create a physical communication port
 *
 */
jam.prototype.createPort = function (dir,options,nodeid) {
  if (!options) options={};
  var multicast=options.multicast;
  switch (dir.tag) { 
    case Aios.DIR.NORTH: 
    case Aios.DIR.SOUTH: 
    case Aios.DIR.WEST: 
    case Aios.DIR.EAST: 
    case Aios.DIR.UP: 
    case Aios.DIR.DOWN: 
      multicast=false;
      break;
  }
  if (dir.ip && typeof dir.ip == 'string' && dir.ip.indexOf('//')>0) {
        // extract proto from url
        var addr = Aios.Amp.url2addr(dir.ip);
        if (!options.proto && addr.proto) options.proto=addr.proto;
        dir.ip=Aios.Amp.addr2url(addr);
  }
  if (options.from==undefined && dir.ip) options.from=dir.ip.toString();
  var  chan=this.world.connectPhy(
            dir,
            this.getNode(nodeid),
            {
              broker  :   options.broker,
              keepAlive : options.keepAlive,
              multicast : multicast,
              name  :     options.name,
              on    :     options.on,
              oneway  :   options.oneway,
              pem    :    options.pem,
              proto :     options.proto||'udp',
              rcv   :     options.from,
              secure :    options.secure,
              sharedSocket:options.sharedSocket,
              snd   :     options.to,
              verbose:(options.verbose!=undefined?options.verbose:this.verbose)
            });
  chan.init();
  chan.start();
  return chan;
}
/** Dynamically disconnect remote endpoint at run-time
 *
 */
jam.prototype.disconnect = function (to,nodeid) {
  var node=this.getNode(nodeid);
  if (node) {
    this.world.disconnect(to,node);
  }
}

/** Emit an event
 ** function emit(@event,@arg1,..)
 */
jam.prototype.emit = function () {
  Aios.emit.apply(this,arguments);
}


/** Execute an agent snapshot on current node delivered in JSON+ text format or read from a file. 
 */
jam.prototype.execute = function (data,file) {
  if (!data && file && fs) 
    try {
      data=fs.readFileSync(file,'utf8');
    } catch (e) {
      this.log('Error: Reading file '+file+' failed: '+e);
      return undefined;
    }
  if (data) return this.world.nodes[this.node].receive(data,true);
}

/** Execute an agent snapshot on node @node delivered in JSON+ text format or read from a file.
 */
jam.prototype.executeOn = function (data,node,file) {
  node=this.getNode(node);
  if (!node) return;
  if (!data && file && fs) 
    try {
      data=fs.readFileSync(file,'utf8');
    } catch (e) {
      this.log('Error: Reading file '+file+' failed: '+e);
      return undefined;
    }
  if (data) return node.receive(data,true);
}

/** Extend AIOS of specific privilege level. The added functions can be accessed by agents.
 *
 * function extend(level:number [],name:string,func:function,argn?:number|number []);
 */
jam.prototype.extend = function (level,name,funcOrObj,argn) {
  var self=this;
  if (Comp.obj.isArray(level)) {
    Comp.array.iter(level,function (l) {self.extend(l,name,funcOrObj,argn)});
    return;
  }
  function range(n) {
    var l=[];
    for(var i=0;i<n+1;i++) l.push(i);
    return l;
  }
  switch (level) {
    case 0: 
      if (Aios.aios0[name]) throw Error('JAM: Cannot extend AIOS(0) with '+name+', existst already!');
      Aios.aios0[name]=funcOrObj; break;
    case 1: 
      if (Aios.aios1[name]) throw Error('JAM: Cannot extend AIOS(1) with '+name+', existst already!');
      Aios.aios1[name]=funcOrObj; break;
    case 2: 
      if (Aios.aios2[name]) throw Error('JAM: Cannot extend AIOS(2) with '+name+', existst already!');
      Aios.aios2[name]=funcOrObj; break;
    case 3: 
      if (Aios.aios3[name]) throw Error('JAM: Cannot extend AIOS(3) with '+name+', existst already!');
      Aios.aios3[name]=funcOrObj; break;
    default:
      throw Error('JAM: Extend: Invalid privilige level argument ([0,1,2,3])');
  }
  if (!JamAnal.corefuncs[name]) {
    if (typeof funcOrObj == 'function')
      JamAnal.corefuncs[name]={argn:Comp.obj.isArray(argn)?argn:argn!=undefined?range(argn):range(funcOrObj.length)}; 
    else {
      // extend an object (may not be nested to get the type signature)
      var obj = {};
      Object.keys(funcOrObj).forEach(function (attr) {
        obj[attr]={argn:range(funcOrObj[attr].length)}; 
      });
      JamAnal.corefuncs[name]={obj:obj}; 
    }
  }
}

jam.prototype.getCurrentNode=function (asname) {
  if (!asname) return this.node;
  else return this.world.nodes[this.node].id;
}

/** Return node object referenced by logical node number, position, or name
 *  If @id is undefined return current node object.
 */
jam.prototype.getNode = function (id) {
  var node;
  if (id==undefined) return this.world.nodes[this.node];
  if (typeof id == 'number') 
    node=this.world.nodes[id];
  else if (typeof id == 'string') {
    // Search node identifier or position;
    loop: for(var i in this.world.nodes) {
      if (this.world.nodes[i] && this.world.nodes[i].id==id) {
        node = this.world.nodes[i];
        break loop;
      } 
    }
  } else if (id.x != undefined && 
             id.y != undefined) {
    // Search node position;
    loop: for(var i in this.world.nodes) {
      if (this.world.nodes[i] && Comp.obj.equal(this.world.nodes[i].position,id)) {
        node = this.world.nodes[i];
        break loop;
      } 
    }
  }
  
  return node;
} 

/** Return node name from logical node number or position
 *
 */
jam.prototype.getNodeName = function (nodeNumberorPosition) {
  var node=this.getNode(nodeNumberorPosition);  
  if (node) return node.id;
} 

/** Get current agent process or search for agent process
 *
 */
jam.prototype.getProcess = function (agent) {
  if (!agent)
    return Aios.current.process;
  else {
    var node = this.getNode();  // current node
    if (node) return node.getAgentProcess(agent);
  }
}


/** Return node name from logical node number or position
 *
 */
jam.prototype.getWorldName = function () {
  return this.world.id;
} 

/** Get info about node, agents, plattform
 *
 */
jam.prototype.info = function (kind,id) {
  switch (kind) {
    case 'node':
      var node=this.getNode(id);
      if (!node) return;
      return { 
        id:node.id, 
        position: node.position, 
        location:node.location,
        type:node.type 
      }
      break;
    case 'agent':
      var agent = this.getProcess(id);
      if (!agent) return;
      var code = Aios.Code.print(agent.agent,true);
      return {
        id:id,
        pid:agent.pid,
        level:agent.level,
        blocked:agent.blocked,
        suspended:agent.suspended,
        resources:agent.resources,
        code:code
      }
      break;
    case 'agent-data':
      var agent = this.getProcess(id);
      if (!agent) return;
      else return agent.agent;
      break;
    case 'version': return Aios.options.version;
    case 'host': return { 
      type:global.TARGET,
      watchdog:Aios.watchdog?true:false,
      protect: Aios.watchdog&&Aios.watchdog.protect?true:false,
      jsonify:Aios.options.json,
      minify:!Aios.Code.options.compactit,
    };
    case 'platform': return platform;     
  }
}

/** INITIALIZE
 *  1. Create and initialize node(s)/world
 *  2. Add optional TS provider/consumer
 *  3. Create physical network conenctions
 */
jam.prototype.init = function (callback) {
  var i=0,j=0, n, p, id, node, connect=[], chan, dir, dirs, pos,
      self=this;
  
  // Current node == root node
  this.node=0;

  ///////////// CREATE NODES /////////
  if (!this.options.network) {
    if (this.options.position) i=this.options.position.x,j=this.options.position.y;
    // Create one (root) node if not already existing
    if (!this.getNode({x:i,y:j})) {
      node = Aios.Node.Node({
          id:this.options.id,
          position:{x:i,y:j},
          TMO:this.options.TMO,
          type:this.options.type
        },true);
      // Add node to world
      if (this.verbose) this.log('Created '+(i==0&&j==0?'root ':'')+'node '+node.id+' ('+i+','+j+').');
      this.world.addNode(node);
    }
    // Register jamlib event handler for the root node
    this.register(node);
  } else if (!this.options.network.cluster) {
    // Create a virtual network of logical nodes. Default: grid
    if (this.options.network.rows && this.options.network.columns) {
      for(j=0;j<this.options.network.rows;j++) 
        for(i=0;i<this.options.network.columns;i++) {
          node = Aios.Node.Node({id:Aios.aidgen(),position:{x:i,y:j},TMO:this.options.TMO},true);
          if (this.verbose) this.log('Created node '+node.id+' at ('+i+','+j+').');
          if (i==0&&j==0) {
            // Register jamlib event handler for the root node
            this.register(node);
          }
          this.world.addNode(node);
        }
      // Connect nodes with virtual links
      for(j=0;j<this.options.network.rows;j++) 
        for(i=0;i<this.options.network.columns;i++) {
          if (i+1<this.options.network.columns)  connect.push({x1:i,y1:j,x2:i+1,y2:j});
          if (j+1<this.options.network.rows)  connect.push({x1:i,y1:j,x2:i,y2:j+1});
        }
      if (this.options.network.connect) connect=connect.filter(this.options.network.connect);
      this.connectNodes(connect);
    }
  } else if (this.options.network.cluster && this.options.fork) {
      // Physical network cluster; each node is executed in a process on this host
      dirs=[DIR.ORIGIN,DIR.EAST,DIR.SOUTH,DIR.SE,DIR.WEST,DIR.SW,DIR.NORTH,DIR.NW,DIR.NE];
      pos={x:[0,1,0,1,-1,-1,0,-1,1],
           y:[0,0,1,1,0,1,-1,-1,-1]};
      // Create a physical network of nodes. Here create only the root node (0,0)
      this.cluster=[]; this.master=true;
      for(j=0;j<this.options.network.rows;j++) 
        for(i=0;i<this.options.network.columns;i++) {
          id=Aios.aidgen();
          if (i==0 && j==0) {
            dir=undefined;
            node = Aios.Node.Node({id:id,position:{x:i,y:j},TMO:this.options.TMO},true);
            if (this.verbose) this.log('Created root node '+node.id+' at ('+i+','+j+').');
            // Register jamlib event handler for the root node
            this.register(node);
            this.world.addNode(node); 
            this.setCurrentNode(id);
          } else {
            n=i+j*this.options.network.columns;
            dir=dirs[n];
            if (this.verbose) this.log('Started cluster node '+id+' at ('+i+','+j+'). with link '+DIR.print(dir));
            this.cluster[id]=this.options.fork(process.argv[1],['autosetup:'+JSON.stringify({
              id:id,
              world:this.world.id,
              cluster:true,
              network:null,
              position:{x:pos.x[n],y:pos.y[n]},
              dir:dir,
              connections:{
                stream:{
                  dir:DIR.opposite(dir)
                }
              }
            })]);
            this.cluster[id].dir=dir;
            // Clustered forked nodes communicate via process.send, receive message via process.on('message') handler
          }
        }
      // Create physical stream links to all child nodes
      for(p in this.cluster) {
        chan=this.world.connectPhy(
            this.cluster[p].dir,
            this.getNode(),
            {
              proto:'stream',
              sock:this.cluster[p],
              mode:'object',
              verbose:this.verbose
            });
        chan.init();                
      }
  }

  //////////// Install host platform tuple provider and consumer //////////
  
  /*
  ** Each time a tuple of a specific dimension is requested by an agent (rd) 
  ** the provider function can return (provide) a mathcing tuple (returning the tuple).
  ** IO gate between agents/JAM and host application.
  */
  if (this.options.provider) this.world.nodes[this.node].ts.register(function (pat) {
    // Caching?
    return self.options.provider(pat);
  });

  /*
  ** Each time a tuple of a specific dimension is stored by an agent (out) 
  ** the consumer function can return consume the tuple (returning true).
  ** IO gate between agents/JAM and host application.
  */
  if (this.options.consumer) this.world.nodes[this.node].ts.register(function (tuple) {
    // Caching?
    return self.options.consumer(tuple);
  },true);
  
  ///////////// CREATE NETWORK CONNECTIVITY /////////

  // Register host application connections {send,status,count,register?} using host app. streams or
  // create physical conenction ports (using the AMP P2P protocol over IP/RS232) {from:*,proto:'udp'|..}
  if (this.options.connections) {
    for (p in this.options.connections) {
      conn=this.options.connections[p];
      if (!conn) continue;
      
      if (p=='ip' || conn.proto) {
        // 1. IP
        // Attach AMP port to root node, actually not linked with endpoint
        n=1;
        switch (p) {
          case 'ip': 
            dir=this.DIR.IP(this.options.connections.ip.from||'*');
                                                        // actually not linked with endpoint
            n = (conn.range && conn.range.length==2 && (conn.range[1]-conn.range[0]+1))||
                conn.num||
                1; // multiple interface are allowed
            break;
          case 'north': dir=this.DIR.NORTH; break;
          case 'south': dir=this.DIR.SOUTH; break;
          case 'west': dir=this.DIR.WEST; break;
          case 'east': dir=this.DIR.EAST; break;
          case 'up': dir=this.DIR.UP; break;
          case 'down': dir=this.DIR.DOWN; break;
        }
        function makeAddr(ip,i) {
          if (!conn.range) return ip;
          else return ip+':'+(conn.range[0]+i);
        }
        for(i=0;i<n;i++) {
          chan=this.world.connectPhy(
            dir,
            this.getNode(),
            {
              broker:conn.broker,
              multicast:conn.multicast,
              name:conn.name,
              on:conn.on,
              oneway:conn.oneway,
              proto:conn.proto||'udp',
              rcv:makeAddr(conn.from,i),
              snd:conn.to,
              verbose:this.verbose
            });
          chan.init();
        }
      } else if (conn.send) {
        // 2. Host stream interface
        node=this.world.nodes[this.node]; // TODO: connections.node -> world node#
        function makeconn (p,conn) {
          var link = { 
            _handler:[],
            emit: function (event,msg) {
              if (link._handler[event]) link._handler[event](msg);
            },
            on: function (event,callback) {
              link._handler[event]=callback;
            },
            send: function (data,dest,context) {
              var res;
              self.world.nodes[self.node].connections[p]._count += data.length;
              res=conn.send(data,dest);
              if (!res) {
                context.error='Migration to destination '+dest+' failed';
                // We're still in the agent process context! Throw an error for this agent ..
                throw 'MOVE';              
              };

              // kill ghost agent
              context.process.finalize();
            },
            status : conn.status?conn.status:(function () {return true}),
            count: conn.count?conn.count:function () {return link._count},
            _count:0
          };
          if (conn.register) conn.register(link);
          return link;       
        }
        node.connections[p] = makeconn(p,conn);
        // register agent receiver and signal handler
        node.connections[p].on('agent',node.receive.bind(node));
        node.connections[p].on('signal',node.handle.bind(node));
      } else if (p=='stream') {
        // 3. Physical process stream interface (cluster); child->parent proecss connection
        chan=this.world.connectPhy(
            conn.dir,
            this.getNode(),
            {
              proto:'stream',
              sock:process,
              mode:'object',
              verbose:this.verbose
            });
        chan.init();
      }    
    } 
  }
  if (callback) callback();

}


/** Tuple space input operation - non blocking, i.e., equiv. to inp(pat,_,0)
 */
jam.prototype.inp = function (pat,all) {
  return this.world.nodes[this.node].ts.extern.inp(pat,all);
}


/** Kill agent with specified id ('*': kill all agents on node or current node)
 */
jam.prototype.kill = function (id,node) {
  if (id=='*') {
    this.world.nodes[this.node].processes.table.forEach(function (p) {
      if (p) Aios.kill(p.agent.id);
    });
  } else
    return Aios.kill(id);
}

/** Try to locate this node (based on network connectivity)
 *  Any geospatial information is attached to current (node=undefined) or specific node
 */
 
jam.prototype.locate = function (nodeid,cb,options) {
  if (typeof nodeid == 'function') { options=cb;cb=nodeid;nodeid=0};
  if (typeof nodeid == 'object') { options=nodeid;cb=null;nodeid=0};
  var node=this.getNode(nodeid);
  if (!node) return;
  return GeoLoc5.locate(function (location,errors) {
    node.location=node.location||{};
    Object.assign(node.location,location);
    if (cb) cb(location,errors);
  },options);
}
/** Lookup nodes and get connection info (more general as connected and broker support)
 *
 */
jam.prototype.lookup = function (dir,callback,nodeid) {
  var node=this.getNode(nodeid);
  if (!node) return;
  return this.world.lookup(dir,callback,node);
}

/** Tuple space output operation with timeout 
 */
jam.prototype.mark = function (tuple,tmo) {
  return this.world.nodes[this.node].ts.extern.mark(tuple,tmo);
}


/** Execute an agent snapshot in JSON+ text form after migration provided from host application
 */
jam.prototype.migrate = function (data) {
  return this.world.nodes[this.node].receive(data,false);
}

/** Install event handler
*
*   typeof @event = {'agent','agent+','agent-','signal+','signal','link+','link-',..}
*   agent+/agent-: Agent creation and destruction event
*   agent: Agent receive event
*   signal+: Signal raise event
*   signal: Signal receive (handle) event
*   route+: A new link was established
*   route-: A link is broken
*/

jam.prototype.on = function (event,handler) {
  Aios.on(event,handler);
}

/** Remove event handler
 */
jam.prototype.off = function (ev) {
  Aios.off(event); 
}



/** Read and parse one agent class from file. Can contain nested open statements.
 *  Browser (no fs module): @file parameter contains source text.
 *  File/source text format: function [ac] (p1,p2,..) { this.x; .. ; this.act = {..}; ..}
 *  open(file:string,options?:{verbose?:number|boolean,classname?:string}) -> function | object
 *  
 *  Output can be processed by method compileClass
 */
jam.prototype.open = function (file,options) {
  var self=this,
      res,
      text,
      name,
      ast=null;
  if (!options) options={};
  name=options.classname||'<unknown>';
  if (options.verbose>0) this.log('Reading agent class template '+name+' from '+file);
  
  function parseModel (text) {
    var modu={},more,module={exports:{}},name=text.match(/[\s]*function[\s]*([a-z0-9]+)[\s]*\(/);
    if (name) name=name[1];
    function open(filename) {
      var text;
      try {
        text=fs?fs.readFileSync(filename,'utf8'):null;
        return parseModel(text);
      } catch (e) {
        self.log('Error: Opening of '+(fs?file:'text')+' failed: '+e); 
      }
    }
    try {
      with (module) {eval('res = '+text)};
      if (name) { modu[name]=res; return modu} 
      else if (module.exports) return module.exports; 
      else return res;
    } catch (e) {
      try {
        ast = Esprima.parse(text, { tolerant: true, loc:true });
        if (ast.errors && ast.errors.length>0) more = ', '+ast.errors[0];
      } catch (e) {
        if (e.lineNumber) more = ', in line '+e.lineNumber; 
      } 
      self.log(e.name+(e.message?': '+e.message:'')+(more?more:''));
    }
  }
  try {
    text=fs?fs.readFileSync(file,'utf8'):file;    // Browser: file parameter contains already source text
    return parseModel(text);
  } catch (e) {
    this.log('Error: Opening of '+(fs?file:'text')+' failed: '+e); 
  }  
};

/** Tuple space output operation 
 */
jam.prototype.out = function (tuple) {
  return this.world.nodes[this.node].ts.extern.out(tuple);
}

/** Tuple space read operation - non blocking, i.e., equiv. to rd(pat,_,0)
 */
jam.prototype.rd = function (pat,all) {
  return this.world.nodes[this.node].ts.extern.rd(pat,all);
}

/** 1. Read agent template classes from file and compile (analyze) agent constructor functions.
 *     Expected file format: module.exports = { ac1: function (p1,p2,..) {}, ac2:.. }
 *  2. Read single agent constructor function from file
 *
 * typeof @options={verbose,error:function}
 */
// TODO: clean up, split fs interface, no require caching ..
if (fs) jam.prototype.readClass = function (file,options) {
  var self=this,
      ac,
      name,
      env,
      interface,
      text,
      modu,
      path,
      p,m,
      regex1,
      ast=null,
      fileText=null,
      off=null;
  this.error=_;
  function errLoc(ast) {
    var err;
    if (ast && ast.errors && ast.errors.length) {
      err=ast.errors[0];
      if (err.lineNumber != undefined) return 'line '+err.lineNumber;
    }
    return 'unknown'
  }
  try {
    if (!options) options={};
    if (options.verbose>0) this.log('Looking up agent class template(s) from '+file);
    //modu=Require(file);
    if (Comp.obj.isEmpty(modu)) {
      if (options.verbose>0) this.log('Reading agent class template(s) from file '+file);
      if (Comp.string.get(file,0)!='/') 
        path = (process.cwd?process.cwd()+'/':'./')+file;
      else
        path = file;
      fileText=fs.readFileSync(path,'utf8');
      ast=Esprima.parse(fileText, { tolerant: true, loc:true });
      if (require.cache) delete require.cache[file]; // force reload of file by require
      modu=require(path);
      if(Comp.obj.isEmpty(modu)) {
        modu={};
        // Try evaluation of fileText containing one single function definition
        if (!fileText) throw 'No such file!';
        name=fileText.match(/[\s]*function[\s]*([a-z0-9]+)[\s]*\(/);
        if (!name) throw ('Export interface of module is empty and file contains no valid function definition!');
        name=name[1];
        eval('(function () {'+fileText+' modu["'+name+'"]='+name+'})()');        
      }
    }
    if (!modu || Comp.obj.isEmpty(modu)) throw 'Empty module.';
    
    for (m in modu) {
      ac=modu[m];
      env={};

      if (fileText)       off=this.syntax.find(fileText,'VariableDeclarator',m);
      if (off && off.loc) this.syntax.offset=off.loc.start.line-1;

      content = 'var ac = '+ac;
      syntax = Esprima.parse(content, { tolerant: true, loc:true });
      interface = this.analyzeSyntax(syntax,
        {
          classname:m,
          level:2,
          verbose:  options.verbose||0,
          err:      options.error||function (msg){throw(msg)},
          out:      function (msg){self.log(msg)},
          warn:     function (msg){self.log(msg)}
        });
      // text=Json.stringify(ac);
      for (var p in interface.activities) env[p]=p;
      with (env) { eval(content) };

      if (options.verbose>0) this.log('Adding agent class constructor '+m+' ('+(typeof ac)+').');
      this.addClass(m,ac,env);
      this.syntax.offset=0;
    }
    this.error=undefined;
    return true;
  } catch (e) {
    this.error='Compiling agent class file "'+file+'" failed: '+e+
               (ast && ast.errors.length?', in '+errLoc(ast):'');
    if (options.error) 
      options.error(e+(ast && ast.errors.length?', in '+errLoc(ast):''));
    else {
      this.log(this.error);
    }
    return false;
  }
};

/** Register jamlib event handler for the (root) node
*/
jam.prototype.register = function (node) {
  this.on('agent', function (msg) { node.receive(msg) });
  this.on('signal', function (msg) { node.handle(msg) });
}

/** Disconnect and remove a virtual node from the world
 *
 */
jam.prototype.removeNode = function (nodeid) {
  this.world.removeNode(nodeid);  
}

/** Tuple space remove operation 
 */
jam.prototype.rm = function (pat,all) {
  return this.world.nodes[this.node].ts.extern.rm(pat,all);
}


/** Take an agent process snapshot executed currently on given node @node:number|string|undefined.
 *  If @file:string is not specified, a string containing the snapshot is
 *  returned, otehrwise it is saved to the file (text format. JSON+).
 *  If @node is undefined, the current node is used.
 *  If @kill is set, the agent is killed after taken the snapshot.
 */
jam.prototype.saveSnapshotOn = function (aid,node,file,kill) {
  var snapshot,pro;
  node=this.getNode(node);
  if (!node) return;
  // Look-up agent process ..
  pro=node.getAgentProcess(aid);
  if (!pro) return;
  // Take snapshot od the process ..
  snapshot=Aios.Code.ofCode(pro,false);
  if (kill) Aios.killOn(aid,node);
  // Save it ..
  if (!file) return snapshot;
  else if (fs) return fs.writeFileSync(file, snapshot, 'utf8');
}

jam.prototype.saveSnapshot = function (aid,file,kill) {
  return this.saveSnapshotOn(aid,_,file,kill);
}

/** Force a scheduler run immediately normally executed by the
 *  jam service loop. Required if there were externeal agent 
 *  management, e.g., by sending signals.
 */
jam.prototype.schedule = function () {
  if (this.loop) {
    clearTimeout(this.loop);
    setImmediate(this.looping);
  } else if (!this.run) setImmediate(this.looping);
}


/** Access to JAM security module
 *
 */
jam.prototype.security = Aios.Sec;

/** Set current node (by index number or node name)
 *
 */
jam.prototype.setCurrentNode=function (n) {
  if (typeof n == 'number') {
    if (n>=0 && n < this.world.nodes.length) this.node=n;
  } else if (typeof n == 'string') {
    this.node=this.world.nodes.indexOf(this.world.getNode(n))
  }
  current.node=this.world.nodes[this.node];
}

/** Send a signal to a specific agent 'to'.
 *
 */
jam.prototype.signal=function (to,sig,arg,broadcast) {
  var node=this.getNode(),
      _process=Aios.current.process;
  Aios.current.process=this.process;
  if (!broadcast)
    Aios.aios.send(to,sig,arg);
  else  
    Aios.aios.broadcast(to,sig,arg);    
    
  Aios.current.process=_process;
  this.schedule();
}


/** Set-up connections, start the JAM, but not the scheduler (used in single-step mode)
 *
 */
jam.prototype.start0=function (callback) {
  if (this.run) return;
  var self=this,cbl=CBL(callback);
  // Start all connections if not already done
  
  this.world.nodes.forEach(function (node) {
    node.connections.forEach(function (chan,kind) {
      if (!chan) return;
      if (chan.start) cbl.push(function (next) {chan.start(next)});
    });
  });
  cbl.start();
  
  Aios.on('schedule',function () {
    self.schedule();
  });

  this.world.start();
  if (this.verbose) this.log('Starting JAM .. ');
  return;
}

/** Set-up connections,  start the JAM scheduler
 *
 */
jam.prototype.start=function (callback) {
  if (this.run) return;
  var self=this,
      current=Aios.current,
      cbl=CBL(callback);
  // Start all connections if not already done
  
  this.world.nodes.forEach(function (node) {
    node.connections.forEach(function (chan,kind) {
      if (!chan) return;
      if (chan.start) cbl.push(function (next) {chan.start(next)});
    });
  });
  cbl.start();
  
  Aios.on('schedule',function () {
    self.schedule();
  });

  function loop() {
    var loop = function () {
      var nexttime,curtime;
      if (self.verbose>3) self.log('loop: Entering scheduler #'+self.ticks);
      self.ticks++;

      nexttime=Aios.scheduler();
      curtime=Aios.time();
      if (self.verbose>3) self.log('loop: Scheduler returned nexttime='+nexttime+
                                           ' ('+(nexttime>0?nexttime-curtime:0)+')');
      if (!self.run) return;
      if (nexttime>0) 
        self.loop=setTimeout(loop,nexttime-curtime);
      else if (nexttime==0) 
        self.loop=setTimeout(loop,1000);
      else setImmediate(loop);
      // else setTimeout(loop,10);
    };
    self.loop = setTimeout(loop,1);
  };
  this.looping=loop;
  
  Aios.config({iterations:100});

  this.run=true;
  this.world.start();
  if (this.verbose) this.log('Starting JAM loop .. ');
  if (!this.options.scheduler) loop(); // Start internal scheduling loop
}

/** Get agent process table info and other statistics
 *
 *  type kind = 'process'|'agent'|'node'|'vm'|'conn'
 */
 
 
jam.prototype.stats = function (kind,id) {
  var p,n,sys,conn,pro,agent,state,stats,allstats={},signals,node;
  switch (kind) {
    case 'process':      
    case 'agent':      
      for(n in this.world.nodes) {        
        stats={};
        node=this.world.nodes[n];
        for (p in node.processes.table) {
          if (node.processes.table[p]) {
            pro=node.processes.table[p];
            if (pro.signals.length == 0) signals=[];
            else signals = pro.signals.map(function (sig)  {return sig[0] });
            agent=pro.agent;
            if (pro.suspended) state='SUSPENDED';
            else if (pro.blocked) state='BLOCKED';
            else if (pro.dead) state='DEAD';
            else if (pro.kill) state='KILL';
            else if (pro.move) state='MOVE';
            else state='READY';
            stats[agent.id]={
              pid:pro.pid,
              gid:pro.gid,
              state:state,
              parent:pro.agent.parent,
              class:pro.agent.ac,
              next:agent.next,
              resources:Comp.obj.copy(pro.resources)
            };
            if (signals.length) stats[agent.id].signals=signals;
          }
        }
        allstats[node.id]=stats;
      }
    break;
    case 'node':
      return Comp.obj.copy(this.getNode(id).stats);
    break;
    case 'conn':
      for(n in this.world.nodes) {        
        stats={};
        node=this.world.nodes[n];
        for (p in node.connections) {
          conn=node.connections[p];
          if (conn) {
            stats[p]={count:conn.count(),conn:conn.status('%')};
          }
        }
        allstats[node.id]=stats;
      }
    break;
    case 'vm':
      // Return VM memory usage in kB units and VM system information
      if (process && process.memoryUsage) {
        sys=process.memoryUsage();
        for ( p in sys) sys[p] = (sys[p]/1024)|0;
        sys.v8 = process.versions && process.versions.v8;
        sys.node = process.versions && process.versions.node;
        sys.arch = process.arch;
        sys.platform = process.platform;
        sys.watchdog = Aios.watchdog?(Aios.watchdog.checkPoint?'semi':'full'):'none'; 
        return sys;
      }
    break;
  }
  if (this.world.nodes.length==1) return stats;
  else return allstats;
}

/** Stepping the scheduler loop 
 */
jam.prototype.step = function (steps,callback) {
  // TODO: accurate timing
  var self=this,
      sync=callback===true,
      milliTime=function () {return Math.ceil(Date.now())},
      current=Aios.current,
      curtime=Aios.time(),// Aios.time();
      lasttime=curtime;

      
  function loop () {
    var loop = function () {
      var nexttime,curtime;
      if (self.verbose>1) self.log('loop: Entering scheduler #'+self.ticks);
      self.ticks++,self.steps--;
      self.time=curtime=Aios.time();

      // Execute scheduler loop
      nexttime=Aios.scheduler();
      
      curtime=Aios.time();
      if (self.verbose>3) self.log('loop: Scheduler returned nexttime='+nexttime+
                                           ' ('+(nexttime>0?nexttime-curtime:0)+')');
      if (sync) {
        self.time=curtime;
        return;
      }
      if (self.steps==0 || !self.run) {
        self.loop=none;
        self.run=false;
        self.time=curtime;
        if (callback) callback();
        return;              
      }
      if (nexttime>0) 
        self.loop=setTimeout(loop,nexttime-curtime);
      else if (nexttime < 0) self.loop=setImmediate(loop);
      else {
        self.loop=none;
        self.run=false;
        self.time=curtime;
        if (callback) callback();        
      }
    };
    if (sync) loop();
    else self.loop = setTimeout(loop,1);
  };
  this.looping=loop;
  
  Aios.config({iterations:1});
  this.steps=steps;
  this.run=true;
  if (this.time>0) current.world.lag=current.world.lag+(curtime-this.time);
  this.time=curtime;
  if (!this.options.scheduler) {
    if (sync) {
      this.run=true;
      for(var step=0;step<steps;step++) loop();
      this.run=false;
    } else 
      loop(); // Start internal scheduling loop
  }
}


/** Stop the JAM scheduler and all network connections
 * 
 */
jam.prototype.stop=function (callback) {
  if (!this.run) return;
  this.run=false,cbl=CBL(callback);
  this.log('Stopping JAM ..');
  Aios.off('schedule');
  if (this.loop)
    clearTimeout(this.loop);
  this.world.nodes.forEach(function (node) {
    node.connections.forEach(function (chan,kind) {
      if (!chan) return;
      if (chan.stop) cbl.push(function (next) {chan.stop(next)});
    });
  });
  cbl.start();
}
/** Tuple space test operation - non blocking
 */
jam.prototype.test = function (pat) {
  return this.world.nodes[this.node].ts.extern.exists(pat);
}

/** Tuple space testandset operation 
 */
jam.prototype.ts = function (pat,callback) {
  return this.world.nodes[this.node].ts.extern.ts(pat,callback);
}

/** Get JAM time
 */
jam.prototype.time=function () {
  return Aios.time();
}

/** Get JAMLIB version
 */
jam.prototype.version=function () {
  return options.version;
}



var Jam = function(options) {
  var obj = new jam(options);
  return obj;
};

/** Embedded cluster setup and start; 
 * Provided by process arguments
 */
if (environment.autosetup) {
  try {
    var _options=JSON.parse(environment.autosetup);
    // console.log('['+process.pid+'] JAM cluster setup with options:',process.argv[_index+1]);
    jam.prototype.setup=function () {
      for(var p in _options) this.options[p]=_options[p];
    }
  } catch (e) {
    console.log('['+process.pid+'] JAM auto setup failed: '+e);
  }
}


module.exports = {
  Aios:Aios,
  Comp:Comp,
  Esprima:Esprima,
  Io:Io,
  Jam:Jam,
  Json:Json,
  environment:environment,
  options:options
}
};
BundleModuleCode['jam/aios']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2006-2024 bLAB
 **    $CREATED:     15-1-16 by sbosse.
 **    $VERSION:     1.67.2
 **    $RCS:         $Id: aios.js,v 1.6 2020/02/03 09:45:01 sbosse Exp sbosse $
 **    $INFO:
 **
 **  JavaScript AIOS: Agent Execution & IO System with Sandbox environment.
 **
 **    $ENDOFINFO
 */
var Io =    Require('com/io');
var Comp =  Require('com/compat');
var Name =  Require('com/pwgen');
var Conf =  Require('jam/conf');
var Code =  Require('jam/code');
var Sig =   Require('jam/sig');
var Node =  Require('jam/node');
var Proc =  Require('jam/proc');
var Sec  =  Require('jam/security');
var Ts =    Require('jam/ts');
var World = Require('jam/world');
var Chan =  Require('jam/chan');
var Mobi =  Require('jam/mobi');
var Simu =  global.config.simulation?Require(global.config.simulation):none;
var Json =  Require('jam/jsonfn');
var watchdog = Require('jam/watchdog');
var util =  Require('util');
var Amp  =  Require('jam/amp')

var aiosExceptions = [
  'CREATE',
  'MOVE',
  'SIGNAL',
  'SCHEDULE',
  'WATCHDOG',
  'KILL'];
var aiosErrors = [
  // error name - violation of
  'SCHEDULE', // TIMESCHED
  'CPU',    // TIMEPOOL
  'EOL',    // LIFETIME
  'EOM',    // MEMPOOL
  'EOT',    // TSPOOL
  'EOA',    // AGENTPOOL
  'EOR',    // AGENTSIZE ..
];

var aiosEvents = ['agent','agent+','agent-','signal','signal+','node+','node-'];

// AIOS OPTIONS //
var options =  {
  version: "1.67.2",
  
  debug:{},
  
  // Fast dirty process forking and migration between logical nodes (virtual)
  // w/o using of/toCode?
  fastcopy:false,
  // Using JSON+ (json compliant) or JSOB (raw object) in to/ofCode?
  json:false,
  // logging parameters
  log : {
    node:false,
    agent:true,
    parent:false,
    pid:false,    // agent process id!
    host:false,   // host id (os pid)
    time:false,   // time in milliseconds
    Time:true,    // time in hour:minute:sec format
    date:false,   // full date of day
    class:false
  },
  // agent ID generator name options
  nameopts : {length:8, memorable:true, lowercase:true},
  // Disable agent checkpointing and resource control
  nolimits:false,
  // No statistics
  nostats:false,
  // Use process memory for resource control? (slows down JAM execution)
  useproc: false,
  // Verbosity level
  verbose:0,
  
  // Default maximal agent life-time on this host (even idle) in ms
  LIFETIME: Infinity,
  // Default maximal agent run-time of an agent process activity in ms
  TIMESCHED:200,
  // Default maximal agent run-time of an agent process in ms
  TIMEPOOL:5000,
  // Default maximal memory of an agent (code+data)
  MEMPOOL:50000,
  // Maximal number of tuple generations on current node per agent
  TSPOOL:1000,
  // Default lifetime of tuples (0: unlimited) => Aios.Ts.options.timeout
  TSTMO : 0,
  // Maximal number of agent generations on current node (by one agent)
  AGENTPOOL:20,
  // Default minimal run-time costs below 1ms resolution (very short activity executions)
  MINCOST:0.1,
  // Default maximal scheduler run-time (ms)
  RUNTIME:1000,
  // Maximal size in bytes of serialized agent (outgoing migration) < MAX
  AGENTSIZE: 60000,
  // Maximal size in bytes of serialized agent (incoming migration)
  AGENTSIZEMAX: 256000,
  
  // Default scheduler idle-time (maximal nexttime interval, ms)
  IDLETIME:0,
  
  // Default AIOS level for received or platform created agents
  LEVEL: 1,
   
  // Random service ports (capability protection)
  // (public service port: private security port) pairs
  security : {
  }
};

var timer,
    ticks=0,  // scheduler execution counter!
    iterations=0,
    events={};

// Current execution environment (scheduler: global scheduler)
var current = {process:none,world:none,node:none,network:none,error:none,scheduler:none};

// System clock in ms (what=true) or hh:mm:ss format (what=undefined) or
// full date+time (what='date')
function clock (what) {
  if (what==undefined) return Io.Time();
  else if (what=='date') return Io.Date();
  else return Io.time();  // ms clock
}

function format(msg,cls) {
  switch (cls) {
    case 'aios':
      return ('['+(options.log.host?('#'+process.pid+'.'):'')+
              (options.log.world&&current.world?(current.world.id+'.'):'')+
              (options.log.node&&current.node?(current.node.id+'.'):'')+
              (options.log.pid&&current.process?('('+current.process.pid+')'):'')+
              (options.log.date?('@'+Io.date()):
              (options.log.time?('@'+Io.time()):
              (options.log.Time?('@'+Io.Time()):'')))+
              '] '+msg);
    case 'agent':
      return ('['+(options.log.host?('#'+process.pid+'.'):'')+
               (options.log.world&&current.world?(current.world.id+'.'):'')+
               (options.log.node&&current.node?(current.node.id+'.'):'')+
               (options.log.class&&current.process?(current.process.agent.ac+'.'):'')+
               (options.log.agent&&current.process?(current.process.agent.id):'')+
               (options.log.parent&&current.process?('<'+current.process.agent.parent):'')+
               (options.log.pid&&current.process?('('+current.process.pid+')'):'')+
               (options.log.date?('@'+Io.date()):
               (options.log.time?('@'+Io.time()):
               (options.log.Time?('@'+Io.Time()):'')))+
               '] '+msg);
    default:
      return ('['+
               (options.log.date?('@'+Io.date()):
               (options.log.time?('@'+Io.time()):
               (options.log.Time?('@'+Io.Time()):'')))+
               '] '+msg)
  }
}
// AIOS smart logging function for Agents
var logAgent = function(){
    var msg='';
    arguments.forEach(function (arg,i) {
      if (typeof arg == 'string' || typeof arg == Number) msg += (i>0?', '+arg:arg);
      else msg += (i>0?' '+Io.inspect(arg):Io.inspect(arg));
    });
    (Aios.printAgent||Aios.print)(format(msg,'agent'))

}
// AIOS smart logging function for AIOS internals (w/o agent messages)
var logAIOS = function(){
    var msg='';
    arguments.forEach(function (arg,i) {
      if (typeof arg == 'string' || typeof arg == Number) msg += (i>0?', '+arg:arg);
      else msg += (i>0?' '+Io.inspect(arg):Io.inspect(arg));
    });
    if (current.process) (Aios.printAgent||Aios.print)(format(msg,'aios'));
    else                 (Aios.printAgent||Aios.print)(format(msg));
}

// AIOS smart logging function for AIOS internals (w/o agent messages) used by async callbacks
var logAIOSasync = function(){
    var msg='';
    arguments.forEach(function (arg,i) {
      if (typeof arg == 'string' || typeof arg == Number) msg += (i>0?', '+arg:arg);
      else msg += (i>0?' '+Io.inspect(arg):Io.inspect(arg));
    });
    if (current.process)  (Aios.printAsync||Aios.print)(Aios.printAgent||Aios.print)(format(msg,'aios'));
    else                  (Aios.printAsync||Aios.print)(format(msg));
}

// Generic messages (used by other modules and drivers)
var log = function () {
    var msg='',pref='';
    arguments.forEach(function (arg,i) {
      if (typeof arg == 'string' || typeof arg == Number) msg += (i>0?', '+arg:arg);
      else msg += (i>0?', '+Io.inspect(arg):Io.inspect(arg));
    });
    if (options.log.host && typeof process != 'undefined') pref='#'+process.pid+': ';
    if (options.log.date) pref=pref+Io.date()+' ';
    else if (options.log.time) pref=pref+Io.time()+' ';
    else if (options.log.Time) pref=pref+Io.Time()+' ';
 
    if (msg[0]=='[')    Aios.print(pref+msg);
    else                Aios.print('[AIOS'+pref+'] '+msg);
}

// Generic async messages (async, from callbacks)
var logAsync = function () {
    var msg='',pref='';
    arguments.forEach(function (arg,i) {
      if (typeof arg == 'string' || typeof arg == Number) msg += (i>0?', '+arg:arg);
      else msg += (i>0?', '+Io.inspect(arg):Io.inspect(arg));
    });
    if (options.log.host && typeof process != 'undefined') pref='#'+process.pid+': ';
    if (options.log.date) pref=pref+Io.date()+' ';
    else if (options.log.time) pref=pref+Io.time()+' ';
    else if (options.log.Time) pref=pref+Io.Time()+' ';
    
    if (msg[0]=='[')  (Aios.printAsync||Aios.print)(pref+msg);
    else              (Aios.printAsync||Aios.print)('[AIOS'+pref+'] '+msg);
}

var eval0=eval;

/** Sandbox module environment for agents (level 0): Untrusted, 
 * minimal set of operations (no move, fork, kill(others),..)
 */
var aios0 = {
  abs:Math.abs,
  add: function (a,b) {
    var res,i;
    if (Comp.obj.isNumber(a) && Comp.obj.isNumber(b)) return a+b;
    if (Comp.obj.isArray(a) && Comp.obj.isArray(b)) {
      if (a.length!=b.length) return none;
      res=Comp.array.copy(a);
      for (i in a) {
        res[i]=aios0.add(a[i],b[i]);
      }
      return res;  
    }
    if (Comp.obj.isArray(a) && Comp.obj.isFunction(b)) {
      res=Comp.array.copy(a);
      for (i in a) {
        res[i]=aios0.add(a[i],b.call(current.process.agent,a[i]));
      }
      return res;  
    }
    if (Comp.obj.isObj(a) && Comp.obj.isObj(b)) {
      res={};
      for (i in a) {
        res[i]=aios0.add(a[i],b[i]);
      }
      return res;     
    }
    return none;
  },
  angle: function (p1,p2) {
    var angle,v1,v2;
    if (Comp.obj.isArray(p1)) v1=p1;
    else if (Comp.obj.isObj(p1)) v1=[p1.x,p1.y];
    if (Comp.obj.isArray(p2)) v2=p2;
    else if (Comp.obj.isObj(p2)) v2=[p2.x,p2.y];
    if (p2==undefined) {v2=v1;v1=[0,0]};
    angle=Math.atan2(v2[1]-v1[1],v2[0]-v1[0]);
    return 180*angle/Math.PI;
  },
  array: function (cols,init) {
    if (init==undefined) init=0;
    var row=[];
    for(var j=0;j<cols;j++) row.push(typeof init == 'function'?init(j):init);
    return row;
  },
  assign : function (src,dst) {
    for(var p in src) dst[p]=src[p]
    return dst;
  },
  Capability: Sec.Capability,
  clock: clock,
  concat: function (a,b,unique) {
    var res,i;
    if (Comp.obj.isArray(a) && Comp.obj.isArray(b)) {
      if (!unique) return a.concat(b);
      res=a.slice();
      for(var i in b) {
        if (res.indexOf(b[i])==-1) res.push(b[i]);
      }
      return res;
    } else if (Comp.obj.isObj(a) && Comp.obj.isObj(b)) {
      res={};
      for (i in a) {
        res[i]=a[i];
      }
      for (i in b) {
        res[i]=b[i];
      }
      return res;     
    } else if (Comp.obj.isString(a) && Comp.obj.isString(b)) {
      return a+b;
    } else
      return undefined;
  },
  contains : function (o,e) {
    // e can be a scalar or array of values
    if (Comp.obj.isArray(o)) 
      return Comp.array.contains(o,e);
    else if (Comp.obj.isObj(o) && (Comp.obj.isString(e) || Comp.obj.isNumber(e))) 
      return o[e] != undefined;
    else if (Comp.obj.isString(o) && Comp.obj.isString(e)) 
      return o.indexOf(e)!=-1
  },
  copy : function (o)  {
    // recursively copy objects
    var _o,p;
    if (Comp.obj.isArray(o)) {
      if (typeof o[0] != 'object') return o.slice();
      else return o.map(function (e) {
            if (typeof e == 'object') return aios0.copy(e);
              else return e;
            });
      
    } else if (Comp.obj.isObject(o)) {
      _o={};
      for(p in o) _o[p]=(typeof o[p]=='object'?aios0.copy(o[p]):o[p]);
      return _o;
    } 
    else if (Comp.obj.isString(o)) 
      return o.slice();
    else return o;
  },
  delta : function (o1,o2) {
    var res;
    if (Comp.obj.isArray(o1) && Comp.obj.isArray(o2)) {
      if (o1.length != o2.length) return;
      res=[];
      for (var i in o1) res[i]=o1[i]-o2[i]; 
    } else if (Comp.obj.isObject(o1) && Comp.obj.isObject(o2)) {
      res={};
      for (var p in o1) res[p]=o1[p]-o2[p];     
    }
    return res; 
  },
  distance: function (p1,p2) {
    var y=0;
    if (p2) for(var p in p1) if (typeof p1[p] == 'number' && 
                                 typeof p2[p] == 'number') y+=Math.pow(p1[p]-p2[p],2);
    else for(var p in p1) if (typeof p1[p] == 'number') y+=Math.pow(p1[p],2);
    return Math.sqrt(y)
  },
  div: div,
  dump: function (x) { 
    if (x=='res') x=Comp.obj.copy(current.process.resources); 
    if (x=='?') x=this; 
    logAgent(util.inspect(x)); },
  empty: function (o) {
    if (Comp.obj.isArray(o) || Comp.obj.isString(o)) return o.length==0;
    else if (Comp.obj.isObj(o)) return Comp.obj.isEmpty(o);
    else return false;
  },
  equal: function (a,b) {
    var i;
    if (Comp.obj.isNumber(a) && Comp.obj.isNumber(b)) return a==b;
    else if (Comp.obj.isArray(a) && Comp.obj.isArray(b)) {
      if (a.length!=b.length) return false;
      for (i in a) {
        if (!aios0.equal(a[i],b[i])) return false;
      }
      return true;     
    }
    else if (Comp.obj.isObj(a) && Comp.obj.isObj(b)) {
      for (i in a) {
        if (!aios0.equal(a[i],b[i])) return false;
      }
      return true;     
    }
    else if (Comp.obj.isString(a) && Comp.obj.isString(b))
      return (a.length==b.length && a==b)
    return false;
  },
  filter:function (a,f) {
    var element,res=[],len,len2,i,j,found;
    if (Comp.obj.isArray(a) && Comp.obj.isFunction(f)) {
        res=[];
        len=a.length;
        for(i=0;i<len;i++) {
            element=a[i];
            if (f.call(current.process.agent,element,i)) res.push(element);
        }
        return res;
    } else if (Comp.obj.isArray(a) && Comp.obj.isArray(f)) {
        res=[];
        len=a.length;
        len2=f.length;
        for(i=0;i<len;i++) {
            element=a[i];
            found=false;
            for (j=0;j<len2;j++) if(element==f[j]){found=true; break;}
            if (!found) res.push(element);
        }
        return res;      
    } else return undefined;   
  },
  flatten : function (a,level) {
    if (Comp.obj.isMatrix(a)) { // [][] -> []
      return a.reduce(function (flat, toFlatten) {
        return flat.concat(Array.isArray(toFlatten) && level>1? aios0.flatten(toFlatten,level-1) : toFlatten);
      }, []);
    } else if (Comp.obj.isObj(a)) { // {{}} {[]} -> {}
      function flo (o) {
        var o2={},o3;
        for(var p in o) {
          if (typeof o[p]=='object') {
            o3=flo(o[p]);
            for(var p2 in o3) {
              o2[p+p2]=o3[p2];
            }
          } else o2[p]=o[p];
        }
        return o2;
      }
      return flo(a);
    }
    return a;
  },
  head:function (a) {
    if (Comp.obj.isArray(a))
      return Comp.array.head(a);
    else return undefined;
  },
  id:aidgen,
  info:function (kind) {
    switch (kind) {
      case 'node':  
        return { 
          id:current.node.id, 
          position: current.node.position, 
          location:current.node.location,
          type:current.node.type, 
        };
      case 'version': 
        return options.version;
      case 'host': 
        return { 
          type:global.TARGET 
        };      
    }
  },
  int: int,
  isin: function (o,v) {
    var p;
    if (Comp.obj.isArray(o)) {
      for(p in o) if (aios0.equal(o[p],v)) return true;
      return false;
    } else if (Comp.obj.isObj(o)) {
      for(p in o) if (aios0.equal(o[p],v)) return true;
      return false;    
    } else if (Comp.obj.isString(o)) {
      return o.indexOf(v)!=-1
    }
  },
  iter:function (obj,fun) {
    var p;
    if (Comp.obj.isArray(obj))
      for(p in obj) fun.call(current.process.agent,obj[p],Number(p));
    else
      for(p in obj) fun.call(current.process.agent,obj[p],p)
  },
  keys: Object.keys,
  kill:function () {kill(current.process.agent.id)},
  last: function (o) {
    if (o==undefined) return;
    else if (Comp.obj.isArray(o) || Comp.obj.isString(o)) 
      return o[o.length-1];
    else if (Comp.obj.isObj(o)) {
      var p,l;
      for(p in o) if (o[p]!=undefined) l=o[p];
      return l; 
    }
  },
  length: function (o) {
    if (o==undefined) return 0;
    else if (Comp.obj.isObj(o)) {
      var p,l=0;
      for(p in o) if (o[p]!=undefined) l++;
      return l; 
    } else return o.length
  },
  log:function () { logAgent.apply(_,arguments) },
  map:function (a,f) {
    var res,i,p;
    if (Comp.obj.isArray(a) && Comp.obj.isFunction(f)) {
      res=[];
      for (i in a) {
        v=f.call(current.process.agent,a[i],i);
        if (v!=undefined) res.push(v);
      }
      return res;
    } else if (Comp.obj.isObject(a) && Comp.obj.isFunction(f)) {
      // Objects can be filtered (on first level), too!
      res={};
      for(p in a) {
        v=f.call(current.process.agent,a[p],p);
        if (v != undefined) res[p]=v;
      }
      return res;
    } else return undefined;   
  },
  matrix: function (x,y,init) {
    var row=[];
    var mat=[];
    for (var j=0;j<y;j++) {
      row=[];
      for(var i=0;i<x;i++) 
        row.push(init||0)
      mat.push(row)
    }
    return mat;
  },
  max: function (a,b) {
    if (Comp.obj.isArray(a)) {
      var f=function (x) {return x},v,vi;
      if (Comp.obj.isFunction(b)) f=b;
      Comp.array.iter(a,function (a0,i) {
        a0=f(a0);
        if (v==undefined || a0>v) {v=a0; vi=i};
      });
      if (vi!=undefined) return a[vi];
    } else return Math.max(a,b);
  },
  me: function () {
    return current.process.agent.id;
  },
  min: function (a,b) {
    if (Comp.obj.isArray(a)) {
      var f=function (x) {return x},v,vi;
      if (Comp.obj.isFunction(b)) f=b;
      Comp.array.iter(a,function (a0,i) {
        a0=f(a0);
        if (v==undefined || a0<v) {v=a0; vi=i};
      });
      if (vi!=undefined) return a[vi];
    } else return Math.min(a,b);
  },
  myClass: function () {
    return current.process.agent.ac;
  },
  myLevel: function () {
    return current.process.level;
  },
  myNode: function () {
    return current.node.id;
  },
  myParent: function () {
    return current.process.agent.parent;
  },
  myPosition: function () {
    return current.node.location||current.node.position;
  },
  neg: function (v) {
    var p;
    if (Comp.obj.isNumber(v)) return -v;
    if (Comp.obj.isArray(v)) return v.map(function (e) {return aios0.neg(e)});
    if (Comp.obj.isObj(v)) {
      var o=v,_o={};
      for(p in o) _o[p]=typeof o[p]=='number'?-o[p]:o[p];
      return _o;
    }
  },
  negotiate:function (res,val,cap) { return negotiate(0,res,val,cap) },
  next:function () {},
  object : function (str) {
    var myobj={data:null};
    with ({myobj:myobj, str:str}) { myobj.data=eval0('var _o='+str+';_o') };
    return myobj.data;
  },
  pluck : function (table,column) {
    var res=[];
    for(var i in table) {
      res.push(table[i][column]);
    }
    return res;
  },
  privilege: function () {return 0},
  Port: Sec.Port,
  Private: Sec.Private,
  random: function (a,b,frac) {
    var r,n,p,i,keys,k;
    if (Comp.obj.isArray(a)) {
      n = a.length;
      if (n>0)
        return a[Comp.random.int(n)];  
      else
        return none;
    } else if (Comp.obj.isObj(a)) {
      keys=Object.keys(a);
      n = keys.length;
      if (n>0)
        return a[keys[Comp.random.int(n)]];  
      else
        return none;
    } else if (b==undefined) {b=a;a=0}; 
    if (!frac ||frac==1)
      return Comp.random.interval(a,b);
    else {
      r=Comp.random.range(a,b);
      return ((r/frac)|0)*frac;
    }
  },
  reduce : function (a,f) {
    if (Comp.obj.isArray(a)) {
      return a.reduce(function (a,b) {
        return current.process?f.call(current.process.agent,a,b):f(a,b);
      });
    }
  },
  reverse: function (a) {
    if (Comp.obj.isArray(a)) 
      return a.slice().reverse(); 
    else if (Comp.obj.isString(a)) 
      return a.split("").reverse().join("")
  }, 
  sleep:Sig.agent.sleep,
  sort: function (a,f) {
    if (Comp.obj.isArray(a) && Comp.obj.isFunction(f)) {
      return Comp.array.sort(a,function (x,y) {
        return f.call(current.process.agent,x,y);
      });
    } else return undefined;       
  },
  sum: function (o,f) {
    if (Comp.obj.isArray(o)) return Comp.array.sum(o,f);
    else if (Comp.obj.isObject(o)) {
      var s=0,p;
      if (!f) f=function(x){return x};
      for(p in o) s+=f(o[p]);
      return s;
    }
  },
  string:function (o) {if (Comp.obj.isString(o)) return o; else return o.toString()},
  tail:function (a) {
    if (Comp.obj.isArray(a))
      return Comp.array.tail(a);
    else return undefined;
  },
  time:function () { return time()-current.world.lag},
  // returns a without b
  without : function (a,b) {
    if (Comp.obj.isArray(a) && (Comp.obj.isArray(b)))
      return a.filter(function (v) {
        return !aios0.contains(b,v);
      });
    else if (Comp.obj.isArray(a)) 
      return a.filter(function (v) {
        return !aios0.equal(b,v);
      });    
  },
  zero: function (a) {
    var i;
    if (Comp.obj.isNumber(a)) return a==0;
    if (Comp.obj.isArray(a)) {
      for (i in a) {
        if (!aios0.zero(a[i])) return false;
      }
      return true;     
    }
    if (Comp.obj.isObj(a)) {
      for (i in a) {
        if (!aios0.zero(a[i])) return false;
      }
      return true;     
    }
    return false;    
  },

  Vector: function (x,y,z) {var o={}; if (x!=_) o['x']=x; if (y!=_) o['y']=y; if (z!=_) o['z']=z; return o},

  // Scheduling and checkpointing
  B:B,
  CP:CP,
  I:I,
  L:L,
  RT:RT,
  
  Math:Math
}

// Sandbox module environment for agents (level 1): Trusted, standard operational set
var aios1 = {
  abs:aios0.abs,
  act:Conf.agent.act,
  add:aios0.add,
  angle:aios0.angle,
  alt:Ts.agent.alt,
  array:aios0.array,
  assign:aios0.assign,
  broadcast:Sig.agent.broadcast,
  Capability: Sec.Capability,
  clock: clock,
  collect:Ts.agent.collect,
  concat:aios0.concat,
  contains:aios0.contains,
  copy:aios0.copy,
  copyto:Ts.agent.copyto,
  // type create = function(ac:string|object,args:object|[]) -> agentid:string
  create: function(ac,args,level) {
    if (level==undefined || level>1) level=1;
    if (args==undefined) args={};
    var process=none,code;
    if (!Comp.obj.isArray(args) && !Comp.obj.isObject(args)) {
      current.error='Invalid argument: Agent argument is neither array nor object'; 
      throw 'CREATE';
    };
    current.process.resources.agents++;
    if (typeof ac == 'object') {
      // indeed a forking with modified act/trans/body
      // { x:this.x, .., act : {}|[], trans:{}, on:[}}
      process = Code.createFromOn(current.node,current.process,ac,level);
    } else if (current.world.classes[ac] && current.world.classes[ac][level])
      process = Code.createOn(current.node,current.world.classes[ac][level],args,level,ac);
    else if (current.process.agent.subclass && current.process.agent.subclass[ac]) {
      process = Code.createOn(current.node,current.process.agent.subclass[ac],args,level,ac);    
    } else {
      current.error='Invalid argument: Unknown agent class '+ac; 
      throw 'CREATE';
    }
    if (process) {
      if (current.process!=none && process.gid==none) {
        process.gid=current.process.pid;
        if (!process.agent.parent) 
          process.agent.parent=current.process.agent.id;
      }
      return process.agent.id; 
    } else return none;    
  },
  delta:aios0.delta,
  distance: aios0.distance,
  div: aios0.div,
  dump: aios0.dump,
  empty:aios0.empty,
  evaluate:Ts.agent.evaluate,
  equal:aios0.equal,
  exists:Ts.agent.exists,
  Export:function (name,code) { current.node.export(name,code) },
  filter:aios0.filter,
  flatten:aios0.flatten,
  fork:function (parameter) {var process = current.process.fork(parameter,undefined,options.fastcopy); return process.agent.id},
  head:aios0.head,
  id:aidgen,
  Import:function (name) { return current.node.import(name) },
  info:aios0.info,
  inp:Ts.agent.inp,
  int: aios0.int,
  isin: aios0.isin,
  iter:aios0.iter,
  keys: Object.keys,
  kill:function (aid) {if (aid==undefined) kill(current.process.agent.id); else kill(aid)},
  last: aios0.last,
  length: aios0.length,
  link:function (dir) {return current.world.connected(dir,current.node)},
  listen:Ts.agent.listen,
  log:aios0.log,
  me:aios0.me,
  mark:Ts.agent.mark,
  map:aios0.map,
  max:aios0.max,
  matrix:aios0.matrix,
  moveto:Mobi.agent.move,
  min:aios0.min,
  myClass:aios0.myClass,
  myNode:aios0.myNode,
  myLevel:aios0.myLevel,
  myParent:aios0.myParent,
  myPosition:aios0.myPosition,
  neg:aios0.neg,
  negotiate:function (res,val,cap) { return negotiate(1,res,val,cap) },
  object:aios0.object,
  opposite:Mobi.agent.opposite,
  out:Ts.agent.out,
  pluck : aios0.pluck,
  Port: Sec.Port,
  position: function () {return current.node.position},
  Private: Sec.Private,
  privilege: function () {return 1},
  random: aios0.random,
  rd:Ts.agent.rd,
  reduce:aios0.reduce,
  reverse:aios0.reverse,
  rm:Ts.agent.rm,
  security: Sec,
  send:Sig.agent.send,
  sendto:Sig.agent.sendto,
  sleep:Sig.agent.sleep,
  sort:aios0.sort,
  store:Ts.agent.store,
  string:aios0.string,
  sum:aios0.sum,
  tail:aios0.tail,
  test:Ts.agent.exists,
  time:aios0.time,
  timer:Sig.agent.timer,
  trans:Conf.agent.trans,
  try_alt:Ts.agent.try.alt,
  try_inp:Ts.agent.try.inp,
  try_rd:Ts.agent.try.rd,
  ts:Ts.agent.ts,
  wakeup:Sig.agent.wakeup,
  without:aios0.without,
  zero:aios0.zero,
  
  B:B,
  CP:CP,
  I:I,
  L:L,
  RT:RT,
  
  Vector:aios0.Vector,
  DIR:Mobi.agent.DIR,
  Math:Math
};

// Sandbox module environment for agents (level 2): Trusted with extended privileges
var aios2 = {
  abs:aios0.abs,
  add:aios0.add,
  act:Conf.agent.act,
  angle:aios0.angle,
  alt:Ts.agent.alt,
  array:aios0.array,
  assign:aios0.assign,
  broadcast:Sig.agent.broadcast,
  Capability: Sec.Capability,
  clock: clock,
  collect:Ts.agent.collect,
  concat:aios0.concat,
  contains:aios0.contains,
  copy:aios0.copy,
  copyto:Ts.agent.copyto,
  create: function(ac,args,level) {
    var process=none;
    if (level==undefined || level>2) level=2;
    if (args==undefined) args={};
    if (!Comp.obj.isArray(args) && !Comp.obj.isObject(args)) {
      current.error='Invalid argument: Agent arguments is neither array nor object'; 
      throw 'CREATE';
    };
    current.process.resources.agents++;
    if (typeof ac == 'object') {
      // indeed a forking with modified act/trans/body
      // { x:this.x, .., act : {}|[], trans:{}, on:[}}
      process = Code.createFromOn(current.node,current.process,ac,level);
    } else if (current.world.classes[ac] && current.world.classes[ac][level])
      process = Code.createOn(current.node,current.world.classes[ac][level],args,level,ac);
    else if (current.process.agent.subclass && current.process.agent.subclass[ac]) {
      process = Code.createOn(current.node,current.process.agent.subclass[ac],args,level,ac);    
    } else {
      current.error='Invalid argument: Unknown agent class '+ac; 
      throw 'CREATE';
    }
    if (process) {
      process.agent.ac=ac;
      if (current.process!=none && process.gid==none) {
        process.gid=current.process.pid;
        if (process.agent.parent==_ || process.agent.parent==none) 
          process.agent.parent=current.process.agent.id;
      }
      return process.agent.id; 
    } else return none;    
  },
  delta:aios0.delta,
  distance: aios0.distance,
  div: aios0.div,
  dump: aios0.dump,
  empty:aios0.empty,
  evaluate:Ts.agent.evaluate,
  equal:aios0.equal,
  exists:Ts.agent.exists,
  Export:function (name,code) { current.node.export(name,code) },
  filter:aios0.filter,
  flatten:aios0.flatten,
  fork:function (parameter) {var process = current.process.fork(parameter); return process.agent.id},
  head:aios0.head,
  id:aidgen,
  Import:function (name) { return current.node.import(name) },
  info:aios0.info,
  inp:Ts.agent.inp,
  int: aios0.int,
  isin: aios0.isin,
  iter:aios0.iter,
  keys: Object.keys,
  kill:function (aid) {if (aid==undefined) kill(current.process.agent.id); else kill(aid)},
  last: aios0.last,
  length: aios0.length,
  link:function (dir) {return current.world.connected(dir,current.node)},
  listen:Ts.agent.listen,
  log:aios0.log,
  max:aios0.max,
  me:aios0.me,
  min:aios0.min,
  myClass:aios0.myClass,
  myNode:aios0.myNode,
  myLevel:aios0.myLevel,
  myParent:aios0.myParent,
  myPosition:aios0.myPosition,
  mark:Ts.agent.mark,
  map:aios0.map,
  matrix:aios0.matrix,
  moveto:Mobi.agent.move,
  neg:aios0.neg,
  negotiate:function (res,val,cap) { return negotiate(2,res,val,cap) },
  object:aios0.object,
  opposite:Mobi.agent.opposite,
  out:Ts.agent.out,
  random: aios0.random,
  reduce:aios0.reduce,
  rd:Ts.agent.rd,
  reverse:aios0.reverse,
  rm:Ts.agent.rm,
  pluck : aios0.pluck,
  Port: Sec.Port,
  position: function () {return current.node.position},
  Private: Sec.Private,
  privilege: function () {return 2},
  security: Sec,
  send:Sig.agent.send,
  sendto:Sig.agent.sendto,
  sleep:Sig.agent.sleep,
  sort:aios0.sort,
  store:Ts.agent.store,
  string:aios0.string,
  sum:aios0.sum,
  tail:aios0.tail,
  test:Ts.agent.exists,
  time:aios0.time,
  timer:Sig.agent.timer,
  trans:Conf.agent.trans,
  try_alt:Ts.agent.try.alt,
  try_inp:Ts.agent.try.inp,
  try_rd:Ts.agent.try.rd,
  ts:Ts.agent.ts,
  wakeup:Sig.agent.wakeup,
  without:aios0.without,
  zero:aios0.zero,
  
  B:B,
  CP:CP,
  I:I,
  L:L,
  RT:RT,
  
  Vector:aios0.Vector,
  DIR:Mobi.agent.DIR,
  
  Math:Math,
};

// Sandbox module environment for agents (level 3): Trusted with extended privileges, system level
// May not migrate!!
var aios3 = {
  abs:aios0.abs,
  act:Conf.agent.act,
  add:aios0.add,
  angle:aios0.angle,
  alt:Ts.agent.alt,
  array:aios0.array,
  assign:aios0.assign,
  broadcast:Sig.agent.broadcast,
  Capability: Sec.Capability,
  clock: clock,
  collect:Ts.agent.collect,
  connectTo:function (dir,options) {
    // Connect this node with another node using a virtual or physical channel link
    var node=current.node, world=current.world;
    if (!dir || !dir.tag) throw('CONNECT');
    world.connectTo(dir,node,options);
  },
  concat:aios0.concat,
  contains:aios0.contains,
  copy:aios0.copy,
  copyto:Ts.agent.copyto,
  create: function(ac,args,level) {
    var process=none;
    if (level==undefined) level=3;
    if (args==undefined) args={};
    if (!Comp.obj.isArray(args) && !Comp.obj.isObject(args)) {
      current.error='Invalid argument: Agent arguments is neither array nor object'; 
      throw 'CREATE';
    };
    current.process.resources.agents++;
    if (typeof ac == 'object') {
      // indeed a forking with modified act/trans/body
      // { x:this.x, .., act : {}|[], trans:{}, on:[}}
      process = Code.createFromOn(current.node,current.process,ac,level);
    } else if (current.world.classes[ac] && current.world.classes[ac][level])
      process = Code.createOn(current.node,current.world.classes[ac][level],args,level,ac);
    else if (current.process.agent.subclass && current.process.agent.subclass[ac]) {
      process = Code.createOn(current.node,current.process.agent.subclass[ac],args,level,ac);    
    } else {
      current.error='Invalid argument: Unknown agent class '+ac; 
      throw 'CREATE';
    }
    if (process) {
      process.agent.ac=ac;
      if (current.process!=none && process.gid==none) {
        process.gid=current.process.pid;
        if (process.agent.parent==_ || process.agent.parent==none) 
          process.agent.parent=current.process.agent.id;
      }
      return process.agent.id; 
    } else return none;    
  },
  delta:aios0.delta,
  distance: aios0.distance,
  div: aios0.div,
  dump: aios0.dump,
  empty:aios0.empty,
  equal:aios0.equal,
  evaluate:Ts.agent.evaluate,
  exists:Ts.agent.exists,
  Export:aios2.Export,
  filter:aios0.filter,
  flatten:aios0.flatten,
  fork:aios2.fork,
  head:aios0.head,
  id:aidgen,
  Import:aios2.Import,
  info:aios0.info,
  inp:Ts.agent.inp,
  int: aios0.int,
  isin: aios0.isin,
  iter:aios0.iter,
  keys: Object.keys,
  kill:aios2.kill,
  last: aios0.last,
  length:aios0.length,
  link:aios2.link,
  listen:Ts.agent.listen,
  log:aios0.log,
  max:aios0.max,
  me:aios0.me,
  min:aios0.min,
  myClass:aios0.myClass,
  myLevel:aios0.myLevel,
  myNode:aios0.myNode,
  myParent:aios0.myParent,
  myPosition:aios0.myPosition,
  mark:Ts.agent.mark,
  map:aios0.map,
  matrix:aios0.matrix,
  moveto:function () {/* System level agents may not migrate ! */ current.error='ENOTSUPPORTED';throw 'MOVE';},
  neg:aios0.neg,
  negotiate:function (res,val,cap) { return negotiate(3,res,val,cap) },
  object:aios0.object,
  opposite:Mobi.agent.opposite,
  out:Ts.agent.out,
  pluck : aios0.pluck,
  Port: Sec.Port,
  position: function () {return current.node.position},
  Private: Sec.Private,
  privilege: function () {return 3},
  reduce:aios0.reduce,
  random: aios0.random,
  rd:Ts.agent.rd,
  reverse:aios0.reverse,
  rm:Ts.agent.rm,
  send:Sig.agent.send,
  sendto:Sig.agent.sendto,
  sleep:aios0.sleep,
  sort:aios0.sort,
  store:Ts.agent.store,
  string:aios0.string,
  sum:aios0.sum,
  tail:aios0.tail,
  test:Ts.agent.exists,
  time:aios0.time,
  timer:Sig.agent.timer,
  trans:Conf.agent.trans,
  try_alt:Ts.agent.try.alt,
  try_inp:Ts.agent.try.inp,
  try_rd:Ts.agent.try.rd,
  ts:Ts.agent.ts,
  wakeup:Sig.agent.wakeup,
  without:aios0.without,
  zero:aios0.zero,
  
  B:B,
  CP:CP,
  I:I,
  L:L,
  RT:RT,
  
  Vector:aios0.Vector,
  DIR:Mobi.agent.DIR,
  
  Math:Math,
  
  // Exucute an IO block sequence in an agent process context
  IOB: function (block) {
    var proc=current.process;
    setImmediate(function () {
      var index=0;
      function next (to) {
        var _proc=current.process,_node=current.node;        
        if (to==none) {
          // done or failiure
          proc.mask.next=undefined;
          proc.wakeup();
          return;
        }
        index=index+to;        
        try {
          current.process=proc; current.node=proc.node;
          block[index].call(proc.agent);
        } catch (e) {
          logAgent('Caught IOB error: '+e);
        }
        current.process=_proc; current.node=_node;
      }
      proc.mask.next=next;      
      next(0);
    });
    proc.suspend();
  }
};

var aios = aios1;

/*
** Agent code scheduling blocks can migrate 
** - must be handled different from internal scheduling blocks!
*/
// Schedule linear sequence of functions that may block (suspending execution of current agent process).
function B(block) {
  if (current.process.schedule.length==0) 
    current.process.schedule = block;
  else 
    current.process.schedule = Comp.array.concat(block,current.process.schedule);
}

/** Add pending callback call to process scheduling block
 *
 */
function CB(process,cb,args) {
  if (args)
    Comp.array.push(process.schedule,function () { cb.apply(this,args) });  
  else
    Comp.array.push(process.schedule,cb);
}

/** Agent process activity check pointing (injected in loops/functions)
 *
 */
function CP() {
  if (current.process.runtime && (current.process.runtime+current.world.lag-Date.now())<0) throw "SCHEDULE";
  return true;
}

/** Agent exception checker; agents may not consume scheduler/watchdog exceptions!!
*/

function RT(e) {
  if (['WATCHDOG','SCHEDULE'].indexOf(e.toString())!=-1) throw(e);
}

/** Schedule an object iteration sequence that may block (suspending execution of current agent process).
 *
 */
function I(obj,next,block,finalize) {
  /*
  ** Iterate and schedule a block
   * obj: []
   * next: function(next) {}
  */
  var index=0;
  var length=obj.length;
   
  var iterator = [
      function() {
        next(obj[index]);
        if (index<length) {
          B(block.slice());
          index++;
        }           
      },
      function () {
        if (index<length) B(iterator.slice());
        else if (finalize) finalize.call(this);
      }
   ];
  B(iterator.slice());
}

// Schedule a loop iteration sequence that may block (suspending execution of current agent process).
function L(init,cond,next,block,finalize) {
   /*
   ** Iterate and schedule a block
    * init: function() {}
    * cond: function() { return cond; }
    * next: function() {}
   */
   var loop = [
       function() {
          if (cond.call(this)) B(block.slice());
       },
       next,
       function () {
          if (cond.call(this)) B(loop.slice());           
       }
   ];
  B(loop.slice());
  B([init]);
}


/** Agent Identifier Generator
 *
 */ 
function aidgen(_options) {
  return Name.generate(_options||options.nameopts);
}

/** AIOS configuration 
 *
 */
function config(settings) {
  for (var p in settings) {
    switch (p) {
      case 'iterations': iterations=settings[p]; break;
      case 'fastcopy':  options.fastcopy=settings[p]; break;
      case 'verbose':   options.verbose=settings[p]; break;
      case 'log+':      options.log[settings[p]]=true; break;
      case 'log-':      options.log[settings[p]]=false; break;
      case 'log':
        // log options object override
        for(var l in settings[p]) options.log[l]=settings[p][l];
        break;
      case 'nolimits': 
        if (settings[p]) 
          Aios.watchdog=undefined,
          options.nolimits=true,
          Aios.Code.inject.cp=undefined,
          Aios.Code.inject.rt=undefined; 
        break;
      case 'nowatch': 
        if (settings[p]) 
          Aios.watchdog=undefined,
          Aios.Code.inject.cp=undefined,
          Aios.Code.inject.rt=undefined; 
        break;
      case 'checkpoint': 
        if (settings[p]) 
          Aios.watchdog=undefined,
          Aios.Code.inject.cp='CP',
          Aios.Code.inject.rt='RT';
        break;
      case 'security':
        // Capability port-random pairs
        for (var q in settings[p]) {
          var port=Sec.Port.ofString(q),
              random=Sec.Port.ofString(settings[p][q]);
          options.security[port]=random;
        }
        break;
      case 'print':       Aios.print=settings[p]; break;
      case 'printAgent':  Aios.printAgent=settings[p]; break;
      case 'printAsync':  Aios.printAsync=settings[p]; break;
      case 'LEVEL':       options.LEVEL=settings[p]; break;
      case 'LIFETIME':    options.LIFETIME=settings[p]; break;
      case 'TIMESCHED':   options.TIMESCHED=settings[p]; break;
      case 'TIMEPOOL':    options.TIMEPOOL=settings[p]; break;
      case 'MEMPOOL':     options.MEMPOOL=settings[p]; break;
      case 'TSPOOL':      options.TSPOOL=settings[p]; break;
      case 'AGENTPOOL':   options.AGENTPOOL=settings[p]; break;
      case 'MINCOST':     options.MINCOST=settings[p]; break;
      case 'RUNTIME' :    options.RUNTIME=settings[p]; break;
      case 'IDLETIME' :   options.IDLETIME=settings[p]; break;
      case 'TSTMO':       options.TSTMO=Aios.Ts.options.timeout=settings[p]; break;
      case 'time': 
        time=settings[p]; 
        // Update alle time and CP references
        Aios.time=aios0.time=aios1.time=aios2.time=aios3.time=time;
        Aios.CP=aios0.CP=aios1.CP=aios2.CP=aios3.CP=function () {
          if (current.process.runtime && (current.process.runtime+current.world.lag-time())<0) throw "SCHEDULE";
          return true;
        };
        break;
    }
  }
}

function configGet() {
  return {
    LEVEL     : options.LEVEL,
    LIFETIME  : options.LIFETIME,
    TIMESCHED : options.TIMESCHED,
    TIMEPOOL  : options.TIMEPOOL,
    TSPOOL    : options.TSPOOL,
    AGENTPOOL : options.AGENTPOOL,
    MEMPOOL   : options.MEMPOOL,
    MINCOST   : options.MINCOST,
    RUNTIME   : options.RUNTIME,
    IDLETIME  : options.IDLETIME,
    TSTMO     : Aios.Ts.options.timeout,
    security  : Object.keys(options.security).map(function (port) {
      return { port:Sec.Port.toString(port), random:Sec.Port.toString(options.security[port])}
    }),
    checkpoint: { cp:Aios.Code.inject.cp, rt:Aios.Code.inject.rt, watchdog: Aios.watchdog?true:fals },
    nolimits  : options.nolimits,
    iterations: iterations,
    fastcopy  : options.fastcopy,
    verbose   : options.verbose,
    log       : options.log,
  }
}

function dump(e) {
        var e = e ||(new Error('dummy'));
        var stack = e.stack.replace(/^[^\(]+?[\n$]/gm, '')
            .replace(/^\s+at\s+/gm, '')
            .replace(/^Object.<anonymous>\s*\(/gm, '{anonymous}()@')
            .split('\n');
        log(e);
        log('Stack Trace');
        log('--------------------------------');
        for(var i in stack) {
            if (i>0) {
                var line = stack[i];
                if(line.indexOf('Module.',0)>=0) break;
                log(line);
            }
        }
        log('--------------------------------');
};
/** Emit event
 *  function emit(@event,@arg1,..)
 */
function emit() {
  if (events[arguments[0]]) 
    events[arguments[0]](arguments[1],arguments[2],arguments[3],arguments[4],arguments[5]);
}
/** Try to get the source position of an error raised in an agent activity
 *
 */
function errorLocation(process,err) {
  try {
    var stack = err.stack.split('\n');
    for (var i in stack) {
      var line=stack[i];
      if (line.indexOf('at act.')>=0||line.indexOf('at F.act.')>=0) {        
        return line.replace(/\([^\)]+\)/,'').replace(/\)/,'');
      }
      else if (line.indexOf('at trans.')>=0 || line.indexOf('at F.trans.')>=0) {        
        return line.replace(/\([^\)]+\)/,'').replace(/\)/,'');
      }
    }
    return '';
  } catch (e) {
    return '';
  } 
}

// Execute a block scheduling function
function exec_block_fun(next) {
    var fun = next[0]||next,
        argn = next.length-1;
    switch (argn) {
        case 0:
        case -1:
            fun(); break;
        case 1: fun(next[1]); break;
        case 2: fun(next[1],next[2]); break;
        case 3: fun(next[1],next[2],next[3]); break;
        case 4: fun(next[1],next[2],next[3],next[4]); break;
        case 5: fun(next[1],next[2],next[3],next[4],next[5]); break;
        case 6: fun(next[1],next[2],next[3],next[4],next[5],next[6]); break;
        case 7: fun(next[1],next[2],next[3],next[4],next[5],next[6],next[7]); break;
        case 8: fun(next[1],next[2],next[3],next[4],next[5],next[6],next[7],next[8]); break;
        case 9: fun(next[1],next[2],next[3],next[4],next[5],next[6],next[7],next[8],next[9]); break;
        default:
            // TODO: fun.apply(undefined,next.slice(1))
            Io.err('Aios.exec_block_fun: more than 9 function arguments');
    }
}


/** Fork the current agent with an optional new set of parameters.
 *
 */
function fork(parameters) {
  return current.process.fork(parameters);
}

/** Kill an agent (if agent identifier is undefined the current agent will be killed).
 *
 */
function kill(agent) {
  var process;
  if (!agent) {
    process=current.process;
  } else {
    process=current.node.processes.process(agent);
  }
  if (options.debug.kill) console.log('Aios.kill',agent,process!=null);
  if (process) {
    process.kill=true;
    current.node.unregister(process);
    return true;
  } else if (current.node.processes.gone[agent]) {
    // migrated agent: try to send kill signal!
    Sig.agent.send(agent,'PROC.KILL',9,current.process.agent.id);
  } else return false; 
}

function killOn(agent,node) {
  var process;
  process=node.processes.process(agent); 
  if (process) {
    process.kill=true;
    node.unregister(process);
  };
}

/** Lock the global namespace. Disable inter-agent communication
 *  by using the global namespace => Sandbox (level 2)
 *
 */
function lock() {
  Object.preventExtensions(global);
}

/** Execute agent processes until there are no more schedulable agents.
 *  Loop returns if there are no more runnable agents. If there are waiting
 *  agent processes, the loop will be rescheduled on the earliest time event.
 *
 */
 
function loop(services) {
  var nexttime = scheduler(services);
  if (nexttime>0) {
    // Nothing to do.
    // Sleep until next event and re-enter the scheduling loop.
    if (options.verbose>3) log('[LOOP '+current.node.id+'] next schedule on '+ nexttime);
    timer=setTimeout(function () {loop (services)},nexttime-time());
  }
}

function min0(a,b) { return a==0?b:(b==0?a:Comp.pervasives.min(a,b)) };

/** Call agent exception handler. If exception was handled by agent return true, otherwise false.
 *
 */
function handleException(process,exc,arg1,arg2,arg3,arg4) {
  var agent=process.agent;
  if (Aios.watchdog && Aios.watchdog.protect) {
    try { Aios.watchdog.protect(function () {agent.on[exc].call(agent,arg1,arg2,arg3,arg4)})} catch(e) {
      // If there is no handler managing the error (e.g. SCHEDULE), the agent must be terminated!
      if (options.verbose) logAIOS ('Agent '+agent.id+' ['+agent.ac+'] failed handling '+exc+'('+arg1+')');
      process.kill=true
      return false;
    };
  } else
    try {agent.on[exc].call(agent,arg1,arg2,arg3,arg4)} catch(e) {
      // If there is no handler managing the error (e.g. SCHEDULE), the agent must be terminated!
      if (options.verbose) logAIOS ('Agent '+agent.id+' ['+agent.ac+'] failed handling '+exc+'('+arg1+')');
      process.kill=true
      return false;
    }
  return true;
}

/** Agent resource constraint negotiation
 *
 */
function negotiate (level,resource,value,cap) {
  var obj,security=options.security;
  // Check capability rights
  function checkRights(r) {
    return (level > 1 || 
           (cap && security[cap.cap_port] && Sec.Private.rights_check(cap.cap_priv,security[cap.cap_port],r))) 
  
  }
  switch (resource) {
    case 'LIFE':
    case 'LIFETIME':
      if (!checkRights(Sec.Rights.NEG_LIFE)) return false;
      current.process.resources.LIFE=value; break;
    case 'CPU':
    case 'TIMEPOOL':
      if (!checkRights(Sec.Rights.NEG_CPU)) return false;
      current.process.resources.CPU=value; break;
    case 'SCHED': 
    case 'SCHEDULE': 
    case 'TIMESCHED': 
      if (!checkRights(Sec.Rights.NEG_SCHED)) return false;
      current.process.resources.SCHED=value; break;
    case 'MEM': 
    case 'MEMORY': 
    case 'MEMPOOL': 
      if (!checkRights(Sec.Rights.NEG_RES)) return false;
      current.process.resources.MEM=value; break;
    case 'TS': 
    case 'TSPOOL': 
      if (!checkRights(Sec.Rights.NEG_RES)) return false;
      current.process.resources.TS=value; break;
    case 'AGENT': 
    case 'AGENTPPOL': 
      if (!checkRights(Sec.Rights.NEG_RES)) return false;
      current.process.resources.AGENT=value; break;
    case 'LEVEL': 
      if (!checkRights(Sec.Rights.NEG_LEVEL)) return false;
      // Extend process mask TODO!
      switch (value) {
        case 1:
        case 2:
          current.process.upgrade(value);
          break;
      }
      break;
    case '?':
      obj=Comp.obj.copy(current.process.resources);
      Comp.obj.extend(obj,{
        SCHED:  current.process.resources.SCHED||options.TIMESCHED,
        CPU:    current.process.resources.CPU||options.TIMEPOOL,
        MEM:    current.process.resources.MEM||options.MEMPOOL,
        TS:     current.process.resources.TS||options.TSPOOL,
        AGENT:  current.process.resources.AGENT||options.AGENTPOOL,
      });
      return obj;
      break;
    default: return false;
  }  
  return true;
}



/** Event callback management
 *
 */
function off(event) {
  // TODO: care of function chains??
  events[event]=undefined;
}
function on(event,fun) {
  if (events[event]) {
    // Implement callback function chain
    var funorig=events[event];
    events[event]=function () {
      funorig.apply(this,arguments);
      fun.apply(this,arguments);    
    };
  } else
    events[event]=fun;
}

function out(str) {log(str)};

/** Get current resource allocation of process memory
 *
 */
function resource(r0) {
  var r;
  if (!options.useproc) return 0;
  // Time expensive operation: requires system call and a lot of internal computation
  r=process.memoryUsage();
  // console.log(r)
  if (r0==undefined) 
    return {r:r.rss-r.heapTotal,h:r.heapUsed};
  else return int((Math.max(0,r.rss-r.heapTotal-r0.r)+Math.max(0,r.heapUsed-r0.h))/1024);
}

/** Scheduling function for one agent process.
 *
 *  Scheduling order:
 *    1. Process Blocks (process.block, passed to global DOS scheduler)
 *    2. Signals (process.signals, handled by AIOS scheduler)
 *    3. Transition (process.transition==true, handled by AIOS scheduler)
 *    4. Agent Blocks (process.schedule, handled by AIOS scheduler)
 *    5. Activity (handled by AIOS scheduler)
 *
 */
var SA = {
  NOOP:0,
  BLOCK:1,
  NORES:2,
  SIG:3,
  TRANS:4,
  SCHED:5,
  ACT:6,
  print: function (op) {
    switch (op) {
      case SA.NOOP: return 'NOOP';
      case SA.BLOCK: return 'BLOCK';
      case SA.NORES: return 'NORES';
      case SA.SIG: return 'SIG';
      case SA.TRANS: return 'TRANS';
      case SA.SCHED: return 'SCHED';
      case SA.ACT: return 'ACT';
    }
  }
}

// One scheduler run
function schedule(process) {
  var exec,sig,start,delta,next,
      _current,
      node=current.node,
      agent=process.agent,
      action='',
      op=SA.NOOP,
      handled,
      exception,
      curtime,
      r0;

  ticks++;   // move to scheduler ???
  // console.log(process);
  assert((process.agent!=undefined && process.id=='agent')||('Aios.schedule: not an agent process: '+process.id));

  /* Order of operation selection:
  **
  ** -1: Lifetime check
  ** 0. Process (internal) block scheduling [block]
  ** 1. Resource exception handling
  ** 2. Signal handling [signals]
  **    - Signals only handled if process priority < HIGH 
  **    - Signal handling increase proecss priority to enable act scheduling!
  ** 3. Transition execution
  ** 4. Agent schedule block execution [schedule]
  ** 5. Next activity execution
  */
  curtime = time();
  
  if (!options.nolimits && !process.kill && 
      (process.resources.start+(process.resources.LIFE||options.LIFETIME))<
       (curtime-current.world.lag)) op=SA.NORES; 
  else if (process.blocked ||
      (process.suspended==true && process.block.length==0 && process.signals.length==0) ||
      process.dead==true ||
      (agent.next==none && process.signals.length==0 && process.schedule.length == 0)) op=SA.NOOP;
  // if (process.suspended==true && process.schedule.length==0 && process.signals.length==0) op=SA.NOOP;
  else if (!process.blocked && process.block.length > 0) op=SA.BLOCK;
  else if (!options.nolimits && 
           (process.resources.consumed>(process.resources.CPU||options.TIMEPOOL) || 
            process.resources.memory>(process.resources.MEM||options.MEMPOOL)
           ))  
          op=SA.NORES;
  else if (process.priority<Proc.PRIO.HIGH && process.signals.length>0) op=SA.SIG;
  else if (!process.suspended && process.transition) op=SA.TRANS;
  else if (!process.suspended && process.schedule.length > 0) op=SA.SCHED;
  else if (!process.suspended) op=SA.ACT;
  else if (process.signals.length>0) op=SA.SIG;
  
  if (options.verbose>3) print('[SCH] '+time()+' '+process.agent.id+' : '+
                               SA.print(op)+' [susp='+process.suspended+
                               ',trans='+process.transition+',tmo='+process.timeout+']');
  
  if (op==SA.NOOP) return 0;

  start=curtime;
  
  if (Aios.watchdog) Aios.watchdog.start(process.resources.SCHED||options.TIMESCHED);
  else if (!options.nolimits)
    process.runtime=start-current.world.lag+(process.resources.SCHED||options.TIMESCHED); 
  if (!options.nolimits)
    r0=resource(); // Start resource monitor
  
  current.process=process;
  current.error=none;
  if (current.scheduler) _current=current.scheduler.SetCurrent(process);
  try {
    switch (op) {  
      case SA.BLOCK:
        // An internal schedule block [Linear/Loop]
        // Pass to global scheduler
        // console.log(process.block)
        schedule_block(process);  
        break;
      case SA.NORES:
        throw 'EOL';
        break;
      case SA.SIG:
        /* Execute a signal handler 
        ** 1. A signal handler can wakeup a suspended agent process by calling wakeup()
        ** 2. A signal handler can wakeup a suspended agent process by modifying variables and satisfying the current
        **    transition condition resulting in an activity transition!
        */
        if (!process.suspended && !process.transition) process.priority++;   
          // Pending activity execution -> block signal handling temporarily
        action='signal';
        sig=Comp.array.pop(process.signals);
        try {
          // sig=[signal,argument?,from?]
          agent.on[sig[0]].call(agent,sig[1],sig[2],sig[3]);
          if (process.suspended && process.transition) process.suspended=false; // ==> 2.)
        } catch(e) {
          if (!agent.on[sig[0]]) 
            logAIOS ('Signal handler '+sig[0]+' in agent '+agent.id+' ['+agent.ac+'] not defined, ignoring signal.');
          else 
            logAIOS ('Signal handler '+sig[0]+' in agent '+agent.id+' ['+agent.ac+'] failed: '+e+
                      (current.error?' / '+current.error:'')+', in: \n'+Code.print(agent.on[sig[0]])+
                      +errorLocation(process,e))
          current.error=none;
          process.kill=true; // Always?
        };  
        Aios.emit('signal+',process,node,sig[0],sig[1],sig[2]);
        break;
      case SA.TRANS:
        // Pending next computation: Compute next transition after wakeup or after a signal was handled.
        // If still not successfull, suspend agent process.
        try {
          action='transition';
          if (!agent.trans[agent.next]) throw "NOTDEFINED";
          next=(typeof agent.trans[agent.next] == 'function')?
                agent.trans[agent.next].call(agent):
                agent.trans[agent.next];
          // TODO: check blocking state - transitions may not block!
          if (next) {
            agent.next=next;
            process.suspended=false;
            process.transition=false;
          } else {
            process.suspended=true;      
          }
        } catch (e) {
          if (agent.trans[agent.next]==undefined) 
            logAIOS ('Transition table entry '+agent.next+' not defined in agent '+agent.id+' ['+agent.ac+'].');
          else 
            logAIOS ('Agent '+agent.id+' ['+agent.ac+'] in transition '+agent.next+
                      ' failed:\n'+e+(current.error?' / '+current.error:'')+
                      +errorLocation(process,e));
          process.kill=true;
          current.error=none;      
        }
        break;
      case SA.SCHED:
        // An agent schedule block function [Linear/Loop] executed in agent context
        action='block';
        exec = Comp.array.pop(process.schedule);
        Aios.watchdog&&Aios.watchdog.protect?Aios.watchdog.protect(exec.bind(agent)):exec.call(agent);
        if (!process.kill && !process.suspended && process.schedule.length == 0) {
          if (process.notransition) {
            // prevent transition after this process.schedule was executed.
            process.notransition=false;
          } else {
            // next=agent.trans[agent.next].call(agent);      
            next=(typeof agent.trans[agent.next] == 'function')?agent.trans[agent.next].call(agent):agent.trans[agent.next];
            if (!next) process.suspend(0,true); // no current transition enabled; suspend process
            else agent.next=next;
          } 
        }
        break;
      case SA.ACT:
        // Normal activity execution
        // console.log('[SCH] next:'+agent.next)
        if (process.priority==Proc.PRIO.HIGH) process.priority--;
        action='activity';
        if (agent.next==none) throw 'KILL';
        Aios.watchdog&&Aios.watchdog.protect?
          Aios.watchdog.protect(agent.act[agent.next].bind(agent)):
          agent.act[agent.next].call(agent);
        if (!process.kill && !process.suspended && process.schedule.length == 0) {
          action='transition';
          // next=agent.trans[agent.next].call(agent);
          if (!agent.trans[agent.next]) throw "NOTDEFINED";
          next=(typeof agent.trans[agent.next] == 'function')?
                  agent.trans[agent.next].call(agent):
                  agent.trans[agent.next];
          // TODO: check blocking state - transitions may not block!
          if (!next) process.suspend(0,true); // no current transition enabled; suspend process
          else agent.next=next; 
        } 
        break;
    }   
  } catch (e) {
    if (Aios.watchdog) Aios.watchdog.stop();
    curtime=time()-current.world.lag;
    exception=true;
    switch (e) {
      case 'SCHEDULE':
      case 'WATCHDOG':
        e='SCHEDULE';
        if (Aios.watchdog) Aios.watchdog.start(options.TIMESCHED/10); 
        else process.runtime=curtime+options.TIMESCHED/10;
        handleException(process,'error',e,options.TIMESCHED,agent.next);
        break;
      case 'EOL':
        if (Aios.watchdog) Aios.watchdog.start(options.TIMESCHED/10); else
        process.runtime=curtime+options.TIMESCHED/10;
        // New time or memory contingent must be negotiated based on policy!
        if (process.resources.consumed>=(process.resources.CPU||options.TIMEPOOL)) {
          handleException(process,'error','CPU',e,process.resources.consumed,agent.next);
          if (process.resources.consumed>=(process.resources.CPU||options.TIMEPOOL)) 
            process.kill=true;
        } else if (process.resources.memory>=(process.resources.MEM||options.MEMPOOL)) {
          handleException(process,'error','EOM',process.resources.memory,agent.next);
          if (process.resources.memory>=(process.resources.MEM||options.MEMPOOL))
            process.kill=true;
        } else if (process.resources.tuples>=(process.resources.TS||options.TSPOOL)) {
          handleException(process,'error','EOT',process.resources.memory,agent.next);
          if (process.resources.tuples>=(process.resources.TS||options.TSPOOL))
            process.kill=true;
        } else if ((process.resources.start+(process.resources.LIFE||options.LIFETIME))
                   <curtime) {
          handleException(process,'error','EOL',process.resources.memory,agent.next);
          if ((process.resources.start+(process.resources.LIFE||options.LIFETIME))
              <curtime)
            process.kill=true;
        } else {
          // TODO generic resource overflow?
          handleException(process,'error','EOR',0,agent.next);
          process.kill=true;
        }
        break;
      case 'KILL':
        if (Aios.watchdog) Aios.watchdog.start(options.TIMESCHED/10); 
        else process.runtime=curtime+options.TIMESCHED/10;
        handleException(process,'exit');
        process.kill=true;
        break;
      case 'NOTDEFINED':
        if (agent.act[agent.next]==undefined && options.verbose) 
          logAIOS('Activity '+agent.next+' not defined in agent '+
                   agent.id+' ['+agent.ac+'].');
        else if (agent.trans[agent.next]==undefined && options.verbose) 
          logAIOS('Transition table entry '+agent.next+' not defined in agent '+agent.id+' ['+agent.ac+'].');
        process.kill=true;
        current.error=none;
        break;
      default:
        handled=handleException(process,aiosExceptions.indexOf(e.toString())!=-1?e:'error',e,current.error,agent.next);
        if (!handled && options.verbose) 
          logAIOS ('Agent '+agent.id+' ['+agent.ac+'] in '+(action=='block'?'block in':action)+' '+
                  (action=='signal'?sig[0]:agent.next)+
                  ' failed: Error '+e+(current.error?('; '+current.error):'')+
                  (options.verbose>1?(
                    ', in code: \n'+(
                      action=='activity'?Code.print(agent.act[agent.next]):
                        (action=='transition'?Code.print(agent.trans[agent.next]):
                          (agent.on && sig && agent.on[sig[0]])?Code.print(agent.on[sig[0]]):'none')  
                    )+
                    errorLocation(process,e)
                  ):'')
                  );
        if (options.verbose>2 && ['CREATE','MOVE','SIGNAL'].indexOf(e) == -1) Io.printstack(e);             
        if (!handled) process.kill=true;
        else {
          action='transition';
          // next=agent.trans[agent.next].call(agent);
          if (!agent.trans[agent.next]) throw "NOTDEFINED";
          next=(typeof agent.trans[agent.next] == 'function')?
                  agent.trans[agent.next].call(agent):
                  agent.trans[agent.next];
          // TODO: check blocking state - transitions may not block!
          if (!next) process.suspend(0,true); // no current transition enabled; suspend process
          else agent.next=next;
        }
        current.error=none;
    }
  }
  if (Aios.watchdog) Aios.watchdog.stop();
  else process.runtime=0;
  
  if (!options.nostats) {
    delta=(time()-start)||options.MINCOST;
    process.resources.consumed += delta;
    process.resources.memory += resource(r0);
    current.node.stats.cpu += delta;
  }

  if (options.verbose && exception && process.kill) logAIOS('Killed agent '+agent.id);

  if (current.scheduler) current.scheduler.SetCurrent(_current);

  current.process=none;

  if (options.verbose>3) print(time()+' <- '+process.print());
  
  return 1;
}

/**
 * Internal block scheduling
 */
 

function schedule_block(process) {
    var next;
    /*
     ** Process current function block sequence first!
     ** Format: [[fun,arg1,arg2,...],[block2], [block3], ..]
     ** Simplified: [fun,fun,...]
     */
    if (!process.blocked) {
        next = process.block[0];
        process.block.splice(0,1);
        /*
         ** Do no execute handler blocks maybe at the end of a subsection
         ** of the block list.
         */
        while (!Comp.array.empty(process.block) && next.handler!=undefined) {
            next = process.block[0];
            process.block.splice(0,1);
        }
        if (next.handler==undefined) {
            try {exec_block_fun(next)} catch(e) {
                /*
                 ** Iterate through the block list and try to find a handler entry.
                 */
                while (next.handler==undefined && !Comp.array.empty(process.block)) {
                    next = process.block[0];
                    process.block.splice(0,1);
                }
                if (next.handler!=undefined) {
                    /*
                     ** Call handler ...
                     */
                    // console.log(next.handler.toString())
                    try {exec_block_fun([next.handler,e])} 
                    catch (e) {
                      Io.out('Aios.schedule_block [Internal B], in agent context '+
                             process.agent.id+', got exception in exception handler: '+e);
                      // Io.printstack(e);
                      Io.out(Json.stringify(next).replace(/\\n/g,'\n'));
                    };
                } else {
                    logAIOS ('Agent '+process.agent.id+' ['+process.agent.ac+'] in activity '+
                              process.agent.next+
                              ' failed:\n'+e+(current.error?' / '+current.error:', in: \n'+
                              Code.print(process.agent.act[process.agent.next]))+
                              '');// '\nat:\n'+Io.sprintstack(e)));
                    process.kill=true;
                    current.error=none;
                }
            }
        }
    }
}

var scheduled=0;

/** Main scheduler entry.
 *  Returns the next event time (absolute time!), negative number of scheduled agent processes, or zero
 *  if there is nothing to schedule.
 *  If result is negative, the scheduler should be executed immediately again because there
 *  can be pending agent signals created in the current run.
 */
function scheduler(services) {
  var run=1,nexttime=0,n=0,curtime,process,env,node,pro,
      timeout=time()+options.RUNTIME;
      
  scheduled=0;
  while (run && (iterations==0 || n<iterations) && time()<timeout) {
    run=0; n++;
    if (services) services();
    nexttime=options.IDLETIME?Sig.agent.timeout(options.IDLETIME):0;

    for (env in current.world.nodes) {
      node=current.world.nodes[env];
      if (!node) continue;
      current.node=node;
      curtime=time()-current.world.lag;
      // 1. Timer management
      if (node.timers.length>0) {
        remove=false;
        // 1.1. Check timers and execute runnable signaled agents
        Comp.array.iter(node.timers, function(timer,i) {
            if (timer && timer[1]<=curtime) {
              var process=timer[0],
                  agent=process.agent,
              // Save original process state
                  suspended=process.suspended,
                  timeout=process.timeout;
              
              // process.suspeneded=false;  ?? Signal handler can be executed even with blocked process
              process.signals.push([timer[2],timer[3],agent.id]);
              // TODO: A wakeup call in the signal handler re-enters schedule() !!!
              run += schedule(process);
              curtime=time()-current.world.lag;
              if (timer[4]>0) { 
                // repeat
                timer[1] = curtime + timer[4];
              } else {
                remove=true;        
                node.timers[i]=undefined;
              }
              // Restore original process state
              //process.suspended=suspended; ??
              process.timeout=timeout;
            } else if (timer) nexttime=min0(nexttime,timer[1]);
          });
        // 1.2. Timer destruction
        if (remove) 
          node.timers=
            Comp.array.filter(node.timers,function (timer) {
              return timer!=undefined;
            });
      }
      
      curtime=time()-current.world.lag;
      // Node service management (caches, TS)
      node.service(curtime);
      
      // 3. Agent process management
      for (pro in node.processes.table) {
        if (node.processes.table[pro]) {
          // 2.1 Agent execution
          curtime=time()-current.world.lag;
          process=node.processes.table[pro];
          // Io.out('scheduler: checking '+process.agent.id+': '+process.suspended+' '+process.timeout);
          if (process.suspended && process.timeout && process.timeout<=curtime) {
            // Io.out('scheduler: waking up '+process.agent.id);
            process.wakeup();
          }
          run += schedule(process);
          // 2.2 Agent destruction
          if (node.processes.table[pro] && node.processes.table[pro].kill) 
            node.unregister(node.processes.table[pro]);
          if (node.processes.table[pro] && process.suspended && process.timeout>0) 
            nexttime=min0(nexttime,process.timeout);
        }
      }
    }
    scheduled += run;
  }
  if (scheduled>0) return -scheduled;
  else if (nexttime>0) return nexttime;
  else return 0;
}

/*
** The time function can be changed, e.g., by simulators handling simulation
** steps instead of real time. Can be changed with Aios.config({time:fun}), 
** updating all Aios/aiosX references and CP as well.
*/
var time = function () {return Math.ceil(Date.now())};
  
var Aios = {
  aidgen:aidgen,
  aios:aios1,
  aios0:aios0,
  aios1:aios1,
  aios2:aios2,
  aios3:aios3,
  aiosEvents:aiosEvents,
  Amp:Amp,
  callback:undefined,
  clock:clock,
  collect:Ts.agent.collect,
  // External API: Change AIOS settings only using config!
  config:config,
  configGet:configGet,
  current:current,
  emit:emit,          // Emit event
  err: function (msg) {if (options.verbose) log('Error: '+msg)},
  fork:fork,
  kill:kill,
  killOn:killOn,
  lock:lock,
  loop:loop,
  log:log,            // Generic AIOS logging function
  logAgent:logAgent,  // Agent message logging (with details about current)
  logAIOS:logAIOS,    // AIOS logging function related with agent proecssing (with details about current)  
  logAIOSasync:logAIOSasync,    // AIOS logging function related with agent proecssing (with details about current)  
  logAsync:logAsync,  // AIOS logging function related with agent proecssing (with details about current) async  
  off:off,            // Remove event handler
  on:on,              // Add event handler
  options:options,
  print:Io.out,         // Low-level print IO function for agent messages via Aios.aiosX.log and internal Aios.log; 
                        // OR if printAgent is set only AIOS internal messages; can be modified by host app
  printAgent:undefined, // Low-level print IO function for agent messages only via Aios.aiosX.log; can be modified by host app
  printAsync:undefined, // Low-level print IO function for async callback messages by host app
  schedule:schedule,
  scheduled:scheduled,
  scheduler:scheduler,
  ticks:function (v) { if (v!=undefined) ticks=v; else return ticks},
  time:time,
  timeout:function (tmo) { return tmo>0?Aios.time()-current.world.lag+tmo:0 },  // Compute absolute time from relative timeout
  Chan:Chan,
  Code:Code,
  Json:Json,
  Mobi:Mobi,
  Name:Name,
  Node:Node,
  Proc:Proc,
  Sec:Sec,
  Sig:Sig,
  Simu:Simu,
  Ts:Ts,
  World:World,
  CB:CB,
  CP:CP,
  RT:RT,
  B:B,
  DIR:Mobi.DIR,
  I:I,
  L:L,
  warn: function (msg) {if (options.verbose>1) log('Warning: '+msg)},
  watchdog: undefined,
  version : options.version
}

// Builtin watchdog support by JS VM platform?
if (watchdog && watchdog.start) Aios.watchdog=watchdog;
if (watchdog && watchdog.init)  watchdog.init('WATCHDOG');
if (watchdog && watchdog.checkPoint) {
  // only partial watchdog support by platform
  aios0.CP=watchdog.checkPoint;
  aios1.CP=watchdog.checkPoint;
  aios2.CP=watchdog.checkPoint;
  aios3.CP=watchdog.checkPoint;
  Aios.CP=watchdog.checkPoint;
}

Conf.current(Aios);
Code.current(Aios);
Sig.current(Aios);
Sec.current(Aios);
Ts.current(Aios);
Proc.current(Aios);
Node.current(Aios);
World.current(Aios);
Mobi.current(Aios);
if (Simu) Simu.current(Aios);
Chan.current(Aios);
Json.current(Aios);

module.exports = Aios;
};
BundleModuleCode['jam/conf']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2006-2022 bLAB
 **    $CREATED:     15-1-16 by sbosse.
 **    $RCS:         $Id: conf.js,v 1.2 2017/05/23 07:00:43 sbosse Exp $
 **    $VERSION:     1.3.1
 **
 **    $INFO:
 **
 **  JavaScript AIOS Agent Reconfiguration Sub-System
 **
 **    $ENDOFINFO
 */

var Json = Require('jam/jsonfn');
var Comp = Require('com/compat');
var current=none;
var Aios = none;

var act = {
  add: function (act,code) {
    if (typeof code == 'function') current.process.agent.act[act]=code;
    else if (typeof code == 'string') {
      with(current.process.mask) {
        current.process.agent.act[act]=eval(code);
      }    
    } 
    // Add the new activity to the mask environment of the agent for further referencing.
    current.process.mask[act]=act;
  },
  // return deleted activity(ies)
  delete: function (act) {
    if(Comp.obj.isArray(act)) return Comp.array.map(act,function (a) { 
      var f=current.process.agent.act[a]; 
      current.process.agent.act[a]=undefined;
      return f;
    });
    else {
      var f = current.process.agent.act[act];
      current.process.agent.act[act]=undefined;
      return f;
    }
  },
  update: function (act,code) {
    if (typeof code == 'function') current.process.agent.act[act]=code;
    else if (typeof code == 'string') current.process.agent.act[act]=Json.parse(code,current.process.mask);
  }
};
var __eval=eval;
function sandBoxCode(mask,code) {
  var __code;
  with (mask) {
    __code=__eval('__code='+code);
  }
  // console.log(typeof __code);
  return __code;
}
var trans = {
  add: function (from,cond,data) {
    if (current.process.agent.trans[from]) {
      function wrap(s) { return s[0]=='"'||s[0]=="'"?s:'"'+s+'"' };
      var old = current.process.agent.trans[from];
      if (typeof old != 'function') old='function () { return'+wrap(old)+'}';
      else old = old.toString();
      if (typeof cond != 'function') cond='function () { return'+wrap(cond)+'}';
      else cond = cond.toString();
      if (data) data=JSON.stringify(data);
      else data='null';
      var merged = 'function () { var next = ('+cond+').call(this,'+data+'); if (next) return next; else return ('+old+').call(this) }';
      // console.log(merged);
      var code = sandBoxCode(current.process.mask,merged);
      current.process.agent.trans[from]=code;
      // console.log(typeof current.process.agent.trans[from])
    } else current.process.agent.trans[from]=cond;
  },
  // return deleted transaction(s)
  delete: function (trans) {
    if(Comp.obj.isArray(trans)) Comp.array.iter(trans,function (t) {
      var f = current.process.agent.trans[t];
      current.process.agent.trans[t]=undefined;
      return f
    });
    else {
      var f = current.process.agent.trans[trans];
      current.process.agent.trans[trans]=undefined;
      return f;
    }
  },
  update: function (from,cond) {
    current.process.agent.trans[from]=cond;
  }  
}


module.exports = {
  agent:{
    act:act,
    trans:trans
  },
  current:function (module) { current=module.current; Aios=module; }
}
};
BundleModuleCode['jam/code']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2006-2022 bLAB
 **    $CREATED:     15-1-16 by sbosse.
 **    $RCS:         $Id: code.js,v 1.4 2020/02/03 09:45:01 sbosse Exp sbosse $
 **    $VERSION:     1.12.1
 **
 **    $INFO:
 **
 **  JavaScript AIOS Agent Code Management Sub-System
 **
 **  New: Correct soft code minifier with char state machine (CSM) if there is no builtin minifier
 **  New: Check pointing (CP) can be replaced with JS VM watchdog timer.
 **  New: Fast sandboxed constructor
 **  New: Dual mode JSON+/JSOB (with auto detection in toCode)
 **  New: Dirty fastcopy process copy (JS object copy)
 **  New: Function toString with minification (if supported by platform)
 **
 **  JSOB: Simplified and compact textual representation of JS object including function code
 **        (Aios.options.json==false)
 **
 **    $ENDOFINFO
 */
 
var options = {
  debug:{},
  compactit : false,  // no default compaction, only useful with Aios.options.json==true
  version   : '1.12.1'
}
// Fallback minifier with char state machine
var minify = Require('com/minify');

var Esprima = Require('parser/esprima');

try {
  // Use built-in JS code minimizer if available
  var M = process.binding('minify');
  Function.prototype._toString=Function.prototype.toString;
  Function.prototype.toString = function (compact) {
    return compact?M.minify(this._toString()):this._toString();
  }
  options.minifier=true;
  options.minifyC=true;
  minify=M.minify;
} catch (e) {
  Function.prototype._toString=Function.prototype.toString;
  Function.prototype.toString = function (compact) {
    return compact?minify(this._toString()):this._toString();
  }
  options.minifier=true;
}; 
 
var Io = Require('com/io');
var Json = Require('jam/jsonfn');
var Comp = Require('com/compat');
var sandbox = Require('jam/sandbox')();
var current=none;
var Aios = none;
var util = Require('util');

/* Test if Json.stringify returns compacted code, otherwise text must be compacted here */
function _testac (p1,p2) {
  /* comment */
  this.x = p1;
  this.y = p2;
  this.z = 0;
  this.act = {
    init: function () {
      /* comment */
      this.z=this.x;
      this.x++;
    }
  }
}
var _testobj = new _testac(1,2);
options.compactit=Json.stringify(_testobj).length>93;
var inject = {cp:undefined,rt:undefined};

// options.compactit=false;

/** Construct an agent object with given arguments (array)
 *
 */

function construct(constructor,argArray) {
  var inst = Object.create(constructor.prototype);
  constructor.apply(inst, argArray);
  return inst;  
}

/** Fast dirty copy (fork): Return a fresh copy of the agent object (i.e., process.agent, instead using ofCode/toCode transf.)
 *  attached to a new process object.
 *  All function and AIOS references will be copied as is. The AIOS level cannot be changed. The mask of the 
 *  parent process is now valid for the copied process, too. Any changes in the parent environment effects the child
 *  process, too, and vice versa.
 *
 */
function copyProcess(process) {
  var _process,_agent,agent=process.agent,mask=process.mask;

  process.node.stats.fastcopy++;

  agent.process={};
  
  for (var p in process) {
    switch (p) {
      case 'schedule':
        if (process.schedule.length > 0) 
          agent.process[p]=process.schedule;
        // keep it only if <> []        
        break;
      case 'blocked':
        if (agent.process.suspended==true) 
          agent.process[p]=true;
        // keep it only if it is true   
        break;
      case 'gid':
      case 'pid':
        break; // ?????
      // case 'delta':
      case 'back':
      case 'dir':
        // keep it
         agent.process[p]=process[p];
        break;
    }
  }
  if (Comp.obj.isEmpty(agent.process)) agent['process']=undefined;
  agent['self']=undefined;

  _agent=Comp.obj.copy(agent);
  
  if (!_agent.process) 
    _process=Aios.Proc.Proc({mask:mask,agent:_agent});
  else {
    _process=Aios.Proc.Proc(agent.process);
    _process.init({timeout:0,schedule:[],blocked:false,mask:mask,agent:_agent});
    _agent['process']=undefined;
  }
  agent['self']=agent;
  return _process;
}

/** Return name of a class constructor function
 *
 */
function className (f) {
  var name=f.toString().match(/[\s]*function[\s]*([a-z0-9]+)[\s]*\(/);
  return name?name[1]:"unknown"
}

/** Create a sandboxed agent object process on the current node
 *  using either a sandboxed agent constructor object {fun,mask}
 *  or a generic agent class constructor function that is sandboxed here.
 *
 *  Returns process object.
 *
 * type create = 
 *  function (node:node,
 *            constructor:function|{fun:function,mask:{}},
 *            args:{}|[],level?:number,className?:string) -> process
 */
function create(constructor,args,level,className) {
  return createOn(current.node,constructor,args,level,className);
}

/** Create a sandboxed agent process on a specified node
 *  using either a sandboxed agent constructor object {fun,mask}
 *  or a generic agent class constructor function that is sandboxed here.
 *  
 *
 *  Returns process object.
 *
 * type createOn = 
 *  function (node:node,
 *            constructor:function|{fun:function,mask:{}},
 *            args:{}|*[],level?:number,className?:string) -> process
 */
function createOn(node,constructor,args,level,className) {
  if (!constructor.fun && !Comp.obj.isFunction(constructor)) {
    Aios.err('Code.create: No valid constructor function specified.');
    return;
  }
    
  var code,
      agent0,
      agent,
      process,
      _process;
      
  _process=current.process; 
  current.process={timeout:0};
  if (level==undefined) level=Aios.options.LEVEL;
  
  try {
    if (!constructor.fun) 
      constructor=makeSandbox(constructor,level);
    if (!(args instanceof Array)) args=[args];
    
    agent0= construct(constructor.fun,args);

    if (!agent0) {
      Aios.err('Code.createOn ('+className+'): Agent constructor failed.');
      current.process=_process;  
      return null;
    }

    process=makeProcess(agent0,constructor.mask);
    process.resources.memory=constructor.size||0;
    current.process=_process;  
  
  } catch (e) {
    current.process=_process;  
    Aios.err('Code.createOn ('+className+'): '+e);
    return;
  }
  
  agent=process.agent;
  if (!Comp.obj.isArray(args) && Comp.obj.isObject(args))
    for (var p in args) {
      if (Comp.obj.hasProperty(agent,p)) agent[p]=args[p];
    }
  // Test minimal structure requirements
  if (!agent['next'] || !agent['trans'] || !agent['act']) {    
    Aios.err('Code.createOn: Missing next/trans/act attribute in agent constructor '+className);
    return none;  // must be defined and initialized
  }
  process.level=level;
  agent['self']=agent;
  if (className) agent['ac']=className;
  node.register(process);

  node.stats.create++;
  
  return process;
}

// fork an egent from { x:this.x, .., act : {}|[], trans:{}, on:[}}
function createFromOn(node,_process,subclass,level) {
  var code,process;
  code = forkCode(_process,subclass);
  process = toCode(code,level);
  process.agent.id=Aios.aidgen();
  process.init({gid:_process.pid});
  node.register(process);
  var agent_=process.agent;
  if (!subclass.next) try {
    agent_.next=(typeof agent_.trans[agent_.next] == 'function')?agent_.trans[agent_.next].call(agent_):
                                                                 agent_.trans[agent_.next];
  } catch (e) { /*kill agent?*/ process.kill=true; };
  return process;
}

/** Create a compiled agent process in a sandbox environment from an 
 *  agent class constructor function on the current node.
 *  Returns JSON+/JSOB representation of agent process snapshot and
 *  the newly created process.
 *
 */
function createAndReturn(constructor,ac,args,level) {
  if (!(args instanceof Array)) args=[args];
/*
  var code = ofCode({agent:new constructor(args[0],args[1],args[2],args[3],
                                           args[4],args[5],args[6],args[7],
                                           args[8],args[9])},true);
*/
  var process,agent,
      code = ofCode({agent:construct(constructor,args)},true);
  if (level==undefined) level=Aios.options.LEVEL;
  process = toCode(code,level);
  agent=process.agent;
  agent.id=Aios.aidgen();
  agent.ac=ac;
  return {code:ofCode(process,false),process:process};
}

/** Fork an agent object and return JSON+/JSOB text code.
 *  Note: Forking discards current scheduling blocks (in contrast to migration)!!!
 *
 *  New: subclass: { x:this.x, .., act : {}|[], trans:{}, on:[}}
 *  Used to subclass parent agent
 *
 *  Returns cleaned code (w/o CP and internal AIOS properties).
 *
 */
function forkCode(process,subclass) {
  var code='',p;
  var agent,self;
  var agent;
  if (!subclass) {
    agent = process.agent;
    self = agent.self;
    // Clean up current agent process
    agent['process']=undefined;
    agent['self']=undefined;
  } else if (typeof subclass == 'object') {
    var agent = { act:{}, trans:{}, next:process.agent.next}
    console.log(subclass)
    for (p in subclass) {
      var o = subclass[p];
      switch (p) {
        case 'act':
          if (Comp.obj.isArray(o)) {
            for(var q in o) {
              var v = o[q];
              if (process.agent.act[v]) 
                agent.act[v]=process.agent.act[v];
            }           
          } else if (Comp.obj.isObject(o)) {
            for(var q in o) {
              agent.act[q]=o[q];
            }           
          }
          break;
        case 'trans':
          // only updates here
          if (Comp.obj.isObject(o)) {
            for(var q in o) {
              agent.trans[q]=o[q];
            }           
          }
          break;
        case 'on':
          agent.on={};
          if (Comp.obj.isArray(o)) {
            for(var q in o) {
              var v = o[q];
              if (process.agent.on[v])
                agent.on[v]=process.agent.on[v];
            }           
          } else if (Comp.obj.isObject(o)) {
            for(var q in o) {
              agent.on[q]=o[q];
            }           
          }
          break;
        case 'var':
          if (Comp.obj.isArray(o)) {
            for(var q in o) {
              var v = o[q];
              agent[v]=process.agent[v];
            } 
          }
          break;
        default:
          agent[p]=o;
          break;
      }
    }
    // include all remaining necessary transitions
    for(var p in agent.act) {
      if (!agent.trans[p] && process.agent.trans[p])
        agent.trans[p]=process.agent.trans[p];
    }
  } else throw "forkCode: invalid subclass"
    
  code=Aios.options.json?Json.stringify(agent):toString(agent);
  
  if (!subclass) {
    // Restore current agent process
    agent.process=process;
    agent.self=self;
  }
  
  // Cleanup required?
    
  // CP/RT removal
  if (inject.cp || inject.rt)
    code=removeInjection(code);
  return code;
}

/** Convert agent object code from a process to text JSON+/JSOB.
 *  Returns cleaned code (w/o CP and internal AIOS properties).
 *  @clean: Code is already clean, no further filtering
 *
 */
function ofCode(process,clean) {
  var code='',p;
  var agent=process.agent;
  agent.process={};
  
  for (var p in process) {
    switch (p) {
      case 'schedule':
        if (process.schedule.length > 0) 
          agent.process[p]=process.schedule;
        // keep it only if <> []        
        break;
      case 'blocked':
        if (agent.process.suspended==true) 
          agent.process[p]=true;
        // keep it only if it is true   
        break;
      case 'gid':
      case 'pid':
        break; // ?????
      // case 'delta':
      case 'back':
      case 'dir':
        // keep it
         agent.process[p]=process[p];
        break;
    }
  }
  if (Comp.obj.isEmpty(agent.process)) agent['process']=undefined;
  agent['self']=undefined;

  try {
    code=Aios.options.json?Json.stringify(agent):toString(agent);
  } catch (e) {
    throw 'Code.ofCode: Code-Text serialization failed ('+e.toString()+')';
  }
  if (clean && !options.compactit) return code;
  
  
  /* Compaction should only be done one time  on agent creation with compact=true option. 
  **
  */
    
  if (!clean && (inject.cp||inject.rt)) 
    // CP/RT removal; no or only partial watchdog support by platform
    code=removeInjection(code);

  if (options.compactit) code=minimize(code);

  return code;
}

/** Fast copy agent process creation (virtual, migrate).
 *  All function and AIOS references will remain unchanged. The AIOS level cannot be changed. The mask of the 
 *  original (died) process is now valid for the new process, too.
 */
function ofObject(agent) {
  var process;
  
  if (!agent.process) 
    process=Aios.Proc.Proc({mask:agent.mask,agent:agent});
  else {
    process=Aios.Proc.Proc(agent.process);
    process.init({timeout:0,schedule:[],blocked:false,mask:agent.mask,agent:agent});
    agent['process']=undefined;
  }
  agent['mask']=undefined;

  process.node.stats.fastcopy++;
    
  return process;
}

/** Convert agent text sources to agent code in JSOB format
 *
 */
function _ofString(source,mask) {
  var code;
  try {
    // execute script in private context
    with (mask) {
      eval('"use strict"; code = '+source);
    }
  } catch (e) { console.log(e) };
  return code; 
}
function ofString(source,mask) {
  // arrow functions do not have a this variable
  // they are bound to this in the current context!
  // need a wrapper function and a temporary this object
  var self={},temp;
  function evalCode() {
    var code;
    try {
      // execute script in private context
      with (mask) {
        eval('"use strict"; code = '+source);
      }
    } catch (e) { console.log(e) };
    return code;
  }
  temp = evalCode.call(self);
  // arrow functions are now bound to self, 
  // update self with temp. agent object first-level attributes
  self=Object.assign(self,temp);
  return self; 
}

function parseString (source,mask) {
  // deal with arrow functions correctly
 // arrow functions do not have a this variable
  // they are bound to this in the current context!
  // need a wrapper function and a temporary this object
  var self={},temp;
  function evalCode() {
    var code;
    try {
      code = Json.parse(source,mask);
    } catch (e) { console.log(e) };
    return code;
  }
  temp = evalCode.call(self);
  // arrow functions are now bound to self, 
  // update self with temp. agent object first-level attributes
  self=Object.assign(self,temp);
  return self; 
}
/** Create an agent process from agent object code
 *
 */
function makeProcess (agent,mask) {
  var process;
  // Add all activities to the masked environment:
  if (mask) for(var p in agent.act) {
    mask[p]=p;
  }
  if (!agent.process) 
    process=Aios.Proc.Proc({mask:mask,agent:agent});
  else {
    process=Aios.Proc.Proc(agent.process);
    process.init({timeout:0,schedule:[],blocked:false,mask:mask,agent:agent});
    agent['process']=undefined;
  }
  agent['self']=agent;
  
  return process;
}

/** Create a sandboxed agent class constructor object {fun,mask} from
 *  an agent class template constructor function providing 
 *  a sandboxed agent constructor function and the sandbox 
 *  mask agent environment. 
 *  The optional environment object 'env' can contain additional references, e.g.,
 *  activitiy references.
 *
 * Note: All agents created using the constructor function share the same mask
 *       object!
 *
 * typeof constructor = function|string
 * typeof sac = {fun:function, mask: {}, size:number } 
 */
function makeSandbox (constructor,level,env) {
  var _process,sac,aios;
  switch (level) {
    case 0: aios=Aios.aios0; break;
    case 1: aios=Aios.aios1; break;
    case 2: aios=Aios.aios2; break;
    case 3: aios=Aios.aios3; break;
    default: aios=Aios.aios0; break;
  }
  _process=current.process; 
  current.process={timeout:0};
  sac=sandbox(constructor,aios,inject,env);
  current.process=_process;
  return sac;
}

/** Minimize code text
 *
 */
function minimize (code) { return minify(code) }

/** Print agent code
 */
 
function print(agent,compact) {
  var process = agent.process;
  var self = agent.self;
  agent['process']=undefined;
  agent['self']=undefined;

  var text=Aios.options.json?Json.stringify(agent):toString(agent);

  agent.process=process;
  agent.self=self;

  if (!text) return 'undefined';
  
  var regex4= /\\n/g;

  if (inject.cp || inject.rt)
    // CP/RT removal; i.e., none or only partial watchdog support by platform
    text= removeInjection(text);

  if (compact && options.compactit)
    text=minimize(text);
    
  return text.replace(regex4,'\n');  
}

/** Remove CP/RT injections from code text
 *
 */
function removeInjection(text) {
  // CP removal
  if (inject.cp) {
    var regex1= /CP\(\);/g;
    var regex2= /\(\(([^\)]+)\)\s&&\sCP\(\)\)/g;
    var regex3= /,CP\(\)/g;
    text=text.replace(regex1,"").replace(regex2,"($1)").replace(regex3,"");
  }
  // RT removal
  if (inject.rt) {
    var regex4= /RT\(\);/g;
    text=text.replace(regex4,"");
  }
  return text;
}

/**  Returns size of cleaned code (w/o CP and internal AIOS properties).
 *
 */
function size(agent) {
  var text='',p;
  var process = agent.process;
  var self = agent.self;
  agent['process']=undefined;
  agent['self']=undefined;
  
  text=Aios.options.json?Json.stringify(agent):toString(agent);
  
  agent.process=process;
  agent.self=self;
  
  if (inject.cp || inject.rt) {   
    text=removeInjection(text);
  }

  return text.length;
}

function test (what) {
  switch (what) {
    case 'minify': if (minify.test) minify.test(); break;
  }
}

/** Convert JSON+/or JSOB text to an agent object process encapsulated in a sandbox (aios access only).
 *  Returns process container with CP injected agent code (process.agent).
 *
 *  CP Injection (required on generic JS VM platform w/o watchdog, e.g., node.js, browser):
 *    1. In all loop expressions (for/while)
 *    2. In all function bodies (start)
 *
 *  No watchdog: Aios.watchdog == undefined (nodes.js, browser)
 *  Full watchdog implementation: Aios.watchdog && Aios.watchdog.checkPoint==undefined (jvm)
 *  Partial watchdog implementation with checkPoint function: Aios.watchdog.checkPoint (jxcore)
 * 
 *
 */
function toCode(text,level) {
  var agent,
      process,
      p,
      aios,
      next;
  switch (level) {
    case undefined:
    case 0: aios=Aios.aios0; break;
    case 1: aios=Aios.aios1; break;
    case 2: aios=Aios.aios2; break;
    case 3: aios=Aios.aios3; break;
    default: aios=Aios.aios0; break;
  }
  if (inject.cp) {
    // CP injection; no or only partial watchdog support
    var regex1= /while[\s]*\(([^\'")]+)\)/g;
    var regex2= /for[\s]*\(([^\)'"]+)\)/g;
    var regex3= /function([^\{'"]+)\{/g;
  
    text=text.replace(regex1,"while (($1) && CP())")
             .replace(regex2,"for ($1,CP())")
             .replace(regex3,"function $1{CP();");
  }
  if (inject.rt) {
    // RT injection
    var regex4 = /catch[\s]*\([\s]*([a-zA-Z0-9_]+)[\s]*\)[\s]*\{/g;
    text=text.replace(regex4,'catch ($1) {'+inject.rt+'($1);');    
  }
  
  /* Set up an object to serve as the local context for the code
  ** being evaluated. The entire global scope must be masked out!
  ** Additionally, Json defines a variable current, which must be 
  ** masked, too.
  */  
  var mask = {current:undefined,require:undefined};
  // mask local properties 
  for (p in this)
    mask[p] = undefined;
  // mask global properties 
  for (p in global)
    mask[p] = undefined;
  // add sandbox content
  for (p in aios) {
    mask[p]=aios[p];
  }
  // Auto detect JSON+ / JSOB format
  var isjson=Comp.string.startsWith(text,'{"');
  try {agent=isjson?parseString(text,mask):ofString(text,mask);}
  catch (e) {    
    if (Aios.options.verbose) Aios.log('Aios.code.toCode: '+e+(current.error?(',\nin: '+current.error):''));
    return null;
  }
  if (!agent) {
    var more;
    try {        
      var ast = Esprima.parse('code='+text, { tolerant: true, loc:true });
      if (ast.errors && ast.errors.length>0) console.log(ast.errors[0]);
    } catch (e) {
      console.log(e);
    }
    __LASTCODE=text;
    return  Aios.log('Aios.code.toCode: Invalid agent code received (invalid source text?) See __LASTCODE');
  } 
  // Add all activities to the masked environment:
  for(var p in agent.act) {
    mask[p]=p;
  }
  if (!agent.process) 
    process=Aios.Proc.Proc({mask:mask,agent:agent});
  else {
    process=Aios.Proc.Proc(agent.process);
    process.init({timeout:0,schedule:[],blocked:false,mask:mask,agent:agent});
    agent['process']=undefined;
  }
  process.level=level;
  process.resources.memory=text.length;
  agent['self']=agent;
  
  return process;
}

/** Convert agent object (i.e., process.agent) to a snapshot object.
  * 
  */
function toObject(process) {
  var _process,_agent,agent=process.agent,mask=process.mask; 
   
  agent.process={};
  
  for (var p in process) {
    switch (p) {
      case 'schedule':
        if (process.schedule.length > 0) 
          agent.process[p]=process.schedule;
        // keep it only if <> []        
        break;
      case 'blocked':
        if (agent.process.suspended==true) 
          agent.process[p]=true;
        // keep it only if it is true   
        break;
      case 'gid':
      case 'pid':
        break; // ?????
      // case 'delta':
      case 'back':
      case 'dir':
        // keep it
         agent.process[p]=process[p];
        break;
    }
  }
  if (Comp.obj.isEmpty(agent.process)) agent['process']=undefined;
  agent['self']=undefined;

  _agent=Comp.obj.copy(agent);
  _agent.mask = mask;
  
  agent['self']=agent;
  return _agent;
}

/** Convert agent object to text source in JSOB format
 *
 */
function toString(o) {
  var p,i,s='',sep;
  if (Comp.obj.isArray(o)) {
    s='[';sep='';
    for(p in o) {
      s=s+sep+toString(o[p]);
      sep=',';
    }
    s+=']';
  } else if (o instanceof Buffer) {    
    s='[';sep='';
    for(i=0;i<o.length;i++) {
      s=s+sep+toString(o[i]);
      sep=',';
    }
    s+=']';  
  } else if (typeof o == 'object') {
    s='{';sep='';
    for(p in o) {
      if (o[p]==undefined) continue;
      s=s+sep+"'"+p+"'"+':'+toString(o[p]);
      sep=',';
    }
    s+='}';
  } else if (typeof o == 'string')
    s="'"+o.toString()+"'"; 
  else if (typeof o == 'function') 
    s=o.toString(true);   // try minification (true) if supported by platform
  else if (o != undefined)
    s=o.toString();
  else s='undefined';
  return s;
}


module.exports = {
  className:className,
  copyProcess:copyProcess,
  create:create,
  createAndReturn:createAndReturn,
  createFromOn:createFromOn,
  createOn:createOn,
  forkCode:forkCode,
  ofCode:ofCode,
  ofObject:ofObject,
  ofString:ofString,
  inject:inject,
  Jsonf:Json,
  makeProcess:makeProcess,
  makeSandbox:makeSandbox,
  minimize:minimize,
  print:print,
  size:size,
  test:test,
  toCode:toCode,
  toObject:toObject,
  toString:toString,
  options:options,
  current:function (module) { 
    current=module.current; 
    Aios=module;
    // Set-up inject configuration
    inject.cp=(Aios.watchdog && !Aios.watchdog.checkPoint)?undefined:'CP';
    inject.rt=(Aios.watchdog && Aios.watchdog.protect)?undefined:'RT';
    if (inject.cp||inject.rt) options.inject=inject;
    if (Aios.watchdog && Aios.watchdog.protect) options.watchdog='native';
    else options.watchdog=false;
  }
}
};
BundleModuleCode['com/minify']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse, Douglas Crockford (jsmin, C, 2002)
 **    $INITIAL:     (C) 2006-2020 bLAB
 **    $CREATED:     09-06-20 by sbosse.
 **    $RCS:         $Id: minify.js,v 1.4 2020/02/03 09:45:01 sbosse Exp sbosse $
 **    $VERSION:     1.1.1
 **
 **    $INFO:
 **
 **  Fast JavaScript Code Minifier (C-to-JS port)
 **
 **    $ENDOFINFO
 */

var EOF = null;
var   theA;
var   theB;
var   theLookahead = EOF;
var   theX = EOF;
var   theY = EOF;
var   theInbuf = null;
var   theOutbuf = null;
var   theInbufIndex = 0;
var   theOutbufIndex = 0;

/* isAlphanum -- return true if the character is a letter, digit, underscore,
        dollar sign, or non-ASCII character.
*/
function isAlphanum(c)
{
    return ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') ||
            (c >= 'A' && c <= 'Z') || c == '_' || c == '$' || c == '\\' ||
             c > String.fromCharCode(126));
}

function get() {
  var c = theLookahead;
  theLookahead = EOF;
  if (c == EOF) {
    c = theInbuf[theInbufIndex++];
  }
  if (c >= ' ' || c == '\n' || c == EOF) {
    return c;
  }
  if (c == '\r') {
    return '\n';
  }
  return ' ';
}

function put(c) {
  theOutbuf += c;
}

function peek()
{
  theLookahead = get();
  return theLookahead;
}

function error(msg) { throw msg }

function next()
{
    var c = get();
    if  (c == '/') {
     switch (peek()) {
        case '/':
            for (;;) {
                c = get();
                if (c <= '\n') {
                    break;
                }
            }
            break;
        case '*':
            get();
            while (c != ' ') {
                switch (get()) {
                case '*':
                    if (peek() == '/') {
                        get();
                        c = ' ';
                    }
                    break;
                case EOF:
                  error("Unterminated comment.");
                }
            }
            break;
      }
    }
    theY = theX;
    theX = c;
    return c;
}

/* action -- do something! What you do is determined by the argument:
        1   Output A. Copy B to A. Get the next B.
        2   Copy B to A. Get the next B. (Delete A).
        3   Get the next B. (Delete B).
   action treats a string as a single character. Wow!
   action recognizes a regular expression if it is preceded by ( or , or =.
*/

function action(d)
{
  switch (d) {
    case 1:
        put(theA);
        if (
            (theY == '\n' || theY == ' ') &&
            (theA == '+' || theA == '-' || theA == '*' || theA == '/') &&
            (theB == '+' || theB == '-' || theB == '*' || theB == '/')
        ) {
            put(theY);
        }
    case 2:
        theA = theB;
        if (theA == '\'' || theA == '"' || theA == '`') {
            for (;;) {
                put(theA);
                theA = get();
                if (theA == theB) {
                    break;
                }
                if (theA == '\\') {
                    put(theA);
                    theA = get();
                }
                if (theA == EOF) {
                    error("Unterminated string literal.");
                }
            }
        }
    case 3:
        theB = next();
        if (theB == '/' && (
            theA == '(' || theA == ',' || theA == '=' || theA == ':' ||
            theA == '[' || theA == '!' || theA == '&' || theA == '|' ||
            theA == '?' || theA == '+' || theA == '-' || theA == '~' ||
            theA == '*' || theA == '/' || theA == '{' || theA == '\n'
        )) {
            put(theA);
            if (theA == '/' || theA == '*') {
                put(' ');
            }
            put(theB);
            for (;;) {
                theA = get();
                if (theA == '[') {
                    for (;;) {
                        put(theA);
                        theA = get();
                        if (theA == ']') {
                            break;
                        }
                        if (theA == '\\') {
                            put(theA);
                            theA = get();
                        }
                        if (theA == EOF) {
                            error("Unterminated set in Regular Expression literal.");
                        }
                    }
                } else if (theA == '/') {
                    switch (peek()) {
                    case '/':
                    case '*':
                        error("Unterminated set in Regular Expression literal.");
                        break;
                    case '\n': 
                        put(theA); 
                        theA='\n'; /* Newline required after end of regex */
                    }                    
                    break;
                } else if (theA =='\\') {
                    put(theA);
                    theA = get();
                }
                if (theA == EOF) {
                    error("Unterminated Regular Expression literal.");
                }
                put(theA);
            }
            theB = next();
        }
    }
}


function minify(text) {
  theA=0;
  theB=0;
  theLookahead = EOF;
  theX = EOF;
  theY = EOF;
  theInbuf=text;
  theInbufIndex=0;
  theOutbuf='';
  if (peek() == 0xEF) {
      get();
      get();
      get();
  }
  theA = get();
  action(3);
  while (theA != EOF) {
    switch (theA) {
      case ' ':
          action(isAlphanum(theB) ? 1 : 2);
          break;
      case '\n':
        switch (theB) {
          case '{':
          case '[':
          case '(':
          case '+':
          case '-':
          case '!':
          case '~':
              action(1);
              break;
          case ' ':
              action(3);
              break;
          default:
              action(isAlphanum(theB) ? 1 : 2);
          }
          break;
      default:
        switch (theB) {
          case ' ':
              action(isAlphanum(theA) ? 1 : 3);
              break;
          case '\n':
              switch (theA) {
              case '}':
              case ']':
              case ')':
              case '+':
              case '-':
              case '"':
              case '\'':
              case '`':
                  action(1);
                  break;
              default:
                  action(isAlphanum(theA) ? 1 : 3);
              }
              break;
          default:
              action(1);
              break;
          }
      }
  }
  return theOutbuf;    
}

// old faulty regex minimizer from Code module!!
function minimize0 (code) {
  // Inline and multi-line comments
  var regex4= /\/\*([\S\s]*?)\*\//g;
  var regex5= /([^\\}])\\n/g;                     
  var regex6= /\/\/[^\n]+/g;
   // Newline after {},;
  var regex7= /[ ]*([{},; ]|else)[ ]*\n[\n]*/g;
  // Filter for string quotes
  var regex8= /([^\'"]+)|([\'"](?:[^\'"\\]|\\.)+[\'"])/g;
  // Multi-spaces reduction
  var regex9= / [ ]+/g;
  // relax } <identifier> syntax errors after newline removal; exclude keywords!
  var regex10= /}\s+(?!else|finally|catch)([a-zA-Z_]+)/g;      
  // relax ) <identifier> syntax errors after newline removal
  var regex11= /\)\s+([a-zA-Z_]+)/g; 

  code=code.replace(regex4,"")
           .replace(regex5,'$1\n')
           .replace(regex5,'$1\n')
           .replace(regex6,"")
           .replace(regex7,"$1")
           .replace(regex8, function($0, $1, $2) {
              if ($1) {
                return $1.replace(regex9,' ').replace(regex10,'};$1').replace(regex11,')\n$1');
              } else {
                return $2; 
              } 
            });
  return code;
}

// compare regex minimizer versa character state machine
function test() {
  var n=1E4;
  var text=minify.toString(); 
  var t0=Date.now()
  for(var i=0;i<n;i++) {
    minify(text);
  }
  var t1=Date.now();
  console.log('minify: '+((t1-t0)/n/text.length*1000)+' us/char');
  var t0=Date.now()
  for(var i=0;i<n;i++) {
    minimize0(text);
  }
  var t1=Date.now();
  console.log('minimize0: '+((t1-t0)/n/text.length*1000)+' us/char');
  try {
    var M = process.binding('minify');
    var t0=Date.now()
    for(var i=0;i<n;i++) {
      M.minify(text);
    }
    var t1=Date.now();
    console.log('minifyC: '+((t1-t0)/n/text.length*1000)+' us/char');
    
  } catch (e) {
  
  }
}
minify.test=test;

module.exports = minify;

};
BundleModuleCode['parser/esprima']=function (module,exports){
/*
  Copyright (c) jQuery Foundation, Inc. and Contributors, All Rights Reserved.

  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  
  $Id: esprima.js,v 1.3.2 2017/06/08 15:41:11 sbosse Exp sbosse $
*/

(function (root, factory) {
    'use strict';

    // Universal Module Definition (UMD) to support AMD, CommonJS/Node.js,
    // Rhino, and plain browser loading.

    /* istanbul ignore next */
    if (typeof define === 'function' && define.amd) {
        define(['exports'], factory);
    } else if (typeof exports !== 'undefined') {
        factory(exports);
    } else {
        factory((root.esprima = {}));
    }
}(this, function (exports) {
    'use strict';

    var Token,
        TokenName,
        FnExprTokens,
        Syntax,
        PlaceHolders,
        Messages,
        Regex,
        source,
        strict,
        index,
        lineNumber,
        lineStart,
        hasLineTerminator,
        lastIndex,
        lastLineNumber,
        lastLineStart,
        startIndex,
        startLineNumber,
        startLineStart,
        scanning,
        length,
        lookahead,
        state,
        extra,
        isBindingElement,
        isAssignmentTarget,
        firstCoverInitializedNameError;

    Token = {
        BooleanLiteral: 1,
        EOF: 2,
        Identifier: 3,
        Keyword: 4,
        NullLiteral: 5,
        NumericLiteral: 6,
        Punctuator: 7,
        StringLiteral: 8,
        RegularExpression: 9,
        Template: 10
    };

    TokenName = {};
    TokenName[Token.BooleanLiteral] = 'Boolean';
    TokenName[Token.EOF] = '<end>';
    TokenName[Token.Identifier] = 'Identifier';
    TokenName[Token.Keyword] = 'Keyword';
    TokenName[Token.NullLiteral] = 'Null';
    TokenName[Token.NumericLiteral] = 'Numeric';
    TokenName[Token.Punctuator] = 'Punctuator';
    TokenName[Token.StringLiteral] = 'String';
    TokenName[Token.RegularExpression] = 'RegularExpression';
    TokenName[Token.Template] = 'Template';

    // A function following one of those tokens is an expression.
    FnExprTokens = ['(', '{', '[', 'in', 'typeof', 'instanceof', 'new',
                    'return', 'case', 'delete', 'throw', 'void',
                    // assignment operators
                    '=', '+=', '-=', '*=', '/=', '%=', '<<=', '>>=', '>>>=',
                    '&=', '|=', '^=', ',',
                    // binary/unary operators
                    '+', '-', '*', '/', '%', '++', '--', '<<', '>>', '>>>', '&',
                    '|', '^', '!', '~', '&&', '||', '?', ':', '===', '==', '>=',
                    '<=', '<', '>', '!=', '!=='];

    Syntax = {
        AssignmentExpression: 'AssignmentExpression',
        AssignmentPattern: 'AssignmentPattern',
        ArrayExpression: 'ArrayExpression',
        ArrayPattern: 'ArrayPattern',
        ArrowFunctionExpression: 'ArrowFunctionExpression',
        BlockStatement: 'BlockStatement',
        BinaryExpression: 'BinaryExpression',
        BreakStatement: 'BreakStatement',
        CallExpression: 'CallExpression',
        CatchClause: 'CatchClause',
        ClassBody: 'ClassBody',
        ClassDeclaration: 'ClassDeclaration',
        ClassExpression: 'ClassExpression',
        ConditionalExpression: 'ConditionalExpression',
        ContinueStatement: 'ContinueStatement',
        DoWhileStatement: 'DoWhileStatement',
        DebuggerStatement: 'DebuggerStatement',
        EmptyStatement: 'EmptyStatement',
        ExportAllDeclaration: 'ExportAllDeclaration',
        ExportDefaultDeclaration: 'ExportDefaultDeclaration',
        ExportNamedDeclaration: 'ExportNamedDeclaration',
        ExportSpecifier: 'ExportSpecifier',
        ExpressionStatement: 'ExpressionStatement',
        ForStatement: 'ForStatement',
        ForOfStatement: 'ForOfStatement',
        ForInStatement: 'ForInStatement',
        FunctionDeclaration: 'FunctionDeclaration',
        FunctionExpression: 'FunctionExpression',
        Identifier: 'Identifier',
        IfStatement: 'IfStatement',
        ImportDeclaration: 'ImportDeclaration',
        ImportDefaultSpecifier: 'ImportDefaultSpecifier',
        ImportNamespaceSpecifier: 'ImportNamespaceSpecifier',
        ImportSpecifier: 'ImportSpecifier',
        Literal: 'Literal',
        LabeledStatement: 'LabeledStatement',
        LogicalExpression: 'LogicalExpression',
        MemberExpression: 'MemberExpression',
        MetaProperty: 'MetaProperty',
        MethodDefinition: 'MethodDefinition',
        NewExpression: 'NewExpression',
        ObjectExpression: 'ObjectExpression',
        ObjectPattern: 'ObjectPattern',
        Program: 'Program',
        Property: 'Property',
        RestElement: 'RestElement',
        ReturnStatement: 'ReturnStatement',
        SequenceExpression: 'SequenceExpression',
        SpreadElement: 'SpreadElement',
        Super: 'Super',
        SwitchCase: 'SwitchCase',
        SwitchStatement: 'SwitchStatement',
        TaggedTemplateExpression: 'TaggedTemplateExpression',
        TemplateElement: 'TemplateElement',
        TemplateLiteral: 'TemplateLiteral',
        ThisExpression: 'ThisExpression',
        ThrowStatement: 'ThrowStatement',
        TryStatement: 'TryStatement',
        UnaryExpression: 'UnaryExpression',
        UpdateExpression: 'UpdateExpression',
        VariableDeclaration: 'VariableDeclaration',
        VariableDeclarator: 'VariableDeclarator',
        WhileStatement: 'WhileStatement',
        WithStatement: 'WithStatement',
        YieldExpression: 'YieldExpression'
    };

    PlaceHolders = {
        ArrowParameterPlaceHolder: 'ArrowParameterPlaceHolder'
    };

    // Error messages should be identical to V8.
    Messages = {
        UnexpectedToken: 'Unexpected token %0',
        UnexpectedNumber: 'Unexpected number',
        UnexpectedString: 'Unexpected string',
        UnexpectedIdentifier: 'Unexpected identifier',
        UnexpectedReserved: 'Unexpected reserved word',
        UnexpectedTemplate: 'Unexpected quasi %0',
        UnexpectedEOS: 'Unexpected end of input',
        NewlineAfterThrow: 'Illegal newline after throw',
        InvalidRegExp: 'Invalid regular expression',
        UnterminatedRegExp: 'Invalid regular expression: missing /',
        InvalidLHSInAssignment: 'Invalid left-hand side in assignment',
        InvalidLHSInForIn: 'Invalid left-hand side in for-in',
        InvalidLHSInForLoop: 'Invalid left-hand side in for-loop',
        MultipleDefaultsInSwitch: 'More than one default clause in switch statement',
        NoCatchOrFinally: 'Missing catch or finally after try',
        UnknownLabel: 'Undefined label \'%0\'',
        Redeclaration: '%0 \'%1\' has already been declared',
        IllegalContinue: 'Illegal continue statement',
        IllegalBreak: 'Illegal break statement',
        IllegalReturn: 'Illegal return statement',
        StrictModeWith: 'Strict mode code may not include a with statement',
        StrictCatchVariable: 'Catch variable may not be eval or arguments in strict mode',
        StrictVarName: 'Variable name may not be eval or arguments in strict mode',
        StrictParamName: 'Parameter name eval or arguments is not allowed in strict mode',
        StrictParamDupe: 'Strict mode function may not have duplicate parameter names',
        StrictFunctionName: 'Function name may not be eval or arguments in strict mode',
        StrictOctalLiteral: 'Octal literals are not allowed in strict mode.',
        StrictDelete: 'Delete of an unqualified identifier in strict mode.',
        StrictLHSAssignment: 'Assignment to eval or arguments is not allowed in strict mode',
        StrictLHSPostfix: 'Postfix increment/decrement may not have eval or arguments operand in strict mode',
        StrictLHSPrefix: 'Prefix increment/decrement may not have eval or arguments operand in strict mode',
        StrictReservedWord: 'Use of future reserved word in strict mode',
        TemplateOctalLiteral: 'Octal literals are not allowed in template strings.',
        ParameterAfterRestParameter: 'Rest parameter must be last formal parameter',
        DefaultRestParameter: 'Unexpected token =',
        ObjectPatternAsRestParameter: 'Unexpected token {',
        DuplicateProtoProperty: 'Duplicate __proto__ fields are not allowed in object literals',
        ConstructorSpecialMethod: 'Class constructor may not be an accessor',
        DuplicateConstructor: 'A class may only have one constructor',
        StaticPrototype: 'Classes may not have static property named prototype',
        MissingFromClause: 'Unexpected token',
        NoAsAfterImportNamespace: 'Unexpected token',
        InvalidModuleSpecifier: 'Unexpected token',
        IllegalImportDeclaration: 'Unexpected token',
        IllegalExportDeclaration: 'Unexpected token',
        DuplicateBinding: 'Duplicate binding %0'
    };

    // See also tools/generate-unicode-regex.js.
    Regex = {
        // ECMAScript 6/Unicode v7.0.0 NonAsciiIdentifierStart:
        NonAsciiIdentifierStart: /[\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0-\u08B2\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2118-\u211D\u2124\u2126\u2128\u212A-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309B-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA7AD\uA7B0\uA7B1\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB5F\uAB64\uAB65\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDD40-\uDD74\uDE80-\uDE9C\uDEA0-\uDED0\uDF00-\uDF1F\uDF30-\uDF4A\uDF50-\uDF75\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF\uDFD1-\uDFD5]|\uD801[\uDC00-\uDC9D\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00\uDE10-\uDE13\uDE15-\uDE17\uDE19-\uDE33\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE4\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48]|\uD804[\uDC03-\uDC37\uDC83-\uDCAF\uDCD0-\uDCE8\uDD03-\uDD26\uDD50-\uDD72\uDD76\uDD83-\uDDB2\uDDC1-\uDDC4\uDDDA\uDE00-\uDE11\uDE13-\uDE2B\uDEB0-\uDEDE\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D\uDF5D-\uDF61]|\uD805[\uDC80-\uDCAF\uDCC4\uDCC5\uDCC7\uDD80-\uDDAE\uDE00-\uDE2F\uDE44\uDE80-\uDEAA]|\uD806[\uDCA0-\uDCDF\uDCFF\uDEC0-\uDEF8]|\uD808[\uDC00-\uDF98]|\uD809[\uDC00-\uDC6E]|[\uD80C\uD840-\uD868\uD86A-\uD86C][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDED0-\uDEED\uDF00-\uDF2F\uDF40-\uDF43\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDF00-\uDF44\uDF50\uDF93-\uDF9F]|\uD82C[\uDC00\uDC01]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB]|\uD83A[\uDC00-\uDCC4]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D]|\uD87E[\uDC00-\uDE1D]/,

        // ECMAScript 6/Unicode v7.0.0 NonAsciiIdentifierPart:
        NonAsciiIdentifierPart: /[\xAA\xB5\xB7\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0300-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u0483-\u0487\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u05D0-\u05EA\u05F0-\u05F2\u0610-\u061A\u0620-\u0669\u066E-\u06D3\u06D5-\u06DC\u06DF-\u06E8\u06EA-\u06FC\u06FF\u0710-\u074A\u074D-\u07B1\u07C0-\u07F5\u07FA\u0800-\u082D\u0840-\u085B\u08A0-\u08B2\u08E4-\u0963\u0966-\u096F\u0971-\u0983\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BC-\u09C4\u09C7\u09C8\u09CB-\u09CE\u09D7\u09DC\u09DD\u09DF-\u09E3\u09E6-\u09F1\u0A01-\u0A03\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A59-\u0A5C\u0A5E\u0A66-\u0A75\u0A81-\u0A83\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABC-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AD0\u0AE0-\u0AE3\u0AE6-\u0AEF\u0B01-\u0B03\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3C-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B56\u0B57\u0B5C\u0B5D\u0B5F-\u0B63\u0B66-\u0B6F\u0B71\u0B82\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD0\u0BD7\u0BE6-\u0BEF\u0C00-\u0C03\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C58\u0C59\u0C60-\u0C63\u0C66-\u0C6F\u0C81-\u0C83\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBC-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CDE\u0CE0-\u0CE3\u0CE6-\u0CEF\u0CF1\u0CF2\u0D01-\u0D03\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D-\u0D44\u0D46-\u0D48\u0D4A-\u0D4E\u0D57\u0D60-\u0D63\u0D66-\u0D6F\u0D7A-\u0D7F\u0D82\u0D83\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DE6-\u0DEF\u0DF2\u0DF3\u0E01-\u0E3A\u0E40-\u0E4E\u0E50-\u0E59\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB9\u0EBB-\u0EBD\u0EC0-\u0EC4\u0EC6\u0EC8-\u0ECD\u0ED0-\u0ED9\u0EDC-\u0EDF\u0F00\u0F18\u0F19\u0F20-\u0F29\u0F35\u0F37\u0F39\u0F3E-\u0F47\u0F49-\u0F6C\u0F71-\u0F84\u0F86-\u0F97\u0F99-\u0FBC\u0FC6\u1000-\u1049\u1050-\u109D\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u135D-\u135F\u1369-\u1371\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1714\u1720-\u1734\u1740-\u1753\u1760-\u176C\u176E-\u1770\u1772\u1773\u1780-\u17D3\u17D7\u17DC\u17DD\u17E0-\u17E9\u180B-\u180D\u1810-\u1819\u1820-\u1877\u1880-\u18AA\u18B0-\u18F5\u1900-\u191E\u1920-\u192B\u1930-\u193B\u1946-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19DA\u1A00-\u1A1B\u1A20-\u1A5E\u1A60-\u1A7C\u1A7F-\u1A89\u1A90-\u1A99\u1AA7\u1AB0-\u1ABD\u1B00-\u1B4B\u1B50-\u1B59\u1B6B-\u1B73\u1B80-\u1BF3\u1C00-\u1C37\u1C40-\u1C49\u1C4D-\u1C7D\u1CD0-\u1CD2\u1CD4-\u1CF6\u1CF8\u1CF9\u1D00-\u1DF5\u1DFC-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u200C\u200D\u203F\u2040\u2054\u2071\u207F\u2090-\u209C\u20D0-\u20DC\u20E1\u20E5-\u20F0\u2102\u2107\u210A-\u2113\u2115\u2118-\u211D\u2124\u2126\u2128\u212A-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D7F-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2DE0-\u2DFF\u3005-\u3007\u3021-\u302F\u3031-\u3035\u3038-\u303C\u3041-\u3096\u3099-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA62B\uA640-\uA66F\uA674-\uA67D\uA67F-\uA69D\uA69F-\uA6F1\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA7AD\uA7B0\uA7B1\uA7F7-\uA827\uA840-\uA873\uA880-\uA8C4\uA8D0-\uA8D9\uA8E0-\uA8F7\uA8FB\uA900-\uA92D\uA930-\uA953\uA960-\uA97C\uA980-\uA9C0\uA9CF-\uA9D9\uA9E0-\uA9FE\uAA00-\uAA36\uAA40-\uAA4D\uAA50-\uAA59\uAA60-\uAA76\uAA7A-\uAAC2\uAADB-\uAADD\uAAE0-\uAAEF\uAAF2-\uAAF6\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB5F\uAB64\uAB65\uABC0-\uABEA\uABEC\uABED\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE00-\uFE0F\uFE20-\uFE2D\uFE33\uFE34\uFE4D-\uFE4F\uFE70-\uFE74\uFE76-\uFEFC\uFF10-\uFF19\uFF21-\uFF3A\uFF3F\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDD40-\uDD74\uDDFD\uDE80-\uDE9C\uDEA0-\uDED0\uDEE0\uDF00-\uDF1F\uDF30-\uDF4A\uDF50-\uDF7A\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF\uDFD1-\uDFD5]|\uD801[\uDC00-\uDC9D\uDCA0-\uDCA9\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00-\uDE03\uDE05\uDE06\uDE0C-\uDE13\uDE15-\uDE17\uDE19-\uDE33\uDE38-\uDE3A\uDE3F\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE6\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48]|\uD804[\uDC00-\uDC46\uDC66-\uDC6F\uDC7F-\uDCBA\uDCD0-\uDCE8\uDCF0-\uDCF9\uDD00-\uDD34\uDD36-\uDD3F\uDD50-\uDD73\uDD76\uDD80-\uDDC4\uDDD0-\uDDDA\uDE00-\uDE11\uDE13-\uDE37\uDEB0-\uDEEA\uDEF0-\uDEF9\uDF01-\uDF03\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3C-\uDF44\uDF47\uDF48\uDF4B-\uDF4D\uDF57\uDF5D-\uDF63\uDF66-\uDF6C\uDF70-\uDF74]|\uD805[\uDC80-\uDCC5\uDCC7\uDCD0-\uDCD9\uDD80-\uDDB5\uDDB8-\uDDC0\uDE00-\uDE40\uDE44\uDE50-\uDE59\uDE80-\uDEB7\uDEC0-\uDEC9]|\uD806[\uDCA0-\uDCE9\uDCFF\uDEC0-\uDEF8]|\uD808[\uDC00-\uDF98]|\uD809[\uDC00-\uDC6E]|[\uD80C\uD840-\uD868\uD86A-\uD86C][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDE60-\uDE69\uDED0-\uDEED\uDEF0-\uDEF4\uDF00-\uDF36\uDF40-\uDF43\uDF50-\uDF59\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDF00-\uDF44\uDF50-\uDF7E\uDF8F-\uDF9F]|\uD82C[\uDC00\uDC01]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99\uDC9D\uDC9E]|\uD834[\uDD65-\uDD69\uDD6D-\uDD72\uDD7B-\uDD82\uDD85-\uDD8B\uDDAA-\uDDAD\uDE42-\uDE44]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB\uDFCE-\uDFFF]|\uD83A[\uDC00-\uDCC4\uDCD0-\uDCD6]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D]|\uD87E[\uDC00-\uDE1D]|\uDB40[\uDD00-\uDDEF]/
    };

    // Ensure the condition is true, otherwise throw an error.
    // This is only to have a better contract semantic, i.e. another safety net
    // to catch a logic error. The condition shall be fulfilled in normal case.
    // Do NOT use this to enforce a certain condition on any user input.

    function assert(condition, message) {
        /* istanbul ignore if */
        if (!condition) {
            throw new Error('ASSERT: ' + message);
        }
    }

    function isDecimalDigit(ch) {
        return (ch >= 0x30 && ch <= 0x39);   // 0..9
    }

    function isHexDigit(ch) {
        return '0123456789abcdefABCDEF'.indexOf(ch) >= 0;
    }

    function isOctalDigit(ch) {
        return '01234567'.indexOf(ch) >= 0;
    }

    function octalToDecimal(ch) {
        // \0 is not octal escape sequence
        var octal = (ch !== '0'), code = '01234567'.indexOf(ch);

        if (index < length && isOctalDigit(source[index])) {
            octal = true;
            code = code * 8 + '01234567'.indexOf(source[index++]);

            // 3 digits are only allowed when string starts
            // with 0, 1, 2, 3
            if ('0123'.indexOf(ch) >= 0 &&
                    index < length &&
                    isOctalDigit(source[index])) {
                code = code * 8 + '01234567'.indexOf(source[index++]);
            }
        }

        return {
            code: code,
            octal: octal
        };
    }

    // ECMA-262 11.2 White Space

    function isWhiteSpace(ch) {
        return (ch === 0x20) || (ch === 0x09) || (ch === 0x0B) || (ch === 0x0C) || (ch === 0xA0) ||
            (ch >= 0x1680 && [0x1680, 0x180E, 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006, 0x2007, 0x2008, 0x2009, 0x200A, 0x202F, 0x205F, 0x3000, 0xFEFF].indexOf(ch) >= 0);
    }

    // ECMA-262 11.3 Line Terminators

    function isLineTerminator(ch) {
        return (ch === 0x0A) || (ch === 0x0D) || (ch === 0x2028) || (ch === 0x2029);
    }

    // ECMA-262 11.6 Identifier Names and Identifiers

    function fromCodePoint(cp) {
        return (cp < 0x10000) ? String.fromCharCode(cp) :
            String.fromCharCode(0xD800 + ((cp - 0x10000) >> 10)) +
            String.fromCharCode(0xDC00 + ((cp - 0x10000) & 1023));
    }

    function isIdentifierStart(ch) {
        return (ch === 0x24) || (ch === 0x5F) ||  // $ (dollar) and _ (underscore)
            (ch >= 0x41 && ch <= 0x5A) ||         // A..Z
            (ch >= 0x61 && ch <= 0x7A) ||         // a..z
            (ch === 0x5C) ||                      // \ (backslash)
            ((ch >= 0x80) && Regex.NonAsciiIdentifierStart.test(fromCodePoint(ch)));
    }

    function isIdentifierPart(ch) {
        return (ch === 0x24) || (ch === 0x5F) ||  // $ (dollar) and _ (underscore)
            (ch >= 0x41 && ch <= 0x5A) ||         // A..Z
            (ch >= 0x61 && ch <= 0x7A) ||         // a..z
            (ch >= 0x30 && ch <= 0x39) ||         // 0..9
            (ch === 0x5C) ||                      // \ (backslash)
            ((ch >= 0x80) && Regex.NonAsciiIdentifierPart.test(fromCodePoint(ch)));
    }

    // ECMA-262 11.6.2.2 Future Reserved Words

    function isFutureReservedWord(id) {
        switch (id) {
        case 'enum':
        case 'export':
        case 'import':
        case 'super':
            return true;
        default:
            return false;
        }
    }

    function isStrictModeReservedWord(id) {
        switch (id) {
        case 'implements':
        case 'interface':
        case 'package':
        case 'private':
        case 'protected':
        case 'public':
        case 'static':
        case 'yield':
        case 'let':
            return true;
        default:
            return false;
        }
    }

    function isRestrictedWord(id) {
        return id === 'eval' || id === 'arguments';
    }

    // ECMA-262 11.6.2.1 Keywords

    function isKeyword(id) {
        switch (id.length) {
        case 2:
            return (id === 'if') || (id === 'in') || (id === 'do');
        case 3:
            return (id === 'var') || (id === 'for') || (id === 'new') ||
                   (id === 'try') || (id === 'let');
        case 4:
            return (id === 'this') || (id === 'else') || (id === 'case') ||
                (id === 'void') || (id === 'with') || (id === 'enum');
        case 5:
            return (id === 'while') || (id === 'break') || (id === 'catch') ||
                (id === 'throw') || (id === 'const') || (id === 'yield') ||
                (id === 'class') || (id === 'super');
        case 6:
            return (id === 'return') || (id === 'typeof') || (id === 'delete') ||
                (id === 'switch') || (id === 'export') || (id === 'import');
        case 7:
            return (id === 'default') || (id === 'finally') || (id === 'extends');
        case 8:
            return (id === 'function') || (id === 'continue') || (id === 'debugger');
        case 10:
            return (id === 'instanceof');
        default:
            return false;
        }
    }

    // ECMA-262 11.4 Comments

    function addComment(type, value, start, end, loc) {
        var comment;

        assert(typeof start === 'number', 'Comment must have valid position');

        state.lastCommentStart = start;

        comment = {
            type: type,
            value: value
        };
        if (extra.range) {
            comment.range = [start, end];
        }
        if (extra.loc) {
            comment.loc = loc;
        }
        extra.comments.push(comment);
        if (extra.attachComment) {
            extra.leadingComments.push(comment);
            extra.trailingComments.push(comment);
        }
        if (extra.tokenize) {
            comment.type = comment.type + 'Comment';
            if (extra.delegate) {
                comment = extra.delegate(comment);
            }
            extra.tokens.push(comment);
        }
    }

    function skipSingleLineComment(offset) {
        var start, loc, ch, comment;

        start = index - offset;
        loc = {
            start: {
                line: lineNumber,
                column: index - lineStart - offset
            }
        };

        while (index < length) {
            ch = source.charCodeAt(index);
            ++index;
            if (isLineTerminator(ch)) {
                hasLineTerminator = true;
                if (extra.comments) {
                    comment = source.slice(start + offset, index - 1);
                    loc.end = {
                        line: lineNumber,
                        column: index - lineStart - 1
                    };
                    addComment('Line', comment, start, index - 1, loc);
                }
                if (ch === 13 && source.charCodeAt(index) === 10) {
                    ++index;
                }
                ++lineNumber;
                lineStart = index;
                return;
            }
        }

        if (extra.comments) {
            comment = source.slice(start + offset, index);
            loc.end = {
                line: lineNumber,
                column: index - lineStart
            };
            addComment('Line', comment, start, index, loc);
        }
    }

    function skipMultiLineComment() {
        var start, loc, ch, comment;

        if (extra.comments) {
            start = index - 2;
            loc = {
                start: {
                    line: lineNumber,
                    column: index - lineStart - 2
                }
            };
        }

        while (index < length) {
            ch = source.charCodeAt(index);
            if (isLineTerminator(ch)) {
                if (ch === 0x0D && source.charCodeAt(index + 1) === 0x0A) {
                    ++index;
                }
                hasLineTerminator = true;
                ++lineNumber;
                ++index;
                lineStart = index;
            } else if (ch === 0x2A) {
                // Block comment ends with '*/'.
                if (source.charCodeAt(index + 1) === 0x2F) {
                    ++index;
                    ++index;
                    if (extra.comments) {
                        comment = source.slice(start + 2, index - 2);
                        loc.end = {
                            line: lineNumber,
                            column: index - lineStart
                        };
                        addComment('Block', comment, start, index, loc);
                    }
                    return;
                }
                ++index;
            } else {
                ++index;
            }
        }

        // Ran off the end of the file - the whole thing is a comment
        if (extra.comments) {
            loc.end = {
                line: lineNumber,
                column: index - lineStart
            };
            comment = source.slice(start + 2, index);
            addComment('Block', comment, start, index, loc);
        }
        tolerateUnexpectedToken();
    }

    function skipComment() {
        var ch, start;
        hasLineTerminator = false;

        start = (index === 0);
        while (index < length) {
            ch = source.charCodeAt(index);

            if (isWhiteSpace(ch)) {
                ++index;
            } else if (isLineTerminator(ch)) {
                hasLineTerminator = true;
                ++index;
                if (ch === 0x0D && source.charCodeAt(index) === 0x0A) {
                    ++index;
                }
                ++lineNumber;
                lineStart = index;
                start = true;
            } else if (ch === 0x2F) { // U+002F is '/'
                ch = source.charCodeAt(index + 1);
                if (ch === 0x2F) {
                    ++index;
                    ++index;
                    skipSingleLineComment(2);
                    start = true;
                } else if (ch === 0x2A) {  // U+002A is '*'
                    ++index;
                    ++index;
                    skipMultiLineComment();
                } else {
                    break;
                }
            } else if (start && ch === 0x2D) { // U+002D is '-'
                // U+003E is '>'
                if ((source.charCodeAt(index + 1) === 0x2D) && (source.charCodeAt(index + 2) === 0x3E)) {
                    // '-->' is a single-line comment
                    index += 3;
                    skipSingleLineComment(3);
                } else {
                    break;
                }
            } else if (ch === 0x3C) { // U+003C is '<'
                if (source.slice(index + 1, index + 4) === '!--') {
                    ++index; // `<`
                    ++index; // `!`
                    ++index; // `-`
                    ++index; // `-`
                    skipSingleLineComment(4);
                } else {
                    break;
                }
            } else {
                break;
            }
        }
    }

    function scanHexEscape(prefix) {
        var i, len, ch, code = 0;

        len = (prefix === 'u') ? 4 : 2;
        for (i = 0; i < len; ++i) {
            if (index < length && isHexDigit(source[index])) {
                ch = source[index++];
                code = code * 16 + '0123456789abcdef'.indexOf(ch.toLowerCase());
            } else {
                return '';
            }
        }
        return String.fromCharCode(code);
    }

    function scanUnicodeCodePointEscape() {
        var ch, code;

        ch = source[index];
        code = 0;

        // At least, one hex digit is required.
        if (ch === '}') {
            throwUnexpectedToken();
        }

        while (index < length) {
            ch = source[index++];
            if (!isHexDigit(ch)) {
                break;
            }
            code = code * 16 + '0123456789abcdef'.indexOf(ch.toLowerCase());
        }

        if (code > 0x10FFFF || ch !== '}') {
            throwUnexpectedToken();
        }

        return fromCodePoint(code);
    }

    function codePointAt(i) {
        var cp, first, second;

        cp = source.charCodeAt(i);
        if (cp >= 0xD800 && cp <= 0xDBFF) {
            second = source.charCodeAt(i + 1);
            if (second >= 0xDC00 && second <= 0xDFFF) {
                first = cp;
                cp = (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000;
            }
        }

        return cp;
    }

    function getComplexIdentifier() {
        var cp, ch, id;

        cp = codePointAt(index);
        id = fromCodePoint(cp);
        index += id.length;

        // '\u' (U+005C, U+0075) denotes an escaped character.
        if (cp === 0x5C) {
            if (source.charCodeAt(index) !== 0x75) {
                throwUnexpectedToken();
            }
            ++index;
            if (source[index] === '{') {
                ++index;
                ch = scanUnicodeCodePointEscape();
            } else {
                ch = scanHexEscape('u');
                cp = ch.charCodeAt(0);
                if (!ch || ch === '\\' || !isIdentifierStart(cp)) {
                    throwUnexpectedToken();
                }
            }
            id = ch;
        }

        while (index < length) {
            cp = codePointAt(index);
            if (!isIdentifierPart(cp)) {
                break;
            }
            ch = fromCodePoint(cp);
            id += ch;
            index += ch.length;

            // '\u' (U+005C, U+0075) denotes an escaped character.
            if (cp === 0x5C) {
                id = id.substr(0, id.length - 1);
                if (source.charCodeAt(index) !== 0x75) {
                    throwUnexpectedToken();
                }
                ++index;
                if (source[index] === '{') {
                    ++index;
                    ch = scanUnicodeCodePointEscape();
                } else {
                    ch = scanHexEscape('u');
                    cp = ch.charCodeAt(0);
                    if (!ch || ch === '\\' || !isIdentifierPart(cp)) {
                        throwUnexpectedToken();
                    }
                }
                id += ch;
            }
        }

        return id;
    }

    function getIdentifier() {
        var start, ch;

        start = index++;
        while (index < length) {
            ch = source.charCodeAt(index);
            if (ch === 0x5C) {
                // Blackslash (U+005C) marks Unicode escape sequence.
                index = start;
                return getComplexIdentifier();
            } else if (ch >= 0xD800 && ch < 0xDFFF) {
                // Need to handle surrogate pairs.
                index = start;
                return getComplexIdentifier();
            }
            if (isIdentifierPart(ch)) {
                ++index;
            } else {
                break;
            }
        }

        return source.slice(start, index);
    }

    function scanIdentifier() {
        var start, id, type;

        start = index;

        // Backslash (U+005C) starts an escaped character.
        id = (source.charCodeAt(index) === 0x5C) ? getComplexIdentifier() : getIdentifier();

        // There is no keyword or literal with only one character.
        // Thus, it must be an identifier.
        if (id.length === 1) {
            type = Token.Identifier;
        } else if (isKeyword(id)) {
            type = Token.Keyword;
        } else if (id === 'null') {
            type = Token.NullLiteral;
        } else if (id === 'true' || id === 'false') {
            type = Token.BooleanLiteral;
        } else {
            type = Token.Identifier;
        }

        return {
            type: type,
            value: id,
            lineNumber: lineNumber,
            lineStart: lineStart,
            start: start,
            end: index
        };
    }


    // ECMA-262 11.7 Punctuators

    function scanPunctuator() {
        var token, str;

        token = {
            type: Token.Punctuator,
            value: '',
            lineNumber: lineNumber,
            lineStart: lineStart,
            start: index,
            end: index
        };

        // Check for most common single-character punctuators.
        str = source[index];
        switch (str) {

        case '(':
            if (extra.tokenize) {
                extra.openParenToken = extra.tokenValues.length;
            }
            ++index;
            break;

        case '{':
            if (extra.tokenize) {
                extra.openCurlyToken = extra.tokenValues.length;
            }
            state.curlyStack.push('{');
            ++index;
            break;

        case '.':
            ++index;
            if (source[index] === '.' && source[index + 1] === '.') {
                // Spread operator: ...
                index += 2;
                str = '...';
            }
            break;

        case '}':
            ++index;
            state.curlyStack.pop();
            break;
        case ')':
        case ';':
        case ',':
        case '[':
        case ']':
        case ':':
        case '?':
        case '~':
            ++index;
            break;

        default:
            // 4-character punctuator.
            str = source.substr(index, 4);
            if (str === '>>>=') {
                index += 4;
            } else {

                // 3-character punctuators.
                str = str.substr(0, 3);
                if (str === '===' || str === '!==' || str === '>>>' ||
                    str === '<<=' || str === '>>=') {
                    index += 3;
                } else {

                    // 2-character punctuators.
                    str = str.substr(0, 2);
                    if (str === '&&' || str === '||' || str === '==' || str === '!=' ||
                        str === '+=' || str === '-=' || str === '*=' || str === '/=' ||
                        str === '++' || str === '--' || str === '<<' || str === '>>' ||
                        str === '&=' || str === '|=' || str === '^=' || str === '%=' ||
                        str === '<=' || str === '>=' || str === '=>') {
                        index += 2;
                    } else {

                        // 1-character punctuators.
                        str = source[index];
                        if ('<>=!+-*%&|^/'.indexOf(str) >= 0) {
                            ++index;
                        }
                    }
                }
            }
        }

        if (index === token.start) {
            throwUnexpectedToken();
        }

        token.end = index;
        token.value = str;
        return token;
    }

    // ECMA-262 11.8.3 Numeric Literals

    function scanHexLiteral(start) {
        var number = '';

        while (index < length) {
            if (!isHexDigit(source[index])) {
                break;
            }
            number += source[index++];
        }

        if (number.length === 0) {
            throwUnexpectedToken();
        }

        if (isIdentifierStart(source.charCodeAt(index))) {
            throwUnexpectedToken();
        }

        return {
            type: Token.NumericLiteral,
            value: parseInt('0x' + number, 16),
            lineNumber: lineNumber,
            lineStart: lineStart,
            start: start,
            end: index
        };
    }

    function scanBinaryLiteral(start) {
        var ch, number;

        number = '';

        while (index < length) {
            ch = source[index];
            if (ch !== '0' && ch !== '1') {
                break;
            }
            number += source[index++];
        }

        if (number.length === 0) {
            // only 0b or 0B
            throwUnexpectedToken();
        }

        if (index < length) {
            ch = source.charCodeAt(index);
            /* istanbul ignore else */
            if (isIdentifierStart(ch) || isDecimalDigit(ch)) {
                throwUnexpectedToken();
            }
        }

        return {
            type: Token.NumericLiteral,
            value: parseInt(number, 2),
            lineNumber: lineNumber,
            lineStart: lineStart,
            start: start,
            end: index
        };
    }

    function scanOctalLiteral(prefix, start) {
        var number, octal;

        if (isOctalDigit(prefix)) {
            octal = true;
            number = '0' + source[index++];
        } else {
            octal = false;
            ++index;
            number = '';
        }

        while (index < length) {
            if (!isOctalDigit(source[index])) {
                break;
            }
            number += source[index++];
        }

        if (!octal && number.length === 0) {
            // only 0o or 0O
            throwUnexpectedToken();
        }

        if (isIdentifierStart(source.charCodeAt(index)) || isDecimalDigit(source.charCodeAt(index))) {
            throwUnexpectedToken();
        }

        return {
            type: Token.NumericLiteral,
            value: parseInt(number, 8),
            octal: octal,
            lineNumber: lineNumber,
            lineStart: lineStart,
            start: start,
            end: index
        };
    }

    function isImplicitOctalLiteral() {
        var i, ch;

        // Implicit octal, unless there is a non-octal digit.
        // (Annex B.1.1 on Numeric Literals)
        for (i = index + 1; i < length; ++i) {
            ch = source[i];
            if (ch === '8' || ch === '9') {
                return false;
            }
            if (!isOctalDigit(ch)) {
                return true;
            }
        }

        return true;
    }

    function scanNumericLiteral() {
        var number, start, ch;

        ch = source[index];
        assert(isDecimalDigit(ch.charCodeAt(0)) || (ch === '.'),
            'Numeric literal must start with a decimal digit or a decimal point');

        start = index;
        number = '';
        if (ch !== '.') {
            number = source[index++];
            ch = source[index];

            // Hex number starts with '0x'.
            // Octal number starts with '0'.
            // Octal number in ES6 starts with '0o'.
            // Binary number in ES6 starts with '0b'.
            if (number === '0') {
                if (ch === 'x' || ch === 'X') {
                    ++index;
                    return scanHexLiteral(start);
                }
                if (ch === 'b' || ch === 'B') {
                    ++index;
                    return scanBinaryLiteral(start);
                }
                if (ch === 'o' || ch === 'O') {
                    return scanOctalLiteral(ch, start);
                }

                if (isOctalDigit(ch)) {
                    if (isImplicitOctalLiteral()) {
                        return scanOctalLiteral(ch, start);
                    }
                }
            }

            while (isDecimalDigit(source.charCodeAt(index))) {
                number += source[index++];
            }
            ch = source[index];
        }

        if (ch === '.') {
            number += source[index++];
            while (isDecimalDigit(source.charCodeAt(index))) {
                number += source[index++];
            }
            ch = source[index];
        }

        if (ch === 'e' || ch === 'E') {
            number += source[index++];

            ch = source[index];
            if (ch === '+' || ch === '-') {
                number += source[index++];
            }
            if (isDecimalDigit(source.charCodeAt(index))) {
                while (isDecimalDigit(source.charCodeAt(index))) {
                    number += source[index++];
                }
            } else {
                throwUnexpectedToken();
            }
        }

        if (isIdentifierStart(source.charCodeAt(index))) {
            throwUnexpectedToken();
        }

        return {
            type: Token.NumericLiteral,
            value: parseFloat(number),
            lineNumber: lineNumber,
            lineStart: lineStart,
            start: start,
            end: index
        };
    }

    // ECMA-262 11.8.4 String Literals

    function scanStringLiteral() {
        var str = '', quote, start, ch, unescaped, octToDec, octal = false;

        quote = source[index];
        assert((quote === '\'' || quote === '"'),
            'String literal must starts with a quote');

        start = index;
        ++index;

        while (index < length) {
            ch = source[index++];

            if (ch === quote) {
                quote = '';
                break;
            } else if (ch === '\\') {
                ch = source[index++];
                if (!ch || !isLineTerminator(ch.charCodeAt(0))) {
                    switch (ch) {
                    case 'u':
                    case 'x':
                        if (source[index] === '{') {
                            ++index;
                            str += scanUnicodeCodePointEscape();
                        } else {
                            unescaped = scanHexEscape(ch);
                            if (!unescaped) {
                                throw throwUnexpectedToken();
                            }
                            str += unescaped;
                        }
                        break;
                    case 'n':
                        str += '\n';
                        break;
                    case 'r':
                        str += '\r';
                        break;
                    case 't':
                        str += '\t';
                        break;
                    case 'b':
                        str += '\b';
                        break;
                    case 'f':
                        str += '\f';
                        break;
                    case 'v':
                        str += '\x0B';
                        break;
                    case '8':
                    case '9':
                        str += ch;
                        tolerateUnexpectedToken();
                        break;

                    default:
                        if (isOctalDigit(ch)) {
                            octToDec = octalToDecimal(ch);

                            octal = octToDec.octal || octal;
                            str += String.fromCharCode(octToDec.code);
                        } else {
                            str += ch;
                        }
                        break;
                    }
                } else {
                    ++lineNumber;
                    if (ch === '\r' && source[index] === '\n') {
                        ++index;
                    }
                    lineStart = index;
                }
            } else if (isLineTerminator(ch.charCodeAt(0))) {
                break;
            } else {
                str += ch;
            }
        }

        if (quote !== '') {
            throwUnexpectedToken();
        }

        return {
            type: Token.StringLiteral,
            value: str,
            octal: octal,
            lineNumber: startLineNumber,
            lineStart: startLineStart,
            start: start,
            end: index
        };
    }

    // ECMA-262 11.8.6 Template Literal Lexical Components

    function scanTemplate() {
        var cooked = '', ch, start, rawOffset, terminated, head, tail, restore, unescaped;

        terminated = false;
        tail = false;
        start = index;
        head = (source[index] === '`');
        rawOffset = 2;

        ++index;

        while (index < length) {
            ch = source[index++];
            if (ch === '`') {
                rawOffset = 1;
                tail = true;
                terminated = true;
                break;
            } else if (ch === '$') {
                if (source[index] === '{') {
                    state.curlyStack.push('${');
                    ++index;
                    terminated = true;
                    break;
                }
                cooked += ch;
            } else if (ch === '\\') {
                ch = source[index++];
                if (!isLineTerminator(ch.charCodeAt(0))) {
                    switch (ch) {
                    case 'n':
                        cooked += '\n';
                        break;
                    case 'r':
                        cooked += '\r';
                        break;
                    case 't':
                        cooked += '\t';
                        break;
                    case 'u':
                    case 'x':
                        if (source[index] === '{') {
                            ++index;
                            cooked += scanUnicodeCodePointEscape();
                        } else {
                            restore = index;
                            unescaped = scanHexEscape(ch);
                            if (unescaped) {
                                cooked += unescaped;
                            } else {
                                index = restore;
                                cooked += ch;
                            }
                        }
                        break;
                    case 'b':
                        cooked += '\b';
                        break;
                    case 'f':
                        cooked += '\f';
                        break;
                    case 'v':
                        cooked += '\v';
                        break;

                    default:
                        if (ch === '0') {
                            if (isDecimalDigit(source.charCodeAt(index))) {
                                // Illegal: \01 \02 and so on
                                throwError(Messages.TemplateOctalLiteral);
                            }
                            cooked += '\0';
                        } else if (isOctalDigit(ch)) {
                            // Illegal: \1 \2
                            throwError(Messages.TemplateOctalLiteral);
                        } else {
                            cooked += ch;
                        }
                        break;
                    }
                } else {
                    ++lineNumber;
                    if (ch === '\r' && source[index] === '\n') {
                        ++index;
                    }
                    lineStart = index;
                }
            } else if (isLineTerminator(ch.charCodeAt(0))) {
                ++lineNumber;
                if (ch === '\r' && source[index] === '\n') {
                    ++index;
                }
                lineStart = index;
                cooked += '\n';
            } else {
                cooked += ch;
            }
        }

        if (!terminated) {
            throwUnexpectedToken();
        }

        if (!head) {
            state.curlyStack.pop();
        }

        return {
            type: Token.Template,
            value: {
                cooked: cooked,
                raw: source.slice(start + 1, index - rawOffset)
            },
            head: head,
            tail: tail,
            lineNumber: lineNumber,
            lineStart: lineStart,
            start: start,
            end: index
        };
    }

    // ECMA-262 11.8.5 Regular Expression Literals

    function testRegExp(pattern, flags) {
        // The BMP character to use as a replacement for astral symbols when
        // translating an ES6 "u"-flagged pattern to an ES5-compatible
        // approximation.
        // Note: replacing with '\uFFFF' enables false positives in unlikely
        // scenarios. For example, `[\u{1044f}-\u{10440}]` is an invalid
        // pattern that would not be detected by this substitution.
        var astralSubstitute = '\uFFFF',
            tmp = pattern;

        if (flags.indexOf('u') >= 0) {
            tmp = tmp
                // Replace every Unicode escape sequence with the equivalent
                // BMP character or a constant ASCII code point in the case of
                // astral symbols. (See the above note on `astralSubstitute`
                // for more information.)
                .replace(/\\u\{([0-9a-fA-F]+)\}|\\u([a-fA-F0-9]{4})/g, function ($0, $1, $2) {
                    var codePoint = parseInt($1 || $2, 16);
                    if (codePoint > 0x10FFFF) {
                        throwUnexpectedToken(null, Messages.InvalidRegExp);
                    }
                    if (codePoint <= 0xFFFF) {
                        return String.fromCharCode(codePoint);
                    }
                    return astralSubstitute;
                })
                // Replace each paired surrogate with a single ASCII symbol to
                // avoid throwing on regular expressions that are only valid in
                // combination with the "u" flag.
                .replace(
                    /[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
                    astralSubstitute
                );
        }

        // First, detect invalid regular expressions.
        try {
            RegExp(tmp);
        } catch (e) {
            throwUnexpectedToken(null, Messages.InvalidRegExp);
        }

        // Return a regular expression object for this pattern-flag pair, or
        // `null` in case the current environment doesn't support the flags it
        // uses.
        try {
            return new RegExp(pattern, flags);
        } catch (exception) {
            return null;
        }
    }

    function scanRegExpBody() {
        var ch, str, classMarker, terminated, body;

        ch = source[index];
        assert(ch === '/', 'Regular expression literal must start with a slash');
        str = source[index++];

        classMarker = false;
        terminated = false;
        while (index < length) {
            ch = source[index++];
            str += ch;
            if (ch === '\\') {
                ch = source[index++];
                // ECMA-262 7.8.5
                if (isLineTerminator(ch.charCodeAt(0))) {
                    throwUnexpectedToken(null, Messages.UnterminatedRegExp);
                }
                str += ch;
            } else if (isLineTerminator(ch.charCodeAt(0))) {
                throwUnexpectedToken(null, Messages.UnterminatedRegExp);
            } else if (classMarker) {
                if (ch === ']') {
                    classMarker = false;
                }
            } else {
                if (ch === '/') {
                    terminated = true;
                    break;
                } else if (ch === '[') {
                    classMarker = true;
                }
            }
        }

        if (!terminated) {
            throwUnexpectedToken(null, Messages.UnterminatedRegExp);
        }

        // Exclude leading and trailing slash.
        body = str.substr(1, str.length - 2);
        return {
            value: body,
            literal: str
        };
    }

    function scanRegExpFlags() {
        var ch, str, flags, restore;

        str = '';
        flags = '';
        while (index < length) {
            ch = source[index];
            if (!isIdentifierPart(ch.charCodeAt(0))) {
                break;
            }

            ++index;
            if (ch === '\\' && index < length) {
                ch = source[index];
                if (ch === 'u') {
                    ++index;
                    restore = index;
                    ch = scanHexEscape('u');
                    if (ch) {
                        flags += ch;
                        for (str += '\\u'; restore < index; ++restore) {
                            str += source[restore];
                        }
                    } else {
                        index = restore;
                        flags += 'u';
                        str += '\\u';
                    }
                    tolerateUnexpectedToken();
                } else {
                    str += '\\';
                    tolerateUnexpectedToken();
                }
            } else {
                flags += ch;
                str += ch;
            }
        }

        return {
            value: flags,
            literal: str
        };
    }

    function scanRegExp() {
        var start, body, flags, value;
        scanning = true;

        lookahead = null;
        skipComment();
        start = index;

        body = scanRegExpBody();
        flags = scanRegExpFlags();
        value = testRegExp(body.value, flags.value);
        scanning = false;
        if (extra.tokenize) {
            return {
                type: Token.RegularExpression,
                value: value,
                regex: {
                    pattern: body.value,
                    flags: flags.value
                },
                lineNumber: lineNumber,
                lineStart: lineStart,
                start: start,
                end: index
            };
        }

        return {
            literal: body.literal + flags.literal,
            value: value,
            regex: {
                pattern: body.value,
                flags: flags.value
            },
            start: start,
            end: index
        };
    }

    function collectRegex() {
        var pos, loc, regex, token;

        skipComment();

        pos = index;
        loc = {
            start: {
                line: lineNumber,
                column: index - lineStart
            }
        };

        regex = scanRegExp();

        loc.end = {
            line: lineNumber,
            column: index - lineStart
        };

        /* istanbul ignore next */
        if (!extra.tokenize) {
            // Pop the previous token, which is likely '/' or '/='
            if (extra.tokens.length > 0) {
                token = extra.tokens[extra.tokens.length - 1];
                if (token.range[0] === pos && token.type === 'Punctuator') {
                    if (token.value === '/' || token.value === '/=') {
                        extra.tokens.pop();
                    }
                }
            }

            extra.tokens.push({
                type: 'RegularExpression',
                value: regex.literal,
                regex: regex.regex,
                range: [pos, index],
                loc: loc
            });
        }

        return regex;
    }

    function isIdentifierName(token) {
        return token.type === Token.Identifier ||
            token.type === Token.Keyword ||
            token.type === Token.BooleanLiteral ||
            token.type === Token.NullLiteral;
    }

    // Using the following algorithm:
    // https://github.com/mozilla/sweet.js/wiki/design

    function advanceSlash() {
        var regex, previous, check;

        function testKeyword(value) {
            return value && (value.length > 1) && (value[0] >= 'a') && (value[0] <= 'z');
        }

        previous = extra.tokenValues[extra.tokens.length - 1];
        regex = (previous !== null);

        switch (previous) {
        case 'this':
        case ']':
            regex = false;
            break;

        case ')':
            check = extra.tokenValues[extra.openParenToken - 1];
            regex = (check === 'if' || check === 'while' || check === 'for' || check === 'with');
            break;

        case '}':
            // Dividing a function by anything makes little sense,
            // but we have to check for that.
            regex = false;
            if (testKeyword(extra.tokenValues[extra.openCurlyToken - 3])) {
                // Anonymous function, e.g. function(){} /42
                check = extra.tokenValues[extra.openCurlyToken - 4];
                regex = check ? (FnExprTokens.indexOf(check) < 0) : false;
            } else if (testKeyword(extra.tokenValues[extra.openCurlyToken - 4])) {
                // Named function, e.g. function f(){} /42/
                check = extra.tokenValues[extra.openCurlyToken - 5];
                regex = check ? (FnExprTokens.indexOf(check) < 0) : true;
            }
        }

        return regex ? collectRegex() : scanPunctuator();
    }

    function advance() {
        var cp, token;

        if (index >= length) {
            return {
                type: Token.EOF,
                lineNumber: lineNumber,
                lineStart: lineStart,
                start: index,
                end: index
            };
        }

        cp = source.charCodeAt(index);

        if (isIdentifierStart(cp)) {
            token = scanIdentifier();
            if (strict && isStrictModeReservedWord(token.value)) {
                token.type = Token.Keyword;
            }
            return token;
        }

        // Very common: ( and ) and ;
        if (cp === 0x28 || cp === 0x29 || cp === 0x3B) {
            return scanPunctuator();
        }

        // String literal starts with single quote (U+0027) or double quote (U+0022).
        if (cp === 0x27 || cp === 0x22) {
            return scanStringLiteral();
        }

        // Dot (.) U+002E can also start a floating-point number, hence the need
        // to check the next character.
        if (cp === 0x2E) {
            if (isDecimalDigit(source.charCodeAt(index + 1))) {
                return scanNumericLiteral();
            }
            return scanPunctuator();
        }

        if (isDecimalDigit(cp)) {
            return scanNumericLiteral();
        }

        // Slash (/) U+002F can also start a regex.
        if (extra.tokenize && cp === 0x2F) {
            return advanceSlash();
        }

        // Template literals start with ` (U+0060) for template head
        // or } (U+007D) for template middle or template tail.
        if (cp === 0x60 || (cp === 0x7D && state.curlyStack[state.curlyStack.length - 1] === '${')) {
            return scanTemplate();
        }

        // Possible identifier start in a surrogate pair.
        if (cp >= 0xD800 && cp < 0xDFFF) {
            cp = codePointAt(index);
            if (isIdentifierStart(cp)) {
                return scanIdentifier();
            }
        }

        return scanPunctuator();
    }

    function collectToken() {
        var loc, token, value, entry;

        loc = {
            start: {
                line: lineNumber,
                column: index - lineStart
            }
        };

        token = advance();
        loc.end = {
            line: lineNumber,
            column: index - lineStart
        };

        if (token.type !== Token.EOF) {
            value = source.slice(token.start, token.end);
            entry = {
                type: TokenName[token.type],
                value: value,
                range: [token.start, token.end],
                loc: loc
            };
            if (token.regex) {
                entry.regex = {
                    pattern: token.regex.pattern,
                    flags: token.regex.flags
                };
            }
            if (extra.tokenValues) {
                extra.tokenValues.push((entry.type === 'Punctuator' || entry.type === 'Keyword') ? entry.value : null);
            }
            if (extra.tokenize) {
                if (!extra.range) {
                    delete entry.range;
                }
                if (!extra.loc) {
                    delete entry.loc;
                }
                if (extra.delegate) {
                    entry = extra.delegate(entry);
                }
            }
            extra.tokens.push(entry);
        }

        return token;
    }

    function lex() {
        var token;
        scanning = true;

        lastIndex = index;
        lastLineNumber = lineNumber;
        lastLineStart = lineStart;

        skipComment();

        token = lookahead;

        startIndex = index;
        startLineNumber = lineNumber;
        startLineStart = lineStart;

        lookahead = (typeof extra.tokens !== 'undefined') ? collectToken() : advance();
        scanning = false;
        return token;
    }

    function peek() {
        scanning = true;

        skipComment();

        lastIndex = index;
        lastLineNumber = lineNumber;
        lastLineStart = lineStart;

        startIndex = index;
        startLineNumber = lineNumber;
        startLineStart = lineStart;

        lookahead = (typeof extra.tokens !== 'undefined') ? collectToken() : advance();
        scanning = false;
    }

    function Position() {
        this.line = startLineNumber;
        this.column = startIndex - startLineStart;
    }

    function SourceLocation() {
        this.start = new Position();
        this.end = null;
    }

    function WrappingSourceLocation(startToken) {
        this.start = {
            line: startToken.lineNumber,
            column: startToken.start - startToken.lineStart
        };
        this.end = null;
    }

    function Node() {
        if (extra.range) {
            this.range = [startIndex, 0];
        }
        if (extra.loc) {
            this.loc = new SourceLocation();
        }
    }

    function WrappingNode(startToken) {
        if (extra.range) {
            this.range = [startToken.start, 0];
        }
        if (extra.loc) {
            this.loc = new WrappingSourceLocation(startToken);
        }
    }

    WrappingNode.prototype = Node.prototype = {

        processComment: function () {
            var lastChild,
                innerComments,
                leadingComments,
                trailingComments,
                bottomRight = extra.bottomRightStack,
                i,
                comment,
                last = bottomRight[bottomRight.length - 1];

            if (this.type === Syntax.Program) {
                if (this.body.length > 0) {
                    return;
                }
            }
            /**
             * patch innnerComments for properties empty block
             * `function a() {/** comments **\/}`
             */

            if (this.type === Syntax.BlockStatement && this.body.length === 0) {
                innerComments = [];
                for (i = extra.leadingComments.length - 1; i >= 0; --i) {
                    comment = extra.leadingComments[i];
                    if (this.range[1] >= comment.range[1]) {
                        innerComments.unshift(comment);
                        extra.leadingComments.splice(i, 1);
                        extra.trailingComments.splice(i, 1);
                    }
                }
                if (innerComments.length) {
                    this.innerComments = innerComments;
                    //bottomRight.push(this);
                    return;
                }
            }

            if (extra.trailingComments.length > 0) {
                trailingComments = [];
                for (i = extra.trailingComments.length - 1; i >= 0; --i) {
                    comment = extra.trailingComments[i];
                    if (comment.range[0] >= this.range[1]) {
                        trailingComments.unshift(comment);
                        extra.trailingComments.splice(i, 1);
                    }
                }
                extra.trailingComments = [];
            } else {
                if (last && last.trailingComments && last.trailingComments[0].range[0] >= this.range[1]) {
                    trailingComments = last.trailingComments;
                    delete last.trailingComments;
                }
            }

            // Eating the stack.
            while (last && last.range[0] >= this.range[0]) {
                lastChild = bottomRight.pop();
                last = bottomRight[bottomRight.length - 1];
            }

            if (lastChild) {
                if (lastChild.leadingComments) {
                    leadingComments = [];
                    for (i = lastChild.leadingComments.length - 1; i >= 0; --i) {
                        comment = lastChild.leadingComments[i];
                        if (comment.range[1] <= this.range[0]) {
                            leadingComments.unshift(comment);
                            lastChild.leadingComments.splice(i, 1);
                        }
                    }

                    if (!lastChild.leadingComments.length) {
                        lastChild.leadingComments = undefined;
                    }
                }
            } else if (extra.leadingComments.length > 0) {
                leadingComments = [];
                for (i = extra.leadingComments.length - 1; i >= 0; --i) {
                    comment = extra.leadingComments[i];
                    if (comment.range[1] <= this.range[0]) {
                        leadingComments.unshift(comment);
                        extra.leadingComments.splice(i, 1);
                    }
                }
            }


            if (leadingComments && leadingComments.length > 0) {
                this.leadingComments = leadingComments;
            }
            if (trailingComments && trailingComments.length > 0) {
                this.trailingComments = trailingComments;
            }

            bottomRight.push(this);
        },

        finish: function () {
            if (extra.range) {
                this.range[1] = lastIndex;
            }
            if (extra.loc) {
                this.loc.end = {
                    line: lastLineNumber,
                    column: lastIndex - lastLineStart
                };
                if (extra.source) {
                    this.loc.source = extra.source;
                }
            }

            if (extra.attachComment) {
                this.processComment();
            }
        },

        finishArrayExpression: function (elements) {
            this.type = Syntax.ArrayExpression;
            this.elements = elements;
            this.finish();
            return this;
        },

        finishArrayPattern: function (elements) {
            this.type = Syntax.ArrayPattern;
            this.elements = elements;
            this.finish();
            return this;
        },

        finishArrowFunctionExpression: function (params, defaults, body, expression) {
            this.type = Syntax.ArrowFunctionExpression;
            this.id = null;
            this.params = params;
            this.defaults = defaults;
            this.body = body;
            this.generator = false;
            this.expression = expression;
            this.finish();
            return this;
        },

        finishAssignmentExpression: function (operator, left, right) {
            this.type = Syntax.AssignmentExpression;
            this.operator = operator;
            this.left = left;
            this.right = right;
            this.finish();
            return this;
        },

        finishAssignmentPattern: function (left, right) {
            this.type = Syntax.AssignmentPattern;
            this.left = left;
            this.right = right;
            this.finish();
            return this;
        },

        finishBinaryExpression: function (operator, left, right) {
            this.type = (operator === '||' || operator === '&&') ? Syntax.LogicalExpression : Syntax.BinaryExpression;
            this.operator = operator;
            this.left = left;
            this.right = right;
            this.finish();
            return this;
        },

        finishBlockStatement: function (body) {
            this.type = Syntax.BlockStatement;
            this.body = body;
            this.finish();
            return this;
        },

        finishBreakStatement: function (label) {
            this.type = Syntax.BreakStatement;
            this.label = label;
            this.finish();
            return this;
        },

        finishCallExpression: function (callee, args) {
            this.type = Syntax.CallExpression;
            this.callee = callee;
            this.arguments = args;
            this.finish();
            return this;
        },

        finishCatchClause: function (param, body) {
            this.type = Syntax.CatchClause;
            this.param = param;
            this.body = body;
            this.finish();
            return this;
        },

        finishClassBody: function (body) {
            this.type = Syntax.ClassBody;
            this.body = body;
            this.finish();
            return this;
        },

        finishClassDeclaration: function (id, superClass, body) {
            this.type = Syntax.ClassDeclaration;
            this.id = id;
            this.superClass = superClass;
            this.body = body;
            this.finish();
            return this;
        },

        finishClassExpression: function (id, superClass, body) {
            this.type = Syntax.ClassExpression;
            this.id = id;
            this.superClass = superClass;
            this.body = body;
            this.finish();
            return this;
        },

        finishConditionalExpression: function (test, consequent, alternate) {
            this.type = Syntax.ConditionalExpression;
            this.test = test;
            this.consequent = consequent;
            this.alternate = alternate;
            this.finish();
            return this;
        },

        finishContinueStatement: function (label) {
            this.type = Syntax.ContinueStatement;
            this.label = label;
            this.finish();
            return this;
        },

        finishDebuggerStatement: function () {
            this.type = Syntax.DebuggerStatement;
            this.finish();
            return this;
        },

        finishDoWhileStatement: function (body, test) {
            this.type = Syntax.DoWhileStatement;
            this.body = body;
            this.test = test;
            this.finish();
            return this;
        },

        finishEmptyStatement: function () {
            this.type = Syntax.EmptyStatement;
            this.finish();
            return this;
        },

        finishExpressionStatement: function (expression) {
            this.type = Syntax.ExpressionStatement;
            this.expression = expression;
            this.finish();
            return this;
        },

        finishForStatement: function (init, test, update, body) {
            this.type = Syntax.ForStatement;
            this.init = init;
            this.test = test;
            this.update = update;
            this.body = body;
            this.finish();
            return this;
        },

        finishForOfStatement: function (left, right, body) {
            this.type = Syntax.ForOfStatement;
            this.left = left;
            this.right = right;
            this.body = body;
            this.finish();
            return this;
        },

        finishForInStatement: function (left, right, body) {
            this.type = Syntax.ForInStatement;
            this.left = left;
            this.right = right;
            this.body = body;
            this.each = false;
            this.finish();
            return this;
        },

        finishFunctionDeclaration: function (id, params, defaults, body, generator) {
            this.type = Syntax.FunctionDeclaration;
            this.id = id;
            this.params = params;
            this.defaults = defaults;
            this.body = body;
            this.generator = generator;
            this.expression = false;
            this.finish();
            return this;
        },

        finishFunctionExpression: function (id, params, defaults, body, generator) {
            this.type = Syntax.FunctionExpression;
            this.id = id;
            this.params = params;
            this.defaults = defaults;
            this.body = body;
            this.generator = generator;
            this.expression = false;
            this.finish();
            return this;
        },

        finishIdentifier: function (name) {
            this.type = Syntax.Identifier;
            this.name = name;
            this.finish();
            return this;
        },

        finishIfStatement: function (test, consequent, alternate) {
            this.type = Syntax.IfStatement;
            this.test = test;
            this.consequent = consequent;
            this.alternate = alternate;
            this.finish();
            return this;
        },

        finishLabeledStatement: function (label, body) {
            this.type = Syntax.LabeledStatement;
            this.label = label;
            this.body = body;
            this.finish();
            return this;
        },

        finishLiteral: function (token) {
            this.type = Syntax.Literal;
            this.value = token.value;
            this.raw = source.slice(token.start, token.end);
            if (token.regex) {
                this.regex = token.regex;
            }
            this.finish();
            return this;
        },

        finishMemberExpression: function (accessor, object, property) {
            this.type = Syntax.MemberExpression;
            this.computed = accessor === '[';
            this.object = object;
            this.property = property;
            this.finish();
            return this;
        },

        finishMetaProperty: function (meta, property) {
            this.type = Syntax.MetaProperty;
            this.meta = meta;
            this.property = property;
            this.finish();
            return this;
        },

        finishNewExpression: function (callee, args) {
            this.type = Syntax.NewExpression;
            this.callee = callee;
            this.arguments = args;
            this.finish();
            return this;
        },

        finishObjectExpression: function (properties) {
            this.type = Syntax.ObjectExpression;
            this.properties = properties;
            this.finish();
            return this;
        },

        finishObjectPattern: function (properties) {
            this.type = Syntax.ObjectPattern;
            this.properties = properties;
            this.finish();
            return this;
        },

        finishPostfixExpression: function (operator, argument) {
            this.type = Syntax.UpdateExpression;
            this.operator = operator;
            this.argument = argument;
            this.prefix = false;
            this.finish();
            return this;
        },

        finishProgram: function (body, sourceType) {
            this.type = Syntax.Program;
            this.body = body;
            this.sourceType = sourceType;
            this.finish();
            return this;
        },

        finishProperty: function (kind, key, computed, value, method, shorthand) {
            this.type = Syntax.Property;
            this.key = key;
            this.computed = computed;
            this.value = value;
            this.kind = kind;
            this.method = method;
            this.shorthand = shorthand;
            this.finish();
            return this;
        },

        finishRestElement: function (argument) {
            this.type = Syntax.RestElement;
            this.argument = argument;
            this.finish();
            return this;
        },

        finishReturnStatement: function (argument) {
            this.type = Syntax.ReturnStatement;
            this.argument = argument;
            this.finish();
            return this;
        },

        finishSequenceExpression: function (expressions) {
            this.type = Syntax.SequenceExpression;
            this.expressions = expressions;
            this.finish();
            return this;
        },

        finishSpreadElement: function (argument) {
            this.type = Syntax.SpreadElement;
            this.argument = argument;
            this.finish();
            return this;
        },

        finishSwitchCase: function (test, consequent) {
            this.type = Syntax.SwitchCase;
            this.test = test;
            this.consequent = consequent;
            this.finish();
            return this;
        },

        finishSuper: function () {
            this.type = Syntax.Super;
            this.finish();
            return this;
        },

        finishSwitchStatement: function (discriminant, cases) {
            this.type = Syntax.SwitchStatement;
            this.discriminant = discriminant;
            this.cases = cases;
            this.finish();
            return this;
        },

        finishTaggedTemplateExpression: function (tag, quasi) {
            this.type = Syntax.TaggedTemplateExpression;
            this.tag = tag;
            this.quasi = quasi;
            this.finish();
            return this;
        },

        finishTemplateElement: function (value, tail) {
            this.type = Syntax.TemplateElement;
            this.value = value;
            this.tail = tail;
            this.finish();
            return this;
        },

        finishTemplateLiteral: function (quasis, expressions) {
            this.type = Syntax.TemplateLiteral;
            this.quasis = quasis;
            this.expressions = expressions;
            this.finish();
            return this;
        },

        finishThisExpression: function () {
            this.type = Syntax.ThisExpression;
            this.finish();
            return this;
        },

        finishThrowStatement: function (argument) {
            this.type = Syntax.ThrowStatement;
            this.argument = argument;
            this.finish();
            return this;
        },

        finishTryStatement: function (block, handler, finalizer) {
            this.type = Syntax.TryStatement;
            this.block = block;
            this.guardedHandlers = [];
            this.handlers = handler ? [handler] : [];
            this.handler = handler;
            this.finalizer = finalizer;
            this.finish();
            return this;
        },

        finishUnaryExpression: function (operator, argument) {
            this.type = (operator === '++' || operator === '--') ? Syntax.UpdateExpression : Syntax.UnaryExpression;
            this.operator = operator;
            this.argument = argument;
            this.prefix = true;
            this.finish();
            return this;
        },

        finishVariableDeclaration: function (declarations) {
            this.type = Syntax.VariableDeclaration;
            this.declarations = declarations;
            this.kind = 'var';
            this.finish();
            return this;
        },

        finishLexicalDeclaration: function (declarations, kind) {
            this.type = Syntax.VariableDeclaration;
            this.declarations = declarations;
            this.kind = kind;
            this.finish();
            return this;
        },

        finishVariableDeclarator: function (id, init) {
            this.type = Syntax.VariableDeclarator;
            this.id = id;
            this.init = init;
            this.finish();
            return this;
        },

        finishWhileStatement: function (test, body) {
            this.type = Syntax.WhileStatement;
            this.test = test;
            this.body = body;
            this.finish();
            return this;
        },

        finishWithStatement: function (object, body) {
            this.type = Syntax.WithStatement;
            this.object = object;
            this.body = body;
            this.finish();
            return this;
        },

        finishExportSpecifier: function (local, exported) {
            this.type = Syntax.ExportSpecifier;
            this.exported = exported || local;
            this.local = local;
            this.finish();
            return this;
        },

        finishImportDefaultSpecifier: function (local) {
            this.type = Syntax.ImportDefaultSpecifier;
            this.local = local;
            this.finish();
            return this;
        },

        finishImportNamespaceSpecifier: function (local) {
            this.type = Syntax.ImportNamespaceSpecifier;
            this.local = local;
            this.finish();
            return this;
        },

        finishExportNamedDeclaration: function (declaration, specifiers, src) {
            this.type = Syntax.ExportNamedDeclaration;
            this.declaration = declaration;
            this.specifiers = specifiers;
            this.source = src;
            this.finish();
            return this;
        },

        finishExportDefaultDeclaration: function (declaration) {
            this.type = Syntax.ExportDefaultDeclaration;
            this.declaration = declaration;
            this.finish();
            return this;
        },

        finishExportAllDeclaration: function (src) {
            this.type = Syntax.ExportAllDeclaration;
            this.source = src;
            this.finish();
            return this;
        },

        finishImportSpecifier: function (local, imported) {
            this.type = Syntax.ImportSpecifier;
            this.local = local || imported;
            this.imported = imported;
            this.finish();
            return this;
        },

        finishImportDeclaration: function (specifiers, src) {
            this.type = Syntax.ImportDeclaration;
            this.specifiers = specifiers;
            this.source = src;
            this.finish();
            return this;
        },

        finishYieldExpression: function (argument, delegate) {
            this.type = Syntax.YieldExpression;
            this.argument = argument;
            this.delegate = delegate;
            this.finish();
            return this;
        }
    };


    function recordError(error) {
        var e, existing;

        for (e = 0; e < extra.errors.length; e++) {
            existing = extra.errors[e];
            // Prevent duplicated error.
            /* istanbul ignore next */
            if (existing.index === error.index && existing.message === error.message) {
                return;
            }
        }

        extra.errors.push(error);
    }

    function constructError(msg, column) {
        var error = new Error(msg);
        try {
            throw error;
        } catch (base) {
            /* istanbul ignore else */
            if (Object.create && Object.defineProperty) {
                error = Object.create(base);
                Object.defineProperty(error, 'column', { value: column });
            }
        } finally {
            return error;
        }
    }

    function createError(line, pos, description) {
        var msg, column, error;

        column = pos - (scanning ? lineStart : lastLineStart) + 1;
        msg = 'Line ' + line + ' Column '+column+': ' + description;
        error = constructError(msg, column);
        error.lineNumber = line;
        error.description = description;
        error.index = pos;
        return error;
    }

    // Throw an exception

    function throwError(messageFormat) {
        var args, msg;

        args = Array.prototype.slice.call(arguments, 1);
        msg = messageFormat.replace(/%(\d)/g,
            function (whole, idx) {
                assert(idx < args.length, 'Message reference must be in range');
                return args[idx];
            }
        );

        throw createError(lastLineNumber, lastIndex, msg);
    }

    function tolerateError(messageFormat) {
        var args, msg, error;

        args = Array.prototype.slice.call(arguments, 1);
        /* istanbul ignore next */
        msg = messageFormat.replace(/%(\d)/g,
            function (whole, idx) {
                assert(idx < args.length, 'Message reference must be in range');
                return args[idx];
            }
        );

        error = createError(lineNumber, lastIndex, msg);
        if (extra.errors) {
            recordError(error);
        } else {
            throw error;
        }
    }

    // Throw an exception because of the token.

    function unexpectedTokenError(token, message) {
        var value, msg = message || Messages.UnexpectedToken;

        if (token) {
            if (!message) {
                msg = (token.type === Token.EOF) ? Messages.UnexpectedEOS :
                    (token.type === Token.Identifier) ? Messages.UnexpectedIdentifier :
                    (token.type === Token.NumericLiteral) ? Messages.UnexpectedNumber :
                    (token.type === Token.StringLiteral) ? Messages.UnexpectedString :
                    (token.type === Token.Template) ? Messages.UnexpectedTemplate :
                    Messages.UnexpectedToken;

                if (token.type === Token.Keyword) {
                    if (isFutureReservedWord(token.value)) {
                        msg = Messages.UnexpectedReserved;
                    } else if (strict && isStrictModeReservedWord(token.value)) {
                        msg = Messages.StrictReservedWord;
                    }
                }
            }

            value = (token.type === Token.Template) ? token.value.raw : token.value;
        } else {
            value = 'ILLEGAL';
        }

        msg = msg.replace('%0', value);

        return (token && typeof token.lineNumber === 'number') ?
            createError(token.lineNumber, token.start, msg) :
            createError(scanning ? lineNumber : lastLineNumber, scanning ? index : lastIndex, msg);
    }

    function throwUnexpectedToken(token, message) {
        throw unexpectedTokenError(token, message);
    }

    function tolerateUnexpectedToken(token, message) {
        var error = unexpectedTokenError(token, message);
        if (extra.errors) {
            recordError(error);
        } else {
            throw error;
        }
    }

    // Expect the next token to match the specified punctuator.
    // If not, an exception will be thrown.

    function expect(value) {
        var token = lex();
        if (token.type !== Token.Punctuator || token.value !== value) {
            throwUnexpectedToken(token);
        }
    }

    /**
     * @name expectCommaSeparator
     * @description Quietly expect a comma when in tolerant mode, otherwise delegates
     * to <code>expect(value)</code>
     * @since 2.0
     */
    function expectCommaSeparator() {
        var token;

        if (extra.errors) {
            token = lookahead;
            if (token.type === Token.Punctuator && token.value === ',') {
                lex();
            } else if (token.type === Token.Punctuator && token.value === ';') {
                lex();
                tolerateUnexpectedToken(token);
            } else {
                tolerateUnexpectedToken(token, Messages.UnexpectedToken);
            }
        } else {
            expect(',');
        }
    }

    // Expect the next token to match the specified keyword.
    // If not, an exception will be thrown.

    function expectKeyword(keyword) {
        var token = lex();
        if (token.type !== Token.Keyword || token.value !== keyword) {
            throwUnexpectedToken(token);
        }
    }

    // Return true if the next token matches the specified punctuator.

    function match(value) {
        return lookahead.type === Token.Punctuator && lookahead.value === value;
    }

    // Return true if the next token matches the specified keyword

    function matchKeyword(keyword) {
        return lookahead.type === Token.Keyword && lookahead.value === keyword;
    }

    // Return true if the next token matches the specified contextual keyword
    // (where an identifier is sometimes a keyword depending on the context)

    function matchContextualKeyword(keyword) {
        return lookahead.type === Token.Identifier && lookahead.value === keyword;
    }

    // Return true if the next token is an assignment operator

    function matchAssign() {
        var op;

        if (lookahead.type !== Token.Punctuator) {
            return false;
        }
        op = lookahead.value;
        return op === '=' ||
            op === '*=' ||
            op === '/=' ||
            op === '%=' ||
            op === '+=' ||
            op === '-=' ||
            op === '<<=' ||
            op === '>>=' ||
            op === '>>>=' ||
            op === '&=' ||
            op === '^=' ||
            op === '|=';
    }

    function consumeSemicolon() {
        // Catch the very common case first: immediately a semicolon (U+003B).
        if (source.charCodeAt(startIndex) === 0x3B || match(';')) {
            lex();
            return;
        }

        if (hasLineTerminator) {
            return;
        }

        // FIXME(ikarienator): this is seemingly an issue in the previous location info convention.
        lastIndex = startIndex;
        lastLineNumber = startLineNumber;
        lastLineStart = startLineStart;

        if (lookahead.type !== Token.EOF && !match('}')) {
            throwUnexpectedToken(lookahead);
        }
    }

    // Cover grammar support.
    //
    // When an assignment expression position starts with an left parenthesis, the determination of the type
    // of the syntax is to be deferred arbitrarily long until the end of the parentheses pair (plus a lookahead)
    // or the first comma. This situation also defers the determination of all the expressions nested in the pair.
    //
    // There are three productions that can be parsed in a parentheses pair that needs to be determined
    // after the outermost pair is closed. They are:
    //
    //   1. AssignmentExpression
    //   2. BindingElements
    //   3. AssignmentTargets
    //
    // In order to avoid exponential backtracking, we use two flags to denote if the production can be
    // binding element or assignment target.
    //
    // The three productions have the relationship:
    //
    //   BindingElements ⊆ AssignmentTargets ⊆ AssignmentExpression
    //
    // with a single exception that CoverInitializedName when used directly in an Expression, generates
    // an early error. Therefore, we need the third state, firstCoverInitializedNameError, to track the
    // first usage of CoverInitializedName and report it when we reached the end of the parentheses pair.
    //
    // isolateCoverGrammar function runs the given parser function with a new cover grammar context, and it does not
    // effect the current flags. This means the production the parser parses is only used as an expression. Therefore
    // the CoverInitializedName check is conducted.
    //
    // inheritCoverGrammar function runs the given parse function with a new cover grammar context, and it propagates
    // the flags outside of the parser. This means the production the parser parses is used as a part of a potential
    // pattern. The CoverInitializedName check is deferred.
    function isolateCoverGrammar(parser) {
        var oldIsBindingElement = isBindingElement,
            oldIsAssignmentTarget = isAssignmentTarget,
            oldFirstCoverInitializedNameError = firstCoverInitializedNameError,
            result;
        isBindingElement = true;
        isAssignmentTarget = true;
        firstCoverInitializedNameError = null;
        result = parser();
        if (firstCoverInitializedNameError !== null) {
            throwUnexpectedToken(firstCoverInitializedNameError);
        }
        isBindingElement = oldIsBindingElement;
        isAssignmentTarget = oldIsAssignmentTarget;
        firstCoverInitializedNameError = oldFirstCoverInitializedNameError;
        return result;
    }

    function inheritCoverGrammar(parser) {
        var oldIsBindingElement = isBindingElement,
            oldIsAssignmentTarget = isAssignmentTarget,
            oldFirstCoverInitializedNameError = firstCoverInitializedNameError,
            result;
        isBindingElement = true;
        isAssignmentTarget = true;
        firstCoverInitializedNameError = null;
        result = parser();
        isBindingElement = isBindingElement && oldIsBindingElement;
        isAssignmentTarget = isAssignmentTarget && oldIsAssignmentTarget;
        firstCoverInitializedNameError = oldFirstCoverInitializedNameError || firstCoverInitializedNameError;
        return result;
    }

    // ECMA-262 13.3.3 Destructuring Binding Patterns

    function parseArrayPattern(params, kind) {
        var node = new Node(), elements = [], rest, restNode;
        expect('[');

        while (!match(']')) {
            if (match(',')) {
                lex();
                elements.push(null);
            } else {
                if (match('...')) {
                    restNode = new Node();
                    lex();
                    params.push(lookahead);
                    rest = parseVariableIdentifier(kind);
                    elements.push(restNode.finishRestElement(rest));
                    break;
                } else {
                    elements.push(parsePatternWithDefault(params, kind));
                }
                if (!match(']')) {
                    expect(',');
                }
            }

        }

        expect(']');

        return node.finishArrayPattern(elements);
    }

    function parsePropertyPattern(params, kind) {
        var node = new Node(), key, keyToken, computed = match('['), init;
        if (lookahead.type === Token.Identifier) {
            keyToken = lookahead;
            key = parseVariableIdentifier();
            if (match('=')) {
                params.push(keyToken);
                lex();
                init = parseAssignmentExpression();

                return node.finishProperty(
                    'init', key, false,
                    new WrappingNode(keyToken).finishAssignmentPattern(key, init), false, false);
            } else if (!match(':')) {
                params.push(keyToken);
                return node.finishProperty('init', key, false, key, false, true);
            }
        } else {
            key = parseObjectPropertyKey();
        }
        expect(':');
        init = parsePatternWithDefault(params, kind);
        return node.finishProperty('init', key, computed, init, false, false);
    }

    function parseObjectPattern(params, kind) {
        var node = new Node(), properties = [];

        expect('{');

        while (!match('}')) {
            properties.push(parsePropertyPattern(params, kind));
            if (!match('}')) {
                expect(',');
            }
        }

        lex();

        return node.finishObjectPattern(properties);
    }

    function parsePattern(params, kind) {
        if (match('[')) {
            return parseArrayPattern(params, kind);
        } else if (match('{')) {
            return parseObjectPattern(params, kind);
        } else if (matchKeyword('let')) {
            if (kind === 'const' || kind === 'let') {
                tolerateUnexpectedToken(lookahead, Messages.UnexpectedToken);
            }
        }

        params.push(lookahead);
        return parseVariableIdentifier(kind);
    }

    function parsePatternWithDefault(params, kind) {
        var startToken = lookahead, pattern, previousAllowYield, right;
        pattern = parsePattern(params, kind);
        if (match('=')) {
            lex();
            previousAllowYield = state.allowYield;
            state.allowYield = true;
            right = isolateCoverGrammar(parseAssignmentExpression);
            state.allowYield = previousAllowYield;
            pattern = new WrappingNode(startToken).finishAssignmentPattern(pattern, right);
        }
        return pattern;
    }

    // ECMA-262 12.2.5 Array Initializer

    function parseArrayInitializer() {
        var elements = [], node = new Node(), restSpread;

        expect('[');

        while (!match(']')) {
            if (match(',')) {
                lex();
                elements.push(null);
            } else if (match('...')) {
                restSpread = new Node();
                lex();
                restSpread.finishSpreadElement(inheritCoverGrammar(parseAssignmentExpression));

                if (!match(']')) {
                    isAssignmentTarget = isBindingElement = false;
                    expect(',');
                }
                elements.push(restSpread);
            } else {
                elements.push(inheritCoverGrammar(parseAssignmentExpression));

                if (!match(']')) {
                    expect(',');
                }
            }
        }

        lex();

        return node.finishArrayExpression(elements);
    }

    // ECMA-262 12.2.6 Object Initializer

    function parsePropertyFunction(node, paramInfo, isGenerator) {
        var previousStrict, body;

        isAssignmentTarget = isBindingElement = false;

        previousStrict = strict;
        body = isolateCoverGrammar(parseFunctionSourceElements);

        if (strict && paramInfo.firstRestricted) {
            tolerateUnexpectedToken(paramInfo.firstRestricted, paramInfo.message);
        }
        if (strict && paramInfo.stricted) {
            tolerateUnexpectedToken(paramInfo.stricted, paramInfo.message);
        }

        strict = previousStrict;
        return node.finishFunctionExpression(null, paramInfo.params, paramInfo.defaults, body, isGenerator);
    }

    function parsePropertyMethodFunction() {
        var params, method, node = new Node(),
            previousAllowYield = state.allowYield;

        state.allowYield = false;
        params = parseParams();
        state.allowYield = previousAllowYield;

        state.allowYield = false;
        method = parsePropertyFunction(node, params, false);
        state.allowYield = previousAllowYield;

        return method;
    }

    function parseObjectPropertyKey() {
        var token, node = new Node(), expr;

        token = lex();

        // Note: This function is called only from parseObjectProperty(), where
        // EOF and Punctuator tokens are already filtered out.

        switch (token.type) {
        case Token.StringLiteral:
        case Token.NumericLiteral:
            if (strict && token.octal) {
                tolerateUnexpectedToken(token, Messages.StrictOctalLiteral);
            }
            return node.finishLiteral(token);
        case Token.Identifier:
        case Token.BooleanLiteral:
        case Token.NullLiteral:
        case Token.Keyword:
            return node.finishIdentifier(token.value);
        case Token.Punctuator:
            if (token.value === '[') {
                expr = isolateCoverGrammar(parseAssignmentExpression);
                expect(']');
                return expr;
            }
            break;
        }
        throwUnexpectedToken(token);
    }

    function lookaheadPropertyName() {
        switch (lookahead.type) {
        case Token.Identifier:
        case Token.StringLiteral:
        case Token.BooleanLiteral:
        case Token.NullLiteral:
        case Token.NumericLiteral:
        case Token.Keyword:
            return true;
        case Token.Punctuator:
            return lookahead.value === '[';
        }
        return false;
    }

    // This function is to try to parse a MethodDefinition as defined in 14.3. But in the case of object literals,
    // it might be called at a position where there is in fact a short hand identifier pattern or a data property.
    // This can only be determined after we consumed up to the left parentheses.
    //
    // In order to avoid back tracking, it returns `null` if the position is not a MethodDefinition and the caller
    // is responsible to visit other options.
    function tryParseMethodDefinition(token, key, computed, node) {
        var value, options, methodNode, params,
            previousAllowYield = state.allowYield;

        if (token.type === Token.Identifier) {
            // check for `get` and `set`;

            if (token.value === 'get' && lookaheadPropertyName()) {
                computed = match('[');
                key = parseObjectPropertyKey();
                methodNode = new Node();
                expect('(');
                expect(')');

                state.allowYield = false;
                value = parsePropertyFunction(methodNode, {
                    params: [],
                    defaults: [],
                    stricted: null,
                    firstRestricted: null,
                    message: null
                }, false);
                state.allowYield = previousAllowYield;

                return node.finishProperty('get', key, computed, value, false, false);
            } else if (token.value === 'set' && lookaheadPropertyName()) {
                computed = match('[');
                key = parseObjectPropertyKey();
                methodNode = new Node();
                expect('(');

                options = {
                    params: [],
                    defaultCount: 0,
                    defaults: [],
                    firstRestricted: null,
                    paramSet: {}
                };
                if (match(')')) {
                    tolerateUnexpectedToken(lookahead);
                } else {
                    state.allowYield = false;
                    parseParam(options);
                    state.allowYield = previousAllowYield;
                    if (options.defaultCount === 0) {
                        options.defaults = [];
                    }
                }
                expect(')');

                state.allowYield = false;
                value = parsePropertyFunction(methodNode, options, false);
                state.allowYield = previousAllowYield;

                return node.finishProperty('set', key, computed, value, false, false);
            }
        } else if (token.type === Token.Punctuator && token.value === '*' && lookaheadPropertyName()) {
            computed = match('[');
            key = parseObjectPropertyKey();
            methodNode = new Node();

            state.allowYield = true;
            params = parseParams();
            state.allowYield = previousAllowYield;

            state.allowYield = false;
            value = parsePropertyFunction(methodNode, params, true);
            state.allowYield = previousAllowYield;

            return node.finishProperty('init', key, computed, value, true, false);
        }

        if (key && match('(')) {
            value = parsePropertyMethodFunction();
            return node.finishProperty('init', key, computed, value, true, false);
        }

        // Not a MethodDefinition.
        return null;
    }

    function parseObjectProperty(hasProto) {
        var token = lookahead, node = new Node(), computed, key, maybeMethod, proto, value;

        computed = match('[');
        if (match('*')) {
            lex();
        } else {
            key = parseObjectPropertyKey();
        }
        maybeMethod = tryParseMethodDefinition(token, key, computed, node);
        if (maybeMethod) {
            return maybeMethod;
        }

        if (!key) {
            throwUnexpectedToken(lookahead);
        }

        // Check for duplicated __proto__
        if (!computed) {
            proto = (key.type === Syntax.Identifier && key.name === '__proto__') ||
                (key.type === Syntax.Literal && key.value === '__proto__');
            if (hasProto.value && proto) {
                tolerateError(Messages.DuplicateProtoProperty);
            }
            hasProto.value |= proto;
        }

        if (match(':')) {
            lex();
            value = inheritCoverGrammar(parseAssignmentExpression);
            return node.finishProperty('init', key, computed, value, false, false);
        }

        if (token.type === Token.Identifier) {
            if (match('=')) {
                firstCoverInitializedNameError = lookahead;
                lex();
                value = isolateCoverGrammar(parseAssignmentExpression);
                return node.finishProperty('init', key, computed,
                    new WrappingNode(token).finishAssignmentPattern(key, value), false, true);
            }
            return node.finishProperty('init', key, computed, key, false, true);
        }

        throwUnexpectedToken(lookahead);
    }

    function parseObjectInitializer() {
        var properties = [], hasProto = {value: false}, node = new Node();

        expect('{');

        while (!match('}')) {
            properties.push(parseObjectProperty(hasProto));

            if (!match('}')) {
                expectCommaSeparator();
            }
        }

        expect('}');

        return node.finishObjectExpression(properties);
    }

    function reinterpretExpressionAsPattern(expr) {
        var i;
        switch (expr.type) {
        case Syntax.Identifier:
        case Syntax.MemberExpression:
        case Syntax.RestElement:
        case Syntax.AssignmentPattern:
            break;
        case Syntax.SpreadElement:
            expr.type = Syntax.RestElement;
            reinterpretExpressionAsPattern(expr.argument);
            break;
        case Syntax.ArrayExpression:
            expr.type = Syntax.ArrayPattern;
            for (i = 0; i < expr.elements.length; i++) {
                if (expr.elements[i] !== null) {
                    reinterpretExpressionAsPattern(expr.elements[i]);
                }
            }
            break;
        case Syntax.ObjectExpression:
            expr.type = Syntax.ObjectPattern;
            for (i = 0; i < expr.properties.length; i++) {
                reinterpretExpressionAsPattern(expr.properties[i].value);
            }
            break;
        case Syntax.AssignmentExpression:
            expr.type = Syntax.AssignmentPattern;
            reinterpretExpressionAsPattern(expr.left);
            break;
        default:
            // Allow other node type for tolerant parsing.
            break;
        }
    }

    // ECMA-262 12.2.9 Template Literals

    function parseTemplateElement(option) {
        var node, token;

        if (lookahead.type !== Token.Template || (option.head && !lookahead.head)) {
            throwUnexpectedToken();
        }

        node = new Node();
        token = lex();

        return node.finishTemplateElement({ raw: token.value.raw, cooked: token.value.cooked }, token.tail);
    }

    function parseTemplateLiteral() {
        var quasi, quasis, expressions, node = new Node();

        quasi = parseTemplateElement({ head: true });
        quasis = [quasi];
        expressions = [];

        while (!quasi.tail) {
            expressions.push(parseExpression());
            quasi = parseTemplateElement({ head: false });
            quasis.push(quasi);
        }

        return node.finishTemplateLiteral(quasis, expressions);
    }

    // ECMA-262 12.2.10 The Grouping Operator

    function parseGroupExpression() {
        var expr, expressions, startToken, i, params = [];

        expect('(');

        if (match(')')) {
            lex();
            if (!match('=>')) {
                expect('=>');
            }
            return {
                type: PlaceHolders.ArrowParameterPlaceHolder,
                params: [],
                rawParams: []
            };
        }

        startToken = lookahead;
        if (match('...')) {
            expr = parseRestElement(params);
            expect(')');
            if (!match('=>')) {
                expect('=>');
            }
            return {
                type: PlaceHolders.ArrowParameterPlaceHolder,
                params: [expr]
            };
        }

        isBindingElement = true;
        expr = inheritCoverGrammar(parseAssignmentExpression);

        if (match(',')) {
            isAssignmentTarget = false;
            expressions = [expr];

            while (startIndex < length) {
                if (!match(',')) {
                    break;
                }
                lex();

                if (match('...')) {
                    if (!isBindingElement) {
                        throwUnexpectedToken(lookahead);
                    }
                    expressions.push(parseRestElement(params));
                    expect(')');
                    if (!match('=>')) {
                        expect('=>');
                    }
                    isBindingElement = false;
                    for (i = 0; i < expressions.length; i++) {
                        reinterpretExpressionAsPattern(expressions[i]);
                    }
                    return {
                        type: PlaceHolders.ArrowParameterPlaceHolder,
                        params: expressions
                    };
                }

                expressions.push(inheritCoverGrammar(parseAssignmentExpression));
            }

            expr = new WrappingNode(startToken).finishSequenceExpression(expressions);
        }


        expect(')');

        if (match('=>')) {
            if (expr.type === Syntax.Identifier && expr.name === 'yield') {
                return {
                    type: PlaceHolders.ArrowParameterPlaceHolder,
                    params: [expr]
                };
            }

            if (!isBindingElement) {
                throwUnexpectedToken(lookahead);
            }

            if (expr.type === Syntax.SequenceExpression) {
                for (i = 0; i < expr.expressions.length; i++) {
                    reinterpretExpressionAsPattern(expr.expressions[i]);
                }
            } else {
                reinterpretExpressionAsPattern(expr);
            }

            expr = {
                type: PlaceHolders.ArrowParameterPlaceHolder,
                params: expr.type === Syntax.SequenceExpression ? expr.expressions : [expr]
            };
        }
        isBindingElement = false;
        return expr;
    }


    // ECMA-262 12.2 Primary Expressions

    function parsePrimaryExpression() {
        var type, token, expr, node;

        if (match('(')) {
            isBindingElement = false;
            return inheritCoverGrammar(parseGroupExpression);
        }

        if (match('[')) {
            return inheritCoverGrammar(parseArrayInitializer);
        }

        if (match('{')) {
            return inheritCoverGrammar(parseObjectInitializer);
        }

        type = lookahead.type;
        node = new Node();

        if (type === Token.Identifier) {
            if (state.sourceType === 'module' && lookahead.value === 'await') {
                tolerateUnexpectedToken(lookahead);
            }
            expr = node.finishIdentifier(lex().value);
        } else if (type === Token.StringLiteral || type === Token.NumericLiteral) {
            isAssignmentTarget = isBindingElement = false;
            if (strict && lookahead.octal) {
                tolerateUnexpectedToken(lookahead, Messages.StrictOctalLiteral);
            }
            expr = node.finishLiteral(lex());
        } else if (type === Token.Keyword) {
            if (!strict && state.allowYield && matchKeyword('yield')) {
                return parseNonComputedProperty();
            }
            isAssignmentTarget = isBindingElement = false;
            if (matchKeyword('function')) {
                return parseFunctionExpression();
            }
            if (matchKeyword('this')) {
                lex();
                return node.finishThisExpression();
            }
            if (matchKeyword('class')) {
                return parseClassExpression();
            }
            if (!strict && matchKeyword('let')) {
                return node.finishIdentifier(lex().value);
            }
            throwUnexpectedToken(lex());
        } else if (type === Token.BooleanLiteral) {
            isAssignmentTarget = isBindingElement = false;
            token = lex();
            token.value = (token.value === 'true');
            expr = node.finishLiteral(token);
        } else if (type === Token.NullLiteral) {
            isAssignmentTarget = isBindingElement = false;
            token = lex();
            token.value = null;
            expr = node.finishLiteral(token);
        } else if (match('/') || match('/=')) {
            isAssignmentTarget = isBindingElement = false;
            index = startIndex;

            if (typeof extra.tokens !== 'undefined') {
                token = collectRegex();
            } else {
                token = scanRegExp();
            }
            lex();
            expr = node.finishLiteral(token);
        } else if (type === Token.Template) {
            expr = parseTemplateLiteral();
        } else {
            throwUnexpectedToken(lex());
        }

        return expr;
    }

    // ECMA-262 12.3 Left-Hand-Side Expressions

    function parseArguments() {
        var args = [], expr;

        expect('(');

        if (!match(')')) {
            while (startIndex < length) {
                if (match('...')) {
                    expr = new Node();
                    lex();
                    expr.finishSpreadElement(isolateCoverGrammar(parseAssignmentExpression));
                } else {
                    expr = isolateCoverGrammar(parseAssignmentExpression);
                }
                args.push(expr);
                if (match(')')) {
                    break;
                }
                expectCommaSeparator();
            }
        }

        expect(')');

        return args;
    }

    function parseNonComputedProperty() {
        var token, node = new Node();

        token = lex();

        if (!isIdentifierName(token)) {
            throwUnexpectedToken(token);
        }

        return node.finishIdentifier(token.value);
    }

    function parseNonComputedMember() {
        expect('.');

        return parseNonComputedProperty();
    }

    function parseComputedMember() {
        var expr;

        expect('[');

        expr = isolateCoverGrammar(parseExpression);

        expect(']');

        return expr;
    }

    // ECMA-262 12.3.3 The new Operator

    function parseNewExpression() {
        var callee, args, node = new Node();

        expectKeyword('new');

        if (match('.')) {
            lex();
            if (lookahead.type === Token.Identifier && lookahead.value === 'target') {
                if (state.inFunctionBody) {
                    lex();
                    return node.finishMetaProperty('new', 'target');
                }
            }
            throwUnexpectedToken(lookahead);
        }

        callee = isolateCoverGrammar(parseLeftHandSideExpression);
        args = match('(') ? parseArguments() : [];

        isAssignmentTarget = isBindingElement = false;

        return node.finishNewExpression(callee, args);
    }

    // ECMA-262 12.3.4 Function Calls

    function parseLeftHandSideExpressionAllowCall() {
        var quasi, expr, args, property, startToken, previousAllowIn = state.allowIn;

        startToken = lookahead;
        state.allowIn = true;

        if (matchKeyword('super') && state.inFunctionBody) {
            expr = new Node();
            lex();
            expr = expr.finishSuper();
            if (!match('(') && !match('.') && !match('[')) {
                throwUnexpectedToken(lookahead);
            }
        } else {
            expr = inheritCoverGrammar(matchKeyword('new') ? parseNewExpression : parsePrimaryExpression);
        }

        for (;;) {
            if (match('.')) {
                isBindingElement = false;
                isAssignmentTarget = true;
                property = parseNonComputedMember();
                expr = new WrappingNode(startToken).finishMemberExpression('.', expr, property);
            } else if (match('(')) {
                isBindingElement = false;
                isAssignmentTarget = false;
                args = parseArguments();
                expr = new WrappingNode(startToken).finishCallExpression(expr, args);
            } else if (match('[')) {
                isBindingElement = false;
                isAssignmentTarget = true;
                property = parseComputedMember();
                expr = new WrappingNode(startToken).finishMemberExpression('[', expr, property);
            } else if (lookahead.type === Token.Template && lookahead.head) {
                quasi = parseTemplateLiteral();
                expr = new WrappingNode(startToken).finishTaggedTemplateExpression(expr, quasi);
            } else {
                break;
            }
        }
        state.allowIn = previousAllowIn;

        return expr;
    }

    // ECMA-262 12.3 Left-Hand-Side Expressions

    function parseLeftHandSideExpression() {
        var quasi, expr, property, startToken;
        assert(state.allowIn, 'callee of new expression always allow in keyword.');

        startToken = lookahead;

        if (matchKeyword('super') && state.inFunctionBody) {
            expr = new Node();
            lex();
            expr = expr.finishSuper();
            if (!match('[') && !match('.')) {
                throwUnexpectedToken(lookahead);
            }
        } else {
            expr = inheritCoverGrammar(matchKeyword('new') ? parseNewExpression : parsePrimaryExpression);
        }

        for (;;) {
            if (match('[')) {
                isBindingElement = false;
                isAssignmentTarget = true;
                property = parseComputedMember();
                expr = new WrappingNode(startToken).finishMemberExpression('[', expr, property);
            } else if (match('.')) {
                isBindingElement = false;
                isAssignmentTarget = true;
                property = parseNonComputedMember();
                expr = new WrappingNode(startToken).finishMemberExpression('.', expr, property);
            } else if (lookahead.type === Token.Template && lookahead.head) {
                quasi = parseTemplateLiteral();
                expr = new WrappingNode(startToken).finishTaggedTemplateExpression(expr, quasi);
            } else {
                break;
            }
        }
        return expr;
    }

    // ECMA-262 12.4 Postfix Expressions

    function parsePostfixExpression() {
        var expr, token, startToken = lookahead;

        expr = inheritCoverGrammar(parseLeftHandSideExpressionAllowCall);

        if (!hasLineTerminator && lookahead.type === Token.Punctuator) {
            if (match('++') || match('--')) {
                // ECMA-262 11.3.1, 11.3.2
                if (strict && expr.type === Syntax.Identifier && isRestrictedWord(expr.name)) {
                    tolerateError(Messages.StrictLHSPostfix);
                }

                if (!isAssignmentTarget) {
                    tolerateError(Messages.InvalidLHSInAssignment);
                }

                isAssignmentTarget = isBindingElement = false;

                token = lex();
                expr = new WrappingNode(startToken).finishPostfixExpression(token.value, expr);
            }
        }

        return expr;
    }

    // ECMA-262 12.5 Unary Operators

    function parseUnaryExpression() {
        var token, expr, startToken;

        if (lookahead.type !== Token.Punctuator && lookahead.type !== Token.Keyword) {
            expr = parsePostfixExpression();
        } else if (match('++') || match('--')) {
            startToken = lookahead;
            token = lex();
            expr = inheritCoverGrammar(parseUnaryExpression);
            // ECMA-262 11.4.4, 11.4.5
            if (strict && expr.type === Syntax.Identifier && isRestrictedWord(expr.name)) {
                tolerateError(Messages.StrictLHSPrefix);
            }

            if (!isAssignmentTarget) {
                tolerateError(Messages.InvalidLHSInAssignment);
            }
            expr = new WrappingNode(startToken).finishUnaryExpression(token.value, expr);
            isAssignmentTarget = isBindingElement = false;
        } else if (match('+') || match('-') || match('~') || match('!')) {
            startToken = lookahead;
            token = lex();
            expr = inheritCoverGrammar(parseUnaryExpression);
            expr = new WrappingNode(startToken).finishUnaryExpression(token.value, expr);
            isAssignmentTarget = isBindingElement = false;
        } else if (matchKeyword('delete') || matchKeyword('void') || matchKeyword('typeof')) {
            startToken = lookahead;
            token = lex();
            expr = inheritCoverGrammar(parseUnaryExpression);
            expr = new WrappingNode(startToken).finishUnaryExpression(token.value, expr);
            if (strict && expr.operator === 'delete' && expr.argument.type === Syntax.Identifier) {
                tolerateError(Messages.StrictDelete);
            }
            isAssignmentTarget = isBindingElement = false;
        } else {
            expr = parsePostfixExpression();
        }

        return expr;
    }

    function binaryPrecedence(token, allowIn) {
        var prec = 0;

        if (token.type !== Token.Punctuator && token.type !== Token.Keyword) {
            return 0;
        }

        switch (token.value) {
        case '||':
            prec = 1;
            break;

        case '&&':
            prec = 2;
            break;

        case '|':
            prec = 3;
            break;

        case '^':
            prec = 4;
            break;

        case '&':
            prec = 5;
            break;

        case '==':
        case '!=':
        case '===':
        case '!==':
            prec = 6;
            break;

        case '<':
        case '>':
        case '<=':
        case '>=':
        case 'instanceof':
            prec = 7;
            break;

        case 'in':
            prec = allowIn ? 7 : 0;
            break;

        case '<<':
        case '>>':
        case '>>>':
            prec = 8;
            break;

        case '+':
        case '-':
            prec = 9;
            break;

        case '*':
        case '/':
        case '%':
            prec = 11;
            break;

        default:
            break;
        }

        return prec;
    }

    // ECMA-262 12.6 Multiplicative Operators
    // ECMA-262 12.7 Additive Operators
    // ECMA-262 12.8 Bitwise Shift Operators
    // ECMA-262 12.9 Relational Operators
    // ECMA-262 12.10 Equality Operators
    // ECMA-262 12.11 Binary Bitwise Operators
    // ECMA-262 12.12 Binary Logical Operators

    function parseBinaryExpression() {
        var marker, markers, expr, token, prec, stack, right, operator, left, i;

        marker = lookahead;
        left = inheritCoverGrammar(parseUnaryExpression);

        token = lookahead;
        prec = binaryPrecedence(token, state.allowIn);
        if (prec === 0) {
            return left;
        }
        isAssignmentTarget = isBindingElement = false;
        token.prec = prec;
        lex();

        markers = [marker, lookahead];
        right = isolateCoverGrammar(parseUnaryExpression);

        stack = [left, token, right];

        while ((prec = binaryPrecedence(lookahead, state.allowIn)) > 0) {

            // Reduce: make a binary expression from the three topmost entries.
            while ((stack.length > 2) && (prec <= stack[stack.length - 2].prec)) {
                right = stack.pop();
                operator = stack.pop().value;
                left = stack.pop();
                markers.pop();
                expr = new WrappingNode(markers[markers.length - 1]).finishBinaryExpression(operator, left, right);
                stack.push(expr);
            }

            // Shift.
            token = lex();
            token.prec = prec;
            stack.push(token);
            markers.push(lookahead);
            expr = isolateCoverGrammar(parseUnaryExpression);
            stack.push(expr);
        }

        // Final reduce to clean-up the stack.
        i = stack.length - 1;
        expr = stack[i];
        markers.pop();
        while (i > 1) {
            expr = new WrappingNode(markers.pop()).finishBinaryExpression(stack[i - 1].value, stack[i - 2], expr);
            i -= 2;
        }

        return expr;
    }


    // ECMA-262 12.13 Conditional Operator

    function parseConditionalExpression() {
        var expr, previousAllowIn, consequent, alternate, startToken;

        startToken = lookahead;

        expr = inheritCoverGrammar(parseBinaryExpression);
        if (match('?')) {
            lex();
            previousAllowIn = state.allowIn;
            state.allowIn = true;
            consequent = isolateCoverGrammar(parseAssignmentExpression);
            state.allowIn = previousAllowIn;
            expect(':');
            alternate = isolateCoverGrammar(parseAssignmentExpression);

            expr = new WrappingNode(startToken).finishConditionalExpression(expr, consequent, alternate);
            isAssignmentTarget = isBindingElement = false;
        }

        return expr;
    }

    // ECMA-262 14.2 Arrow Function Definitions

    function parseConciseBody() {
        if (match('{')) {
            return parseFunctionSourceElements();
        }
        return isolateCoverGrammar(parseAssignmentExpression);
    }

    function checkPatternParam(options, param) {
        var i;
        switch (param.type) {
        case Syntax.Identifier:
            validateParam(options, param, param.name);
            break;
        case Syntax.RestElement:
            checkPatternParam(options, param.argument);
            break;
        case Syntax.AssignmentPattern:
            checkPatternParam(options, param.left);
            break;
        case Syntax.ArrayPattern:
            for (i = 0; i < param.elements.length; i++) {
                if (param.elements[i] !== null) {
                    checkPatternParam(options, param.elements[i]);
                }
            }
            break;
        case Syntax.YieldExpression:
            break;
        default:
            assert(param.type === Syntax.ObjectPattern, 'Invalid type');
            for (i = 0; i < param.properties.length; i++) {
                checkPatternParam(options, param.properties[i].value);
            }
            break;
        }
    }
    function reinterpretAsCoverFormalsList(expr) {
        var i, len, param, params, defaults, defaultCount, options, token;

        defaults = [];
        defaultCount = 0;
        params = [expr];

        switch (expr.type) {
        case Syntax.Identifier:
            break;
        case PlaceHolders.ArrowParameterPlaceHolder:
            params = expr.params;
            break;
        default:
            return null;
        }

        options = {
            paramSet: {}
        };

        for (i = 0, len = params.length; i < len; i += 1) {
            param = params[i];
            switch (param.type) {
            case Syntax.AssignmentPattern:
                params[i] = param.left;
                if (param.right.type === Syntax.YieldExpression) {
                    if (param.right.argument) {
                        throwUnexpectedToken(lookahead);
                    }
                    param.right.type = Syntax.Identifier;
                    param.right.name = 'yield';
                    delete param.right.argument;
                    delete param.right.delegate;
                }
                defaults.push(param.right);
                ++defaultCount;
                checkPatternParam(options, param.left);
                break;
            default:
                checkPatternParam(options, param);
                params[i] = param;
                defaults.push(null);
                break;
            }
        }

        if (strict || !state.allowYield) {
            for (i = 0, len = params.length; i < len; i += 1) {
                param = params[i];
                if (param.type === Syntax.YieldExpression) {
                    throwUnexpectedToken(lookahead);
                }
            }
        }

        if (options.message === Messages.StrictParamDupe) {
            token = strict ? options.stricted : options.firstRestricted;
            throwUnexpectedToken(token, options.message);
        }

        if (defaultCount === 0) {
            defaults = [];
        }

        return {
            params: params,
            defaults: defaults,
            stricted: options.stricted,
            firstRestricted: options.firstRestricted,
            message: options.message
        };
    }

    function parseArrowFunctionExpression(options, node) {
        var previousStrict, previousAllowYield, body;

        if (hasLineTerminator) {
            tolerateUnexpectedToken(lookahead);
        }
        expect('=>');

        previousStrict = strict;
        previousAllowYield = state.allowYield;
        state.allowYield = true;

        body = parseConciseBody();

        if (strict && options.firstRestricted) {
            throwUnexpectedToken(options.firstRestricted, options.message);
        }
        if (strict && options.stricted) {
            tolerateUnexpectedToken(options.stricted, options.message);
        }

        strict = previousStrict;
        state.allowYield = previousAllowYield;

        return node.finishArrowFunctionExpression(options.params, options.defaults, body, body.type !== Syntax.BlockStatement);
    }

    // ECMA-262 14.4 Yield expression

    function parseYieldExpression() {
        var argument, expr, delegate, previousAllowYield;

        argument = null;
        expr = new Node();

        expectKeyword('yield');

        if (!hasLineTerminator) {
            previousAllowYield = state.allowYield;
            state.allowYield = false;
            delegate = match('*');
            if (delegate) {
                lex();
                argument = parseAssignmentExpression();
            } else {
                if (!match(';') && !match('}') && !match(')') && lookahead.type !== Token.EOF) {
                    argument = parseAssignmentExpression();
                }
            }
            state.allowYield = previousAllowYield;
        }

        return expr.finishYieldExpression(argument, delegate);
    }

    // ECMA-262 12.14 Assignment Operators

    function parseAssignmentExpression() {
        var token, expr, right, list, startToken;

        startToken = lookahead;
        token = lookahead;

        if (!state.allowYield && matchKeyword('yield')) {
            return parseYieldExpression();
        }

        expr = parseConditionalExpression();

        if (expr.type === PlaceHolders.ArrowParameterPlaceHolder || match('=>')) {
            isAssignmentTarget = isBindingElement = false;
            list = reinterpretAsCoverFormalsList(expr);

            if (list) {
                firstCoverInitializedNameError = null;
                return parseArrowFunctionExpression(list, new WrappingNode(startToken));
            }

            return expr;
        }

        if (matchAssign()) {
            if (!isAssignmentTarget) {
                tolerateError(Messages.InvalidLHSInAssignment);
            }

            // ECMA-262 12.1.1
            if (strict && expr.type === Syntax.Identifier) {
                if (isRestrictedWord(expr.name)) {
                    tolerateUnexpectedToken(token, Messages.StrictLHSAssignment);
                }
                if (isStrictModeReservedWord(expr.name)) {
                    tolerateUnexpectedToken(token, Messages.StrictReservedWord);
                }
            }

            if (!match('=')) {
                isAssignmentTarget = isBindingElement = false;
            } else {
                reinterpretExpressionAsPattern(expr);
            }

            token = lex();
            right = isolateCoverGrammar(parseAssignmentExpression);
            expr = new WrappingNode(startToken).finishAssignmentExpression(token.value, expr, right);
            firstCoverInitializedNameError = null;
        }

        return expr;
    }

    // ECMA-262 12.15 Comma Operator

    function parseExpression() {
        var expr, startToken = lookahead, expressions;

        expr = isolateCoverGrammar(parseAssignmentExpression);

        if (match(',')) {
            expressions = [expr];

            while (startIndex < length) {
                if (!match(',')) {
                    break;
                }
                lex();
                expressions.push(isolateCoverGrammar(parseAssignmentExpression));
            }

            expr = new WrappingNode(startToken).finishSequenceExpression(expressions);
        }

        return expr;
    }

    // ECMA-262 13.2 Block

    function parseStatementListItem() {
        if (lookahead.type === Token.Keyword) {
            switch (lookahead.value) {
            case 'export':
                if (state.sourceType !== 'module') {
                    tolerateUnexpectedToken(lookahead, Messages.IllegalExportDeclaration);
                }
                return parseExportDeclaration();
            case 'import':
                if (state.sourceType !== 'module') {
                    tolerateUnexpectedToken(lookahead, Messages.IllegalImportDeclaration);
                }
                return parseImportDeclaration();
            case 'const':
                return parseLexicalDeclaration({inFor: false});
            case 'function':
                return parseFunctionDeclaration(new Node());
            case 'class':
                return parseClassDeclaration();
            }
        }

        if (matchKeyword('let') && isLexicalDeclaration()) {
            return parseLexicalDeclaration({inFor: false});
        }

        return parseStatement();
    }

    function parseStatementList() {
        var list = [];
        while (startIndex < length) {
            if (match('}')) {
                break;
            }
            list.push(parseStatementListItem());
        }

        return list;
    }

    function parseBlock() {
        var block, node = new Node();

        expect('{');

        block = parseStatementList();

        expect('}');

        return node.finishBlockStatement(block);
    }

    // ECMA-262 13.3.2 Variable Statement

    function parseVariableIdentifier(kind) {
        var token, node = new Node();

        token = lex();

        if (token.type === Token.Keyword && token.value === 'yield') {
            if (strict) {
                tolerateUnexpectedToken(token, Messages.StrictReservedWord);
            } if (!state.allowYield) {
                throwUnexpectedToken(token);
            }
        } else if (token.type !== Token.Identifier) {
            if (strict && token.type === Token.Keyword && isStrictModeReservedWord(token.value)) {
                tolerateUnexpectedToken(token, Messages.StrictReservedWord);
            } else {
                if (strict || token.value !== 'let' || kind !== 'var') {
                    throwUnexpectedToken(token);
                }
            }
        } else if (state.sourceType === 'module' && token.type === Token.Identifier && token.value === 'await') {
            tolerateUnexpectedToken(token);
        }

        return node.finishIdentifier(token.value);
    }

    function parseVariableDeclaration(options) {
        var init = null, id, node = new Node(), params = [];

        id = parsePattern(params, 'var');

        // ECMA-262 12.2.1
        if (strict && isRestrictedWord(id.name)) {
            tolerateError(Messages.StrictVarName);
        }

        if (match('=')) {
            lex();
            init = isolateCoverGrammar(parseAssignmentExpression);
        } else if (id.type !== Syntax.Identifier && !options.inFor) {
            expect('=');
        }

        return node.finishVariableDeclarator(id, init);
    }

    function parseVariableDeclarationList(options) {
        var list = [];

        do {
            list.push(parseVariableDeclaration({ inFor: options.inFor }));
            if (!match(',')) {
                break;
            }
            lex();
        } while (startIndex < length);

        return list;
    }

    function parseVariableStatement(node) {
        var declarations;

        expectKeyword('var');

        declarations = parseVariableDeclarationList({ inFor: false });

        consumeSemicolon();

        return node.finishVariableDeclaration(declarations);
    }

    // ECMA-262 13.3.1 Let and Const Declarations

    function parseLexicalBinding(kind, options) {
        var init = null, id, node = new Node(), params = [];

        id = parsePattern(params, kind);

        // ECMA-262 12.2.1
        if (strict && id.type === Syntax.Identifier && isRestrictedWord(id.name)) {
            tolerateError(Messages.StrictVarName);
        }

        if (kind === 'const') {
            if (!matchKeyword('in') && !matchContextualKeyword('of')) {
                expect('=');
                init = isolateCoverGrammar(parseAssignmentExpression);
            }
        } else if ((!options.inFor && id.type !== Syntax.Identifier) || match('=')) {
            expect('=');
            init = isolateCoverGrammar(parseAssignmentExpression);
        }

        return node.finishVariableDeclarator(id, init);
    }

    function parseBindingList(kind, options) {
        var list = [];

        do {
            list.push(parseLexicalBinding(kind, options));
            if (!match(',')) {
                break;
            }
            lex();
        } while (startIndex < length);

        return list;
    }


    function tokenizerState() {
        return {
            index: index,
            lineNumber: lineNumber,
            lineStart: lineStart,
            hasLineTerminator: hasLineTerminator,
            lastIndex: lastIndex,
            lastLineNumber: lastLineNumber,
            lastLineStart: lastLineStart,
            startIndex: startIndex,
            startLineNumber: startLineNumber,
            startLineStart: startLineStart,
            lookahead: lookahead,
            tokenCount: extra.tokens ? extra.tokens.length : 0
        };
    }

    function resetTokenizerState(ts) {
        index = ts.index;
        lineNumber = ts.lineNumber;
        lineStart = ts.lineStart;
        hasLineTerminator = ts.hasLineTerminator;
        lastIndex = ts.lastIndex;
        lastLineNumber = ts.lastLineNumber;
        lastLineStart = ts.lastLineStart;
        startIndex = ts.startIndex;
        startLineNumber = ts.startLineNumber;
        startLineStart = ts.startLineStart;
        lookahead = ts.lookahead;
        if (extra.tokens) {
            extra.tokens.splice(ts.tokenCount, extra.tokens.length);
        }
    }

    function isLexicalDeclaration() {
        var lexical, ts;

        ts = tokenizerState();

        lex();
        lexical = (lookahead.type === Token.Identifier) || match('[') || match('{') ||
            matchKeyword('let') || matchKeyword('yield');

        resetTokenizerState(ts);

        return lexical;
    }

    function parseLexicalDeclaration(options) {
        var kind, declarations, node = new Node();

        kind = lex().value;
        assert(kind === 'let' || kind === 'const', 'Lexical declaration must be either let or const');

        declarations = parseBindingList(kind, options);

        consumeSemicolon();

        return node.finishLexicalDeclaration(declarations, kind);
    }

    function parseRestElement(params) {
        var param, node = new Node();

        lex();

        if (match('{')) {
            throwError(Messages.ObjectPatternAsRestParameter);
        }

        params.push(lookahead);

        param = parseVariableIdentifier();

        if (match('=')) {
            throwError(Messages.DefaultRestParameter);
        }

        if (!match(')')) {
            throwError(Messages.ParameterAfterRestParameter);
        }

        return node.finishRestElement(param);
    }

    // ECMA-262 13.4 Empty Statement

    function parseEmptyStatement(node) {
        expect(';');
        return node.finishEmptyStatement();
    }

    // ECMA-262 12.4 Expression Statement

    function parseExpressionStatement(node) {
        var expr = parseExpression();
        consumeSemicolon();
        return node.finishExpressionStatement(expr);
    }

    // ECMA-262 13.6 If statement

    function parseIfStatement(node) {
        var test, consequent, alternate;

        expectKeyword('if');

        expect('(');

        test = parseExpression();

        expect(')');

        consequent = parseStatement();

        if (matchKeyword('else')) {
            lex();
            alternate = parseStatement();
        } else {
            alternate = null;
        }

        return node.finishIfStatement(test, consequent, alternate);
    }

    // ECMA-262 13.7 Iteration Statements

    function parseDoWhileStatement(node) {
        var body, test, oldInIteration;

        expectKeyword('do');

        oldInIteration = state.inIteration;
        state.inIteration = true;

        body = parseStatement();

        state.inIteration = oldInIteration;

        expectKeyword('while');

        expect('(');

        test = parseExpression();

        expect(')');

        if (match(';')) {
            lex();
        }

        return node.finishDoWhileStatement(body, test);
    }

    function parseWhileStatement(node) {
        var test, body, oldInIteration;

        expectKeyword('while');

        expect('(');

        test = parseExpression();

        expect(')');

        oldInIteration = state.inIteration;
        state.inIteration = true;

        body = parseStatement();

        state.inIteration = oldInIteration;

        return node.finishWhileStatement(test, body);
    }

    function parseForStatement(node) {
        var init, forIn, initSeq, initStartToken, test, update, left, right, kind, declarations,
            body, oldInIteration, previousAllowIn = state.allowIn;

        init = test = update = null;
        forIn = true;

        expectKeyword('for');

        expect('(');

        if (match(';')) {
            lex();
        } else {
            if (matchKeyword('var')) {
                init = new Node();
                lex();

                state.allowIn = false;
                declarations = parseVariableDeclarationList({ inFor: true });
                state.allowIn = previousAllowIn;

                if (declarations.length === 1 && matchKeyword('in')) {
                    init = init.finishVariableDeclaration(declarations);
                    lex();
                    left = init;
                    right = parseExpression();
                    init = null;
                } else if (declarations.length === 1 && declarations[0].init === null && matchContextualKeyword('of')) {
                    init = init.finishVariableDeclaration(declarations);
                    lex();
                    left = init;
                    right = parseAssignmentExpression();
                    init = null;
                    forIn = false;
                } else {
                    init = init.finishVariableDeclaration(declarations);
                    expect(';');
                }
            } else if (matchKeyword('const') || matchKeyword('let')) {
                init = new Node();
                kind = lex().value;

                if (!strict && lookahead.value === 'in') {
                    init = init.finishIdentifier(kind);
                    lex();
                    left = init;
                    right = parseExpression();
                    init = null;
                } else {
                    state.allowIn = false;
                    declarations = parseBindingList(kind, {inFor: true});
                    state.allowIn = previousAllowIn;

                    if (declarations.length === 1 && declarations[0].init === null && matchKeyword('in')) {
                        init = init.finishLexicalDeclaration(declarations, kind);
                        lex();
                        left = init;
                        right = parseExpression();
                        init = null;
                    } else if (declarations.length === 1 && declarations[0].init === null && matchContextualKeyword('of')) {
                        init = init.finishLexicalDeclaration(declarations, kind);
                        lex();
                        left = init;
                        right = parseAssignmentExpression();
                        init = null;
                        forIn = false;
                    } else {
                        consumeSemicolon();
                        init = init.finishLexicalDeclaration(declarations, kind);
                    }
                }
            } else {
                initStartToken = lookahead;
                state.allowIn = false;
                init = inheritCoverGrammar(parseAssignmentExpression);
                state.allowIn = previousAllowIn;

                if (matchKeyword('in')) {
                    if (!isAssignmentTarget) {
                        tolerateError(Messages.InvalidLHSInForIn);
                    }

                    lex();
                    reinterpretExpressionAsPattern(init);
                    left = init;
                    right = parseExpression();
                    init = null;
                } else if (matchContextualKeyword('of')) {
                    if (!isAssignmentTarget) {
                        tolerateError(Messages.InvalidLHSInForLoop);
                    }

                    lex();
                    reinterpretExpressionAsPattern(init);
                    left = init;
                    right = parseAssignmentExpression();
                    init = null;
                    forIn = false;
                } else {
                    if (match(',')) {
                        initSeq = [init];
                        while (match(',')) {
                            lex();
                            initSeq.push(isolateCoverGrammar(parseAssignmentExpression));
                        }
                        init = new WrappingNode(initStartToken).finishSequenceExpression(initSeq);
                    }
                    expect(';');
                }
            }
        }

        if (typeof left === 'undefined') {

            if (!match(';')) {
                test = parseExpression();
            }
            expect(';');

            if (!match(')')) {
                update = parseExpression();
            }
        }

        expect(')');

        oldInIteration = state.inIteration;
        state.inIteration = true;

        body = isolateCoverGrammar(parseStatement);

        state.inIteration = oldInIteration;

        return (typeof left === 'undefined') ?
                node.finishForStatement(init, test, update, body) :
                forIn ? node.finishForInStatement(left, right, body) :
                    node.finishForOfStatement(left, right, body);
    }

    // ECMA-262 13.8 The continue statement

    function parseContinueStatement(node) {
        var label = null, key;

        expectKeyword('continue');

        // Optimize the most common form: 'continue;'.
        if (source.charCodeAt(startIndex) === 0x3B) {
            lex();

            if (!state.inIteration) {
                throwError(Messages.IllegalContinue);
            }

            return node.finishContinueStatement(null);
        }

        if (hasLineTerminator) {
            if (!state.inIteration) {
                throwError(Messages.IllegalContinue);
            }

            return node.finishContinueStatement(null);
        }

        if (lookahead.type === Token.Identifier) {
            label = parseVariableIdentifier();

            key = '$' + label.name;
            if (!Object.prototype.hasOwnProperty.call(state.labelSet, key)) {
                throwError(Messages.UnknownLabel, label.name);
            }
        }

        consumeSemicolon();

        if (label === null && !state.inIteration) {
            throwError(Messages.IllegalContinue);
        }

        return node.finishContinueStatement(label);
    }

    // ECMA-262 13.9 The break statement

    function parseBreakStatement(node) {
        var label = null, key;

        expectKeyword('break');

        // Catch the very common case first: immediately a semicolon (U+003B).
        if (source.charCodeAt(lastIndex) === 0x3B) {
            lex();

            if (!(state.inIteration || state.inSwitch)) {
                throwError(Messages.IllegalBreak);
            }

            return node.finishBreakStatement(null);
        }

        if (hasLineTerminator) {
            if (!(state.inIteration || state.inSwitch)) {
                throwError(Messages.IllegalBreak);
            }
        } else if (lookahead.type === Token.Identifier) {
            label = parseVariableIdentifier();

            key = '$' + label.name;
            if (!Object.prototype.hasOwnProperty.call(state.labelSet, key)) {
                throwError(Messages.UnknownLabel, label.name);
            }
        }

        consumeSemicolon();

        if (label === null && !(state.inIteration || state.inSwitch)) {
            throwError(Messages.IllegalBreak);
        }

        return node.finishBreakStatement(label);
    }

    // ECMA-262 13.10 The return statement

    function parseReturnStatement(node) {
        var argument = null;

        expectKeyword('return');

        if (!state.inFunctionBody) {
            tolerateError(Messages.IllegalReturn);
        }

        // 'return' followed by a space and an identifier is very common.
        if (source.charCodeAt(lastIndex) === 0x20) {
            if (isIdentifierStart(source.charCodeAt(lastIndex + 1))) {
                argument = parseExpression();
                consumeSemicolon();
                return node.finishReturnStatement(argument);
            }
        }

        if (hasLineTerminator) {
            // HACK
            return node.finishReturnStatement(null);
        }

        if (!match(';')) {
            if (!match('}') && lookahead.type !== Token.EOF) {
                argument = parseExpression();
            }
        }

        consumeSemicolon();

        return node.finishReturnStatement(argument);
    }

    // ECMA-262 13.11 The with statement

    function parseWithStatement(node) {
        var object, body;

        if (strict) {
            tolerateError(Messages.StrictModeWith);
        }

        expectKeyword('with');

        expect('(');

        object = parseExpression();

        expect(')');

        body = parseStatement();

        return node.finishWithStatement(object, body);
    }

    // ECMA-262 13.12 The switch statement

    function parseSwitchCase() {
        var test, consequent = [], statement, node = new Node();

        if (matchKeyword('default')) {
            lex();
            test = null;
        } else {
            expectKeyword('case');
            test = parseExpression();
        }
        expect(':');

        while (startIndex < length) {
            if (match('}') || matchKeyword('default') || matchKeyword('case')) {
                break;
            }
            statement = parseStatementListItem();
            consequent.push(statement);
        }

        return node.finishSwitchCase(test, consequent);
    }

    function parseSwitchStatement(node) {
        var discriminant, cases, clause, oldInSwitch, defaultFound;

        expectKeyword('switch');

        expect('(');

        discriminant = parseExpression();

        expect(')');

        expect('{');

        cases = [];

        if (match('}')) {
            lex();
            return node.finishSwitchStatement(discriminant, cases);
        }

        oldInSwitch = state.inSwitch;
        state.inSwitch = true;
        defaultFound = false;

        while (startIndex < length) {
            if (match('}')) {
                break;
            }
            clause = parseSwitchCase();
            if (clause.test === null) {
                if (defaultFound) {
                    throwError(Messages.MultipleDefaultsInSwitch);
                }
                defaultFound = true;
            }
            cases.push(clause);
        }

        state.inSwitch = oldInSwitch;

        expect('}');

        return node.finishSwitchStatement(discriminant, cases);
    }

    // ECMA-262 13.14 The throw statement

    function parseThrowStatement(node) {
        var argument;

        expectKeyword('throw');

        if (hasLineTerminator) {
            throwError(Messages.NewlineAfterThrow);
        }

        argument = parseExpression();

        consumeSemicolon();

        return node.finishThrowStatement(argument);
    }

    // ECMA-262 13.15 The try statement

    function parseCatchClause() {
        var param, params = [], paramMap = {}, key, i, body, node = new Node();

        expectKeyword('catch');

        expect('(');
        if (match(')')) {
            throwUnexpectedToken(lookahead);
        }

        param = parsePattern(params);
        for (i = 0; i < params.length; i++) {
            key = '$' + params[i].value;
            if (Object.prototype.hasOwnProperty.call(paramMap, key)) {
                tolerateError(Messages.DuplicateBinding, params[i].value);
            }
            paramMap[key] = true;
        }

        // ECMA-262 12.14.1
        if (strict && isRestrictedWord(param.name)) {
            tolerateError(Messages.StrictCatchVariable);
        }

        expect(')');
        body = parseBlock();
        return node.finishCatchClause(param, body);
    }

    function parseTryStatement(node) {
        var block, handler = null, finalizer = null;

        expectKeyword('try');

        block = parseBlock();

        if (matchKeyword('catch')) {
            handler = parseCatchClause();
        }

        if (matchKeyword('finally')) {
            lex();
            finalizer = parseBlock();
        }

        if (!handler && !finalizer) {
            throwError(Messages.NoCatchOrFinally);
        }

        return node.finishTryStatement(block, handler, finalizer);
    }

    // ECMA-262 13.16 The debugger statement

    function parseDebuggerStatement(node) {
        expectKeyword('debugger');

        consumeSemicolon();

        return node.finishDebuggerStatement();
    }

    // 13 Statements

    function parseStatement() {
        var type = lookahead.type,
            expr,
            labeledBody,
            key,
            node;

        if (type === Token.EOF) {
            throwUnexpectedToken(lookahead);
        }

        if (type === Token.Punctuator && lookahead.value === '{') {
            return parseBlock();
        }
        isAssignmentTarget = isBindingElement = true;
        node = new Node();

        if (type === Token.Punctuator) {
            switch (lookahead.value) {
            case ';':
                return parseEmptyStatement(node);
            case '(':
                return parseExpressionStatement(node);
            default:
                break;
            }
        } else if (type === Token.Keyword) {
            switch (lookahead.value) {
            case 'break':
                return parseBreakStatement(node);
            case 'continue':
                return parseContinueStatement(node);
            case 'debugger':
                return parseDebuggerStatement(node);
            case 'do':
                return parseDoWhileStatement(node);
            case 'for':
                return parseForStatement(node);
            case 'function':
                return parseFunctionDeclaration(node);
            case 'if':
                return parseIfStatement(node);
            case 'return':
                return parseReturnStatement(node);
            case 'switch':
                return parseSwitchStatement(node);
            case 'throw':
                return parseThrowStatement(node);
            case 'try':
                return parseTryStatement(node);
            case 'var':
                return parseVariableStatement(node);
            case 'while':
                return parseWhileStatement(node);
            case 'with':
                return parseWithStatement(node);
            default:
                break;
            }
        }

        expr = parseExpression();

        // ECMA-262 12.12 Labelled Statements
        if ((expr.type === Syntax.Identifier) && match(':')) {
            lex();

            key = '$' + expr.name;
            if (Object.prototype.hasOwnProperty.call(state.labelSet, key)) {
                throwError(Messages.Redeclaration, 'Label', expr.name);
            }

            state.labelSet[key] = true;
            labeledBody = parseStatement();
            delete state.labelSet[key];
            return node.finishLabeledStatement(expr, labeledBody);
        }

        consumeSemicolon();

        return node.finishExpressionStatement(expr);
    }

    // ECMA-262 14.1 Function Definition

    function parseFunctionSourceElements() {
        var statement, body = [], token, directive, firstRestricted,
            oldLabelSet, oldInIteration, oldInSwitch, oldInFunctionBody, oldParenthesisCount,
            node = new Node();

        expect('{');

        while (startIndex < length) {
            if (lookahead.type !== Token.StringLiteral) {
                break;
            }
            token = lookahead;

            statement = parseStatementListItem();
            body.push(statement);
            if (statement.expression.type !== Syntax.Literal) {
                // this is not directive
                break;
            }
            directive = source.slice(token.start + 1, token.end - 1);
            if (directive === 'use strict') {
                strict = true;
                if (firstRestricted) {
                    tolerateUnexpectedToken(firstRestricted, Messages.StrictOctalLiteral);
                }
            } else {
                if (!firstRestricted && token.octal) {
                    firstRestricted = token;
                }
            }
        }

        oldLabelSet = state.labelSet;
        oldInIteration = state.inIteration;
        oldInSwitch = state.inSwitch;
        oldInFunctionBody = state.inFunctionBody;
        oldParenthesisCount = state.parenthesizedCount;

        state.labelSet = {};
        state.inIteration = false;
        state.inSwitch = false;
        state.inFunctionBody = true;
        state.parenthesizedCount = 0;

        while (startIndex < length) {
            if (match('}')) {
                break;
            }
            body.push(parseStatementListItem());
        }

        expect('}');

        state.labelSet = oldLabelSet;
        state.inIteration = oldInIteration;
        state.inSwitch = oldInSwitch;
        state.inFunctionBody = oldInFunctionBody;
        state.parenthesizedCount = oldParenthesisCount;

        return node.finishBlockStatement(body);
    }

    function validateParam(options, param, name) {
        var key = '$' + name;
        if (strict) {
            if (isRestrictedWord(name)) {
                options.stricted = param;
                options.message = Messages.StrictParamName;
            }
            if (Object.prototype.hasOwnProperty.call(options.paramSet, key)) {
                options.stricted = param;
                options.message = Messages.StrictParamDupe;
            }
        } else if (!options.firstRestricted) {
            if (isRestrictedWord(name)) {
                options.firstRestricted = param;
                options.message = Messages.StrictParamName;
            } else if (isStrictModeReservedWord(name)) {
                options.firstRestricted = param;
                options.message = Messages.StrictReservedWord;
            } else if (Object.prototype.hasOwnProperty.call(options.paramSet, key)) {
                options.stricted = param;
                options.message = Messages.StrictParamDupe;
            }
        }
        options.paramSet[key] = true;
    }

    function parseParam(options) {
        var token, param, params = [], i, def;

        token = lookahead;
        if (token.value === '...') {
            param = parseRestElement(params);
            validateParam(options, param.argument, param.argument.name);
            options.params.push(param);
            options.defaults.push(null);
            return false;
        }

        param = parsePatternWithDefault(params);
        for (i = 0; i < params.length; i++) {
            validateParam(options, params[i], params[i].value);
        }

        if (param.type === Syntax.AssignmentPattern) {
            def = param.right;
            param = param.left;
            ++options.defaultCount;
        }

        options.params.push(param);
        options.defaults.push(def);

        return !match(')');
    }

    function parseParams(firstRestricted) {
        var options;

        options = {
            params: [],
            defaultCount: 0,
            defaults: [],
            firstRestricted: firstRestricted
        };

        expect('(');

        if (!match(')')) {
            options.paramSet = {};
            while (startIndex < length) {
                if (!parseParam(options)) {
                    break;
                }
                expect(',');
            }
        }

        expect(')');

        if (options.defaultCount === 0) {
            options.defaults = [];
        }

        return {
            params: options.params,
            defaults: options.defaults,
            stricted: options.stricted,
            firstRestricted: options.firstRestricted,
            message: options.message
        };
    }

    function parseFunctionDeclaration(node, identifierIsOptional) {
        var id = null, params = [], defaults = [], body, token, stricted, tmp, firstRestricted, message, previousStrict,
            isGenerator, previousAllowYield;

        previousAllowYield = state.allowYield;

        expectKeyword('function');

        isGenerator = match('*');
        if (isGenerator) {
            lex();
        }

        if (!identifierIsOptional || !match('(')) {
            token = lookahead;
            id = parseVariableIdentifier();
            if (strict) {
                if (isRestrictedWord(token.value)) {
                    tolerateUnexpectedToken(token, Messages.StrictFunctionName);
                }
            } else {
                if (isRestrictedWord(token.value)) {
                    firstRestricted = token;
                    message = Messages.StrictFunctionName;
                } else if (isStrictModeReservedWord(token.value)) {
                    firstRestricted = token;
                    message = Messages.StrictReservedWord;
                }
            }
        }

        state.allowYield = !isGenerator;
        tmp = parseParams(firstRestricted);
        params = tmp.params;
        defaults = tmp.defaults;
        stricted = tmp.stricted;
        firstRestricted = tmp.firstRestricted;
        if (tmp.message) {
            message = tmp.message;
        }


        previousStrict = strict;
        body = parseFunctionSourceElements();
        if (strict && firstRestricted) {
            throwUnexpectedToken(firstRestricted, message);
        }
        if (strict && stricted) {
            tolerateUnexpectedToken(stricted, message);
        }

        strict = previousStrict;
        state.allowYield = previousAllowYield;

        return node.finishFunctionDeclaration(id, params, defaults, body, isGenerator);
    }

    function parseFunctionExpression() {
        var token, id = null, stricted, firstRestricted, message, tmp,
            params = [], defaults = [], body, previousStrict, node = new Node(),
            isGenerator, previousAllowYield;

        previousAllowYield = state.allowYield;

        expectKeyword('function');

        isGenerator = match('*');
        if (isGenerator) {
            lex();
        }

        state.allowYield = !isGenerator;
        if (!match('(')) {
            token = lookahead;
            id = (!strict && !isGenerator && matchKeyword('yield')) ? parseNonComputedProperty() : parseVariableIdentifier();
            if (strict) {
                if (isRestrictedWord(token.value)) {
                    tolerateUnexpectedToken(token, Messages.StrictFunctionName);
                }
            } else {
                if (isRestrictedWord(token.value)) {
                    firstRestricted = token;
                    message = Messages.StrictFunctionName;
                } else if (isStrictModeReservedWord(token.value)) {
                    firstRestricted = token;
                    message = Messages.StrictReservedWord;
                }
            }
        }

        tmp = parseParams(firstRestricted);
        params = tmp.params;
        defaults = tmp.defaults;
        stricted = tmp.stricted;
        firstRestricted = tmp.firstRestricted;
        if (tmp.message) {
            message = tmp.message;
        }

        previousStrict = strict;
        body = parseFunctionSourceElements();
        if (strict && firstRestricted) {
            throwUnexpectedToken(firstRestricted, message);
        }
        if (strict && stricted) {
            tolerateUnexpectedToken(stricted, message);
        }
        strict = previousStrict;
        state.allowYield = previousAllowYield;

        return node.finishFunctionExpression(id, params, defaults, body, isGenerator);
    }

    // ECMA-262 14.5 Class Definitions

    function parseClassBody() {
        var classBody, token, isStatic, hasConstructor = false, body, method, computed, key;

        classBody = new Node();

        expect('{');
        body = [];
        while (!match('}')) {
            if (match(';')) {
                lex();
            } else {
                method = new Node();
                token = lookahead;
                isStatic = false;
                computed = match('[');
                if (match('*')) {
                    lex();
                } else {
                    key = parseObjectPropertyKey();
                    if (key.name === 'static' && (lookaheadPropertyName() || match('*'))) {
                        token = lookahead;
                        isStatic = true;
                        computed = match('[');
                        if (match('*')) {
                            lex();
                        } else {
                            key = parseObjectPropertyKey();
                        }
                    }
                }
                method = tryParseMethodDefinition(token, key, computed, method);
                if (method) {
                    method['static'] = isStatic; // jscs:ignore requireDotNotation
                    if (method.kind === 'init') {
                        method.kind = 'method';
                    }
                    if (!isStatic) {
                        if (!method.computed && (method.key.name || method.key.value.toString()) === 'constructor') {
                            if (method.kind !== 'method' || !method.method || method.value.generator) {
                                throwUnexpectedToken(token, Messages.ConstructorSpecialMethod);
                            }
                            if (hasConstructor) {
                                throwUnexpectedToken(token, Messages.DuplicateConstructor);
                            } else {
                                hasConstructor = true;
                            }
                            method.kind = 'constructor';
                        }
                    } else {
                        if (!method.computed && (method.key.name || method.key.value.toString()) === 'prototype') {
                            throwUnexpectedToken(token, Messages.StaticPrototype);
                        }
                    }
                    method.type = Syntax.MethodDefinition;
                    delete method.method;
                    delete method.shorthand;
                    body.push(method);
                } else {
                    throwUnexpectedToken(lookahead);
                }
            }
        }
        lex();
        return classBody.finishClassBody(body);
    }

    function parseClassDeclaration(identifierIsOptional) {
        var id = null, superClass = null, classNode = new Node(), classBody, previousStrict = strict;
        strict = true;

        expectKeyword('class');

        if (!identifierIsOptional || lookahead.type === Token.Identifier) {
            id = parseVariableIdentifier();
        }

        if (matchKeyword('extends')) {
            lex();
            superClass = isolateCoverGrammar(parseLeftHandSideExpressionAllowCall);
        }
        classBody = parseClassBody();
        strict = previousStrict;

        return classNode.finishClassDeclaration(id, superClass, classBody);
    }

    function parseClassExpression() {
        var id = null, superClass = null, classNode = new Node(), classBody, previousStrict = strict;
        strict = true;

        expectKeyword('class');

        if (lookahead.type === Token.Identifier) {
            id = parseVariableIdentifier();
        }

        if (matchKeyword('extends')) {
            lex();
            superClass = isolateCoverGrammar(parseLeftHandSideExpressionAllowCall);
        }
        classBody = parseClassBody();
        strict = previousStrict;

        return classNode.finishClassExpression(id, superClass, classBody);
    }

    // ECMA-262 15.2 Modules

    function parseModuleSpecifier() {
        var node = new Node();

        if (lookahead.type !== Token.StringLiteral) {
            throwError(Messages.InvalidModuleSpecifier);
        }
        return node.finishLiteral(lex());
    }

    // ECMA-262 15.2.3 Exports

    function parseExportSpecifier() {
        var exported, local, node = new Node(), def;
        if (matchKeyword('default')) {
            // export {default} from 'something';
            def = new Node();
            lex();
            local = def.finishIdentifier('default');
        } else {
            local = parseVariableIdentifier();
        }
        if (matchContextualKeyword('as')) {
            lex();
            exported = parseNonComputedProperty();
        }
        return node.finishExportSpecifier(local, exported);
    }

    function parseExportNamedDeclaration(node) {
        var declaration = null,
            isExportFromIdentifier,
            src = null, specifiers = [];

        // non-default export
        if (lookahead.type === Token.Keyword) {
            // covers:
            // export var f = 1;
            switch (lookahead.value) {
                case 'let':
                case 'const':
                    declaration = parseLexicalDeclaration({inFor: false});
                    return node.finishExportNamedDeclaration(declaration, specifiers, null);
                case 'var':
                case 'class':
                case 'function':
                    declaration = parseStatementListItem();
                    return node.finishExportNamedDeclaration(declaration, specifiers, null);
            }
        }

        expect('{');
        while (!match('}')) {
            isExportFromIdentifier = isExportFromIdentifier || matchKeyword('default');
            specifiers.push(parseExportSpecifier());
            if (!match('}')) {
                expect(',');
                if (match('}')) {
                    break;
                }
            }
        }
        expect('}');

        if (matchContextualKeyword('from')) {
            // covering:
            // export {default} from 'foo';
            // export {foo} from 'foo';
            lex();
            src = parseModuleSpecifier();
            consumeSemicolon();
        } else if (isExportFromIdentifier) {
            // covering:
            // export {default}; // missing fromClause
            throwError(lookahead.value ?
                    Messages.UnexpectedToken : Messages.MissingFromClause, lookahead.value);
        } else {
            // cover
            // export {foo};
            consumeSemicolon();
        }
        return node.finishExportNamedDeclaration(declaration, specifiers, src);
    }

    function parseExportDefaultDeclaration(node) {
        var declaration = null,
            expression = null;

        // covers:
        // export default ...
        expectKeyword('default');

        if (matchKeyword('function')) {
            // covers:
            // export default function foo () {}
            // export default function () {}
            declaration = parseFunctionDeclaration(new Node(), true);
            return node.finishExportDefaultDeclaration(declaration);
        }
        if (matchKeyword('class')) {
            declaration = parseClassDeclaration(true);
            return node.finishExportDefaultDeclaration(declaration);
        }

        if (matchContextualKeyword('from')) {
            throwError(Messages.UnexpectedToken, lookahead.value);
        }

        // covers:
        // export default {};
        // export default [];
        // export default (1 + 2);
        if (match('{')) {
            expression = parseObjectInitializer();
        } else if (match('[')) {
            expression = parseArrayInitializer();
        } else {
            expression = parseAssignmentExpression();
        }
        consumeSemicolon();
        return node.finishExportDefaultDeclaration(expression);
    }

    function parseExportAllDeclaration(node) {
        var src;

        // covers:
        // export * from 'foo';
        expect('*');
        if (!matchContextualKeyword('from')) {
            throwError(lookahead.value ?
                    Messages.UnexpectedToken : Messages.MissingFromClause, lookahead.value);
        }
        lex();
        src = parseModuleSpecifier();
        consumeSemicolon();

        return node.finishExportAllDeclaration(src);
    }

    function parseExportDeclaration() {
        var node = new Node();
        if (state.inFunctionBody) {
            throwError(Messages.IllegalExportDeclaration);
        }

        expectKeyword('export');

        if (matchKeyword('default')) {
            return parseExportDefaultDeclaration(node);
        }
        if (match('*')) {
            return parseExportAllDeclaration(node);
        }
        return parseExportNamedDeclaration(node);
    }

    // ECMA-262 15.2.2 Imports

    function parseImportSpecifier() {
        // import {<foo as bar>} ...;
        var local, imported, node = new Node();

        imported = parseNonComputedProperty();
        if (matchContextualKeyword('as')) {
            lex();
            local = parseVariableIdentifier();
        }

        return node.finishImportSpecifier(local, imported);
    }

    function parseNamedImports() {
        var specifiers = [];
        // {foo, bar as bas}
        expect('{');
        while (!match('}')) {
            specifiers.push(parseImportSpecifier());
            if (!match('}')) {
                expect(',');
                if (match('}')) {
                    break;
                }
            }
        }
        expect('}');
        return specifiers;
    }

    function parseImportDefaultSpecifier() {
        // import <foo> ...;
        var local, node = new Node();

        local = parseNonComputedProperty();

        return node.finishImportDefaultSpecifier(local);
    }

    function parseImportNamespaceSpecifier() {
        // import <* as foo> ...;
        var local, node = new Node();

        expect('*');
        if (!matchContextualKeyword('as')) {
            throwError(Messages.NoAsAfterImportNamespace);
        }
        lex();
        local = parseNonComputedProperty();

        return node.finishImportNamespaceSpecifier(local);
    }

    function parseImportDeclaration() {
        var specifiers = [], src, node = new Node();

        if (state.inFunctionBody) {
            throwError(Messages.IllegalImportDeclaration);
        }

        expectKeyword('import');

        if (lookahead.type === Token.StringLiteral) {
            // import 'foo';
            src = parseModuleSpecifier();
        } else {

            if (match('{')) {
                // import {bar}
                specifiers = specifiers.concat(parseNamedImports());
            } else if (match('*')) {
                // import * as foo
                specifiers.push(parseImportNamespaceSpecifier());
            } else if (isIdentifierName(lookahead) && !matchKeyword('default')) {
                // import foo
                specifiers.push(parseImportDefaultSpecifier());
                if (match(',')) {
                    lex();
                    if (match('*')) {
                        // import foo, * as foo
                        specifiers.push(parseImportNamespaceSpecifier());
                    } else if (match('{')) {
                        // import foo, {bar}
                        specifiers = specifiers.concat(parseNamedImports());
                    } else {
                        throwUnexpectedToken(lookahead);
                    }
                }
            } else {
                throwUnexpectedToken(lex());
            }

            if (!matchContextualKeyword('from')) {
                throwError(lookahead.value ?
                        Messages.UnexpectedToken : Messages.MissingFromClause, lookahead.value);
            }
            lex();
            src = parseModuleSpecifier();
        }

        consumeSemicolon();
        return node.finishImportDeclaration(specifiers, src);
    }

    // ECMA-262 15.1 Scripts

    function parseScriptBody() {
        var statement, body = [], token, directive, firstRestricted;

        while (startIndex < length) {
            token = lookahead;
            if (token.type !== Token.StringLiteral) {
                break;
            }

            statement = parseStatementListItem();
            body.push(statement);
            if (statement.expression.type !== Syntax.Literal) {
                // this is not directive
                break;
            }
            directive = source.slice(token.start + 1, token.end - 1);
            if (directive === 'use strict') {
                strict = true;
                if (firstRestricted) {
                    tolerateUnexpectedToken(firstRestricted, Messages.StrictOctalLiteral);
                }
            } else {
                if (!firstRestricted && token.octal) {
                    firstRestricted = token;
                }
            }
        }

        while (startIndex < length) {
            statement = parseStatementListItem();
            /* istanbul ignore if */
            if (typeof statement === 'undefined') {
                break;
            }
            body.push(statement);
        }
        return body;
    }

    function parseProgram() {
        var body, node;

        peek();
        node = new Node();

        body = parseScriptBody();
        return node.finishProgram(body, state.sourceType);
    }

    function filterTokenLocation() {
        var i, entry, token, tokens = [];

        for (i = 0; i < extra.tokens.length; ++i) {
            entry = extra.tokens[i];
            token = {
                type: entry.type,
                value: entry.value
            };
            if (entry.regex) {
                token.regex = {
                    pattern: entry.regex.pattern,
                    flags: entry.regex.flags
                };
            }
            if (extra.range) {
                token.range = entry.range;
            }
            if (extra.loc) {
                token.loc = entry.loc;
            }
            tokens.push(token);
        }

        extra.tokens = tokens;
    }

    function tokenize(code, options, delegate) {
        var toString,
            tokens;

        toString = String;
        if (typeof code !== 'string' && !(code instanceof String)) {
            code = toString(code);
        }

        source = code;
        index = 0;
        lineNumber = (source.length > 0) ? 1 : 0;
        lineStart = 0;
        startIndex = index;
        startLineNumber = lineNumber;
        startLineStart = lineStart;
        length = source.length;
        lookahead = null;
        state = {
            allowIn: true,
            allowYield: true,
            labelSet: {},
            inFunctionBody: false,
            inIteration: false,
            inSwitch: false,
            lastCommentStart: -1,
            curlyStack: []
        };

        extra = {};

        // Options matching.
        options = options || {};

        // Of course we collect tokens here.
        options.tokens = true;
        extra.tokens = [];
        extra.tokenValues = [];
        extra.tokenize = true;
        extra.delegate = delegate;

        // The following two fields are necessary to compute the Regex tokens.
        extra.openParenToken = -1;
        extra.openCurlyToken = -1;

        extra.range = (typeof options.range === 'boolean') && options.range;
        extra.loc = (typeof options.loc === 'boolean') && options.loc;

        if (typeof options.comment === 'boolean' && options.comment) {
            extra.comments = [];
        }
        if (typeof options.tolerant === 'boolean' && options.tolerant) {
            extra.errors = [];
        }

        try {
            peek();
            if (lookahead.type === Token.EOF) {
                return extra.tokens;
            }

            lex();
            while (lookahead.type !== Token.EOF) {
                try {
                    lex();
                } catch (lexError) {
                    if (extra.errors) {
                        recordError(lexError);
                        // We have to break on the first error
                        // to avoid infinite loops.
                        break;
                    } else {
                        throw lexError;
                    }
                }
            }

            tokens = extra.tokens;
            if (typeof extra.errors !== 'undefined') {
                tokens.errors = extra.errors;
            }
        } catch (e) {
            throw e;
        } finally {
            extra = {};
        }
        return tokens;
    }

    function parse(code, options) {
        var program, toString;

        toString = String;
        if (typeof code !== 'string' && !(code instanceof String)) {
            code = toString(code);
        }

        source = code;
        index = 0;
        lineNumber = (source.length > 0) ? 1 : 0;
        lineStart = 0;
        startIndex = index;
        startLineNumber = lineNumber;
        startLineStart = lineStart;
        length = source.length;
        lookahead = null;
        state = {
            allowIn: true,
            allowYield: true,
            labelSet: {},
            inFunctionBody: false,
            inIteration: false,
            inSwitch: false,
            lastCommentStart: -1,
            curlyStack: [],
            sourceType: 'script'
        };
        strict = false;

        extra = {};
        if (typeof options !== 'undefined') {
            extra.range = (typeof options.range === 'boolean') && options.range;
            extra.loc = (typeof options.loc === 'boolean') && options.loc;
            extra.attachComment = (typeof options.attachComment === 'boolean') && options.attachComment;

            if (extra.loc && options.source !== null && options.source !== undefined) {
                extra.source = toString(options.source);
            }

            if (typeof options.tokens === 'boolean' && options.tokens) {
                extra.tokens = [];
            }
            if (typeof options.comment === 'boolean' && options.comment) {
                extra.comments = [];
            }
            if (typeof options.tolerant === 'boolean' && options.tolerant) {
                extra.errors = [];
            }
            if (extra.attachComment) {
                extra.range = true;
                extra.comments = [];
                extra.bottomRightStack = [];
                extra.trailingComments = [];
                extra.leadingComments = [];
            }
            if (options.sourceType === 'module') {
                // very restrictive condition for now
                state.sourceType = options.sourceType;
                strict = true;
            }
        }

        try {
            program = parseProgram();
            if (typeof extra.comments !== 'undefined') {
                program.comments = extra.comments;
            }
            if (typeof extra.tokens !== 'undefined') {
                filterTokenLocation();
                program.tokens = extra.tokens;
            }
            if (typeof extra.errors !== 'undefined') {
                program.errors = extra.errors;
            }
        } catch (e) {
            throw e;
        } finally {
            extra = {};
        }

        return program;
    }

    // Sync with *.json manifests.
    exports.version = '2.7.0';

    exports.tokenize = tokenize;

    exports.parse = parse;

    // Deep copy.
    /* istanbul ignore next */
    exports.Syntax = (function () {
        var name, types = {};

        if (typeof Object.create === 'function') {
            types = Object.create(null);
        }

        for (name in Syntax) {
            if (Syntax.hasOwnProperty(name)) {
                types[name] = Syntax[name];
            }
        }

        if (typeof Object.freeze === 'function') {
            Object.freeze(types);
        }

        return types;
    }());

}));
/* vim: set sw=4 ts=4 et tw=80 : */
};
BundleModuleCode['jam/sandbox']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2006-2022 bLAB
 **    $CREATED:     1-1-17 by sbosse.
 **    $RCS:         $Id: sandbox.js,v 1.2 2020/02/03 09:45:01 sbosse Exp sbosse $
 **    $VERSION:     1.6.2
 **
 **    $INFO:
 **
 **  JavaScript AIOS Sandbox Function Constructor
 **
 **   Two version: (1) with eval (2) with new Function
 **   Evaluated code is sometimes slower than a constructed function, though with(mask){} is a performance
 *    leak, too.
 **
 **    $ENDOFINFO
 */
var Json = Require('jam/jsonfn');

/** Returns a new function f' without global references
 *  except them provided in modules, and the masked environemnt.
 *  An optional checkpointing function call cp can be optionally injected.
 *
 * typeof @inject = {cp?:string,rt?:string}
 * with cp is a checkpointing functions, rt is an exception catcher rethrower
 */
function sandbox(f,modules,inject,env)
{
  var p,
      F,
      mask,
      source;
  // set up an object to serve as the context for the code
  // being evaluated. 
  mask = {mask:undefined,modules:undefined,require:undefined,cp:undefined,f:undefined};
  // mask global properties 
  for (p in this)
    mask[p] = undefined;
  for (p in modules)
    mask[p]=modules[p];
  if (env) for (p in env)
    mask[p]=env[p];
  // execute script in private context
  
  // (new Function( "with(this) { " + scr + "}")).call(mask);
  if (typeof f == 'function') source = f.toString(true);  // try minification (true) if supported
  else source=f;
  
  if (inject.cp) {
    // CP injection
    var regex1= /while[\s]*\(([^\)]+)\)/g;
    var regex2= /for[\s]*\(([^\)]+)\)/g;
    var regex3= /function([^\{]+)\{/g;
  
    source=source.replace(regex1,"while (($1) && "+inject.cp+"())")
                 .replace(regex2,"for ($1,"+inject.cp+"())")
                 .replace(regex3,"function $1{"+inject.cp+"();");
  }
  if (inject.rt) {
    var regex4 = /catch[\s]*\([\s]*([a-zA-Z0-9_]+)[\s]*\)[\s]*\{/g;
    source=source.replace(regex4,'catch ($1) {'+inject.rt+'($1);');
  }
  
  function evalInContext(context, js) {
    return eval('with(context) { "use strict"; F=' + js + ' }');
  }

  // with (mask) {
  //   eval('"use strict"; F='+source);
  //}
  mask.eval=undefined;
  evalInContext(mask,source);
  
  return {fun:F,mask:mask,size:source.length};
}

/** Returns a new function f' without global references
 *  except them provided in modules, and the masked environemnt.
 *  An optional checkpointing function call cp can be optionally injected.
 *
 * typeof @inject = {cp?:string,rt?:string}
 * with cp is a checkpointing functions, rt is an exception catcher rethrower
 */
function Sandbox(f,modules,inject,env) {
  var p,mask={},_mask='process';
  for(p in global) {
    if (p.indexOf('Array')>0) continue;
    _mask = _mask + ',' + p;
  }
  for (p in modules)
    mask[p]=modules[p];
  if (env) for (p in env)
    mask[p]=env[p];
    
  if (typeof f == 'function') source = f.toString(true);  // try minification (true) if supported
  else source=f;

  if (inject.cp) {
    // CP injection
    var regex1= /while[\s]*\(([^\)]+)\)/g;
    var regex2= /for[\s]*\(([^\)]+)\)/g;
    var regex3= /function([^\{]+)\{/g;
  
    source=source.replace(regex1,"while (($1) && "+inject.cp+"())")
                 .replace(regex2,"for ($1,"+inject.cp+"())")
                 .replace(regex3,"function $1{"+inject.cp+"();");
  }
  if (inject.rt) {
    var regex4 = /catch[\s]*\([\s]*([a-zA-Z0-9_]+)[\s]*\)[\s]*\{/g;
    source=source.replace(regex4,'catch ($1) {'+inject.rt+'($1);');
  }

  mask.eval=undefined;_mask += ',eval'
  mask.require=undefined;_mask += ',require'

  var F = new Function(_mask,'"use strict"; with(this) { f=('+source+').bind(this)} return f')
              .bind(mask);
  return {fun:F(),mask:mask,_mask:_mask};
}

function sandboxObject(obj,mask) {
  var objS;
  if (typeof obj == 'object') obj=Json.stringify(obj);
  return Json.parse(obj,mask);
}

module.exports = {
  sandbox:sandbox,
  sandboxObject:sandboxObject,
  Sandbox:Sandbox
}

module.exports = function () {return sandbox}
};
BundleModuleCode['jam/sig']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2006-2024 bLAB
 **    $CREATED:     15/1/16 by sbosse.
 **    $RCS:         $Id: sig.js,v 1.4 2020/02/03 09:45:01 sbosse Exp sbosse $
 **    $VERSION:     1.6.1
 **
 **    $INFO:
 **
 **  JavaScript AIOS Agent Signal Sub-System
 **
 **    $ENDOFINFO
 */
var Io = Require('com/io');
var Comp = Require('com/compat');
var current=none;
var Aios = none;

var options = {
  debug : {},
  version:'1.5.3'
}

/** Search a channel that is connected to node 'destnode'
 *
 */
function lookup(node,destnode) {
  var chan,url;
  if (node.connections.ip && node.connections.ip.lookup) {
    url=node.connections.ip.lookup(destnode);
    if (url) return {
      chan:node.connections.ip,
      url:url
    };
  }
  // (sejam2) virtual connections
  if (node.connections.path && node.connections.path._connected) {
    if (node.connections.path._connected.indexOf(destnode)!=-1) {
      return {
        chan:node.connections.path,
        url:Aios.DIR.NODE(destnode)
      }
    }
  }
}

var sig = {
  broadcast: function (ac,range,sig,arg) {
    var delivered=0;
    // Currently only range=0 is supported => local agents
    if (!Comp.obj.isString(ac)) {current.error='broadcast, invalid class '+ac;throw 'SIGNAL'};
    if (!Comp.obj.isString(sig) && !Comp.obj.isNumber(sig)) {current.error='broadcast, invalid signal '+sig;throw 'SIGNAL'};
    if (range!=0) {current.error='broadcast, invalid range '+range;throw 'SIGNAL'};
    for (var p in current.node.processes.table) {
      var proc=current.node.processes.table[p];
      if (proc && proc.agent.ac == ac && proc.agent.on[sig]) {       
        proc.signals.push([sig,arg,current.process.agent.id]);
        delivered++;
      }
    }
    return delivered;
  },
  // 'to' is the destination agent id
  // 'from' indicates source agent id and remote signal propagation (from node.handle)
  send: function (to,sig,arg,from,hop) {
    var p,node,delivered,curtime;
    // Local agent?
    var pid=current.node.processes.lookup(to);
    if (options.debug.send) console.log('sig.send',to,sig,arg,from,hop,pid);
    if (!Comp.obj.isString(sig) && !Comp.obj.isNumber(sig)) {current.error='send, invalid signal';throw 'SIGNAL'};
    current.node.stats.signal++;
    if (pid!=none) {
      // [sig,arg,from]
      // Check AIOS signals:
      switch (sig) {
        case 'PROC.KILL':
          Aios.kill(to);
          return true;
      }
      current.node.processes.table[pid].signals.push([sig,arg,from||current.process.agent.id]);
      // ?? Aios.emit('schedule',current.node);
      return true;
    } else {
      // console.log('send',current.node.id,to,sig,arg,current.node.processes.gone[to])
      // Agent migrated and still cached?
      if (current.node.processes.gone[to]) {
        if (options.debug.send) print('sig.send',to,sig,arg,from,hop,current.node.processes.gone[to].dir);
        curtime=Aios.time()-current.world.lag;
        // path is in use; update timeout significantly to avoid a lost of the signal path
        current.node.processes.gone[to].timeout = curtime + current.node.TMO*10;
        return route(current.node.processes.gone[to].dir,
                     to,sig,arg,from||current.process.agent.id);
      }
      
      // coupled nodes via grouping? (used in virtual world/simulation)
      // Prevent children-parent ping-pong if agent was not found
      if (hop>2) return true;
      if (current.node.parent) {
        return current.node.parent.handle({to:to,sig:sig,arg:arg,from:from,hop:hop?hop+1:1})
      } else if (current.node.children) {
        delivered=false;
        for(p in current.node.children) {
          node=current.node.children[p];
          delivered=node.handle({to:to,sig:sig,arg:arg,from:from,hop:hop?hop+1:1});
          if (delivered) break;
        }
        if (delivered) return true;
      } 
      
      if (current.node.signals[to]) {
        curtime=Aios.time()-current.world.lag;
        // path is in use; update timeout significantly to avoid a lost of the signal path
        current.node.signals[to].timeout = curtime + current.node.TMO*10;
        return route(current.node.signals[to].dir,
                     to,sig,arg,from||current.process.agent.id);
        
      }
    }
    return false;
  },
  // Send a signal to agents on a specific remote destination node, e.g., to=DIR.DELTA([-1,-2])
  sendto: function (to,sig,arg,from,node) {
    var delivered=0,i;
    if (!Comp.obj.isString(sig) && !Comp.obj.isNumber(sig)) {current.error='sendto, invalid signal '+sig;throw 'SIGNAL'};
    if ((to.tag||to).indexOf('DIR') != 0) {current.error='sendto, invalid destination '+to; throw 'SIGNAL'};
    // console.debug('sendto',to,sig,arg,from,node,current.node.id)
    if (to == Aios.DIR.ORIGIN || (to.delta && Comp.array.zero(to.delta) || to.node==current.node.id)) {
      if (sig=='TS.SIG') {
        // copy/collect from remote TS
        for(i in arg) {
          Aios.Ts.agent.out(arg[i]);
        }
      } else for (var p in current.node.processes.table) {
        var proc=current.node.processes.table[p];
        if (proc && proc.agent.on && proc.agent.on[sig]) {  
          proc.signals.push([sig,arg,from||current.process.agent.id,node]);
          delivered++;
        }
      }
      return delivered;
    } else {
        return route(to,
                     none,sig,arg,current.process.agent.id,node||current.node.id);    
    }
  },
  sleep: function (tmo) {
    current.process.suspend(tmo?Aios.time()-current.world.lag+tmo:0);
  },
  // Returns signal name
  timer: {
    // Add a oneshot or repeating timer raising a signal 'sig' after timeout 'tmo'.
    add : function (tmo,sig,arg,repeat) {
      if (!Comp.obj.isNumber(tmo)) {current.error='timer, invalid timeout '+tmo; throw 'SIGNAL'};
      if (!Comp.obj.isString(sig)) {current.error='timer, invalid signal '+sig; throw 'SIGNAL'};
      current.node.timers.push([current.process,(Aios.time()-current.world.lag+tmo),sig,arg,repeat?tmo:0]);
      return sig;
    },
    delete: function (sig) {
      current.node.timers=current.node.timers.filter(function (t) {
        return t[2]!=sig
      });
    }
  },
  // return process timeout (absolute time)
  timeout: function (tmo) { return Aios.time()-current.world.lag+tmo },
  wakeup: function (process) {
    if (!process) current.process.wakeup();
    else process.wakeup();
  }
}

/** Route signal to next node 
 *  from:agentid, node:source nodeid
 */
function route(dir,to,sig,arg,from,node) {
  var node1=current.node,
      chan=none,
      dest,
      stat,
      alive = function () {return 1},
      sigobj = {sig:sig,to:to||dir,from:from,arg:arg,back:Aios.DIR.opposite(dir,true),node:node},
      msg;
      
  // console.debug('route',dir,to,sig,arg,from,node)
  
  switch (dir.tag||dir) {
    case Aios.DIR.NORTH:  chan=node1.connections.north; break;
    case Aios.DIR.SOUTH:  chan=node1.connections.south; break;
    case Aios.DIR.WEST:   chan=node1.connections.west; break;
    case Aios.DIR.EAST:   chan=node1.connections.east; break;
    case Aios.DIR.UP:     chan=node1.connections.up; break;
    case Aios.DIR.DOWN:   chan=node1.connections.down; break;
    case Aios.DIR.NW:     chan=node1.connections.nw; break;
    case Aios.DIR.NE:     chan=node1.connections.ne; break;
    case Aios.DIR.SE:     chan=node1.connections.se; break;
    case Aios.DIR.SW:     chan=node1.connections.sw; break;
    case 'DIR.IP':        chan=node1.connections.ip; dest=dir.ip; break;
    case 'DIR.DELTA':
      // Simple Delta routing: Minimize [x,y,..] -> [0,0,..] with {x,y,..}
      sigobj.to=Comp.obj.copy(sigobj.to);
      if (dir.delta[0]>0 && node1.connections.east && node1.connections.east.status()) 
        sigobj.to.delta[0]--,chan=node1.connections.east;
      else if (dir.delta[0]<0 && node1.connections.west && node1.connections.west.status()) 
        sigobj.to.delta[0]++,chan=node1.connections.west;
      else if (dir.delta[1]>0 && node1.connections.south && node1.connections.south.status()) 
        sigobj.to.delta[1]--,chan=node1.connections.south;
      else if (dir.delta[1]<0 && node1.connections.north && node1.connections.north.status()) 
        sigobj.to.delta[1]++,chan=node1.connections.north;
      else if (dir.delta[2]>0 && node1.connections.up && node1.connections.up.status()) 
        sigobj.to.delta[2]--,chan=node1.connections.up;
      else if (dir.delta[2]<0 && node1.connections.down && node1.connections.down.status()) 
        sigobj.to.delta[2]++,chan=node1.connections.down;
      break;
    case 'DIR.PATH':
      chan=node1.connections.path; dest=dir.path; 
      break;
    case 'DIR.CAP':
      if (!current.network) {current.error='No connection to server '+dir.cap; return false;};
      chan=node1.connections.dos; dest=Net.Parse.capability(dir.cap).cap;
      break;
    case 'DIR.NODE':
      if (node1.connections.range && 
          node1.connections.range[dir.node] && 
          node1.connections.range[dir.node].status()) 
        chan=node1.connections.range[dir.node],dest=dir.node;
      else {
        // Find node name -> channel mapping
        dest=lookup(node1,dir.node); 
        // console.debug('route',dir.node,node1,dest)
        if (dest) chan=dest.chan,dest=dest.url;
      }
      break;
    default: return false;
  }
  switch (dir.tag||dir) {
    // One hop to next neighbour only?
    case Aios.DIR.NORTH:
    case Aios.DIR.SOUTH:
    case Aios.DIR.WEST:
    case Aios.DIR.EAST:
    case Aios.DIR.UP: 
    case Aios.DIR.DOWN:
    case Aios.DIR.NW:
    case Aios.DIR.NE:
    case Aios.DIR.SE:
    case Aios.DIR.SW:
      sigobj.to=Aios.DIR.ORIGIN; // After messaging signal has arrived
      break;
  }
  if (options.debug.route) console.log('sig.route',node1.id,dir,sigobj,chan!=null);
  
  if (chan==none || !chan.status(dest) /* OLDCOMM || !chan.signal*/) {
    current.error='No connection to direction '+dir; 
    return false;
  };
  node1.stats.signal++;

  if (Aios.options.fastcopy && chan.virtual) msg=sigobj;
  else msg=Aios.Code.toString(sigobj);
  /** OLDCOMM
  chan.signal(msg,dest);
  */
  /* NEWCOMM */
  chan.send({signal:msg,to:dest});
  
  return true;
}

module.exports = {
  agent:sig,
  options:options,
  route:route,
  current:function (module) { current=module.current; Aios=module; }
}
};
BundleModuleCode['jam/node']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2006-2021 bLAB
 **    $CREATED:     15-1-16 by sbosse.
 **    $RCS:         $Id: node.js,v 1.4 2020/02/03 09:45:01 sbosse Exp sbosse $
 **    $VERSION:     1.13.2
 **
 **    $INFO:
 **
 **  JavaScript AIOS Agent Node Sub-System
 **
 **    $ENDOFINFO
 */
var Io = Require('com/io');
var Comp = Require('com/compat');
var Security = Require('jam/security');
var current=none;
var Aios = none;

var options = {
  debug:{},
  verbose:0,
  version:'1.13.2'
}

function aid(process) { return process.agent.id+':'+process.pid }
function min0(a,b) { return a==0?b:(b==0?a:Comp.pervasives.min(a,b)) };

/** Create a node.
 *  typeof options = {id,maxpro,maxts,position:{x,y},defaultLevel?,TMO?}
 *
 */
var node= function (options) {
  var self=this;
  options=checkOptions(options,{});
  this.options=options;
  this.id       = checkOption(this.options.id,Aios.aidgen());
  this.position = checkOption(this.options.position,Aios.DIR.ORIGIN);
  this.type     = checkOption(this.options.type,'generic');
  this.verbose  = checkOption(this.options.verbose,0);
  // Default AIOS privilege level for received agent snapshots
  this.defaultLevel=checkOption(this.options.defaultLevel,Aios.options.LEVEL);
  this.processes={
    free:none,
    max:checkOption(this.options.maxpro,100),
    // (proc|undefined) []
    table:[],
    // number|undefined []
    hash:[],
    top:0,
    used:0,
    // a cache of migrated agents [id]={dir,timeout}
    gone:[]
  };
  this.processes.lookup = function (aid) {
    if(self.processes.hash[aid]!=undefined) 
      return self.processes.hash[aid];
    else 
      return none;
  };
  this.processes.process = function (aid) {
    if (self.processes.hash[aid]!=_)
      return self.processes.table[self.processes.hash[aid]];
    else
      return none;
  };
  
  // Signal propagation cache [from]={dir:timeout}
  this.signals=[];
  // [agent,tmo,sig,arg]
  this.timers=[];
  
  /** Connections to other nodes using P2P/IP/DOS links
  *  type link = {recv: function (callback),send:function(data),
  *               status: function() -> bool,count:number}
  */
  this.connections={north:none,south:none,west:none,east:none};
  // tuple spaces
  this.ts = Aios.Ts.create({maxn:checkOption(this.options.maxts,8),
                            id:this.id,node:self});
  
  // Random ports for negotiation and node security
  this.random = {};
  this.port = Security.Port.unique();
  this.random[this.port]=Security.Port.unique();
  
  // Code dictionary shared by agents
  this.library = {};
  
  // Location (geo) and position (virtual) information
  // location : { ip:string, 
  //              gps:{lat:number, lon:number}, 
  //              geo:{city:string,country:string,countryCode:string,region:string,zip:string} }
  
  this.location = null;
  
  
  this.position = options.position;
  
  this.stats = {
    cpu:0,
    create:0,
    fastcopy:0,
    fork:0,
    handled:0,
    migrate:0,
    received:0,
    signal:0,
    error:0,
    tsout:0,
    tsin:0,
    agents:0,
  }
  
  // Agent migration (gone) cache timeout
  this.TMO = checkOption(this.options.TMO,100000);
  // Needs node's service?
  this.timeout = 0;
  
  Aios.emit('node+',self); 

};

/** Clean-up and destroy this node. Terminate all agent processes.
 */
node.prototype.destroy = function () {
  var p,pro,_node=current.node,self=this;
  this.connections={};
  if (this.verbose>2) console.log('node.destroy',this)
  current.node=this;
  for(p in this.processes.table) {
    pro=this.processes.table[p];
    if (!pro) continue;
    pro.kill=true;
    this.unregister(pro);    
  }
  this.processes.gone=[];
  this.ts = none;
  current.node=_node;
  // unlink this node in an optional parent node (simulation)
  if (this.parent) {
    this.parent.children=this.parent.children.filter(function (node) {
      return node!=self
    });
  }
  // unlink this node from optional child nodes (simulation)
  if (this.children) {
    this.children.forEach(function (node) {
      node.parent=null;
    })
  }
  Aios.emit('node-',self); 
}

/** Export of code library
 *
 */
node.prototype.export = function (name,code) {
  // copy and sandbox code
  if (!this.library[name]) 
    this.library[name]=Aios.Code.toString(code);
}

/** Find an agent of the node by it's id and/or class, or agents matching a regular expression. 
 *
 */
node.prototype.getAgent = function (id,ac) {
  var pros=this.getAgentProcess(id,ac);
  if (pros && Comp.obj.isArray(pros)) 
    return Comp.array.map(pros,function (pro) {return pro.agent});
  else if (pros) return pros.agent;
};


node.prototype.getAgentProcess = function (id,ac) {
  var matches=Comp.obj.isRegex(id)?[]:undefined,
      table=this.processes.table,p;
  if (!matches && this.processes.hash[id]!=undefined) {
    p=table[this.processes.hash[id]];
    if (!ac || p.agent.ac==ac) return p;
  }
  if (typeof id == 'number') return table[id];
  for(var p in table) {
    if (!table[p]) continue;
    if (!matches && table[p].agent.id==id && (!ac || table[p].agent.ac==ac)) 
      return table[p];
    if (matches && id.test(table[p].agent.id) && (!ac || table[p].agent.ac==ac)) 
      matches.push(table[p]);
  }
  return matches;
};



/** Receive a signal to be passed to an agent located here or routed to another node.
 *  Message is in JSOB text format or a JS object (fastcopy mode).
 *
 * typeof sigobj = {to,sig,arg,from,back?}
 * from is an agent id!
 *
 */
node.prototype.handle = function (msg) {
  var delivered,tmo,curtime=Aios.time()-current.world.lag,
      _node=current.node,self=this,
      sigobj=(typeof msg == 'string')?Aios.Code.ofString(msg,{}):msg;
  if (options.debug.handle) console.log('node.handle',this.id,sigobj); 
  if (!sigobj) return; // Error
  current.node=this;
  delivered=(Aios.Mobi.DIR.isDir(sigobj.to)?Aios.Sig.agent.sendto:Aios.Sig.agent.send)
            (sigobj.to,sigobj.sig,sigobj.arg,sigobj.from,sigobj.to?sigobj.node:sigobj.hop);
  if (delivered && sigobj.back) {
    // Update signal route cache
    tmo=curtime+this.TMO;
    this.signals[sigobj.from]={dir:sigobj.back,timeout:tmo};
    this.timeout=min0(this.timeout,tmo);
  };
  this.stats.handled++;
  current.node=_node;
  Aios.emit('schedule',self);
  return delivered;
}

/** Import code from library.
 *  Returns a sandboxed code copy.
 *
 */
node.prototype.import = function (name) {
  var code;
  if (this.library[name]) code=Aios.Code.ofString(this.library[name],current.process.mask);
  return code;
}

/** Get node statistics 
 * 
 */
node.prototype.info = function () {
  var self=this,
      p,
      obj = {};
  ovj.stats = this.stats; 
  obj.id = this.id;
  obj.position = this.position;
  obj.agents={};
  var update=function (obj) {   
    var p;
    for (p in obj) {
      if (p != '_update') delete obj[p];
    }
    for (p in self.processes.hash) {
      if (self.processes.hash[p]!=_)
        obj[p]=self.processes.table[self.processes.hash[p]];
    };
  }
  obj.agents._update=update;
  update(obj.agents);
  
  obj.signals=this.signals;
  obj.timers=this.timers;
  obj.ts=this.ts;
  obj.connections=this.connections;
  return obj;
}

/** Print node statistics
 *
 */
node.prototype.print = function (summary) {
  var i,blocked,pending,total,ghost=0;
  var str='==== NODE '+this.id+' ===='+NL;
  str += 'SYSTIME='+Aios.time()+NL;
  str += 'PROCESS TABLE >>'+NL;
  if (summary) {
    blocked=0; pending=0; total=0; ghost=0;
    for (i in this.processes.table) {
      if (this.processes.table[i]!=_) { 
        total++;
        if (this.processes.table[i].blocked) blocked++;
        if (this.processes.table[i].signals.length>0) pending++;
        if (this.processes.table[i].agent.next==undefined) ghost++;
      };
    }
    str += '  TOTAL='+total+' BLOCKED='+blocked+' DYING='+ghost+' SIGPEND='+pending+NL;
  } else {
    for (i in this.processes.table) {
      if (this.processes.table[i]!=_) { 
        str += '  ['+aid(this.processes.table[i])+'] '+
             'NEXT='+this.processes.table[i].agent.next+' '+
             this.processes.table[i].print();
      };
    }
  }
  if (this.timers.length>0) {
    str += 'TIMER TABLE >>'+NL;
    for (i in this.timers) {
      str += '  ['+aid(this.timers[i][0])+'] TMO='+this.timers[i][1]+' SIG='+this.timers[i][2]+NL;
    }
  }
  str += 'TUPLE SPACES >>'+NL;
  if (summary) str += '  '+this.ts.print(summary); else str += this.ts.print(summary);
  return str;
}

/** Receive migrated agent text code and create a process container registered on this node.
 *  If start=false then the next activity is computed here.
 *
 */
node.prototype.receive = function (msg,start,from) {
  // Save context
  var _process=current.process,
      _node=current.node,
      self=this,
      process,agent;

  if (options.debug.receive) console.log ('node.receive (start='+start+',length='+msg.length+'):\n<'+msg+'>');
  if (typeof msg !== 'object') process=Aios.Code.toCode(msg,this.defaultLevel); 
  else process=Aios.Code.ofObject(msg); // Virtual migration, same physical JAM
  
  if (!process) return; // Error
  agent=process.agent;
  agent['self']=agent;
  this.register(process);
  this.stats.received++;
  
  if (process.dir || process.delta) { 
    /* TODO migration if this node is not the destination */
    return;
  };
  if ((!process.back||process.back.tag=='DIR.IP') && from && from.address && from.port) 
    // update or store back path; don't use old IP entry (along extended paths), it is not reachable from here!
    process.back=Aios.DIR.IP(from.address+':'+from.port);   
  if (process.back && process.agent.parent) { // register child-to-parent signal path
    tmo=Aios.time()-current.world.lag+this.TMO;
    this.signals[process.agent.parent]={dir:process.back,timeout:tmo};
    this.timeout=min0(this.timeout,tmo); 
  }
  
  // console.log('node.receive '+this.position.x+','+this.position.y);
  if (process.schedule.length == 0) {
    // Compute next activity on THIS node
    current.node=this;
    current.process=process;
    try {       
      if (!start) 
        agent.next=(typeof agent.trans[agent.next] == 'function')?agent.trans[agent.next].call(agent):
                                                                  agent.trans[agent.next];
      if (process.blocked) throw 'BLOCKING';
      //console.log(agent.next);
    } catch (e) {
      Aios.aios.log ('Node.receive: Agent '+agent.id+' ['+agent.ac+'] in transition '+agent.next+
                    ' failed:\n'+e+(current.error?' / '+current.error:', in: \n'+Aios.Code.print(agent.trans[agent.next]))+
                    '\nat:\n'+Io.sprintstack(e));
      this.unregister(process);
    };
    // Restore context
    current.node=_node;
    current.process=_process;
  }
}

/** Register agent code and assign a process container.
 *
 */
node.prototype.register = function (process) {
  var i,p,
      self=this,
      agent=process.agent;
  if (this.processes.free==none) {
    loop: for (i in this.processes.table) {
      if (this.processes.table[i]==_) { this.processes.free=i; break loop};
    }
  }
  if (this.processes.free!=none) {
    this.processes.table[this.processes.free]=process;
    process.pid=this.processes.free;
    process.agent=agent;
    this.processes.free=none;
  } else {
    this.processes.table[this.processes.top]=process;
    process.agent=agent;
    process.pid=this.processes.top;
    this.processes.top++;
  }
  if (agent.id==undefined) agent.id=Aios.aidgen();
  this.processes.hash[agent.id]=process.pid;
  this.processes.used++;
  this.stats.agents++;
  
  if (this.processes.gone[process.agent.id]) 
    // Agent returned again!
    this.processes.gone[process.agent.id]=undefined;
  process.node=this;
  Aios.emit('agent+',{agent:agent,proc:process,node:self},self); 
  Aios.emit('schedule',self); 
}

/** Node Garbage Collection and Timeout Service
 *
 */
node.prototype.service = function (curtime) {
  var nexttime=0,p,pro,sig;
  
  // TS cleanup management        
  this.ts.service(curtime);

  if (curtime<this.timeout) return;

  for (p in this.processes.gone) {
    pro=this.processes.gone[p];
    
    if (pro==undefined) continue;
    if (pro.timeout < curtime) {
      this.processes.gone[p]=undefined;
    } 
    else
      nexttime=min0(nexttime,pro.timeout);      
  }
  for (p in this.signals) {
    sig=this.signals[p];
    
    if (sig==undefined) continue;
    if (sig.timeout < curtime) {
      this.signals[p]=undefined;
    } 
    else
      nexttime=min0(nexttime,sig.timeout);      
  }
  this.timeout=nexttime;
}

// RPC STD request handlers
node.prototype.std_info = function (what)  {
  if (what==undefined) return {
    defaultLevel:this.defaultLevel,
    id:this.id,
    location:this.location,
    position:this.position,
    type:this.type,
  }
  if (what.agent) {
    
  }
}

node.prototype.std_status = function (what)  {
  var data,self=this;
  if (!what || what=='node') return this.stats;
  if (what == 'agents') return Comp.array.filtermap(this.processes.table,function (pro,index) {
      if (pro) return {
        index:index,
        agent:pro.agent.id,
        blocked:pro.blocked,
        suspended:pro.suspended,
        signals:pro.signals.length,
        next:pro.agent.next,
      }
    })
  if (what == 'links') {
    data={}
    for(var p in this.connections) {
      if (!this.connections[p]) continue;
      if (p=='ip') data.ip=Comp.array.map(this.connections[p].links, function (link) {
        return {
          ip:link.ip,
          stats:link.stats(),
          status:link.status('%'),
        }
      })
    }
    return data;
  }
}

/** Release a proecss container. If the process migrated,
 *  move the process container to a cache (signal and group comm.)
 *
 */
 
node.prototype.unregister = function (process) {
  var i,p,remove,
      self=this,tmo,
      agent=process.agent,
      curtime=Aios.time()-current.world.lag;
  // Check pending timers
  remove=false;
  Comp.array.iter(this.timers,function (timer,i) {
    if (timer && timer[0].pid==process.pid) {
      self.timers[i]=_;
      remove=true;
    }
  });
  if (remove) 
    this.timers = 
      Comp.array.filter(this.timers,function (timer) {
        return timer!=undefined;
      });
  // Unlink process
  this.processes.table[process.pid]=_;
  delete this.processes.hash[agent.id];
  if (this.processes.free==none) this.processes.free=process.pid;
  this.ts.cleanup(process);
  process.pid=none;
  process.signals=[];
  process.dead=true;
  this.processes.used--;
  this.stats.agents--;
  
  if (process.move) {
    // Cache migrated process
    tmo=curtime+this.TMO;
    this.processes.gone[process.agent.id]={dir:process.move,timeout:tmo};
    // Maganged in world GC
    this.timeout=min0(this.timeout,tmo);
  }
  Aios.emit('agent-',{agent:agent,node:self,proc:process},self); 
}

/** Create a new node object.
 *  If setcurrent is set, the new node will be set as the current node.
 */
var Node = function (options,setcurrent) {
  var obj=new node(options);
  if (setcurrent) current.node=obj;
  return obj;
}

module.exports = {
  isNode: function (o) { return o instanceof Node }, 
  Node:Node,
  options:options,
  current:function (module) { current=module.current; Aios=module; }
}
};
BundleModuleCode['jam/proc']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2006-2022 bLAB
 **    $CREATED:     15-1-16 by sbosse.
 **    $RCS:         $Id: proc.js,v 1.2 2020/02/03 09:45:01 sbosse Exp sbosse $
 **    $VERSION:     1.7.2
 **
 **    $INFO:
 **
 **  JavaScript AIOS Agent Process Module
 **
 **    $ENDOFINFO
 */
var Comp = Require('com/compat');
var current=none;
var Aios = none;

var options = {
  debug : {},
  version:'1.7.2'
}

var PRIO = {
  LOW:0,
  NORMAL:1,
  HIGH:2
}
/*
** Agent process - must be compatible with scheduler context process!
*/

var proc = function (properties) {
  // Agent code
  this.agent={};
  
  // Internal scheudling blocks - can'tmigrate - if any
  // Handled by global scheduler (DOS)
  this.block=[];
  // Process execution suspended?
  this.blocked=false;

  // Process blocking timeout (absolute time)
  this.timeout=0;
  
  // For soft checkpointing
  this.runtime=0;
  
  // Ressource control (node constraints)
  this.resources = {
    start:Aios.clock('ms'),      // start time on this host
    consumed:0,   // total processing time consumed
    memory:0,     // total memory (code+data) consumed
    tuples:0,     // total tuple generation
    agents:0,     // total agents created
  }
  
  this.level=undefined;
  
  // Dynamic process priority effecting scheduling order 
  this.priority = PRIO.NORMAL;  
  
  // Agent scheduling blocks - can migrate!
  // Handled by AIOS scheduler only!
  // function []
  this.schedule=[];
  // Agent activity suspended, waiting for an event?
  this.suspended=false;
  this.suspendedIn=undefined;
  
  this.error=none;
  
  // process id
  this.pid=none;
  // process parent id
  this.gid=none;
  this.id='agent';
  this.mask={};
  // [sig,arg,from] []
  this.signals=[];
  
  // Did we moved?
  this.move=none;
  // Killing state
  this.kill=false;
  // Dead state
  this.dead=false;
  // Pending next transition computatuion?
  this.transition=false;
  // Prevent transition
  this.notransition=false;
  
  for (var p in properties) {
    if (properties[p]!=undefined) this[p]=properties[p];
  }

  // Used in simulators only: A geometric shape object
  this.shape=undefined;  

  if (current.world) this.parent = current.world.context;
  this.node=current.node;
}


/** Execute a callback function in this agent process context immediately (should invoke scheduler and CB!)
 *
 */
proc.prototype.callback = function (cb,args) {
  var _process=current.process,_node=current.node, res;
  current.node=this.node;
  current.process=this;
  try {
    res=cb.apply(this.agent,args||[]);
  } catch (e) {
    Aios.aios.log('Caught callback error: '+e);
  }
  current.process=_process;
  current.node=_node;  
  return res;
}

/** Execute this process immediately
 *
 */
proc.prototype.exec = function() {
  var _process=current.process,_node=current.node, res;
  current.node=this.node;
  res = Aios.schedule(this);
  current.process=_process;
  current.node=_node;  
  return res;
}

/** Finalize this process
 *
 */
proc.prototype.finalize = function() {
  this.kill=true;
  this.suspended=false;
  current.node.unregister(this);
}


/** Fork an agent process.
 *  Returns child process. 
 *  If level is not specified, the parent process level is used.
 */ 
proc.prototype.fork = function(parameters,level,dirty) {
  var code,
      _process=current.process,
      agent=current.process.agent,
      process_,
      agent_,
      p;
  parameters=parameters||{};
  current.process.resources.agents++;
  if (dirty && level!=undefined) dirty=false; // Dirty process copy with level change not possible!    
  if (level==undefined) level=current.process.mask.privilege();
  else level=Math.min(current.process.mask.privilege(),level);
  if (!dirty) {
    code = Aios.Code.forkCode(current.process);
    process_ = Aios.Code.toCode(code,level);
  } else {
    process_ = Aios.Code.copyProcess(current.process);
  }
  agent_ = process_.agent
  agent_.id=Aios.aidgen();
  agent_.parent=current.process.agent.id;
  process_.init({gid:current.process.pid});
  current.process=process_;
  current.node.register(process_);
  // Update forked child agent parameters only if they already exist
  // First we have to restore nullified attributes that can be lost on copy!!!
  for (p in agent) {
    if (agent[p]===null) agent_[p]=null;
  }
  for (p in parameters) {
    if (Comp.obj.hasProperty(agent_,p)) agent_[p]=parameters[p];
  }
  // Should next activity computed in scheduler by setting process.transition ???
  // compute next activity after fork if there is no scheduling block,
  // no parameter next set,
  // and forkCode should always discard all current schedule blocks!
  if (!parameters.next) try {
    agent_.next=(typeof agent_.trans[agent_.next] == 'function')?agent_.trans[agent_.next].call(agent_):
                                                                 agent_.trans[agent_.next];
  } catch (e) { /*kill agent?*/ process_.kill=true; };
  this.node.stats.fork++;
  current.process=_process;
  return process_;
}

proc.prototype.init = function (properties) {
  for (var p in properties) {
    if (this[p]!=undefined) this[p]=properties[p];
  }
}

proc.prototype.print = function () {
  var str='',
      agent=this.agent;
  str = 'PID='+this.pid+
              (this.gid?' GID='+this.gid:'')+
              (this.timeout?(' TMO='+this.timeout):'')+
              (this.blocked?' BLOCKED':'')+
              (this.suspended?' SUSP':'')+
              (this.kill?' KILL':'')+
              (this.dead?' DEAD':'');
  if (this.schedule.length>0) str += ' SCHEDULE='+this.schedule.length;
  if (this.block.length>0) str += ' BLOCK='+this.block.length;
  if (this.signals.length>0) str += ' SIGNALS('+this.signals.length+'):'+
                                    Comp.printf.list(this.signals,function (s) {return s[0]});
  if (this.transition) str += ' TRANS';
  if (this.consumed|0) str += ' CONS='+(this.consumed|0);
  if (agent) str += ' AGENT '+agent.id+' next='+agent.next;
  return str;
}


/**
 * Suspend agent activity processing, but not internal block scheduling!
 */
proc.prototype.suspend = function (timeout,transition,suspendedIn){
  if (!this.kill && !this.dead) {
    this.suspended=true;
    if (timeout!=undefined) this.timeout=timeout;
    if (transition) this.transition=true;  // pending next computation    
    this.suspendedIn = suspendedIn;
  }
}


proc.prototype.update = function (properties) {
  for (var p in properties) {
    this[p]=properties[p];
  }
}

// Change the mask environment of an agent process with a new AIOS level API
proc.prototype.upgrade = function (level) {
  var aios;
  switch (level) {
    case undefined:
    case 0: aios=Aios.aios0; break;
    case 1: aios=Aios.aios1; break;
    case 2: aios=Aios.aios2; break;
    case 3: aios=Aios.aios3; break;
    default: aios=Aios.aios0; break;
  }
  for (p in aios) {
    this.mask[p]=aios[p];
  }
}

/**
 * Wakeup agent process from a previous suspend call (sleep)
 */
proc.prototype.wakeup = function (immediate){
  this.suspended=false;
  this.timeout=0;
  if (!this.kill && !this.dead && (immediate || this.schedule.length == 0)) {
    var _process=current.process,_node=current.node;
    current.node=this.node;
    if (this.suspendedIn=='ts') this.node.ts.cleanup(this,true);  // Have to call callback handler to inform about timeout!?
    this.suspendedIn=undefined;
    this.transition=this.schedule.length == 0;
    // Re-entering the scheduler is a bad idea!?
    Aios.schedule(this);
    current.process=_process;
    current.node=_node;
  } else Aios.scheduled++;
}

function Proc(properties) {
  var obj = new proc(properties);
  return obj;
}


module.exports = {
  agent: {
    fork:function fork(parameters) {
            return current.process.fork(parameters);
    }
  },
  isProc: function (o) { return o instanceof Proc }, 
  Proc:Proc,
  PRIO:PRIO,
  current:function (module) { current=module.current; Aios=module; },
  options:options
}
};
BundleModuleCode['jam/ts']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2006-2022 bLAB
 **    $CREATED:     15-1-16 by sbosse.
 **    $RCS:         $Id: ts.js,v 1.4 2020/02/03 09:45:01 sbosse Exp sbosse $
 **    $VERSION:     1.11.2
 **
 **    $INFO:
 **
 **  JavaScript Agent Tuple-Space Sub-System
 **
 **  New: out/amrk now create copies of mutable object and array elements!
 **  New: Global lifetime limit of all tuples
 **  New: xx.try function synonyms (inp.try, rd.try,..) and try functions now fire callback on timeout
 **  New: testandset
 **  New: eval/listen
 **  New: Patterns can contain regular expression! (p_i instanceof RegExp)
 **  New: A rd/inp operation can return all matching tuples
 **  New: alt operation supporting listening on multiple patterns
 **  New: Distributed TS with collect, copyto, store
 **
 **  Exeception: 'TS' | 'EOL'
 **
 **    $ENDOFINFO
 */
var Io = Require('com/io');
var Comp = Require('com/compat');
var current=none;
var Aios = none;
var verbose=false;

var options = {
  debug:{},
  version:'1.11.2',
  // global lifetime limit of tuples
  timeout : 0,
}

function aid(process) { return process.agent.id+':'+process.pid }
function log(tsi,process,msg) {
  if (verbose && process) Aios.aios.log('[TS'+(tsi.i)+':'+current.node.ts.id+'] Ag '
                                          + aid(process)+ ' '+msg);
  else if (verbose) Io.log('[TS'+(tsi.i)+':'+current.node.ts.id+'] SYS'+msg);
}
function min0(a,b) { return a==0?b:(b==0?a:Comp.pervasives.min(a,b)) };

/*******************************************
** Waiter management
*******************************************/

/** Add a waiter to a tuple space data base
 *
 */
var addwaiter = function (tsi,waiter) {
  var index,key;
  if (tsi.waiters.free.length==0) {
    index=tsi.waiters.length;
    tsi.waiters.push(waiter);
  } else {
    index=tsi.waiters.free[0];
    tsi.waiters[index]=waiter;
    tsi.waiters.free.shift();
  }
  if (typeof (key=waiter.pat[0]) == 'string') switch (waiter.op) {
    case 'listen': 
      tsi.waiters.hash[key]=index; break;
  }
}

/** Check for waiting agent processes and try to match the provided tuple.
 *  Readers can read multiple copies of the tuple, whereby consumers can only read the tuple one time.
 *  Consumers (in-op) can be in a waiting list (next/prev). If one waiter in a list consumes
 *  a tuple, all waiters must be removed. The other waiters (the same process, but different patterns; alt-op)
 *  can be in different tuple data bases!
 *
 */
var checkwaiter = function (tsi,tuple,callback) {
  var res,consumed=false,
      i,waiter;
   // Create agent callback   
  function cb(waiter,res) {
    Aios.CB(waiter.pro,function () {waiter.cb.call(waiter.pro.agent,res)});
  }
  for(i=0;i<tsi.waiters.length;i++) {
    if (!tsi.waiters[i]) continue;
    waiter=tsi.waiters[i];
    if (!consumed) switch (waiter.op) {
      case 'rd':
      case 'rd-all':
        res=match(tuple,waiter.pat);
        log(tsi,current.process,' rd waiter? '+res);
        if (res!=none) {
          cb(waiter,(waiter.op=='rd'?res:[res])),
          waiter.pro.wakeup();
          removewaiter(tsi,i);
        }
        break;
      case 'in':
      case 'in-all':
        res=match(tuple,waiter.pat);
        log(tsi,current.process,' in waiter? '+res);
        if (res!=none) {
          cb(waiter,(waiter.op=='in'?res:[res])),
          waiter.pro.wakeup();
          consumed=true;
          removewaiter(tsi,i);
        }
        break;
      case 'listen':
        res=match(tuple,waiter.pat);
        log(tsi,current.process,' listen waiter? '+res);
        if (res!=none) {
          res=waiter.pro.callback(waiter.cb,[res]);
          if (callback) callback.apply(current.process.agent,[res]);
          consumed=true;
        }
        break;    
    } else break;
  }
  
  if (!consumed && current.node.ts.consumers.length>0) {
    consumed = Comp.array.findmap(current.node.ts.consumers,function (consumer) {
      return consumer(tuple);
    });
  }
  return consumed;
}

var findwaiter = function (tsi,waiter) {
  var i;
  for(i=0;i<tsi.waiters.length;i++) {
    if (!tsi.waiters[i]) continue;
    if (tsi.waiters[i].pro.pid!=waiter.pro.pid) continue;
    if (equal(tsi.waiters[i].pat,waiter.pat)) return i;
  }
  return;
}

var removewaiter = function (tsi,index) {
  var waiter=tsi.waiters[index],_tsi,_index;
  tsi.waiters[index]=undefined;
  tsi.waiters.free.push(index);
  // Waiter in a chained waiter list?
  if (waiter.prev) {
    waiter.prev.next=undefined;    
    _tsi=current.node.ts.db[waiter.prev.pat.length];
    _index=findwaiter(_tsi,waiter.prev);
    if (_index != undefined) removewaiter(_tsi,_index);
  };
  if (waiter.next) {
    waiter.next.prev=undefined;    
    _tsi=current.node.ts.db[waiter.next.pat.length];
    _index=findwaiter(_tsi,waiter.next);
    if (_index != undefined) removewaiter(_tsi,_index);
  };
}

var count = function (tsi) {
  var data=tsi.data,i,n=0;  
  for (i in data) {if (data[i] != undefined) n++};
  return n;
}

/** Find one/all matching tuple(s) in the database based on pattern matching
 *
 */
var lookup = function (pat,all) {
  var tsi,nary=pat.length,res=none;
  if (nary>current.node.ts.n || nary==0) return;
  tsi=current.node.ts.db[nary];
  if (!all && Comp.isString(pat[0]) && tsi.hash[pat[0]]!=undefined) {
    // Speedup trial with hash key
    res=match(tsi.data[tsi.hash[pat[0]]],pat);
  }
  if (res==none) {
    res = (all?Comp.array.filtermap:Comp.array.findmap)(tsi.data,function (tuple) {
      if (tuple==_) return none;
      else return match(tuple,pat);
    });
    if (res && res.length==0) res=none;
    if (res == none && current.node.ts.providers.length>0) {
      res = Comp.array.findmap(current.node.ts.providers,function (provider) {
        return provider(pat);
      });
    }
  }
  return res;
}
// return only the table index of matching data table entry
var lookupIndex = function (pat,all) {
  var tsi,nary=pat.length,res=none;
  if (nary>current.node.ts.n || nary==0) return;
  tsi=current.node.ts.db[nary];
  if (!all && Comp.isString(pat[0]) && tsi.hash[pat[0]]!=undefined) {
    // Speedup trial with hash key
    res=match(tsi.data[tsi.hash[pat[0]]],pat);
    if (res!=none) return tsi.hash[pat[0]];
  }
  if (res==none) {
    res = (all?Comp.array.filtermap:Comp.array.findmap)(tsi.data,function (tuple,index) {
      if (tuple==_) return none;
      else return match(tuple,pat)?index:none;
    });
    if (res && res.length==0) res=none;
  }
  return res;
}

/*******************************************
** Tuple management
*******************************************/

/**
 * Compare two values, check equiality
 */
var equal = function(x,y) {
  var i;
  if(x==y) return true;
  if (Comp.obj.isArray(x) && Comp.obj.isArray(y)) {
    if (x.length!=y.length) return false;
    for(i in x) {
      if (x[i] != y[i]) return false;
    }
    return true;
  }
  return false;  
}

/** Match a tuple element with a template pattern element y.
 *
 */
var match1 = function (x,y) {
  if (y==any) return true;
  if (x==y)   return true;
  if ((x instanceof Array) && (y instanceof Array)) return match(x,y)!=none;
  if (y instanceof RegExp && typeof x == 'string' && y.test(x)) return true; 
  return false;
}

/** Match a tuple with a template and return none or the original tuple (equivalence result?)
 *
 */
var match = function (tuple,templ) {
  var i;
  if (tuple.length != templ.length) return none;
  for(i in tuple) {
    if (!match1(tuple[i],templ[i])) return none;
  };
  return tuple;
}


/** Find and remove one/all matching tuple(s) from the database based on pattern matching
 *
 */
var remove = function (pat,all) {
  var tsi,nary=pat.length,res=none,removed=false,hashed=_;
  if (nary>current.node.ts.n || nary==0) return;
  tsi=current.node.ts.db[nary];
  if (!all && Comp.isString(pat[0])) hashed=tsi.hash[pat[0]];
  if (hashed != _) {
    // Speedup trial with hash key
    res=match(tsi.data[hashed],pat);
    if (res) {
      // invalidate matching tuple in data list
      removed=true;
      tsi.data[hashed]=_;
      tsi.tmo[hashed]=0;
      // remember the free slot in the data list
      if (tsi.free==none) tsi.free=hashed;
      // invalidate hash entry - tuple is consumed
      delete tsi.hash[pat[0]];
    }
  }
  if (res==none || removed==false) {    
    res = (all?Comp.array.filtermap:Comp.array.findmap)(tsi.data,function (tuple,i) {
        if (tuple==_) return none;
        var res_=match(tuple,pat);
        if (res_!=none) {
          if (Comp.isString(pat[0]) && tsi.hash[pat[0]]==i) {
            // Invalidate hash - tuple is consumed
            delete tsi.hash[pat[0]];            
          } 
          tsi.data[i]=_;
          tsi.tmo[i]=0;
          if (tsi.free==none) tsi.free=i;
          return res_;
        } else return none;
    });
    if (res && res.length==0) res=none;
  }
  return res;
}


/*******************************************
** Tuple Space Agent/Client API
*******************************************/

var ts = {
  // consuming - tmo <> 0 => try_alt
  alt: function (pats,callback,all,tmo) {
    var tsi,nary,res,
        i,p,pat,waiters=none,last=none;
    for(i in pats) {
      pat=pats[i];
      nary=pat.length;
      if (nary>current.node.ts.n || nary==0) return none;
      res = remove(pat,all);
      if (res && res.length) current.node.stats.tsin += (all?res.length:1);
      if (res && callback) {
        callback.call(current.process.agent,res);
        return;
      } 
      else if (res) return res;  
    }
    if (callback && tmo==0) return callback();
    if (tmo==0) return;
     
    // No matching tuple found - go to sleep
    
    for(i in pats) {
      pat=pats[i];
      nary=pat.length;
      if (callback  && (tmo==undefined||tmo>0))  {
        if (waiters==none) 
          waiters={pat:pat,
                   pro:current.process,
                   cb:callback,
                   op:'in'+(all?'-all':''),
                   tmo:tmo>0?Aios.time()-current.world.lag+tmo:0
                  },last=waiters;
        else {
          last.next={pat:pat,
                   pro:current.process,
                   cb:callback,
                   op:'in'+(all?'-all':''),
                   tmo:tmo>0?Aios.time()-current.world.lag+tmo:0,
                   prev:last
                  },last=last.next;
        }
      }
    }
    if (waiters!=none) {
      p=waiters;
      while(p) {
        tsi=current.node.ts.db[p.pat.length];
        addwaiter(tsi,p);
        p=p.next;
      }
      log(tsi,current.process,' +waiter');
      current.process.suspend(tmo>0?Aios.time()-current.world.lag+tmo:0,_,'ts');
    }
  },
  
  // The collect primitive moves tuples from this source TS that match template 
  // pattern into destination TS specified by path 'to' (a node destination).
  collect: function (to,pat) {
    var tsi,nary=pat.length,res;
    if (nary>current.node.ts.n || nary==0) return none;
    tsi=current.node.ts.db[nary];
    res = remove(pat,true);    
    if (res.length>0) {
      current.node.stats.tsin += res.length;
      Aios.Sig.agent.sendto(to,'TS.SIG',res);
    }
    return res.length;
  },
  // Copy all matching tuples form this source TS to a remote destination TS
  // specified by path 'to' (a node destination).
  copyto: function (to,pat) {
    var tsi,nary=pat.length,res;
    if (nary>current.node.ts.n || nary==0) return 0;
    tsi=current.node.ts.db[nary];
    res = lookup(pat,true);    
    if (res.length>0) {
      Aios.Sig.agent.sendto(to,'TS.SIG',res);
    }
    return res.length;
  },

  // Access a tuple evaluator - non-blocking: no listener -> callback(null)
  // TODO blocking/tmo
  evaluate: function (pat,callback,tmo) {
    var tsi,nary=pat.length,res;
    if (nary>current.node.ts.n || nary==0) return none;
    tsi=current.node.ts.db[nary];
    consumed=checkwaiter(tsi,pat,callback);
    if (!consumed && callback) callback.call(current.process.agent,null); 
  },  
  
  // Test tuple existence
  exists: function (pat) {
    var tsi,nary=pat.length,res;
    if (nary>current.node.ts.n || nary==0) return none;
    tsi=current.node.ts.db[nary];
    res = lookup(pat);
    return res!=none;  
  },
  
  // consuming - tmo <> 0 => try_in
  inp: function (pat,callback,all,tmo) {
    var tsi,nary=pat.length,res;
    if (nary>current.node.ts.n || nary==0 || typeof pat != 'object') throw 'TS';
    tsi=current.node.ts.db[nary];
    res = remove(pat,all);
    log(tsi,current.process,' in? '+res+' []='+count(tsi));
    if (res && res.length) current.node.stats.tsin += (all?res.length:1);
    if (res==none && callback && (tmo==undefined||tmo>0)) {
      addwaiter(tsi,{pat:pat,
                     pro:current.process,
                     cb:callback,
                     op:'in'+(all?'-all':''),
                     tmo:tmo>0?Aios.time()-current.world.lag+tmo:0
                     });
      log(tsi,current.process,' +waiter');
      current.process.suspend(tmo>0?Aios.time()-current.world.lag+tmo:0,_,'ts');
      return none;
    } else if (callback) callback.call(current.process.agent,res); else return res;
  },

  // Provide a tuple evaluator
  listen: function (pat,callback) {
    var tsi,nary=pat.length,res;
    if (nary>current.node.ts.n || nary==0 || typeof pat != 'object') throw 'TS';
    tsi=current.node.ts.db[nary];
    addwaiter(tsi,{pat:pat,
                     pro:current.process,
                     cb:callback,
                     op:'listen',
                     tmo:0
                     });    
  },  
  
  // Store time-limited tuples
  mark: function (tuple,tmo) {
    var p,tsi,nary=tuple.length,consumed=false;
    if (nary>current.node.ts.n || nary==0 || typeof tuple != 'object') throw 'TS';
    if (current.process && (current.process.resources.tuples+1) > (current.process.resources.TS||Aios.options.TSPOOL)) throw 'EOL';
    tuple=Comp.copy(tuple); // decouple producer context
    tsi=current.node.ts.db[nary];
    current.node.stats.tsout++;
    // Check waiters
    consumed=checkwaiter(tsi,tuple);
    if (!consumed) {
      if (tsi.free==none) {
        loop: for (var i in tsi.data) {
          if (tsi.data[i]==_) {tsi.free=i; break loop}
        }
      }
      if (options.timeout) tmo=min0(options.timeout,tmo);
      tmo=Aios.time()-current.world.lag+tmo;
      if (tsi.free!=none) {
        tsi.data[tsi.free]=tuple;
        tsi.tmo[tsi.free]=tmo;
        current.node.ts.timeout=min0(current.node.ts.timeout,tsi.tmo[tsi.free]);
        if (Comp.obj.isString(tuple[0]))
          tsi.hash[tuple[0]]=tsi.free;
        tsi.free=none;
      } else {
        tsi.data.push(tuple);
        tsi.tmo.push(tmo);
        // hash is only a first guess to find a tuple
        if (Comp.obj.isString(tuple[0]))
          tsi.hash[tuple[0]]=tsi.data.length-1;
      }
    } else current.node.stats.tsin++;
  },
  // Store a tuple in this TS
  out: function (tuple) {
    var tsi,tmo=0,nary=tuple.length,consumed=false,res;
    if (nary>current.node.ts.n || nary==0 || typeof tuple != 'object') throw 'TS';
    if (current.process && (current.process.resources.tuples+1) > (current.process.resources.TS||Aios.options.TSPOOL)) throw 'EOL'; 
    tsi=current.node.ts.db[nary];
    tuple=Comp.copy(tuple); // decouple producer context
    current.node.stats.tsout++;
    // Check waiters
    consumed=checkwaiter(tsi,tuple);
    if (!consumed) {
      if (tsi.free==none) {
        loop: for (var i in tsi.data) {
          if (tsi.data[i]==_) {tsi.free=i; break loop}
        }
      }
      if (options.timeout) tmo=Aios.time()-current.world.lag+options.timeout;
      if (tmo) current.node.ts.timeout=min0(current.node.ts.timeout,tmo);

      if (tsi.free!=none) {
        tsi.data[tsi.free]=tuple;
        tsi.tmo[tsi.free]=tmo;
        if (Comp.obj.isString(tuple[0]))
          tsi.hash[tuple[0]]=tsi.free;
        tsi.free=none;
      }
      else {
        tsi.data.push(tuple);
        tsi.tmo.push(tmo);
        // hash is only a first guess to find a tuple
        if (Comp.obj.isString(tuple[0]))
          tsi.hash[tuple[0]]=tsi.data.length-1;
      }
    } else current.node.stats.tsin++;
    log(tsi,current.process,' out '+tuple+'  ['+nary+'] consumed='+consumed+' []='+count(tsi));
  },
  
  // not consuming - tmo <> undefined => try_rd [0: immed.]
  rd: function (pat,callback,all,tmo) {
    var tsi,nary=pat.length,res;
    if (nary>current.node.ts.n || nary==0 || typeof pat != 'object') throw 'TS';
    tsi=current.node.ts.db[nary];
    res = lookup(pat,all);

    if (res==none && callback && (tmo==_||tmo>0)) {
      addwaiter(tsi,{pat:pat,
                     pro:current.process,
                     cb:callback,
                     op:'rd'+(all?'-all':''),
                     tmo:tmo>0?Aios.time()-current.world.lag+tmo:0
                    });
      current.process.suspend(tmo>0?Aios.time()-current.world.lag+tmo:0,_,'ts');
      return none;
    } else if (callback) callback.call(current.process.agent,res); else return res;
  },
  
  // consuming 
  rm: function (pat,all) {
    var tsi,nary=pat.length,res;
    if (nary>current.node.ts.n || nary==0 || typeof pat != 'object') throw 'TS';
    tsi=current.node.ts.db[nary];
    res = remove(pat,all);
    if (res && res.length) current.node.stats.tsin += (all?res.length:1);
    return (res!=none);
  },

  // Remote tuple storage
  store: function (to,tuple) {
    Aios.Sig.agent.sendto(to,'TS.SIG',[tuple]);
    return 1;
  },
  
  // Test and Set: Atomic modification of a tuple - non blocking
  // Updates timeout of markings and time limited tuples, too.
  // typeof @callbackOrTimeout: function (tuple) -> tuple | timeout number
  ts: function (pat,callbackOrTimeout) {
    var tsi,nary=pat.length,index,res,ret,tmo;
    if (nary>current.node.ts.n || nary==0 || !Comp.obj.isArray(pat)) throw 'TS';
    tsi=current.node.ts.db[nary];
    index = lookupIndex(pat,false);
    if (index!=none) res=tsi.data[index]; else return;
    log(tsi,current.process,' test? '+res+' []='+count(tsi));
   
    if (typeof callbackOrTimeout == 'function') {
      if (current.process)
        ret=callbackOrTimeout.call(current.process.agent,res);
      else
        ret=callbackOrTimeout(res);
      // update the modified tuple
      if (ret && ret.length==res.length) Comp.array.copy(ret,res);
      res=ret;
    } else if (typeof callbackOrTimeout == 'number') {
      // update timestamp
      tmo=min0(options.timeout,callbackOrTimeout);
      tmo=Aios.time()-current.world.lag+tmo;
      tsi.tmo[index]=Math.max(tmo,tsi.tmo[index]);
    }
    return res;
  },
  try : {
    alt : function (tmo,pats,callback,all) {
      if (typeof callback == 'boolean') {
        all=callback;
        callback=null;
      }
      return ts.alt(pats,callback,all,tmo);
    },
    evaluate : function (tmo,pat,callback) {
      return ts.evaluate(pat,callback,tmo);
    },
    inp : function (tmo,pat,callback,all) {
      if (typeof callback == 'boolean') {
        all=callback;
        callback=null;
      }
      return ts.inp(pat,callback,all,tmo);
    },
    rd : function (tmo,pat,callback,all) {
      if (typeof callback == 'boolean') {
        all=callback;
        callback=null;
      }
      return ts.rd(pat,callback,all,tmo);
    }
  }
}

// Synonyms
ts.alt.try = ts.try.alt
ts.evaluate.try = ts.try.evaluate
ts.inp.try = ts.try.inp
ts.rd.try = ts.try.rd

/*******************************************
** Tuple Space Data Base
*******************************************/

var tsd = function (options) {
  var self=this;
  if (!options) options={};
  this.n=options.maxn||8;
  this.id=options.id||'TS';
  this.timeout=0;
  this.db=Comp.array.init(this.n+1,function (i) {
    var tsi;
    if (i==0) return none;
    tsi = {
        i:i,
        hash:[],
        // number|none
        free:none,
        // [*] [] 
        data:[],
        // number []
        tmo:[],
        // [pattern,agent,callback,kind]
        waiters:[]
    };
    tsi.waiters.free=[];
    tsi.waiters.hash={}; // Hash tuple key for consuming waiter
    return tsi;
  });
  /*
  ** Additional external tuple providers implementing a match function.
  */
  this.providers=[];
  /*
  ** Additional external tuple consumers implementing a match function.
  */
  this.consumers=[];
  this.node=options.node;

  // External API w/o blocking and callbacks (i.e., try_ versions with tmo=0)
  // Can be called from any context
  this.extern = {
    exists: function (pat,callback) { 
      var res,_node=current.node;
      current.node=self.node||_node;
      res=ts.exists(pat,callback);
      current.node=_node;
      return res;
    },    
    inp: function (pat,all) {
      var res,tsi,nary=pat.length,_node=current.node;
      current.node=self.node||_node;
      if (nary>current.node.ts.n || nary==0) return none;
      tsi=current.node.ts.db[nary];
      res = remove(pat,all);
      if (res && res.length) current.node.stats.tsin += (all?res.length:1);
      current.node=_node;
      return res;
    },
    mark: function (pat,tmo) { 
      var res,_node=current.node,_proc=current.process;
      current.node=self.node||_node; current.process=null;
      res = ts.mark(pat,tmo);
      current.node=_node;
      current.process=_proc;
      return res;
    },
    out: function (pat) { 
      var res,_node=current.node,_proc=current.process;
      current.node=self.node||_node; current.process=null;
      res = ts.out(pat)
      current.node=_node;
      current.process=_proc;
      return res;
    },
    rd: function (pat,all) {
      var res,tsi,nary=pat.length,_node=current.node;
      current.node=self.node||_node;
      if (nary>current.node.ts.n || nary==0) return none;
      tsi=current.node.ts.db[nary];
      res = lookup(pat,all);
      if (res && res.length) current.node.stats.tsin += (all?res.length:1);
      current.node=_node;
      return res;
    },
    rm: function (pat,all) { 
      var res,_node=current.node;
      current.node=self.node||_node;
      res=ts.rm(pat,all);
      current.node=_node;
      return res;
    },
    ts: function (pat,callback) { 
      var res,_node=current.node;
      current.node=self.node||_node;
      res=ts.ts(pat,callback);
      current.node=_node;
      return res;
    },
  }

}

var create = function (options) {
  return new tsd(options);
}

tsd.prototype.checkwaiter = function (tuple) {
  var tsi,nary=tuple.length;
  if (nary>this.n || nary==0) return none;
  tsi=current.node.ts.db[nary];
  return checkwaiter(tsi,tuple);
}

/** Remove an agent process from waiter queues.
 * If doCallback is set, a pending operation callback handler is executed here (e.g., on timeout or interruption).
 *
 */
tsd.prototype.cleanup = function (process,doCallback) {
  var i,j,tsi,p,waiter,node=process.node;
  function cb(waiter) {
    Aios.CB(waiter.pro,function () {waiter.cb.call(waiter.pro.agent,null)});
  }
  for (i in node.ts.db) {
    if (i==0) continue;
    tsi=node.ts.db[i];
    for(j=0;j<tsi.waiters.length;j++) {
      if (!tsi.waiters[j]) continue;
      waiter=tsi.waiters[j];
      if (waiter.pro.pid==process.pid) {
        if (doCallback && waiter.cb) cb(waiter);
        removewaiter(tsi,j);
      }
    }
  }
} 


/**
 * Register an external tuple provider (function).
 * The provider can immediately return a matching tuple,
 * or can deliver it later on calling the checkwaiter loop 
 * which delivers the tuple to the agent.
 *
 * type of func  : provider|consumer
 * type provider : function ('pat) -> 'tuple
 * type consumer : function ('pat) -> boolean
 */
tsd.prototype.register = function (func,consumer) {
  if (consumer) this.consumers.push(func)
  else this.providers.push(func);
};


tsd.prototype.print = function (summary) {
  var i,tsi,num,str='',sep='';
  if (summary) {
    str += '[';
    for (i in current.node.ts.db) {
      if (i==0) continue;
      tsi=current.node.ts.db[i];
      num = count(tsi);
      if (num>0) {
        str += sep+'TS'+(int(i)+1)+'='+num;
        sep=' ';
      }
    }
    str += ']'+NL;
  }    
  else for (i in current.node.ts.db) {
    if (i==0) continue;
    tsi=current.node.ts.db[i];
    str += '['+Comp.printf.sprintf('%2d',tsi.i)+
           ' free='+(tsi.free?Comp.printf.sprintf('%4d',tsi.free):'none')+
           ' data='+Comp.printf.sprintf('%4d(%4d)',count(tsi),tsi.data.length)+
           ' waiters='+Comp.printf.sprintf('%4d',tsi.waiters.length)+']'+NL;
  }  
  return str;  
}


/** Tuple Space Garbage Collection and Timeout Service
 *
 */
tsd.prototype.service = function (curtime) {
  var i,hashed,tsi,nexttime=0;
  
  // TODO: if (curtime<this.timeout) return;
  for (i in this.db) {
    tsi=this.db[i];
    hashed;
    if (tsi==_) continue;
    Comp.array.iter(tsi.tmo,function (tmo,i) {
      var tuple=tsi.data[i];
      if (tuple && tmo) {
        if (tmo < curtime) {
          if (Comp.isString(tuple[0])) hashed=tsi.hash[tuple[0]];
          if (hashed != _ && hashed==i) delete tsi.hash[tuple[0]];
          tsi.data[i]=_;
          tsi.tmo[i]=0;
          if (tsi.free==none) tsi.free=i;        
        } else nexttime=min0(nexttime,tmo);
      }
    });
  }
  this.timeout=nexttime;
}

module.exports = {
  agent:ts,
  count:count,
  create: create,
  current:function (module) { current=module.current; Aios=module; },
  match:match,
  options:options
}
};
BundleModuleCode['jam/world']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2006-2019 bLAB
 **    $CREATED:     15-1-16 by sbosse.
 **    $RCS:         $Id: world.js,v 1.3 2020/02/03 09:45:01 sbosse Exp sbosse $
 **    $VERSION:     1.11.3
 **
 **    $INFO:
 **
 **  JavaScript AIOS Agent World Module
 **
 **    $ENDOFINFO
 */
var Io = Require('com/io');
var Comp = Require('com/compat');
var current=none;
var Aios=none;

var options = {
  debug:{},
  version:'1.11.3',
  verbose:0,
}

/** Word object
 *
 * typeof options = {
 *    classes?,
 *    id?:string,
 *    scheduler?,
 *    verbose?
 * }
 */
var world= function (nodes,options) {
  var main=this;
  options=checkOptions(options,{});
  this.options=options;
  
  this.nodes=nodes||[];
  this.verbose=checkOption(this.options.verbose,0);
  this.hash={};
  this.id=checkOption(this.options.id,Aios.aidgen().toUpperCase());
  this.classes=checkOption(this.options.classes,[]);
  // A time lag (offset), required for simulation
  this.lag=0;
  this.scheduler = this.options.scheduler;
  this.log = Aios.log;
  this.out = function (msg) { main.log('[JAW '+this.id+'] '+msg)};

  /* Create a task context for the scheduler
  */
  
  this.thread = function (arg) {
    var thr = this;
    var dying=false;
    this.nexttime=0;
    this.number=arg;
    this.curtime=0;
    
    this.init = function () {
      main.out('JAM World is starting ..');
    };
    this.run = function () {
      thr.nexttime=Aios.scheduler();
      thr.curtime=Aios.time();
      if (main.verbose>3) main.out(' .. nexttime = '+thr.nexttime+
                                   ' ('+(thr.nexttime>0?thr.nexttime-thr.curtime:0)+')');
    };
    this.sleep = function () {
      var delta;
      thr.curtime=Aios.time();
      delta=thr.nexttime>0?thr.nexttime-thr.curtime:1000;
      if (main.verbose>3) main.out(' .. sleeping for '+delta+' ms');
      main.scheduler.Delay(delta);
    };
    
    this.transitions = function () {
        var trans;
        trans =
            [
                [undefined, this.init, function (thr) {                    
                    return true
                }],
                [this.init, this.run, function (thr) {
                    return true
                }],
                [this.run, this.run, function (thr) {
                    return thr.nexttime<0;
                }],
                [this.run, this.sleep, function (thr) {
                    return !dying;
                }],
                [this.run, this.terminate, function (thr) {
                    return dying
                }],
                [this.sleep, this.run, function (thr) {
                    return true;
                }]
            ];
        return trans;
    };
    this.context = main.scheduler.TaskContext('JAM World'+main.id, thr);
    
  }

};

// Add an agent class constructor (@env can contain resolved constructor function variables). 
// typepf constructor = function|string

world.prototype.addClass = function (name,constructor,env) {
  this.classes[name]=[
    Aios.Code.makeSandbox(constructor,0,env),
    Aios.Code.makeSandbox(constructor,1,env),
    Aios.Code.makeSandbox(constructor,2,env),
    Aios.Code.makeSandbox(constructor,3,env)     
  ];
}

/** Add a node to the world. 
 *
 */
world.prototype.addNode = function (node) {
  this.nodes.push(node);
  if (node.id) this.hash[node.id]=node;
  if (options.verbose) Io.out('World.addNode <'+node.id+'>');
};

/** Connect two nodes in directions dir:node1->node2 and dir':node2->node1
 *  with two virtual channel links that are created here.
 *
 */
world.prototype.connect = function (dir,node1,node2,options) {
  if (!options) options={};
  var chan=Aios.Chan.Virtual(node1,node2,dir,options); 
  switch (dir) {
    case Aios.DIR.NORTH: 
      node1.connections.north=chan.link1;
      node2.connections.south=chan.link2;
      break;
    case Aios.DIR.SOUTH: 
      node1.connections.south=chan.link1; 
      node2.connections.north=chan.link2; 
      break;
    case Aios.DIR.WEST:  
      node1.connections.west=chan.link1; 
      node2.connections.east=chan.link2; 
      break;
    case Aios.DIR.EAST:
      node1.connections.east=chan.link1; 
      node2.connections.west=chan.link2; 
      break;
    case Aios.DIR.NE:
      node1.connections.ne=chan.link1; 
      node2.connections.sw=chan.link2; 
      break;
    case Aios.DIR.NW:
      node1.connections.nw=chan.link1; 
      node2.connections.se=chan.link2; 
      break;
    case Aios.DIR.SE:
      node1.connections.se=chan.link1; 
      node2.connections.nw=chan.link2; 
      break;
    case Aios.DIR.SW:
      node1.connections.sw=chan.link1; 
      node2.connections.ne=chan.link2; 
      break;
    case Aios.DIR.UP:
      node1.connections.up=chan.link1; 
      node2.connections.down=chan.link2; 
      break;
    case Aios.DIR.DOWN:
      node1.connections.down=chan.link1; 
      node2.connections.up=chan.link2; 
      break;
    default: 
      if (current) current.error='EINVALID';
      throw 'CONNECT';
  } 
  chan.link2.on('agent',node1.receive.bind(node2));
  chan.link1.on('agent',node2.receive.bind(node1));
  chan.link2.on('signal',node1.handle.bind(node2));
  chan.link1.on('signal',node2.handle.bind(node1));
  chan.link1.end=node2.id;
  chan.link2.end=node1.id;
  return chan;
};

/** Connect node via a port in direction dir:node->*. The endpoint node * will be
 *  connected if the @snd parameter is specified. Otherwise only an unconnected port is created.
 *  An endpoint can be later connected using the world.connectTo method (if provided by the interface).
 *
 *  One uni- or bidirectional physical link is created and attached to the given node.
 *
 *  typeof options={
 *    compress?:boolean,
 *    oneway?:boolean,
 *    proto:'udp'|'tcp'|'http'|'device',
 *    device?:string,
 *    rcv:url is node endpoint,
 *    snd?:url is remote endpoint
 *    secure?:string,
 *    pem?:{cert:string,key:string},
 *  }
 *  with type url = "<name>:<ipport>" | "<ip>:<ipport>" | "<ipport>"
 *  and ipport = (1-65535) | "*"
 */
world.prototype.connectPhy = function (dir,node,options) {
  var self=this,chan,name=Aios.DIR.to(dir);
  if (!options) options={};
  chan=Aios.Chan.Physical(node,dir,options); 
  switch (dir.tag||dir) {
    case 'DIR.IP':
      // Update routing table of router!
      if (!node.connections.ip) node.connections.ip=new Aios.Chan.iprouter();
      node.connections.ip.addLink(chan.link);
      chan.router=node.connections.ip;
      break;
    default: 
      if (!name) {
        if (current) current.error='ENOCHANNEL';
        throw 'CONNECT';
      }
      node.connections[name]=chan.link;
  } 
  chan.link.on('agent',node.receive.bind(node));
  chan.link.on('signal',node.handle.bind(node));
  chan.link.on('class',function (obj){ for(var p in obj) self.addClass(p,obj[p].fun,obj[p].env)});
  return chan;
};

/** Connect a physical link of node @node to a remote endpoint (if curerently not connected) specified by the @dir parameter.
 *  typeof @dir = {tag,ip?,device?} with tag='DIR.IP'|'DIR.NORTH',..
 *
 */
world.prototype.connectTo = function (dir,node,options) {
  var chan,tokens,to=dir.ip,name=Aios.DIR.to(dir);
  if (!node) node=current.node;
  chan=node.connections[name];
  if (chan && (chan.status(to) || !chan.connect)) chan=undefined;
  if (chan) chan.connect(to,options);
}

/** Check connectivity to a specific node or a set of nodes
 *
 */
world.prototype.connected = function (dir,node) {
  var name=Aios.DIR.to(dir),list;
  chan=node.connections[name];
  switch (dir.tag||dir) {
    case Aios.DIR.tag.PATH:
      return chan && chan.status(dir.path);
      break;
    case Aios.DIR.tag.IP:
      // DIR.IP('*') returns all linked IP routes
      // DIR.IP('%') returns all linked nodes (names)
      return chan && chan.status(dir.ip); 
      break;
    case Aios.DIR.tag.NODE:
      // DIR.NODE('*') returns all linked nodes on all connections!
      if (dir.node=='*') {
        // Check all conenctions for remote node information
        list=[];
        if (node.connections.ip) list=list.concat(node.connections.ip.status('%'));
        return list; 
      } else if (typeof dir.node == 'string') {
        // Return link (IP)
        if (node.connections.ip && node.connections.ip.lookup) { 
          found=node.connections.ip.lookup(dir.node);
          return found?Aios.DIR.IP(found):none;
        }
      }
      break;
    case Aios.DIR.tag.DELTA:
      // a rough guess (no nw/sw/se/ne)
      if (dir.delta[0]==1) chan=node.connections.east;
      else if (dir.delta[0]==-1) chan=node.connections.west;
      else if (dir.delta[1]==1) chan=node.connections.north;
      else if (dir.delta[1]==-1) chan=node.connections.south;
      else if (dir.delta[2]==1) chan=node.connections.up;
      else if (dir.delta[2]==-1) chan=node.connections.down;
      return chan && chan.status(); 
      break;
    default:
      return (chan && chan.status(dir.ip||dir.node||dir.path))||false;    
  }  
}

/** Disconnect a physical link of node @node to a remote endpoint (if curerently connected) specified by the @dir parameter.
 *
 */
world.prototype.disconnect = function (dir,node) {
  var chan;
  switch (dir.tag||dir) {
    case 'DIR.IP':
      if (node.connections.ip && 
          node.connections.ip.status(dir.ip) && 
          node.connections.ip.disconnect) 
        node.connections.ip.disconnect(dir.ip); 
      break;
  }  
}


/** Find an agent in the world by it's id  and class (option), 
 *  or agents matching a regular expression by their id. 
 *
 */
world.prototype.getAgent = function (id,ac) {
  var res = this.getAgentProcess(id,ac);
  if (res && Comp.obj.isArray(res)) return res.map(function (ap) { return ap.agent });
  if (res) return res.agent;
  return;
};

world.prototype.getAgentProcess = function (id,ac) {
  var matches=Comp.obj.isRegex(id)?[]:undefined;
  for(var n in this.nodes) {
    var table=this.nodes[n].processes.table;
    for(var p in table) {
      if (!table[p]) continue;
      if (!matches && table[p].agent.id==id && (!ac || table[p].agent.ac==ac)) 
        return table[p];
      if (matches && id.test(table[p].agent.id) && (!ac || table[p].agent.ac==ac)) 
        matches.push(table[p]);
    }
  }
  return matches;
};


/** Find a node in the world by it's id or nodes matching a regular expression. 
 *
 */
world.prototype.getNode = function (nodeid) {
  if (Comp.obj.isRegex(nodeid)) {
    var res=[];
    for(var n in this.nodes) {
      if (nodeid.test(this.nodes[n].id)) res.push(this.nodes[n]);
    }
    return res;
  } else {
    if (!this.hash[nodeid] && options.verbose) Io.out('World.getNode: not found <'+nodeid+'>');
    return this.hash[nodeid];
  }
};

world.prototype.info = function () {
  var obj={},stat;
  obj.agents=0;
  obj.transferred=0;
  obj.links=0;
  obj.ports=0;
  for(var n in this.nodes) {
    obj.agents += this.nodes[n].processes.used;
    for (var l in this.nodes[n].connections) {
      if (this.nodes[n].connections[l]) {
        obj.ports++;
        obj.transferred += this.nodes[n].connections[l].count();
        if (this.nodes[n].connections[l].stats) {
          stat = this.nodes[n].connections[l].stats();
          obj.links += (stat.links||0);
        }
      }
    }
  }  
  return obj;
}


world.prototype.init = function () {
}

/** Lookup nodes (using patterns and providing broker support)
 *
 */
world.prototype.lookup = function (dir,callback,node) {
  switch (dir.tag||dir) {
    case Aios.DIR.tag.PATH:
      if (node.connections.ip && node.connections.ip.lookup) return node.connections.ip.lookup(dir.path,callback);
      break;
    default:
      if (callback) callback();
  }
}

world.prototype.print = function (summary) {
  var str='**** WORLD '+this.id+' ****'+NL;
  var res = Io.mem();
  str += 'DATA='+int(res.data/1024)+' MB HEAP='+int(res.heap/1024)+' MB'+NL;
  for(var n in this.nodes) {
    str += this.nodes[n].print(summary);
  }
  return str;
}

/** Disconnect and remove a node from the world. 
 *  The node must be destroyed explicitly.
 *
 */
world.prototype.removeNode = function (nodeid) {
  var c,c2,conn,thenode,chan,node2;
  this.nodes=Comp.array.filter(this.nodes,function (node) {
    if (node.id==nodeid) thenode=node;
    return node.id!=nodeid;
  });
  this.hash[nodeid]=undefined;
  if (thenode) for(c in thenode.connections) {
    conn=thenode.connections[c];
    if (conn && conn.end) {
      node2=this.getNode(conn.end);
      if (node2) for (c2 in node2.connections) {
        // Unlink?
        if (node2.connections[c2] && node2.connections[c2].end==nodeid)
          node2.connections[c2]=undefined;
      }
    }
  }
  if (options.verbose) Io.out('World.removeNode <'+nodeid+'>');

};

world.prototype.start = function () {
  var self=this;
  if (this.scheduler) {
    proc = new this.thread(0);
    this.context=proc.context;
    this.scheduler.Add(proc.context);
  }
  this.gc = setInterval(function () {
    var node,n,p;
    if (self.verbose>3) self.out('GC');
    for(n in self.nodes) {
      node=self.nodes[n];
      for(p in node.processes.gone) {
        if (node.processes.gone[p]) {
          node.processes.gone[p].tmo -= 500;
          if (node.processes.gone[p].tmo<=0) node.processes.gone[p]=undefined;
        }
      }
    }
  },500);
}

world.prototype.stop = function () {
}

var World = function (nodes,options) {
  var obj=new world(nodes,options);
  current.world=obj;
  return obj;
}

module.exports = {
  options:options,
  World:World,
  current:function (module) { current=module.current; Aios=module}
}
};
BundleModuleCode['jam/mobi']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2006-2020 bLAB
 **    $CREATED:     15-1-16 by sbosse.
 **    $RCS:         $Id: mobi.js,v 1.3 2020/02/03 09:45:01 sbosse Exp sbosse $
 **    $VERSION:     1.10.2
 **
 **    $INFO:
 **
 **  JavaScript AIOS Agent Mobilityn Module
 **
 **    $ENDOFINFO
 */

var Comp = Require('com/compat');
var Net;

var options = {
  debug:{},
  version:'1.10.2'
}

function addr2url(addr) {
  return typeof addr=='number'?String(addr):
                (addr.proto?addr.proto+'://':'')+addr.address+':'+addr.port;
}

if (global.config.dos) Net=Require('dos/network');

var current=none;
var Aios = none;

/** Direction type; used with move and link? operations
 *  The link operation can eitehr return a boolean value or
 *  a list of reachable destiantions (PATH/IP only).
 *  NORTH, ..., are used for P2P connections only. 
 */
var DIRS= ['NORTH','SOUTH','WEST','EAST','LEFT','RIGHT','UP','DOWN','ORIGIN','NW','NE','SW','SE',
           'DELTA','RANGE','NODE','IP','PATH','CAP'];

/*

enum DIR = {
  NORTH , SOUTH , .. ,
  IP(ip:string) , .. 
  } : dir
tyoe dir = NORTH | SOUTH | .. | IP {tag,ip:string } | ..
*/
var DIR = {
  NORTH:'DIR.NORTH',
  SOUTH:'DIR.SOUTH',
  WEST:'DIR.WEST',
  EAST:'DIR.EAST',
  LEFT:'DIR.LEFT',
  RIGHT:'DIR.RIGHT',
  UP:'DIR.UP',
  DOWN:'DIR.DOWN',
  ORIGIN:'DIR.ORIGIN',
  NW:'DIR.NW',
  NE:'DIR.NE',
  SW:'DIR.SW',
  SE:'DIR.SE',
  
  BACKWARD:'DIR.BACKWARD',
  FORWARD:'DIR.FORWARD',
  OPPOSITE:'DIR.OPPOSITE',

  // Assuming:  z-> x     N
  //            |        W+E  U(+z)/D(-z)
  //            v y       S
  DELTA: function (addr) { return {tag:"DIR.DELTA",delta:addr} },
  // Only for link? operation
  RANGE: function (r) { return {tag:"DIR.RANGE",radius:r} },  
  // Address a node (identifier name) directly
  NODE: function (node) { return {tag:"DIR.NODE",node:node} },
  IP:function (addr) { return {tag:"DIR.IP",ip:addr} },
  /*
  ** 
  ** Path can contain filter, e.g. range /distance[0-5], /distance[5], .. 
  ** or sets of destinations, e.g., /node*
  ** or a hopping array [dest1,dest2,..]
  ** type of path = string | string array
  */
  PATH:function (path) { return {tag:"DIR.PATH",path:path} },
  CAP:function (cap) { return {tag:"DIR.CAP",cap:cap} }
}

DIR.tag = {
  NORTH:'DIR.NORTH',
  SOUTH:'DIR.SOUTH',
  WEST:'DIR.WEST',
  EAST:'DIR.EAST',
  LEFT:'DIR.LEFT',
  RIGHT:'DIR.RIGHT',
  UP:'DIR.UP',
  DOWN:'DIR.DOWN',
  ORIGIN:'DIR.ORIGIN',
  NW:'DIR.NW',
  NE:'DIR.NE',
  SW:'DIR.SW',
  SE:'DIR.SE',
  BACKWARD:'DIR.BACKWARD',
  FORWARD:'DIR.FORWARD',
  OPPOSITE:'DIR.OPPOSITE',
  DELTA:'DIR.DELTA',
  RANGE:'DIR.RANGE',
  NODE:'DIR.NODE',
  IP:'DIR.IP',
  PATH:'DIR.PATH',
  CAP:'DIR.CAP',
}
/** Back direction. In case of IP, the remote address on receiving agent code is used.
 */

function opposite (dir,next) {
  var chan;
  switch (dir.tag||dir) {
    case DIR.NORTH: return DIR.SOUTH;
    case DIR.SOUTH: return DIR.NORTH;
    case DIR.WEST:  return DIR.EAST;
    case DIR.EAST:  return DIR.WEST;
    case DIR.LEFT:  return DIR.RIGHT;
    case DIR.RIGHT: return DIR.LEFT;
    case DIR.UP:    return DIR.DOWN;
    case DIR.DOWN:  return DIR.UP;
    case DIR.NW:    return DIR.SE;
    case DIR.NE:    return DIR.SW;
    case DIR.SE:    return DIR.NW;
    case DIR.SW:    return DIR.NE;
    case DIR.BACKWARD:  return DIR.FORWARD;
    case DIR.FORWARD:   return DIR.BACKWARD;
    case DIR.OPPOSITE:  return DIR.OPPOSITE;
    case DIR.tag.DELTA: 
      if (!next) return DIR.DELTA(dir.delta.map(function (v) {return -v}));
      else return;
    case DIR.tag.IP: 
      // try to use current process back attribute containing remote IP address upon receiving
      if (current.process && current.process.back && current.process.back.tag==DIR.tag.IP) return current.process.back;
      else return none;
    case DIR.tag.NODE:
      // try to use current process back attribute containing remote IP address upon receiving
      if (current.process && current.process.back) {
        switch (current.process.back.tag) {
          case DIR.tag.IP: 
            // Try to resolve node name
            if (current.node && current.node.connections.ip && current.node.connections.ip.reverse) 
              return DIR.NODE(current.node.connections.ip.reverse(current.process.back.ip));
            else 
              return current.process.back;
          case DIR.tag.NODE: 
            return current.process.back;
          default:
            return none;
        }
      } else return none;
    
    case 'DIR.PATH': 
      // TODO: this node name/path!
      return none;
    case 'DIR.CAP': 
      // TODO: this node capability!
      return none;
    default: 
      return none;
  }
};

// Create a valid DIR compatible type from a lowercase name specifier (e.g., north -> DIR.NORTH
DIR.from = function (name) {
  var Dir=name.toUpperCase();
  if (DIRS.indexOf(Dir) == -1) return;
  return {tag:'DIR.'+Dir}
}
// Create a valid lowercase name specifier from DIR (e.g. DIR.NORTH -> north)
DIR.to = function (dir) {
  if ((dir.tag||dir).substr(0,4)!='DIR.') return;
  return (dir.tag||dir).substr(4).toLowerCase();
}

DIR.isDir = function (o) {
  return (o.tag||o).indexOf('DIR')==0;
}
DIR.opposite=opposite;
DIR.print = function (dir) {
  if (!dir) return 'undefined';
  var name=(dir.tag||dir).substring(4);
  switch (dir.tag||dir) {
    case 'DIR.DELTA':
      return name+'('+Comp.printf.list(dir.delta)+')';
    case 'DIR.RANGE':
      return name+'('+dir.radius+')';
    case 'DIR.NODE':
      return name+'('+dir.node+')';
    case 'DIR.IP': 
      return name+'('+(dir.ip==undefined?'*':dir.ip)+')';
    case 'DIR.PATH': 
      return name+'('+dir.path+')';
    case 'DIR.CAP':
      return name+'('+dir.cao+')';
    default:
      if (!dir.ip) return name
      else return name+'('+addr2url(dir.ip)+')';
  }
};
DIR.toString = function (dir) {
  function format(ip) {
    if (ip==undefined) return '';
    if (typeof ip == 'number') return ip;
    if (typeof ip == 'string') return '"'+ip+'"';
  }
  switch (dir.tag) {
    case DIR.NORTH: return 'DIR.North('+format(dir.ip)+')';
    case DIR.SOUTH: return 'DIR.South('+format(dir.ip)+')';
    case DIR.WEST:  return 'DIR.West('+format(dir.ip)+')';
    case DIR.EAST:  return 'DIR.East('+format(dir.ip)+')';
    case DIR.tag.IP:    return 'DIR.IP('+format(dir.ip)+')';
  }
  return dir;
}
/** Search a channel that is connected to node 'destnode'
 *
 */
function lookup(node,destnode) {
  var chan,path;
  if (node.connections.ip && node.connections.ip.lookup) {
    path=node.connections.ip.lookup(destnode);
    if (path) return {chan:node.connections.ip,dest:path};
  }
}

/** Move current agent to new node 
 *
 */
function move(dir) {
  var node1=current.node,
      chan=none,
      dest,
      stat,
      path,
      alive = function () {return 1},
      nokill=false,
      name=DIR.to(dir),
      msg;
  switch (dir.tag||dir) {
    case 'DIR.IP':    
      chan=node1.connections.ip; 
      dest=dir.ip; 
      break;
    case 'DIR.DELTA':
      current.process.dir=Comp.obj.copy(dir);
      if (dir.delta[0]>0 && node1.connections.east && node1.connections.east.status()) 
        current.process.dir.delta[0]--,chan=node1.connections.east;
      else if (dir.delta[0]<0 && node1.connections.west && node1.connections.west.status()) 
        current.process.dir.delta[0]++,chan=node1.connections.west;
      else if (dir.delta[1]>0 && node1.connections.south && node1.connections.south.status()) 
        current.process.dir.delta[1]--,chan=node1.connections.south;
      else if (dir.delta[1]<0 && node1.connections.north && node1.connections.north.status()) 
        current.process.dir.delta[1]++,chan=node1.connections.north;
      else if (dir.delta[2]>0 && node1.connections.up && node1.connections.up.status()) 
        current.process.dir.delta[2]--,chan=node1.connections.up;
      else if (dir.delta[2]<0 && node1.connections.down && node1.connections.down.status()) 
        current.process.dir.delta[2]++,chan=node1.connections.down;
      break;
    case 'DIR.NODE':
      if (node1.connections.range && 
          node1.connections.range[dir.node] && 
          node1.connections.range[dir.node].status()) 
        chan=node1.connections.range[dir.node],dest=dir.node;
      else {
        // Find node name -> channel mapping
        dest=lookup(node1,dir.node); 
        if (dest) chan=dest.chan,dest=dest.dest;
      }
      break;
    case 'DIR.PATH':
      // TODO
      // if (!current.network) {current.error='No connection to path '+dir.path; throw 'MOVE'};
      if (Comp.obj.isArray(dir.path)) {
        path=Comp.array.pop(dir.path);
      } else path = dir.path;
      chan=node1.connections.path; dest=path; 
      nokill=true;
      break;
    case 'DIR.CAP':
      // TODO
      if (!current.network) {current.error='No connection to server '+dir.cap; throw 'MOVE'};
      chan=node1.connections.dos; dest=Net.Parse.capability(dir.cap).cap; 
      nokill=true; 
      break;
    default:
      if (!name) {
        current.error='ENOCHANNEL';
        throw 'MOVE';
      }
      chan=node1.connections[name];
  }
  // print(node1.connections);
  // print(chan)
  if (chan==none || !chan.status(dest)) {
    current.error='No connection to direction '+DIR.print(dir); throw 'MOVE'
  };
  
  if (!current.process.back) current.process.back=Aios.DIR.opposite(dir);
  node1.stats.migrate++;
    
  if (Aios.options.fastcopy && chan.virtual) msg=Aios.Code.toObject(current.process);
  else msg=Aios.Code.ofCode(current.process,false);
  
  current.process.move=dir;
  
  if (options.debug.move) console.log(dest,msg);
  
  /* NEWCOMM | context is current process !!!! */ 
  chan.send({agent:msg,to:dest,context:current.process});
  // kill or supend ghost agent
  if (!nokill) current.process.kill=true;       // discard process now
  else         current.process.suspended=true;  // discard process after send op finished
  //print(current.process.print());

}

module.exports = {
  agent:{
    move:move,
    opposite:opposite,
    DIR:DIR
  },
  current:function (module) { current=module.current; Aios=module; },
  DIR:DIR,
  DIRS:DIRS,
  options:options
}
};
BundleModuleCode['jam/watchdog']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2006-2022 bLAB
 **    $CREATED:     6-12-17 by sbosse.
 **    $RCS:         $Id: watchdog.js,v 1.1 2020/02/03 09:45:01 sbosse Exp sbosse $
 **    $VERSION:     1.2.1
 **
 **    $INFO:
 **
 **  JavaScript AIOS native platform Watchdog Interface
 **
 **
 **   A watchdog provides a timer and some kind of protected envrionment executing a function.
 **   If the function execution time exceeds the timeout of the timer, an exception is thrown (pre-emptive).
 **   This exception can be handled by a scheduler for round-robinson scheduling.
 **   There are different watchdog functionalities provided by different JS VM platforms:
 **
 **   1A. Full (jvm) {start,stop}
 **   1B. Full (jxcore+,pl3) {start,stop,protect}
 **   2. Protected (Node + watchdog.node module) {start, stop, protect}
 **   3. Partial with injected checkpointing (jxcore) {start, stop, checkpoint}
 **   4. No (generic node, browser) {}
 **
 **    $ENDOFINFO
 */
var version = '1.2.2'
if (typeof process != 'undefined' && process.env && !process.env.BOOTSTRAP) PATH.push('/opt/jam');
function search(index,module) {
  if (PATH.length==index) return module;
  var path=PATH[index];
  if (Fs.existsSync(path+'/'+module)) return path+'/'+module;
  else return search(index+1,module);
}

try {
  var watchdog;
  var Fs = Require('fs');
  try {watchdog = process && process.binding && process.binding && process.binding('watchdog')} catch (e){}; // JX/JX+
  if (!watchdog) watchdog = process && process.watchdog; // JX+
  if (!watchdog && process && process.startWatchdog) watchdog={
    // JVM
    start:process.startWatchdog,
    stop:process.stopWatchdog,
    init:process.initWatchdog,
    tick:process.tick
  };
  if (!watchdog) {
    try  { watchdog = require(search(0,'watchdog.node')) } catch (e) { }
  }
  if (!watchdog && process && process.version && Fs) {
    // NODE
    var nativePath,platformVersion;
    if (process.version.match(/^v0.12/)) platformVersion="0.12";
    else if (process.version.match(/^v3/)) platformVersion="3.x";
    else if (process.version.match(/^v4/)) platformVersion="4.x";
    else if (process.version.match(/^v5/)) platformVersion="5.x";
    else if (process.version.match(/^v6/)) platformVersion="6.x";
    else if (process.version.match(/^v7/)) platformVersion="7.x";
    else if (process.version.match(/^v8/)) platformVersion="8.x";
    else if (process.version.match(/^v9/)) platformVersion="9.x";
    if (platformVersion && process.platform && process.arch)
      nativePath = 'native/'+process.platform+'/'+platformVersion+'/' + process.arch + '/watchdog.node'; 
    if (PATH) 
    if (nativePath) {
      var _watchdog = require(search(0,nativePath));
      watchdog = {
        start : _watchdog.start,
        stop  : _watchdog.clear,
        protect : _watchdog.protect,
        init : function () {},
      }
    }
  }
} catch (e) {
  // console.log(e)
}

if (watchdog) {
  module.exports={
    start:watchdog.start||watchdog.startWatchdog,
    stop:watchdog.stop||watchdog.stopWatchdog,
    init:watchdog.init||watchdog.initWatchdog,
    checkPoint:watchdog.checkPoint,
    tick:watchdog.tick,
    protect:watchdog.protect,
    version : version
  }
} else module=undefined;
};
BundleModuleCode['geoip/gps5']=function (module,exports){
/* GPS location based on an external database lookup */

/* requires https */

var serviceHost="location.services.mozilla.com",
    servicePath="/v1/geolocate?key=test";
    


function geolocate (cb) {
  var https;
  if (typeof require == 'function') try {
    https = require('https');
  } catch (e) { /* TODO Browser */ }
  if (!https) return cb(new Error('ENETWORK'));
  var req = https.request({
    hostname: serviceHost,
    port: 443,
    path: servicePath,
    method: 'GET'
  }, function (res) {
    res.on('data', function (d){
      try {
        var json = JSON.parse(d);
        cb(json)
      } catch (e) { cb(e) };
    });
  })
  
  req.on('error', function (e) {
    console.error(e);
    cb(e);
  });
  req.end();
}

module.exports = { geolocate : geolocate };
};
BundleModuleCode['geoip/geoloc5']=function (module,exports){

var Options = {
  // stage1
  locate : {
    primary   : {http:'ag-0.de:9999',https:'ag-0.de:9998',timeout:1000},
    secondary : {http:'ip-api.com:80',https:'ip-api.com:80',path:'/json',timeout:2000},
  },
  // stage2
  locate5 : {
    secondary : {https:'location.services.mozilla.com:443',path:'/v1/geolocate?key=test'},
  },
  // stage 3: lat,lon -> location info lookup
  reverse : {
    primary : {https:'api.opencagedata.com',path:'/geocode/v1/json?q=LAT+LON&key=8e7c3730678842468d6acf450ecbca16'},
    secondary: {https:'nominatim.openstreetmap.org',path:'/reverse/?format=json&lat=LAT&lon=LON'},
  },
  verbose : 0
}

var http=Require("http"),https; /* https on demand */

function ip(url)    { return url.split(':')[0] }
function port(url)  { return url.split(':')[1] }

// 1. GPS/GEO via IP and external database service
function stage1 (cb,options,location) {
  var e,r;
  if (!http.xhr && http.request) {
    // node.js
    e={hostname:ip(options.http),
       path:options.path||'',method:"GET",
       port:port(options.http)};
    if (Options.verbose) console.log('locate.stage1',e);
    r=http.request(e,function(a){
        a.setEncoding("utf8");
        var c="";
        a.on("data",function(a){c+=a}),
        a.on("end",function(){
          try {
            var info=JSON.parse(c);
            if (Options.verbose) console.log('locate.stage1.res',info);
            Object.assign(location,{
              ip:info.query,
              gps:{lat:info.lat,lon:info.lon},
              geo:{city:info.city,country:info.country,countryCode:info.countryCode,region:info.region,zip:info.zip}
            })
            return cb(location);
          } catch (err) {
            return cb(err); 
          }
        })
    });
    r.on("error",function(a)  { 
      if (Options.verbose) console.log('locate.stage1.err',a);
      return cb(a);
    })
    r.setTimeout(options.timeout,function() {  
      if (Options.verbose) console.log('locate.stage1.err',"ETIMEOUT");
      return cb(new Error("ETIMEOUT"));
    }) 
    r.end();
  } else {
    // Browser
    var proto=document.URL.indexOf('https')==0?'https':'http';
    e={uri:proto+'://'+options[proto]+'/'+options.path,
       method:"GET",
       headers:{}};
    if (Options.verbose) console.log('stage1',e);
    http.request(e,function(err,xhr,body){
          if (err) {
            if (Options.verbose) console.log('locate.stage1.err',err);
            return cb(err);
          }
          try {
            var info=JSON.parse(body);
            if (Options.verbose) console.log('locate.stage1.res',info);
            Object.assign(location,{
              ip:info.query,
              gps:{lat:info.lat,lon:info.lon},
              geo:{city:info.city,country:info.country,countryCode:info.countryCode,region:info.region,zip:info.zip}
            })
            return cb(location);
          } catch (err) {
            if (Options.verbose) console.log('locate.stage1.err',err);
            return cb(err); 
          }
    })
  }
}

// 2. GPS via IP and external database service
function stage2 (cb,options,location) {
  if (!https || !https.request) return cb(new Error('ENETWORK'));
  if (!https.xhr && https.request) {
    var e = {
      hostname: ip(options.https),
      port: port(options.https),
      path: options.path,
      method: 'GET'
    }
    if (Options.verbose) console.log('locate.stage2',e);
    var req = https.request(e, function (res) {
      res.on('data', function (d){
        try {
          var pos = JSON.parse(d);
          if (Options.verbose) console.log('locate.stage3.res',pos);
          location.gps5 = { lat: pos.location.lat, lon:pos.location.lng }
          cb(location)
        } catch (e) { if (Options.verbose) console.log('locate.stage2.err',e); cb(e) };
      });
    })

    req.on('error', function (e) {
      cb(e);
    });
    req.end();
  } else {
    // Browser
    e={uri:'https://'+options.https+'/'+options.path,
       method:"GET",
       headers:{}};
    if (Options.verbose) console.log('locate.stage2',e);
    https.request(e,function(err,xhr,body){
          if (err) {
            if (Options.verbose) console.log('locate.stage2.err',err);
            return cb(err);
          }
          try {
            var pos = JSON.parse(body);
            if (Options.verbose) console.log('locate.stage2.res',pos);
            location.gps5 = { lat: pos.location.lat, lon:pos.location.lng }
            return cb(location);
          } catch (err) {
            if (Options.verbose) console.log('locate.stage2.err',err);
            return cb(err); 
          }
    })
    
  }
}

// GPS -> GEO mapping by external database service
function stage3 (cb,options,location) {
  if (!https || !https.request) return cb(new Error('ENETWORK'));
  options.path=options.path
                  .replace(/LAT/,location.gps5.lat)
                  .replace(/LON/,location.gps5.lon);
  if (!https.xhr && https.request) {
    var e = {
      hostname: ip(options.https),
      port: port(options.https),
      path: options.path,
      method: 'GET'
    }
    if (Options.verbose) console.log('locate.stage3',e);
    var req = https.request(e, function (res) {
      res.on('data', function (d){
        try {
          var res = JSON.parse(d);
          var loc;
          if (Options.verbose) console.log('locate.stage3.res',res);
          if (res.address) loc=res.address;
          else if (res.results && res.results[0]) loc=res.results[0].components;
          location.geo5 = {
            city:loc.city,
            zip:loc.postcode,
            street:loc.road,
            number:loc.house_number,
            country:loc.country
          }
          cb(location)
        } catch (e) { if (Options.verbose) console.log('locate.stage3.err',e); cb(e) };
      });
    })

    req.on('error', function (e) {
      if (Options.verbose) console.log('locate.stage3.err',e);
      cb(e);
    });
    req.end();
  } else {
    // Browser
    e={uri:'https://'+options.https+'/'+options.path,
       method:"GET",
       headers:{}};
    if (Options.verbose) console.log('locate.stage3',e);
    https.request(e,function(err,xhr,body){
       if (err) {
        if (Options.verbose) console.log('locate.stage3.err',err);
        return cb(err);
       }
       try {
          var res = JSON.parse(body);
          var loc;
          if (Options.verbose) console.log('locate.stage3.res',res);
          if (res.address) loc=res.address;
          else if (res.results && res.results[0]) loc=res.results[0].components;
          location.geo5 = {
            city:loc.city,
            zip:loc.postcode,
            street:loc.road,
            number:loc.house_number,
            country:loc.country
          }
          return cb(location);
        } catch (err) {
          if (Options.verbose) console.log('locate.stage3.err',err);
          return cb(err); 
        }
    })
    
  }
}

                                   
var todo = {
  // 1. Direct ISP - IP - GPS/GEO lookup
  // 1a. with proxy
  stage1A: function (cb,errors,location) {
    stage1(function (res) {
      if (res instanceof Error) {
        errors.push({url:Options.locate.primary,error:res});
        todo.stage1B(cb,errors,location);
      } else {
        todo.stage2A(cb,errors,location);        
      }
    },Options.locate.primary,location);
  },
  // 1b. w/o proxy
  stage1B: function (cb,errors,location) {
    stage1(function (res) {
      if (res instanceof Error) {
        errors.push({url:Options.locate.secondary,error:res});
        todo.stage2A(cb,errors,location);
      } else {
        todo.stage2A(cb,errors,location);        
      }
    },Options.locate.secondary,location);    
  },
  // 2. Get geo position (lat,lon)
  stage2A: function (cb,errors,location) {
    stage2(function (res) {
      if (res instanceof Error) {
        errors.push({url:Options.locate5.secondary,error:res});
        todo.finalize(cb,errors,location);
      } else  {
        todo.stage2B(cb,errors,location);        
      }
    },Options.locate5.secondary,location);
  },
  // 3. Get geo location (country,..)
  stage2B: function (cb,errors,location) {
    stage3(function (res) {
      if (res instanceof Error) {
        errors.push({url:Options.reverse.primary,x:1,error:res});
        todo.finalize(cb,errors,location);
      } else  {
        todo.finalize(cb,errors,location);
      }
    },Options.reverse.primary,location);
  },
  finalize : function (cb,errors,location) {
    cb(location,errors);
  }
}

function locate (cb,options) {
  var e;
  if (options) Options=Object.assign(Options,options);
  if (typeof require == 'function' && !https) try {
    https = require('https');
  } catch (e) { /* TODO Browser */ } else  if (http.xhr) https = http;
  if (!http) return cb(new Error('ENOTSUPPORTED'));
  todo.stage1A(cb,[],{});
  return;
}

module.exports={locate:locate,options:Options};

};
BundleModuleCode['os/platform']=function (module,exports){
/*!
 * Platform.js
 * get OS/Cpu/Arch/memory/.. information in nodejs and browser
 * Copyright 2014-2018 Benjamin Tan
 * Copyright 2011-2013 John-David Dalton
 * Available under MIT license
 * Modified by @blab
 * Ver. 1.2.3
 */
if (global.TARGET=='browser') {
/*
Returns: 

platform.name; // 'Safari'
platform.version; // '5.1'
platform.product; // 'iPad'
platform.manufacturer; // 'Apple'
platform.engine; // 'WebKit'
platform.os; // 'iOS 5.0'
platform.description; // 'Safari 5.1 on Apple iPad (iOS 5.0)'
platform.mobile; // 'true'
platform.touch; // 'false'
platform.geoloc; // 'true'
*/
  /** Used to determine if values are of the language type `Object`. */
  var objectTypes = {
    'function': true,
    'object': true
  };

  /** Used as a reference to the global object. */
  var root = (objectTypes[typeof window] && window) || this;

  /** Backup possible global object. */
  var oldRoot = root;

  /** Detect free variable `exports`. */
  var freeExports = objectTypes[typeof exports] && exports;

  /** Detect free variable `module`. */
  var freeModule = objectTypes[typeof module] && module && !module.nodeType && module;

  /** Detect free variable `global` from Node.js or Browserified code and use it as `root`. */
  var freeGlobal = freeExports && freeModule && typeof global == 'object' && global;
  if (freeGlobal && (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal || freeGlobal.self === freeGlobal)) {
    root = freeGlobal;
  }

  /**
   * Used as the maximum length of an array-like object.
   * See the [ES6 spec](http://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength)
   * for more details.
   */
  var maxSafeInteger = Math.pow(2, 53) - 1;

  /** Regular expression to detect Opera. */
  var reOpera = /\bOpera/;

  /** Possible global object. */
  var thisBinding = this;

  /** Used for native method references. */
  var objectProto = Object.prototype;

  /** Used to check for own properties of an object. */
  var hasOwnProperty = objectProto.hasOwnProperty;

  /** Used to resolve the internal `[[Class]]` of values. */
  var toString = objectProto.toString;

  /*--------------------------------------------------------------------------*/

  /**
   * Capitalizes a string value.
   *
   * @private
   * @param {string} string The string to capitalize.
   * @returns {string} The capitalized string.
   */
  function capitalize(string) {
    string = String(string);
    return string.charAt(0).toUpperCase() + string.slice(1);
  }

  /**
   * A utility function to clean up the OS name.
   *
   * @private
   * @param {string} os The OS name to clean up.
   * @param {string} [pattern] A `RegExp` pattern matching the OS name.
   * @param {string} [label] A label for the OS.
   */
  function cleanupOS(os, pattern, label) {
    // Platform tokens are defined at:
    // http://msdn.microsoft.com/en-us/library/ms537503(VS.85).aspx
    // http://web.archive.org/web/20081122053950/http://msdn.microsoft.com/en-us/library/ms537503(VS.85).aspx
    var data = {
      '10.0': '10',
      '6.4':  '10 Technical Preview',
      '6.3':  '8.1',
      '6.2':  '8',
      '6.1':  'Server 2008 R2 / 7',
      '6.0':  'Server 2008 / Vista',
      '5.2':  'Server 2003 / XP 64-bit',
      '5.1':  'XP',
      '5.01': '2000 SP1',
      '5.0':  '2000',
      '4.0':  'NT',
      '4.90': 'ME'
    };
    // Detect Windows version from platform tokens.
    if (pattern && label && /^Win/i.test(os) && !/^Windows Phone /i.test(os) &&
        (data = data[/[\d.]+$/.exec(os)])) {
      os = 'Windows ' + data;
    }
    // Correct character case and cleanup string.
    os = String(os);

    if (pattern && label) {
      os = os.replace(RegExp(pattern, 'i'), label);
    }

    os = format(
      os.replace(/ ce$/i, ' CE')
        .replace(/\bhpw/i, 'web')
        .replace(/\bMacintosh\b/, 'Mac OS')
        .replace(/_PowerPC\b/i, ' OS')
        .replace(/\b(OS X) [^ \d]+/i, '$1')
        .replace(/\bMac (OS X)\b/, '$1')
        .replace(/\/(\d)/, ' $1')
        .replace(/_/g, '.')
        .replace(/(?: BePC|[ .]*fc[ \d.]+)$/i, '')
        .replace(/\bx86\.64\b/gi, 'x86_64')
        .replace(/\b(Windows Phone) OS\b/, '$1')
        .replace(/\b(Chrome OS \w+) [\d.]+\b/, '$1')
        .split(' on ')[0]
    );

    return os;
  }

  /**
   * An iteration utility for arrays and objects.
   *
   * @private
   * @param {Array|Object} object The object to iterate over.
   * @param {Function} callback The function called per iteration.
   */
  function each(object, callback) {
    var index = -1,
        length = object ? object.length : 0;

    if (typeof length == 'number' && length > -1 && length <= maxSafeInteger) {
      while (++index < length) {
        callback(object[index], index, object);
      }
    } else {
      forOwn(object, callback);
    }
  }

  /**
   * Trim and conditionally capitalize string values.
   *
   * @private
   * @param {string} string The string to format.
   * @returns {string} The formatted string.
   */
  function format(string) {
    string = trim(string);
    return /^(?:webOS|i(?:OS|P))/.test(string)
      ? string
      : capitalize(string);
  }

  /**
   * Iterates over an object's own properties, executing the `callback` for each.
   *
   * @private
   * @param {Object} object The object to iterate over.
   * @param {Function} callback The function executed per own property.
   */
  function forOwn(object, callback) {
    for (var key in object) {
      if (hasOwnProperty.call(object, key)) {
        callback(object[key], key, object);
      }
    }
  }

  /**
   * Gets the internal `[[Class]]` of a value.
   *
   * @private
   * @param {*} value The value.
   * @returns {string} The `[[Class]]`.
   */
  function getClassOf(value) {
    return value == null
      ? capitalize(value)
      : toString.call(value).slice(8, -1);
  }

  /**
   * Host objects can return type values that are different from their actual
   * data type. The objects we are concerned with usually return non-primitive
   * types of "object", "function", or "unknown".
   *
   * @private
   * @param {*} object The owner of the property.
   * @param {string} property The property to check.
   * @returns {boolean} Returns `true` if the property value is a non-primitive, else `false`.
   */
  function isHostType(object, property) {
    var type = object != null ? typeof object[property] : 'number';
    return !/^(?:boolean|number|string|undefined)$/.test(type) &&
      (type == 'object' ? !!object[property] : true);
  }

  /**
   * Prepares a string for use in a `RegExp` by making hyphens and spaces optional.
   *
   * @private
   * @param {string} string The string to qualify.
   * @returns {string} The qualified string.
   */
  function qualify(string) {
    return String(string).replace(/([ -])(?!$)/g, '$1?');
  }

  /**
   * A bare-bones `Array#reduce` like utility function.
   *
   * @private
   * @param {Array} array The array to iterate over.
   * @param {Function} callback The function called per iteration.
   * @returns {*} The accumulated result.
   */
  function reduce(array, callback) {
    var accumulator = null;
    each(array, function(value, index) {
      accumulator = callback(accumulator, value, index, array);
    });
    return accumulator;
  }

  /**
   * Removes leading and trailing whitespace from a string.
   *
   * @private
   * @param {string} string The string to trim.
   * @returns {string} The trimmed string.
   */
  function trim(string) {
    return String(string).replace(/^ +| +$/g, '');
  }

  /*--------------------------------------------------------------------------*/

  /**
   * Creates a new platform object.
   *
   * @memberOf platform
   * @param {Object|string} [ua=navigator.userAgent] The user agent string or
   *  context object.
   * @returns {Object} A platform object.
   */
  function parse(ua) {

    /** The environment context object. */
    var context = root;

    /** Used to flag when a custom context is provided. */
    var isCustomContext = ua && typeof ua == 'object' && getClassOf(ua) != 'String';

    // Juggle arguments.
    if (isCustomContext) {
      context = ua;
      ua = null;
    }

    /** Browser navigator object. */
    var nav = context.navigator || {};

    /** Browser user agent string. */
    var userAgent = nav.userAgent || '';

    ua || (ua = userAgent);

    /** Used to flag when `thisBinding` is the [ModuleScope]. */
    var isModuleScope = isCustomContext || thisBinding == oldRoot;

    /** Used to detect if browser is like Chrome. */
    var likeChrome = isCustomContext
      ? !!nav.likeChrome
      : /\bChrome\b/.test(ua) && !/internal|\n/i.test(toString.toString());

    /** Internal `[[Class]]` value shortcuts. */
    var objectClass = 'Object',
        airRuntimeClass = isCustomContext ? objectClass : 'ScriptBridgingProxyObject',
        enviroClass = isCustomContext ? objectClass : 'Environment',
        javaClass = (isCustomContext && context.java) ? 'JavaPackage' : getClassOf(context.java),
        phantomClass = isCustomContext ? objectClass : 'RuntimeObject';

    /** Detect Java environments. */
    var java = /\bJava/.test(javaClass) && context.java;

    /** Detect Rhino. */
    var rhino = java && getClassOf(context.environment) == enviroClass;

    /** A character to represent alpha. */
    var alpha = java ? 'a' : '\u03b1';

    /** A character to represent beta. */
    var beta = java ? 'b' : '\u03b2';

    /** Browser document object. */
    var doc = context.document || {};

    /**
     * Detect Opera browser (Presto-based).
     * http://www.howtocreate.co.uk/operaStuff/operaObject.html
     * http://dev.opera.com/articles/view/opera-mini-web-content-authoring-guidelines/#operamini
     */
    var opera = context.operamini || context.opera;

    /** Opera `[[Class]]`. */
    var operaClass = reOpera.test(operaClass = (isCustomContext && opera) ? opera['[[Class]]'] : getClassOf(opera))
      ? operaClass
      : (opera = null);

    /*------------------------------------------------------------------------*/

    /** Temporary variable used over the script's lifetime. */
    var data;

    /** The CPU architecture. */
    var arch = ua;

    /** Platform description array. */
    var description = [];

    /** Platform alpha/beta indicator. */
    var prerelease = null;

    /** A flag to indicate that environment features should be used to resolve the platform. */
    var useFeatures = ua == userAgent;

    /** The browser/environment version. */
    var version = useFeatures && opera && typeof opera.version == 'function' && opera.version();

    /** A flag to indicate if the OS ends with "/ Version" */
    var isSpecialCasedOS;

    /* Detectable layout engines (order is important). */
    var layout = getLayout([
      { 'label': 'EdgeHTML', 'pattern': '(?:Edge|EdgA|EdgiOS)' },
      'Trident',
      { 'label': 'WebKit', 'pattern': 'AppleWebKit' },
      'iCab',
      'Presto',
      'NetFront',
      'Tasman',
      'KHTML',
      'Gecko'
    ]);

    /* Detectable browser names (order is important). */
    var name = getName([
      'Adobe AIR',
      'Arora',
      'Avant Browser',
      'Breach',
      'Camino',
      'Electron',
      'Epiphany',
      'Fennec',
      'Flock',
      'Galeon',
      'GreenBrowser',
      'iCab',
      'Iceweasel',
      'K-Meleon',
      'Konqueror',
      'Lunascape',
      'Maxthon',
      { 'label': 'Microsoft Edge', 'pattern': '(?:Edge|Edg|EdgA|EdgiOS)' },
      'Midori',
      'Nook Browser',
      'PaleMoon',
      'PhantomJS',
      'Raven',
      'Rekonq',
      'RockMelt',
      { 'label': 'Samsung Internet', 'pattern': 'SamsungBrowser' },
      'SeaMonkey',
      { 'label': 'Silk', 'pattern': '(?:Cloud9|Silk-Accelerated)' },
      'Sleipnir',
      'SlimBrowser',
      { 'label': 'SRWare Iron', 'pattern': 'Iron' },
      'Sunrise',
      'Swiftfox',
      'Waterfox',
      'WebPositive',
      'Opera Mini',
      { 'label': 'Opera Mini', 'pattern': 'OPiOS' },
      'Opera',
      { 'label': 'Opera', 'pattern': 'OPR' },
      'Chrome',
      { 'label': 'Chrome Mobile', 'pattern': '(?:CriOS|CrMo)' },
      { 'label': 'Firefox', 'pattern': '(?:Firefox|Minefield)' },
      { 'label': 'Firefox for iOS', 'pattern': 'FxiOS' },
      { 'label': 'IE', 'pattern': 'IEMobile' },
      { 'label': 'IE', 'pattern': 'MSIE' },
      'Safari'
    ]);

    /* Detectable products (order is important). */
    var product = getProduct([
      { 'label': 'BlackBerry', 'pattern': 'BB10' },
      'BlackBerry',
      { 'label': 'Galaxy S', 'pattern': 'GT-I9000' },
      { 'label': 'Galaxy S2', 'pattern': 'GT-I9100' },
      { 'label': 'Galaxy S3', 'pattern': 'GT-I9300' },
      { 'label': 'Galaxy S4', 'pattern': 'GT-I9500' },
      { 'label': 'Galaxy S5', 'pattern': 'SM-G900' },
      { 'label': 'Galaxy S6', 'pattern': 'SM-G920' },
      { 'label': 'Galaxy S6 Edge', 'pattern': 'SM-G925' },
      { 'label': 'Galaxy S7', 'pattern': 'SM-G930' },
      { 'label': 'Galaxy S7 Edge', 'pattern': 'SM-G935' },
      'Google TV',
      'Lumia',
      'iPad',
      'iPod',
      'iPhone',
      'Kindle',
      { 'label': 'Kindle Fire', 'pattern': '(?:Cloud9|Silk-Accelerated)' },
      'Nexus',
      'Nook',
      'PlayBook',
      'PlayStation Vita',
      'PlayStation',
      'TouchPad',
      'Transformer',
      { 'label': 'Wii U', 'pattern': 'WiiU' },
      'Wii',
      'Xbox One',
      { 'label': 'Xbox 360', 'pattern': 'Xbox' },
      'Xoom'
    ]);

    /* Detectable manufacturers. */
    var manufacturer = getManufacturer({
      'Apple': { 'iPad': 1, 'iPhone': 1, 'iPod': 1 },
      'Archos': {},
      'Amazon': { 'Kindle': 1, 'Kindle Fire': 1 },
      'Asus': { 'Transformer': 1 },
      'Barnes & Noble': { 'Nook': 1 },
      'BlackBerry': { 'PlayBook': 1 },
      'Google': { 'Google TV': 1, 'Nexus': 1 },
      'HP': { 'TouchPad': 1 },
      'HTC': {},
      'LG': {},
      'Microsoft': { 'Xbox': 1, 'Xbox One': 1 },
      'Motorola': { 'Xoom': 1 },
      'Nintendo': { 'Wii U': 1,  'Wii': 1 },
      'Nokia': { 'Lumia': 1 },
      'Samsung': { 'Galaxy S': 1, 'Galaxy S2': 1, 'Galaxy S3': 1, 'Galaxy S4': 1 },
      'Sony': { 'PlayStation': 1, 'PlayStation Vita': 1 }
    });

    /* Detectable operating systems (order is important). */
    var os = getOS([
      'Windows Phone',
      'Android',
      'CentOS',
      { 'label': 'Chrome OS', 'pattern': 'CrOS' },
      'Debian',
      'Fedora',
      'FreeBSD',
      'Gentoo',
      'Haiku',
      'Kubuntu',
      'Linux Mint',
      'OpenBSD',
      'Red Hat',
      'SuSE',
      'Ubuntu',
      'Xubuntu',
      'Cygwin',
      'Symbian OS',
      'hpwOS',
      'webOS ',
      'webOS',
      'Tablet OS',
      'Tizen',
      'Linux',
      'Mac OS X',
      'Macintosh',
      'Mac',
      'Windows 98;',
      'Windows ',
      'SunOS',
    ]);

    /*------------------------------------------------------------------------*/

    /**
     * Picks the layout engine from an array of guesses.
     *
     * @private
     * @param {Array} guesses An array of guesses.
     * @returns {null|string} The detected layout engine.
     */
    function getLayout(guesses) {
      return reduce(guesses, function(result, guess) {
        return result || RegExp('\\b' + (
          guess.pattern || qualify(guess)
        ) + '\\b', 'i').exec(ua) && (guess.label || guess);
      });
    }

    /**
     * Picks the manufacturer from an array of guesses.
     *
     * @private
     * @param {Array} guesses An object of guesses.
     * @returns {null|string} The detected manufacturer.
     */
    function getManufacturer(guesses) {
      return reduce(guesses, function(result, value, key) {
        // Lookup the manufacturer by product or scan the UA for the manufacturer.
        return result || (
          value[product] ||
          value[/^[a-z]+(?: +[a-z]+\b)*/i.exec(product)] ||
          RegExp('\\b' + qualify(key) + '(?:\\b|\\w*\\d)', 'i').exec(ua)
        ) && key;
      });
    }

    /**
     * Picks the browser name from an array of guesses.
     *
     * @private
     * @param {Array} guesses An array of guesses.
     * @returns {null|string} The detected browser name.
     */
    function getName(guesses) {
      return reduce(guesses, function(result, guess) {
        return result || RegExp('\\b' + (
          guess.pattern || qualify(guess)
        ) + '\\b', 'i').exec(ua) && (guess.label || guess);
      });
    }

    /**
     * Picks the OS name from an array of guesses.
     *
     * @private
     * @param {Array} guesses An array of guesses.
     * @returns {null|string} The detected OS name.
     */
    function getOS(guesses) {
      return reduce(guesses, function(result, guess) {
        var pattern = guess.pattern || qualify(guess);
        if (!result && (result =
              RegExp('\\b' + pattern + '(?:/[\\d.]+|[ \\w.]*)', 'i').exec(ua)
            )) {
          result = cleanupOS(result, pattern, guess.label || guess);
        }
        return result;
      });
    }

    /**
     * Picks the product name from an array of guesses.
     *
     * @private
     * @param {Array} guesses An array of guesses.
     * @returns {null|string} The detected product name.
     */
    function getProduct(guesses) {
      return reduce(guesses, function(result, guess) {
        var pattern = guess.pattern || qualify(guess);
        if (!result && (result =
              RegExp('\\b' + pattern + ' *\\d+[.\\w_]*', 'i').exec(ua) ||
              RegExp('\\b' + pattern + ' *\\w+-[\\w]*', 'i').exec(ua) ||
              RegExp('\\b' + pattern + '(?:; *(?:[a-z]+[_-])?[a-z]+\\d+|[^ ();-]*)', 'i').exec(ua)
            )) {
          // Split by forward slash and append product version if needed.
          if ((result = String((guess.label && !RegExp(pattern, 'i').test(guess.label)) ? guess.label : result).split('/'))[1] && !/[\d.]+/.test(result[0])) {
            result[0] += ' ' + result[1];
          }
          // Correct character case and cleanup string.
          guess = guess.label || guess;
          result = format(result[0]
            .replace(RegExp(pattern, 'i'), guess)
            .replace(RegExp('; *(?:' + guess + '[_-])?', 'i'), ' ')
            .replace(RegExp('(' + guess + ')[-_.]?(\\w)', 'i'), '$1 $2'));
        }
        return result;
      });
    }

    /**
     * Resolves the version using an array of UA patterns.
     *
     * @private
     * @param {Array} patterns An array of UA patterns.
     * @returns {null|string} The detected version.
     */
    function getVersion(patterns) {
      return reduce(patterns, function(result, pattern) {
        return result || (RegExp(pattern +
          '(?:-[\\d.]+/|(?: for [\\w-]+)?[ /-])([\\d.]+[^ ();/_-]*)', 'i').exec(ua) || 0)[1] || null;
      });
    }

    /**
     * Returns `platform.description` when the platform object is coerced to a string.
     *
     * @name toString
     * @memberOf platform
     * @returns {string} Returns `platform.description` if available, else an empty string.
     */
    function toStringPlatform() {
      return this.description || '';
    }

    /*------------------------------------------------------------------------*/

    // Convert layout to an array so we can add extra details.
    layout && (layout = [layout]);

    // Detect product names that contain their manufacturer's name.
    if (manufacturer && !product) {
      product = getProduct([manufacturer]);
    }
    // Clean up Google TV.
    if ((data = /\bGoogle TV\b/.exec(product))) {
      product = data[0];
    }
    // Detect simulators.
    if (/\bSimulator\b/i.test(ua)) {
      product = (product ? product + ' ' : '') + 'Simulator';
    }
    // Detect Opera Mini 8+ running in Turbo/Uncompressed mode on iOS.
    if (name == 'Opera Mini' && /\bOPiOS\b/.test(ua)) {
      description.push('running in Turbo/Uncompressed mode');
    }
    // Detect IE Mobile 11.
    if (name == 'IE' && /\blike iPhone OS\b/.test(ua)) {
      data = parse(ua.replace(/like iPhone OS/, ''));
      manufacturer = data.manufacturer;
      product = data.product;
    }
    // Detect iOS.
    else if (/^iP/.test(product)) {
      name || (name = 'Safari');
      os = 'iOS' + ((data = / OS ([\d_]+)/i.exec(ua))
        ? ' ' + data[1].replace(/_/g, '.')
        : '');
    }
    // Detect Kubuntu.
    else if (name == 'Konqueror' && !/buntu/i.test(os)) {
      os = 'Kubuntu';
    }
    // Detect Android browsers.
    else if ((manufacturer && manufacturer != 'Google' &&
        ((/Chrome/.test(name) && !/\bMobile Safari\b/i.test(ua)) || /\bVita\b/.test(product))) ||
        (/\bAndroid\b/.test(os) && /^Chrome/.test(name) && /\bVersion\//i.test(ua))) {
      name = 'Android Browser';
      os = /\bAndroid\b/.test(os) ? os : 'Android';
    }
    // Detect Silk desktop/accelerated modes.
    else if (name == 'Silk') {
      if (!/\bMobi/i.test(ua)) {
        os = 'Android';
        description.unshift('desktop mode');
      }
      if (/Accelerated *= *true/i.test(ua)) {
        description.unshift('accelerated');
      }
    }
    // Detect PaleMoon identifying as Firefox.
    else if (name == 'PaleMoon' && (data = /\bFirefox\/([\d.]+)\b/.exec(ua))) {
      description.push('identifying as Firefox ' + data[1]);
    }
    // Detect Firefox OS and products running Firefox.
    else if (name == 'Firefox' && (data = /\b(Mobile|Tablet|TV)\b/i.exec(ua))) {
      os || (os = 'Firefox OS');
      product || (product = data[1]);
    }
    // Detect false positives for Firefox/Safari.
    else if (!name || (data = !/\bMinefield\b/i.test(ua) && /\b(?:Firefox|Safari)\b/.exec(name))) {
      // Escape the `/` for Firefox 1.
      if (name && !product && /[\/,]|^[^(]+?\)/.test(ua.slice(ua.indexOf(data + '/') + 8))) {
        // Clear name of false positives.
        name = null;
      }
      // Reassign a generic name.
      if ((data = product || manufacturer || os) &&
          (product || manufacturer || /\b(?:Android|Symbian OS|Tablet OS|webOS)\b/.test(os))) {
        name = /[a-z]+(?: Hat)?/i.exec(/\bAndroid\b/.test(os) ? os : data) + ' Browser';
      }
    }
    // Add Chrome version to description for Electron.
    else if (name == 'Electron' && (data = (/\bChrome\/([\d.]+)\b/.exec(ua) || 0)[1])) {
      description.push('Chromium ' + data);
    }
    // Detect non-Opera (Presto-based) versions (order is important).
    if (!version) {
      version = getVersion([
        '(?:Cloud9|CriOS|CrMo|Edge|Edg|EdgA|EdgiOS|FxiOS|IEMobile|Iron|Opera ?Mini|OPiOS|OPR|Raven|SamsungBrowser|Silk(?!/[\\d.]+$))',
        'Version',
        qualify(name),
        '(?:Firefox|Minefield|NetFront)'
      ]);
    }
    // Detect stubborn layout engines.
    if ((data =
          layout == 'iCab' && parseFloat(version) > 3 && 'WebKit' ||
          /\bOpera\b/.test(name) && (/\bOPR\b/.test(ua) ? 'Blink' : 'Presto') ||
          /\b(?:Midori|Nook|Safari)\b/i.test(ua) && !/^(?:Trident|EdgeHTML)$/.test(layout) && 'WebKit' ||
          !layout && /\bMSIE\b/i.test(ua) && (os == 'Mac OS' ? 'Tasman' : 'Trident') ||
          layout == 'WebKit' && /\bPlayStation\b(?! Vita\b)/i.test(name) && 'NetFront'
        )) {
      layout = [data];
    }
    // Detect Windows Phone 7 desktop mode.
    if (name == 'IE' && (data = (/; *(?:XBLWP|ZuneWP)(\d+)/i.exec(ua) || 0)[1])) {
      name += ' Mobile';
      os = 'Windows Phone ' + (/\+$/.test(data) ? data : data + '.x');
      description.unshift('desktop mode');
    }
    // Detect Windows Phone 8.x desktop mode.
    else if (/\bWPDesktop\b/i.test(ua)) {
      name = 'IE Mobile';
      os = 'Windows Phone 8.x';
      description.unshift('desktop mode');
      version || (version = (/\brv:([\d.]+)/.exec(ua) || 0)[1]);
    }
    // Detect IE 11 identifying as other browsers.
    else if (name != 'IE' && layout == 'Trident' && (data = /\brv:([\d.]+)/.exec(ua))) {
      if (name) {
        description.push('identifying as ' + name + (version ? ' ' + version : ''));
      }
      name = 'IE';
      version = data[1];
    }
    // Leverage environment features.
    if (useFeatures) {
      // Detect server-side environments.
      // Rhino has a global function while others have a global object.
      if (isHostType(context, 'global')) {
        if (java) {
          data = java.lang.System;
          arch = data.getProperty('os.arch');
          os = os || data.getProperty('os.name') + ' ' + data.getProperty('os.version');
        }
        if (rhino) {
          try {
            version = context.require('ringo/engine').version.join('.');
            name = 'RingoJS';
          } catch(e) {
            if ((data = context.system) && data.global.system == context.system) {
              name = 'Narwhal';
              os || (os = data[0].os || null);
            }
          }
          if (!name) {
            name = 'Rhino';
          }
        }
        else if (
          typeof context.process == 'object' && !context.process.browser &&
          (data = context.process)
        ) {
          if (typeof data.versions == 'object') {
            if (typeof data.versions.electron == 'string') {
              description.push('Node ' + data.versions.node);
              name = 'Electron';
              version = data.versions.electron;
            } else if (typeof data.versions.nw == 'string') {
              description.push('Chromium ' + version, 'Node ' + data.versions.node);
              name = 'NW.js';
              version = data.versions.nw;
            }
          }
          if (!name) {
            name = 'Node.js';
            arch = data.arch;
            os = data.platform;
            version = /[\d.]+/.exec(data.version);
            version = version ? version[0] : null;
          }
        }
      }
      // Detect Adobe AIR.
      else if (getClassOf((data = context.runtime)) == airRuntimeClass) {
        name = 'Adobe AIR';
        os = data.flash.system.Capabilities.os;
      }
      // Detect PhantomJS.
      else if (getClassOf((data = context.phantom)) == phantomClass) {
        name = 'PhantomJS';
        version = (data = data.version || null) && (data.major + '.' + data.minor + '.' + data.patch);
      }
      // Detect IE compatibility modes.
      else if (typeof doc.documentMode == 'number' && (data = /\bTrident\/(\d+)/i.exec(ua))) {
        // We're in compatibility mode when the Trident version + 4 doesn't
        // equal the document mode.
        version = [version, doc.documentMode];
        if ((data = +data[1] + 4) != version[1]) {
          description.push('IE ' + version[1] + ' mode');
          layout && (layout[1] = '');
          version[1] = data;
        }
        version = name == 'IE' ? String(version[1].toFixed(1)) : version[0];
      }
      // Detect IE 11 masking as other browsers.
      else if (typeof doc.documentMode == 'number' && /^(?:Chrome|Firefox)\b/.test(name)) {
        description.push('masking as ' + name + ' ' + version);
        name = 'IE';
        version = '11.0';
        layout = ['Trident'];
        os = 'Windows';
      }
      os = os && format(os);
    }
    // Detect prerelease phases.
    if (version && (data =
          /(?:[ab]|dp|pre|[ab]\d+pre)(?:\d+\+?)?$/i.exec(version) ||
          /(?:alpha|beta)(?: ?\d)?/i.exec(ua + ';' + (useFeatures && nav.appMinorVersion)) ||
          /\bMinefield\b/i.test(ua) && 'a'
        )) {
      prerelease = /b/i.test(data) ? 'beta' : 'alpha';
      version = version.replace(RegExp(data + '\\+?$'), '') +
        (prerelease == 'beta' ? beta : alpha) + (/\d+\+?/.exec(data) || '');
    }
    // Detect Firefox Mobile.
    if (name == 'Fennec' || name == 'Firefox' && /\b(?:Android|Firefox OS)\b/.test(os)) {
      name = 'Firefox Mobile';
    }
    // Obscure Maxthon's unreliable version.
    else if (name == 'Maxthon' && version) {
      version = version.replace(/\.[\d.]+/, '.x');
    }
    // Detect Xbox 360 and Xbox One.
    else if (/\bXbox\b/i.test(product)) {
      if (product == 'Xbox 360') {
        os = null;
      }
      if (product == 'Xbox 360' && /\bIEMobile\b/.test(ua)) {
        description.unshift('mobile mode');
      }
    }
    // Add mobile postfix.
    else if ((/^(?:Chrome|IE|Opera)$/.test(name) || name && !product && !/Browser|Mobi/.test(name)) &&
        (os == 'Windows CE' || /Mobi/i.test(ua))) {
      name += ' Mobile';
    }
    // Detect IE platform preview.
    else if (name == 'IE' && useFeatures) {
      try {
        if (context.external === null) {
          description.unshift('platform preview');
        }
      } catch(e) {
        description.unshift('embedded');
      }
    }
    // Detect BlackBerry OS version.
    // http://docs.blackberry.com/en/developers/deliverables/18169/HTTP_headers_sent_by_BB_Browser_1234911_11.jsp
    else if ((/\bBlackBerry\b/.test(product) || /\bBB10\b/.test(ua)) && (data =
          (RegExp(product.replace(/ +/g, ' *') + '/([.\\d]+)', 'i').exec(ua) || 0)[1] ||
          version
        )) {
      data = [data, /BB10/.test(ua)];
      os = (data[1] ? (product = null, manufacturer = 'BlackBerry') : 'Device Software') + ' ' + data[0];
      version = null;
    }
    // Detect Opera identifying/masking itself as another browser.
    // http://www.opera.com/support/kb/view/843/
    else if (this != forOwn && product != 'Wii' && (
          (useFeatures && opera) ||
          (/Opera/.test(name) && /\b(?:MSIE|Firefox)\b/i.test(ua)) ||
          (name == 'Firefox' && /\bOS X (?:\d+\.){2,}/.test(os)) ||
          (name == 'IE' && (
            (os && !/^Win/.test(os) && version > 5.5) ||
            /\bWindows XP\b/.test(os) && version > 8 ||
            version == 8 && !/\bTrident\b/.test(ua)
          ))
        ) && !reOpera.test((data = parse.call(forOwn, ua.replace(reOpera, '') + ';'))) && data.name) {
      // When "identifying", the UA contains both Opera and the other browser's name.
      data = 'ing as ' + data.name + ((data = data.version) ? ' ' + data : '');
      if (reOpera.test(name)) {
        if (/\bIE\b/.test(data) && os == 'Mac OS') {
          os = null;
        }
        data = 'identify' + data;
      }
      // When "masking", the UA contains only the other browser's name.
      else {
        data = 'mask' + data;
        if (operaClass) {
          name = format(operaClass.replace(/([a-z])([A-Z])/g, '$1 $2'));
        } else {
          name = 'Opera';
        }
        if (/\bIE\b/.test(data)) {
          os = null;
        }
        if (!useFeatures) {
          version = null;
        }
      }
      layout = ['Presto'];
      description.push(data);
    }
    // Detect WebKit Nightly and approximate Chrome/Safari versions.
    if ((data = (/\bAppleWebKit\/([\d.]+\+?)/i.exec(ua) || 0)[1])) {
      // Correct build number for numeric comparison.
      // (e.g. "532.5" becomes "532.05")
      data = [parseFloat(data.replace(/\.(\d)$/, '.0$1')), data];
      // Nightly builds are postfixed with a "+".
      if (name == 'Safari' && data[1].slice(-1) == '+') {
        name = 'WebKit Nightly';
        prerelease = 'alpha';
        version = data[1].slice(0, -1);
      }
      // Clear incorrect browser versions.
      else if (version == data[1] ||
          version == (data[2] = (/\bSafari\/([\d.]+\+?)/i.exec(ua) || 0)[1])) {
        version = null;
      }
      // Use the full Chrome version when available.
      data[1] = (/\bChrome\/([\d.]+)/i.exec(ua) || 0)[1];
      // Detect Blink layout engine.
      if (data[0] == 537.36 && data[2] == 537.36 && parseFloat(data[1]) >= 28 && layout == 'WebKit') {
        layout = ['Blink'];
      }
      // Detect JavaScriptCore.
      // http://stackoverflow.com/questions/6768474/how-can-i-detect-which-javascript-engine-v8-or-jsc-is-used-at-runtime-in-androi
      if (!useFeatures || (!likeChrome && !data[1])) {
        layout && (layout[1] = 'like Safari');
        data = (data = data[0], data < 400 ? 1 : data < 500 ? 2 : data < 526 ? 3 : data < 533 ? 4 : data < 534 ? '4+' : data < 535 ? 5 : data < 537 ? 6 : data < 538 ? 7 : data < 601 ? 8 : '8');
      } else {
        layout && (layout[1] = 'like Chrome');
        data = data[1] || (data = data[0], data < 530 ? 1 : data < 532 ? 2 : data < 532.05 ? 3 : data < 533 ? 4 : data < 534.03 ? 5 : data < 534.07 ? 6 : data < 534.10 ? 7 : data < 534.13 ? 8 : data < 534.16 ? 9 : data < 534.24 ? 10 : data < 534.30 ? 11 : data < 535.01 ? 12 : data < 535.02 ? '13+' : data < 535.07 ? 15 : data < 535.11 ? 16 : data < 535.19 ? 17 : data < 536.05 ? 18 : data < 536.10 ? 19 : data < 537.01 ? 20 : data < 537.11 ? '21+' : data < 537.13 ? 23 : data < 537.18 ? 24 : data < 537.24 ? 25 : data < 537.36 ? 26 : layout != 'Blink' ? '27' : '28');
      }
      // Add the postfix of ".x" or "+" for approximate versions.
      layout && (layout[1] += ' ' + (data += typeof data == 'number' ? '.x' : /[.+]/.test(data) ? '' : '+'));
      // Obscure version for some Safari 1-2 releases.
      if (name == 'Safari' && (!version || parseInt(version) > 45)) {
        version = data;
      }
    }
    // Detect Opera desktop modes.
    if (name == 'Opera' &&  (data = /\bzbov|zvav$/.exec(os))) {
      name += ' ';
      description.unshift('desktop mode');
      if (data == 'zvav') {
        name += 'Mini';
        version = null;
      } else {
        name += 'Mobile';
      }
      os = os.replace(RegExp(' *' + data + '$'), '');
    }
    // Detect Chrome desktop mode.
    else if (name == 'Safari' && /\bChrome\b/.exec(layout && layout[1])) {
      description.unshift('desktop mode');
      name = 'Chrome Mobile';
      version = null;

      if (/\bOS X\b/.test(os)) {
        manufacturer = 'Apple';
        os = 'iOS 4.3+';
      } else {
        os = null;
      }
    }
    // Strip incorrect OS versions.
    if (version && version.indexOf((data = /[\d.]+$/.exec(os))) == 0 &&
        ua.indexOf('/' + data + '-') > -1) {
      os = trim(os.replace(data, ''));
    }
    // Add layout engine.
    if (layout && !/\b(?:Avant|Nook)\b/.test(name) && (
        /Browser|Lunascape|Maxthon/.test(name) ||
        name != 'Safari' && /^iOS/.test(os) && /\bSafari\b/.test(layout[1]) ||
        /^(?:Adobe|Arora|Breach|Midori|Opera|Phantom|Rekonq|Rock|Samsung Internet|Sleipnir|Web)/.test(name) && layout[1])) {
      // Don't add layout details to description if they are falsey.
      (data = layout[layout.length - 1]) && description.push(data);
    }
    // Combine contextual information.
    if (description.length) {
      description = ['(' + description.join('; ') + ')'];
    }
    // Append manufacturer to description.
    if (manufacturer && product && product.indexOf(manufacturer) < 0) {
      description.push('on ' + manufacturer);
    }
    // Append product to description.
    if (product) {
      description.push((/^on /.test(description[description.length - 1]) ? '' : 'on ') + product);
    }
    // Parse the OS into an object.
    if (os) {
      data = / ([\d.+]+)$/.exec(os);
      isSpecialCasedOS = data && os.charAt(os.length - data[0].length - 1) == '/';
      os = {
        'architecture': 32,
        'family': (data && !isSpecialCasedOS) ? os.replace(data[0], '') : os,
        'version': data ? data[1] : null,
        'toString': function() {
          var version = this.version;
          return this.family + ((version && !isSpecialCasedOS) ? ' ' + version : '') + (this.architecture == 64 ? ' 64-bit' : '');
        }
      };
    }
    // Add browser/OS architecture.
    if ((data = /\b(?:AMD|IA|Win|WOW|x86_|x)64\b/i.exec(arch)) && !/\bi686\b/i.test(arch)) {
      if (os) {
        os.architecture = 64;
        os.family = os.family.replace(RegExp(' *' + data), '');
      }
      if (
          name && (/\bWOW64\b/i.test(ua) ||
          (useFeatures && /\w(?:86|32)$/.test(nav.cpuClass || nav.platform) && !/\bWin64; x64\b/i.test(ua)))
      ) {
        description.unshift('32-bit');
      }
    }
    // Chrome 39 and above on OS X is always 64-bit.
    else if (
        os && /^OS X/.test(os.family) &&
        name == 'Chrome' && parseFloat(version) >= 39
    ) {
      os.architecture = 64;
    }

    ua || (ua = null);

    /*------------------------------------------------------------------------*/

    /**
     * The platform object.
     *
     * @name platform
     * @type Object
     */
    var platform = {};

    /**
     * The platform description.
     *
     * @memberOf platform
     * @type string|null
     */
    platform.description = ua;

    /**
     * The name of the browser's layout engine.
     *
     * The list of common layout engines include:
     * "Blink", "EdgeHTML", "Gecko", "Trident" and "WebKit"
     *
     * @memberOf platform
     * @type string|null
     */
    platform.engine = layout && layout[0];

    /**
     * The name of the product's manufacturer.
     *
     * The list of manufacturers include:
     * "Apple", "Archos", "Amazon", "Asus", "Barnes & Noble", "BlackBerry",
     * "Google", "HP", "HTC", "LG", "Microsoft", "Motorola", "Nintendo",
     * "Nokia", "Samsung" and "Sony"
     *
     * @memberOf platform
     * @type string|null
     */
    platform.manufacturer = manufacturer;

    /**
     * The name of the browser/environment.
     *
     * The list of common browser names include:
     * "Chrome", "Electron", "Firefox", "Firefox for iOS", "IE",
     * "Microsoft Edge", "PhantomJS", "Safari", "SeaMonkey", "Silk",
     * "Opera Mini" and "Opera"
     *
     * Mobile versions of some browsers have "Mobile" appended to their name:
     * eg. "Chrome Mobile", "Firefox Mobile", "IE Mobile" and "Opera Mobile"
     *
     * @memberOf platform
     * @type string|null
     */
    platform.name = name;

    /**
     * The alpha/beta release indicator.
     *
     * @memberOf platform
     * @type string|null
     */
    platform.prerelease = prerelease;

    /**
     * The name of the product hosting the browser.
     *
     * The list of common products include:
     *
     * "BlackBerry", "Galaxy S4", "Lumia", "iPad", "iPod", "iPhone", "Kindle",
     * "Kindle Fire", "Nexus", "Nook", "PlayBook", "TouchPad" and "Transformer"
     *
     * @memberOf platform
     * @type string|null
     */
    platform.product = product;

    /**
     * The browser's user agent string.
     *
     * @memberOf platform
     * @type string|null
     */
    platform.ua = ua;

    /**
     * The browser/environment version.
     *
     * @memberOf platform
     * @type string|null
     */
    platform.version = name && version;

    /**
     * The name of the operating system.
     *
     * @memberOf platform
     * @type Object
     */
    platform.os = os || {

      /**
       * The CPU architecture the OS is built for.
       *
       * @memberOf platform.os
       * @type number|null
       */
      'architecture': null,

      /**
       * The family of the OS.
       *
       * Common values include:
       * "Windows", "Windows Server 2008 R2 / 7", "Windows Server 2008 / Vista",
       * "Windows XP", "OS X", "Ubuntu", "Debian", "Fedora", "Red Hat", "SuSE",
       * "Android", "iOS" and "Windows Phone"
       *
       * @memberOf platform.os
       * @type string|null
       */
      'family': null,

      /**
       * The version of the OS.
       *
       * @memberOf platform.os
       * @type string|null
       */
      'version': null,

      /**
       * Returns the OS string.
       *
       * @memberOf platform.os
       * @returns {string} The OS string.
       */
      'toString': function() { return 'null'; }
    };

    platform.parse = parse;
    platform.toString = toStringPlatform;

    if (platform.version) {
      description.unshift(version);
    }
    if (platform.name) {
      description.unshift(name);
    }
    if (os && name && !(os == String(os).split(' ')[0] && (os == name.split(' ')[0] || product))) {
      description.push(product ? '(' + os + ')' : 'on ' + os);
    }
    if (description.length) {
      platform.description = description.join(' ');
    }
    
    // Are pop-up windows allowed for this site? (i. e. has the user a pop-up blocker?)
    function popupsAllowed() {
        var allowed = false;
        if (!window.open) return;
        var w = window.open("about:blank","","directories=no,height=1,width=1,menubar=no,resizable=no,scrollbars=no,status=no,titlebar=no,left=0,top=0,location=no");
        if (w) {
            allowed = true;
            w.close();
        }
        return allowed;
    }
    function isTouch() {
      try{ document.createEvent("TouchEvent"); return true; }
      catch(e){ return ("ontouchstart" in window)?true:false; }
    }
    function isMobile() {
      try {
        if (typeof navigator == 'undefined') return false;
        if (typeof sessionStorage != 'undefined' && sessionStorage.desktop) // desktop storage 
            return false;
        else if (typeof localStorage != 'undefined' &&  localStorage.mobile) // mobile storage
            return true;
        var mobile = ['iphone','ipad','android','blackberry','nokia','opera mini','windows mobile','windows phone','iemobile']; 
        for (var i in mobile) if (navigator.userAgent.toLowerCase().indexOf(mobile[i].toLowerCase()) > 0) return true;
      } catch (e) {}
      return false;
    }
    global.jsVersion=1.0;
    // Helper function to detect Javascript version
    function _detectJsVersion() {
        if (!document.write) return;

        document.write('<script language="JavaScript1.0">');
        document.write('jsVersion=1.0;');
        document.write('<\/script>');

        document.write('<script language="JavaScript1.1">');
        document.write('jsVersion=1.1;');
        document.write('<\/script>');

        document.write('<script language="JavaScript1.2">');
        document.write('jsVersion=1.2;');
        document.write('<\/script>');

        document.write('<script language="JavaScript1.3">');
        document.write('jsVersion=1.3;');
        document.write('<\/script>');

        document.write('<script language="JavaScript1.4">');
        document.write('jsVersion=1.4;');
        document.write('<\/script>');

        document.write('<script language="JavaScript1.5">');
        document.write('jsVersion=1.5;');
        document.write('<\/script>');

        document.write('<script language="JavaScript1.6">');
        document.write('jsVersion=1.6;');
        document.write('<\/script>');

        document.write('<script language="JavaScript1.7">');
        document.write('jsVersion=1.7;');
        document.write('<\/script>');

        document.write('<script language="JavaScript1.8">');
        document.write('jsVersion=1.8;');
        document.write('<\/script>');

        document.write('<script language="JavaScript2.0">');
        document.write('jsVersion=2.0;');
        document.write('<\/script>');

    }

    // What is the newest version of Javascript does the browser report as supported?
    function detectJsVersion() {
       _detectJsVersion(); 
    }
    detectJsVersion();
    platform.jsVersion=function () { return jsVersion };
    // platform.popups=popupsAllowed();
    
    if (typeof navigator != 'undefined')
      platform.hasWebRTC = (navigator.getUserMedia ||
                            navigator.webkitGetUserMedia ||
                            navigator.mozGetUserMedia ||
                            navigator.msGetUserMedia)?1:undefined;
    platform.touch = isTouch();
    platform.mobile = isMobile();

    platform.geoloc = (typeof navigator != 'undefined' && 
                       navigator.geolocation && 
                       typeof navigator.geolocation.getCurrentPosition == 'function')?true:false;

    return platform;
  }

  /*--------------------------------------------------------------------------*/

  // Export platform.
  var platform = parse();
} else {
  var platform = {}
}

module.exports=platform;
};
BundleModuleCode['jam/analyzer']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2006-2022 bLAB
 **    $CREATED:     4-5-16 by sbosse.
 **    $RCS:         $Id: analyzer.js,v 1.5 2020/02/03 09:45:01 sbosse Exp sbosse $
 **    $VERSION:     1.7.1
 **
 **    $INFO:
 **
 **  JAM AgentJS Analyzer. A branch tree monster! 
 **  Uses esprima parser AST structure.
 **
 ** TODO: Many checks are missing or are incomplete!
 **
 **    $ENDOFINFO
 */
var Io = Require('com/io');
var Comp = Require('com/compat');
var Aios = none;
var current = none;
var util = Require('util');

var options = {
  version:'1.7.1',
}
var out = function (msg) { Io.out('[AJS] '+msg)};

/** All functions with 'this' property pass the agent 'this' object to their bodies or callback functions!
 *  The 'this' property array contains the argument index indicating functions or arrays of functions inheriting the
 *  agent 'this' object.
 */
var corefuncs = {
  act:    {obj:{add:{argn:2},delete:{argn:1},update:{argn:2}}},  // TODO obj handling!
  add:    {argn:2},
  angle:  {argn:[1,2]},
  alt :   {argn:[2,3], obj:{try:{argn:[2,3,4]}}},
  collect:    {argn:2},
  concat: {argn:2},
  connectTo: {argn:1},
  copyto:    {argn:2},
  delta:     {argn:2},
  distance:  {argn:[1,2]},
  empty:  {argn:1},
  equal:  {argn:2},
  Export:  {argn:2},
  exists: {argn:1},
  filter: {argn:2, this:[1]},
  head:   {argn:1},
  iter:   {argn:2, this:[1]},
  Import:  {argn:1},
  inp:    {argn:[2,3], this:[1], obj:{try:{argn:[2,3,4]}}},
  kill:   {argn:[0,1]},
  last:   {argn:1},
  length: {argn:1},
  log:    {argn:[1,2,3,4,5,6,7,8,9,10]},
  mark:   {argn:2},
  map:    {argn:2, this:[1]},
  matrix: {argn:[2,3]},
  max:    {argn:[1,2]},
  me:     {argn:0},
  min:    {argn:[1,2]},
  myClass: {argn:0},
  myNode: {argn:0},
  myParent: {argn:0},
  myPosition: {argn:0},
  Number: {argn:1},
  object:    {argn:1},
  out:    {argn:1},
  opposite:    {argn:1},
  privilege: {argn:0},
  random: {argn:[1,2,3]},
  reduce:  {argn:2, this:[1]},
  reverse: {argn:1,this:[1]},
  rd:     {argn:[2,3], this:[1], obj:{try:{argn:[2,3,4]}}},
  rm:     {argn:[1,2]},
  send:   {argn:[2,3]},
  sendto:   {argn:[2,3]},
  sort:   {argn:2, this:[1]},
  sleep:  {argn:[0,1]},
  store:    {argn:2},
  string: {argn:1},
  sum:    {argn:[1,2]},
  tail:   {argn:1},
  time:   {argn:0}, 
  timer:    {obj:{add:{argn:[2,3]},delete:{argn:1},update:{argn:2}}},  // TODO obj handling!
  try_alt:     {argn:[2,3,4], this:[2]},
  try_inp:     {argn:[2,3,4], this:[2]},
  try_rd:     {argn:[2,3,4], this:[2]},
  ts:     {argn:2},
  zero:   {argn:1},
  B:      {argn:[1,2], this:[0,1]},
  I:      {argn:[3,4], this:[1,2,3]},
  L:      {argn:[4,5], this:[1,2,3,4]},
  Vector: {argn:[1,2,3]}
};

function check_args (arguments,corefun) {
  var len=arguments?arguments.length:0,passed=false;
  if (Comp.obj.isArray(corefun.argn)) Comp.array.iter(corefun.argn,function (n) {
    if (n==len) passed=true;
  });
  else passed=(len==corefun.argn);
  return passed;
}


var jamc = function (options) {
  // Dummy constructor
};

var properties = {
  indexOf:'string.indexOf',
  push:'array.push',
  shift:'array.shift'
}

var literals={
  none:'Literal',
  undefined:'Literal'
}
literals['_']='Literal';

var syntax = {
  find: function (root,typ,name) {
    if (root.type==typ && root.id && root.id.type == 'Identifier' && root.id.name==name) return root;
    switch (root.type) {
        case 'Program': 
          return Comp.array.findmap(root.body,function (el) { return syntax.find(el,typ,name)}); 
          break;
        case 'VariableDeclaration':
          return Comp.array.findmap(root.declarations,function (el) { return syntax.find(el,typ,name)});         
          break;
    }
    return null;
  },
  location: function (elem,short) {
    var str='';
    if (elem.loc) {
      if (elem.loc.start) str='line '+(elem.loc.start.line+syntax.offset)+', position '+elem.loc.start.column;
      if (elem.loc.end) str +=' to line '+(elem.loc.end.line+syntax.offset)+', position '+elem.loc.end.column;
      return str;
    } else return "unknown location";
  },
  name: function (elem) {
    switch (elem.type) {
      case 'ThisExpression': return 'this';
      case 'Identifier': return elem.name;
      case 'MemberExpression':
        return syntax.name(elem.object)+'.'+syntax.name(elem.property);
      default: return elem.toString();
    }
  },
  offset:0
}

jamc.prototype.syntax = syntax;


/** The Big Machine: Analyze and check an agent class constructor function.
 * 
 * Checks performed:
 *  - references (top-level this, local this, local variables, no free variable access)
 *  - AIOS function calls, privilege level sensitive
 *  - basic structure (act, trans, on? , next)
 *
 */
 
/*
 * options and evaluation order:
 * 
 *  {left:true,this:true, syms: *[]}
 *  {right:true, this:true, target:elem, syms: *[]}
 *  {funbody:true, this:true, syms: *[]}
 *  {funbody:true, syms: *[]}
 *  {this:true}
 *  {reference:true, syms: *[]}
 *  {funcall:true, external:true, arguments:elem [], syms: *[]}
 *  {trans:true}
 *
 *
 *
 * type Esprima.syntax=object|*;
 * type jamc.prototype.analyze = function(syntax:Esprima.syntax,options:{classname:string,level:number}) ->
 * {
 *   activities:object,
 *   transitions:object,
 *   subclasses:object,
 *   symbols:object,
 *   errors: string []
 * }
 */
 
jamc.prototype.analyze = function (syntax,_options) {
  var self=this,
      classname=_options.classname,
      level=_options.level,
      ep,elem,cp,declarator,ac,val,
      // Pre-defined top-level this.XX symbols
      syms={id:{type:'Literal'},ac:{type:'Literal'}},
      nextVal,
      transObj,
      actObj,
      subclassObj,
      transitions={},
      activities={},
      subclasses,
      aios,
      options={},
      errors=[],
      verbose=_options.verbose||0,
      err=function (msg) { errors.push(msg); (_options.err||this.err||Io.err)(msg)},
      out=_options.out||this.out||Io.out,
      warn=_options.warn||this.warn||Io.warn;
      
  switch (level) {
    case 0: aios=Aios.aios0; break;
    case 1: aios=Aios.aios1; break;
    case 2: aios=Aios.aios2; break;
    case 3: aios=Aios.aios3; break;
  }
  
  function unwrap(elem) {
    switch (elem.type) {
      case 'BlockStatement':
        if (elem.body.length==1) return elem.body[0];
        else return elem;
        break;
      default:
        return elem;
    }
  }
  
  function isThisExpr(elem) {
    switch (elem.type) {
      case 'MemberExpression':
        return isThisExpr(elem.object);
      case 'ThisExpression':
        return true;
    }
    return false;
  }
  
  function isEmpty(o) {
    if (!o) return true;
    for (var p in o) { if (o[p]!=undefined) return false;};
    return true;
  }
  
  /**************************
  ** Iterate Member Expression
  **************************/
  
  function iterMemberExpression (elem,options) {
    var part,corefun,obj;

    switch (elem.type) {
      case 'Identifier':
        if (!aios[elem.name] && !corefuncs[elem.name])
          err('['+classname+'] Call of undefined function: '+
              elem.name+', in level '+level+' ,at '+self.syntax.location(elem));            
        else if (corefuncs[elem.name]) {
          corefun=corefuncs[elem.name];
          if (!check_args(options.arguments,corefun)) {
            err('['+classname+']: Call of AIOS function '+elem.name+' with invalid number of arguments, '+
                '(expecting '+(corefun.argn.toString())+' argument(s), got '+options.arguments.length+
                '), in level '+level+
                ' ,at '+self.syntax.location(elem));
          }         
          return aios[elem.name];
        } else return aios[elem.name];    
        break;

      case 'MemberExpression':
        switch (elem.object.type) {
          case 'ThisExpression':
            if (!syms[elem.property.name] || syms[elem.property.name].context!='ThisExpression') 
                err("['+classname+'] Undefined 'this' reference: "+
                    elem.property.name+', at '+self.syntax.location(elem));
            if(syms[elem.property.name].type=='ObjectExpression') {
              var Osyms={};
              Comp.array.iter(syms[elem.property.name].properties,function (p) {
                Osyms[p.key.name]=p.type;
              });
              if (!isEmpty(Osyms))               
                return Osyms;
              else
                return none;
            } else return none;
            break;

          case 'Identifier':
            if (!aios[elem.object.name] && 
                !corefuncs[elem.object.name] &&
                !options.syms[elem.object.name]) {
              // console.log(elem);
              err('['+classname+'] Access of undefined object variable: '+
                  elem.object.name+', in level '+level+' ,at '+self.syntax.location(elem));
            } 

            if (properties[elem.property.name]) return undefined;
            if (elem.computed) return undefined; // TODO, check property!
            
            if (corefuncs[elem.object.name]) {
              obj=corefuncs[elem.object.name].obj||corefuncs[elem.object.name];
              if (!obj[elem.property.name]) {
                // console.log(corefuncs[elem.object.name])
                err('['+classname+'] Access of unknown AIOS(corefuncs) object attribute: '+
                    elem.object.name+'.'+elem.property.name+', in level '+level+' ,at '+self.syntax.location(elem));
              }
              return obj[elem.property.name];
            } else if (aios[elem.object.name]) {
              // console.log(elem);
              obj=aios[elem.object.name].obj||aios[elem.object.name];
              if (!obj[elem.property.name])
                err('['+classname+'] Access of unknown AIOS object attribute: '+
                    elem.object.name+'.'+elem.property.name+', in level '+level+' ,at '+self.syntax.location(elem));
              return obj[elem.property.name];
            } 
            else if (options.syms[elem.object.name]) {
              // console.log(elem);
              // User defined object, can't be resolved further
              return none;
            }                                   
            return;
            break;

          case 'MemberExpression': 
            part=iterMemberExpression(elem.object,options);
            if (part && part.obj) part=part.obj;
            if (!elem.computed && part && !part[elem.property.name] && !properties[elem.property.name]) {
              err('['+classname+'] Access of unknown object attribute: '+
                  self.syntax.name(elem)+' ('+elem.property.name+'), in level '+
                  level+' ,at '+self.syntax.location(elem));
            }
            if (elem.computed) check(elem.property,{reference:true,syms:options.syms});
            if (part && (typeof part[elem.property.name] == 'object') && !isEmpty(part[elem.property.name])) 
              return part[elem.property.name];
            else 
              return none;
            break;
        }
        break;
    } 
    return;
  }
  
  
  /**********************************
  ** Check for a declaration and add it to the symbol table
  **********************************/
  function addDeclaration(elem,options) {
    var ep,el;
    switch (elem.type) {
      case 'VariableDeclaration':
        for (ep in elem.declarations) {
          el=elem.declarations[ep];           
          if (!options.shadow[el.id.name]) options.shadow[el.id.name]=options.syms[el.id.name];
          if (el.type=='VariableDeclarator') {
            if (el.id.type=='Identifier') {
              options.syms[el.id.name]=el;
            }
          }
        }
        break;
      case 'FunctionDeclaration':
        if (!options.shadow[elem.id.name]) options.shadow[elem.id.name]=options.syms[elem.id.name];
        options.syms[elem.id.name]=elem;
        break;

      case 'ForStatement':
        addDeclaration(elem.init,options);
        break;

    }
  }
  
  /*********************************
  ** Main checker function
  *********************************/
  
  function check(elem,options) {
    var ep,el,name,thismaybe,shadow,locshadow;
/*    
console.log(elem);
console.log(options);    
*/
    /*
    ** Top-level statements 
    */
    if (options.left && options.this) {
      // LHS check of agent class top-level statements
      switch (elem.type) {
      
        case 'Identifier':
          err('['+classname+'] Assignment may not contain free variables: var '+
              elem.name+', at '+self.syntax.location(elem));
          break;
          
        case 'MemberExpression':
          if (elem.object.type != 'ThisExpression')
            err('['+classname+'] Assignment may not contain non-this MemberExpression on left side: '+
                self.syntax.name(elem.object)+', at '+self.syntax.location(elem));
          switch (elem.property.type) {
          
            case 'Identifier':
              if (syms[elem.property.name])
                err('['+classname+'] Found duplicate property definition: '+
                    elem.property.name+' ('+syms[elem.property.name].type+'), at '+self.syntax.location(elem));
              else {
                syms[elem.property.name]=options.target;
                syms[elem.property.name].context=elem.object.type;
              }
              switch (elem.property.name) {
                case 'act':     actObj = options.target; break;
                case 'trans':   transObj = options.target; break;
                case 'subclass':   subclassObj = options.target; break;
              }
              break;
          }
          break;
      }
    }
    else if (options.right && options.this) {
      // RHS check of agent class top-level statements
      switch (elem.type) {
        case 'Literal':
        case 'Identifier':
          switch (options.target.property.name) {
            case 'next':
              val = elem.value||elem.name;
              if (!Comp.obj.isString(val)) 
                  err('['+classname+'] Invalid next property, expected string, got '+
                      val+', at '+self.syntax.location(elem));
              nextVal = val;
              break;
          }
          break;
          
        case 'ObjectExpression':
          switch (options.target.property.name) {        
            case 'trans':
              for (ep in elem.properties) {
                el=elem.properties[ep];
                //console.log(el)
                if (el.type=='Property') {
                  transitions[el.key.name]=el.value;
                }
              }
              break;

            case 'act':
              for (ep in elem.properties) {
                el=elem.properties[ep];
                // console.log(el)
                if (el.type=='Property') {
                  if (aios[el.key.name])
                    err('['+classname+'] Activity name '+el.key.name+
                        ' shadows AIOS function or object, at '+self.syntax.location(elem));
                  
                  
                  activities[el.key.name]=el.value;
                }
              }
              break;

            case 'subclass':
              subclasses={};
              for (ep in elem.properties) {
                el=elem.properties[ep];
                // console.log(el)
                if (el.type=='Property') {
                  subclasses[el.key.name]=el.value;
                }
              }
              break;
          }
          break; 
          
        case 'FunctionExpression':
          // Check and add function parameters
          locshadow={};
          for (ep in elem.params) {
            param=elem.params[ep];
            if (param.type!='Identifier')
              err('['+classname+'] Invalid function parameter type'+param.type+', expected Identifier'+
                  ', at '+self.syntax.location(elem));
            locshadow[param.name]=options.syms[param.name];
            options.syms[param.name]=param.type;
          }
          check(elem.body,{funbody:true,this:true,syms:options.syms});
          // Restore symbol table
          for (ep in locshadow) {
            options.syms[ep]=locshadow[ep];
          }
          break;     
      }
    }
    
    /*
    ** Function body statements that can access the agent object by 'this' 
    */
    else if (options.funbody && options.this) {
      // Activity or transition top- or second level function bodies - 'this' references always the agent object!
      elem=unwrap(elem);
      
      switch (elem.type) {
        case 'BlockStatement':
          // Local symbols 
          if (options.shadow) shadow=options.shadow;
          options.shadow={};
          // First get all function and variable definitions in current scope
          if (!options.syms) options.syms={};
          Comp.array.iter(elem.body,function (el) {
            addDeclaration(el,options);
          });
          // Now check the body statements
          Comp.array.iter(elem.body,function (el) {check(el,options)});
          if (options.syms) for (ep in options.shadow) {
            options.syms[ep]=options.shadow[ep];
          }
          options.shadow=shadow;    
          break;
          
        case 'ExpressionStatement':
          switch (elem.expression.type) {
          
            case 'AssignmentExpression':
              switch (elem.expression.left.type) {
                case 'MemberExpression':  
                  if (isThisExpr(elem.expression.left.object))
                    check(elem.expression.left,{this:true});
                  break;
                case 'Identifier':
                  check(elem.expression.left,{reference:true,syms:options.syms});
                  break;
              }
              check(elem.expression.right,{reference:true,syms:options.syms});              
              break;
              
            case 'CallExpression':
              thismaybe=[]; // for 'this' propagation to arguments
              if (elem.expression.callee.object && isThisExpr(elem.expression.callee.object)) {
                check(elem.expression.callee,{this:true,funcall:true,arguments:elem.expression.arguments});
              } else {
                if (corefuncs[elem.expression.callee.name] && corefuncs[elem.expression.callee.name].this)
                {
                    thismaybe=corefuncs[elem.expression.callee.name].this;           
                }
                if (options.syms[elem.expression.callee.name]) {
                  if (options.syms[elem.expression.callee.name].type != 'FunctionDeclaration')
                    err('['+classname+'] Not a function:'+elem.expression.callee.name+
                        ', at '+self.syntax.location(elem));
// TODO                  
                } else
                  /* AIOS function call */
                  check(elem.expression.callee,{funcall:true,external:true,syms:options.syms,
                                                arguments:elem.expression.arguments});
              }
              // Check arguments
              Comp.array.iter(elem.expression.arguments,function (el,i) {
                var ep,param,shadow;
                if (!Comp.array.member(thismaybe,i)) {
                  check(el,{reference:true,syms:options.syms});                
                } else {
                  // It's a AIOS function call with a function argument. 
                  // Check function body with 'this' referencing the agent object.
                  
                  switch (el.type) {
                    case 'ArrayExpression':
                      // Block of functions ...
                      Comp.array.iter(el.elements,function (el_block,block_i) {
                        if (el_block.type != 'FunctionExpression')
                          err('['+classname+'] Invalid argument '+(i+1)+' of AIOS core function '+
                              elem.expression.callee.name+': Expeceted FunctionExpression array, but got '+
                              el_block.type+ ' element (array index '+(block_i+1)+')'+
                              ', at '+self.syntax.location(elem));
                        check(el_block.body,{funbody:true,this:true,syms:options.syms});                          
                      });
                      break;
                      
                    case 'FunctionExpression':
                      // Check and add function parameters
                      shadow={};
                      for (ep in el.params) {
                        param=el.params[ep];
                        if (param.type!='Identifier')
                          err('['+classname+'] Invalid function parameter type'+param.type+', expected Identifier'+
                              ', at '+self.syntax.location(elem));
                        if (options.syms[param.name]) shadow[param.name]=options.syms[param.name];
                        options.syms[param.name]=param.type;
                      }
                      check(el.body,{funbody:true,this:true,syms:options.syms});
                      // Restore symbol table
                      for (ep in shadow) {
                        options.syms[ep]=shadow[ep];
                      }
                      break;

                    case 'ArrowFunctionExpression':
                      // Check and add function parameters
                      shadow={};
                      for (ep in el.params) {
                        param=el.params[ep];
                        if (param.type!='Identifier')
                          err('['+classname+'] Invalid function parameter type'+param.type+', expected Identifier'+
                              ', at '+self.syntax.location(elem));
                        if (options.syms[param.name]) shadow[param.name]=options.syms[param.name];
                        options.syms[param.name]=param.type;
                      }
                      check(el.body,{funbody:true,this:true,syms:options.syms});
                      // Restore symbol table
                      for (ep in shadow) {
                        options.syms[ep]=shadow[ep];
                      }
                      break;
                      
                    case 'CallExpression':
                      // TODO, check arguments ..
                      break;
                      
                    case 'Identifier':
                      // Nothing to do?
                      break;
                      
                    default:
                      err('['+classname+'] Invalid argument '+(i+1)+' of AIOS core function '+
                          elem.expression.callee.name+': Expeceted FunctionExpression, ArrowFunctionExpression, ArrayExpression, or Identifier, but got '+
                          el.type+
                          ', at '+self.syntax.location(elem));
                  }
                }  
              });
              break;
              
            case 'UpdateExpression':
              check(elem.expression.argument,{reference:true,syms:options.syms});
              break;
          }
          break;
          
        case 'VariableDeclaration':
          // console.log(elem.declarations);
          if (!options.shadow) options.shadow={};
          for (ep in elem.declarations) {
            el=elem.declarations[ep];           
            if (!options.shadow[el.id.name]) options.shadow[el.id.name]=options.syms[el.id.name];
            if (el.type=='VariableDeclarator') {
              if (el.id.type=='Identifier') {
                options.syms[el.id.name]=el;
              }
            }
          }
          break;
          
        case 'IfStatement':
          check(elem.consequent,options);
          if (elem.alternate) check(elem.alternate,options);
          check(elem.test,{reference:true,syms:options.syms});
          break;
          
        case 'ForStatement':
          //console.log(elem)
          check(elem.body,options);
          check(elem.init,{reference:true,syms:options.syms});
          check(elem.test,{reference:true,syms:options.syms});
          check(elem.update,{reference:true,syms:options.syms});
          break;
          
        case 'WhileStatement':
          //console.log(elem)
          check(elem.body,options);
          check(elem.test,{reference:true,syms:options.syms});
          break;

        case 'ReturnStatement':
          if (elem.argument)
            check(elem.argument,{reference:true,syms:options.syms});
          break;
          
        case 'FunctionDeclaration':
          if (!options.shadow[elem.id.name]) options.shadow[elem.id.name]=options.syms[elem.id.name];
          options.syms[elem.id.name]=elem;
          /* agent object not accessible in function body! */
          // Check and add function parameters
          locshadow={};
          for (ep in elem.params) {
            param=elem.params[ep];
            if (param.type!='Identifier')
              err('['+classname+'] Invalid function parameter type'+param.type+', expected Identifier'+
                  ', at '+self.syntax.location(elem));
            locshadow[param.name]=options.syms[param.name];
            options.syms[param.name]=param.type;
          }
          check(elem.body,{funbody:true,syms:options.syms});
          // Restore symbol table
          for (ep in locshadow) {
            options.syms[ep]=locshadow[ep];
          }
          
          break;
      }
    }
    /*
    ** Funcion body that cannot access the agent object (local functions) 
    */
    else if (options.funbody) {
// TODO    
      elem=unwrap(elem);
      
      switch (elem.type) {
        case 'BlockStatement':
          // Local symbols 
          if (options.shadow) shadow=options.shadow;
          options.shadow={};
          // First get all function and variable definitions in current scope
          if (!options.syms) options.syms={};
          Comp.array.iter(elem.body,function (el) {
            addDeclaration(el,options);
          });
          Comp.array.iter(elem.body,function (el) {check(el,options)});
          if (options.syms) for (ep in options.shadow) {
            options.syms[ep]=options.shadow[ep];
          }
          options.shadow=shadow;    
          break;

        case 'ExpressionStatement':
          switch (elem.expression.type) {
          
            case 'AssignmentExpression':
              switch (elem.expression.left.type) {
                case 'MemberExpression':  
                  if (elem.expression.left.object && isThisExpr(elem.expression.left.object))
                    check(elem.expression.left,{syms:options.syms});
                  break;
                case 'Identifier':
                  check(elem.expression.left,{reference:true,syms:options.syms});
                  break;
              }
              check(elem.expression.right,{reference:true,syms:options.syms});              
              break;
              
            case 'CallExpression':
              thismaybe=[]; // for 'this' propagation to arguments
              if (elem.expression.callee.object && isThisExpr(elem.expression.callee.object)) {
                check(elem.expression.callee,{this:true,funcall:true,arguments:elem.expression.arguments});
              } else {
                if (corefuncs[elem.expression.callee.name] && corefuncs[elem.expression.callee.name].this)
                {
                    thismaybe=corefuncs[elem.expression.callee.name].this;           
                }
                if (options.syms[elem.expression.callee.name]) {
                  if (options.syms[elem.expression.callee.name].type != 'FunctionDeclaration')
                    err('['+classname+'] Not a function:'+elem.expression.callee.name+
                        ', at '+self.syntax.location(elem));
// TODO                  
                } else
                  /* AIOS function call */
                  check(elem.expression.callee,{funcall:true,external:true,syms:options.syms,
                                                arguments:elem.expression.arguments});
              }
              // Check arguments
              Comp.array.iter(elem.expression.arguments,function (el,i) {
                var ep,param,shadow;
                if (!Comp.array.member(thismaybe,i)) {
                  check(el,{reference:true,syms:options.syms});                
                } else {
                  // It's a AIOS function call with a function argument. 
                  // Check function body with 'this' referencing the agent object.
                  
                  switch (el.type) {
                    case 'ArrayExpression':
                      // Block of functions ...
                      Comp.array.iter(el.elements,function (el_block,block_i) {
                        if (el_block.type != 'FunctionExpression')
                          err('['+classname+'] Invalid argument '+(i+1)+' of AIOS core function '+
                              elem.expression.callee.name+': Expeceted FunctionExpression array, but got '+
                              el_block.type+ ' element (array index '+(block_i+1)+')'+
                              ', at '+self.syntax.location(elem));
                        check(el_block.body,{funbody:true,this:true,syms:options.syms});                          
                      });
                      break;
                      
                    case 'FunctionExpression':
                      // Check and add function parameters
                      shadow={};
                      for (ep in el.params) {
                        param=el.params[ep];
                        if (param.type!='Identifier')
                          err('['+classname+'] Invalid function parameter type'+param.type+', expected Identifier'+
                              ', at '+self.syntax.location(elem));
                        if (options.syms[param.name]) shadow[param.name]=options.syms[param.name];
                        options.syms[param.name]=param.type;
                      }
                      check(el.body,{funbody:true,this:true,syms:options.syms});
                      // Restore symbol table
                      for (ep in shadow) {
                        options.syms[ep]=shadow[ep];
                      }
                      break;

                    case 'ArrowFunctionExpression':
                      // Check and add function parameters
                      shadow={};
                      for (ep in el.params) {
                        param=el.params[ep];
                        if (param.type!='Identifier')
                          err('['+classname+'] Invalid function parameter type'+param.type+', expected Identifier'+
                              ', at '+self.syntax.location(elem));
                        if (options.syms[param.name]) shadow[param.name]=options.syms[param.name];
                        options.syms[param.name]=param.type;
                      }
                      check(el.body,{funbody:true,this:true,syms:options.syms});
                      // Restore symbol table
                      for (ep in shadow) {
                        options.syms[ep]=shadow[ep];
                      }
                      break;

                    case 'CallExpression':
                      // TODO, check arguments ..
                      break;
                      
                    case 'Identifier':
                      // Nothing to do?
                      break;
                      
                     default:
                      err('['+classname+'] Invalid argument '+(i+1)+' of AIOS core function '+
                          elem.expression.callee.name+': Expeceted FunctionExpression, ArrowFunctionExpression, ArrayExpression, or Identifier, but got '+
                          el.type+
                          ', at '+self.syntax.location(elem));
                  }
                }  
              });
              break;
              
            case 'UpdateExpression':
              check(elem.expression.argument,{reference:true,syms:options.syms});
              break;
          }
          break;
          
        case 'VariableDeclaration':
          for (ep in elem.declarations) {
            el=elem.declarations[ep];
            if (!options.shadow[el.id.name]) options.shadow[el.id.name]=options.syms[el.id.name];
            if (el.type=='VariableDeclarator') {
              if (el.id.type=='Identifier') {
                options.syms[el.id.name]=el;
              }
            }
          }
          break;
          
        case 'IfStatement':
          check(elem.consequent,options);
          if (elem.alternate) check(elem.alternate,options);
          check(elem.test,{reference:true,syms:options.syms});
          break;
          
        case 'ForStatement':
          //console.log(elem)
          check(elem.body,options);
          check(elem.init,{reference:true,syms:options.syms});
          check(elem.test,{reference:true,syms:options.syms});
          check(elem.update,{reference:true,syms:options.syms});
          break;
          
        case 'WhileStatement':
          //console.log(elem)
          check(elem.body,options);
          check(elem.test,{reference:true,syms:options.syms});
          break;

        case 'ReturnStatement':
          if (elem.argument)
            check(elem.argument,{reference:true,syms:options.syms});
          break;

        case 'FunctionDeclaration':
          if (!options.shadow[elem.id.name]) options.shadow[elem.id.name]=options.syms[elem.id.name];
          options.syms[elem.id.name]=elem;
          /* agent object not accessible in function body! */
          // Check and add function parameters
          locshadow={};
          for (ep in elem.params) {
            param=elem.params[ep];
            if (param.type!='Identifier')
              err('['+classname+'] Invalid function parameter type '+param.type+', expected Identifier'+
                  ', at '+self.syntax.location(elem));
            locshadow[param.name]=options.syms[param.name];
            options.syms[param.name]=param.type;
          }
          check(elem.body,{funbody:true,syms:options.syms});
          // Restore symbol table
          for (ep in locshadow) {
            options.syms[ep]=locshadow[ep];
          }
          
          break;
      }      
    } 
    /*
    ** Check agent object 'this' reference
    */
    else if (options.this) {
      // Check symbol reference for ThisExpression only
      switch (elem.object.type) {
        case 'MemberExpression':
          check(elem.object,{this:true});
          break;
        case 'ThisExpression':
          if (!syms[elem.property.name]) 
            err('['+classname+"] Undefined 'this' reference: "+
                elem.property.name+', at '+self.syntax.location(elem));
          if(options.funcall && syms[elem.property.name].type != 'FunctionExpression')
            err('['+classname+"] Not a function: this."+
                elem.property.name+', at '+self.syntax.location(elem));
      }
    }
    /*
    ** Check generic references
    */
    else if (options.reference) {
      // Check symbol reference for local symbols only
      switch (elem.type) {
        case 'Identifier':
          if (!options.syms[elem.name] && !literals[elem.name] && !aios[elem.name] && !activities[elem.name]) 
            err('['+classname+'] Undefined variable reference: '+
                elem.name+', at '+self.syntax.location(elem));
          break;
          
        case 'BinaryExpression':
          check(elem.left,options);
          check(elem.right,options);
          break;

        case 'AssignmentExpression':
          switch (elem.left.type) {
            case 'MemberExpression':
              if (elem.left.object && isThisExpr(elem.left.object))
                check(elem.left,{this:true});
              break;
            case 'Identifier':
              check(elem.left,{reference:true,syms:options.syms});
              break;
          }
          check(elem.right,options);
          break;

        case 'UpdateExpression':
          check(elem.argument,options);
          break;
          
        case 'MemberExpression':
          switch (elem.object.type) {
            case 'ThisExpression':
              check(elem,{this:true,syms:options.syms});
              break;
            case 'Identifier':
              check(elem.object,{reference:true,syms:options.syms});
              if (elem.computed) switch (elem.property.type) {
                case 'Identifier':
                  check(elem.property,{reference:true,syms:options.syms});
                  break;            
              }
              break;
            case 'MemberExpression':
              iterMemberExpression(elem,options);

              //if (isThisExpr(elem.object))
              //    check(elem.object,{this:true,syms:options.syms});                
          }
          break;

        case 'ArrayExpression':
          Comp.array.iter(elem.elements, function (el2,i) {
            if (el2) check(el2,{reference:true,syms:options.syms});          
          });
          break;
          
        case 'CallExpression':         
          if (elem.callee.object && isThisExpr(elem.callee.object)) {
            check(elem.callee,{this:true,funobj:true,arguments:elem.arguments});
          } else {
            if (options.syms[elem.callee.name]) {
              if (options.syms[elem.callee.name].type != 'FunctionDeclaration')
                err('['+classname+'] Not a function:'+elem.callee.name+
                    ', at '+self.syntax.location(elem));
              /* Internal function call, nothing to do */
            } else 
              check(elem.callee,{funcall:true,external:true,syms:options.syms,
                                 arguments:elem.arguments});
          }
          Comp.array.iter(elem.arguments,function (el) {
            check(el,{reference:true,syms:options.syms,arguments:elem.arguments})
          });          
          break;
      }
    }
    /*
    ** AIOS function calls and objects
    */
    else if (options.funcall && options.external) {
      // Check external AIOS function references
      switch (elem.type) {
        case 'Identifier':
        case 'MemberExpression':
          iterMemberExpression(elem,options);
          break;
      }      
    } 
    /*
    ** Check transition function body statements
    */
    else if (options.trans) {
      switch (elem.type) {
        case 'BlockStatement': 
          Comp.array.iter(elem.body,function (el) {check(el,options)});
          break;     
               
        case 'IfStatement':
          check(elem.consequent,options);
          if (elem.alternate) check(elem.alternate,options);
          break;
          
        case 'ReturnStatement':
          options.ret++;
          if (elem.argument) 
            check(elem.argument,options);
          else
            if (verbose) warn('['+classname+'] Returns undefined in transition '+
                              options.trans+', at '+self.syntax.location(elem)+'.');
          break;
          
        case 'Literal':
          if (!activities[elem.value])
            err('['+classname+'] Returns unknown activity reference '+
                elem.value+' in transition '+options.trans+', at '+self.syntax.location(elem)+'.');
          break;
          
        case 'Identifier':
          if (!activities[elem.name])
            err('['+classname+'] Returns unknown activity reference '+
                elem.name+' in transition '+options.trans+', at '+self.syntax.location(elem)+'.');
          break;
      }      
    }
    
  } /* End of check */
  
  /************************
  ** Analyzer
  ************************/
  
  if (verbose) out('Analyzing agent class "'+classname+'" ..');
  if (syntax.type!='Program') 
    err('Syntax is not a program: '+syntax.type);
    
  // Phase 1 
  loop1: for (ep in syntax.body) {
    var elem=syntax.body[ep];
    if (elem.type!='VariableDeclaration') 
      err('Body element is not a variable declaration: '+elem.type);
    for(cp in elem.declarations) {
      var declarator=elem.declarations[cp];
      if (declarator.type!='VariableDeclarator') {
        err('VariableDeclaration element is not a variable declarator: '+declarator.type);
      }
      if (declarator.id.name!='ac') 
        err('['+classname+'] Entry not found, expected ac, got: '+declarator.id.name);
      else { ac=declarator; break loop1;};
    }
  }
  if (!ac)
    err('No agent class template found.');
  if (!ac.init || ac.init.type != 'FunctionExpression')
    err('['+classname+'] Entry is invalid, expected function, got: '+ac.init.type);
  if (ac.init.type != 'FunctionExpression')
    err('['+classname+'] Entry is invalid, expected function, got: '+ac.init.type);

  if (ac.init.body.type != 'BlockStatement')
    err('['+classname+'] Entry is invalid, expected function body, got: '+ac.init.body.type);
    
  // Phase 2 Agent Class Pre-check / Top-level / Top symbol table creation
  loop2: for (ep in ac.init.body.body) {
    var elem=ac.init.body.body[ep];

    switch (elem.type) {
      case 'VariableDeclaration':
        err('['+classname+'] May not contain free variable declarations: '+
            Comp.printf.list(Comp.array.map(elem.declarations,function (decl) {
                  if (decl.type!='VariableDeclarator') return '?'; 
                    else return 'var '+self.syntax.name(decl.id)
                }))+', at '+self.syntax.location(elem));
        break;
      case 'ExpressionStatement': 
        switch (elem.expression.type) {
          case 'AssignmentExpression':
            check(elem.expression.left,{left:true,this:true,target:elem.expression.right});
            check(elem.expression.right,{right:true,this:true,target:elem.expression.left,syms:syms});
            break;
          case 'MemberExpression':          
            if (elem.expression.object && elem.expression.object.type=='ThisExpression')
              check(elem.expression,{left:true,this:true,target:{type:'undefined'}});
            break;
        }
        break;
      default:
        err('['+classname+'] Invalid top-level '+elem.type+
            ', at '+self.syntax.location(elem));
        break;
    }
  }
  
  if (!syms['act'] || syms['act'].type != 'ObjectExpression') 
    err('['+classname+'] Found no or no valid activity section, expecting this.act={..}.');
  if (!syms['trans'] || syms['trans'].type != 'ObjectExpression') 
    err('['+classname+'] Found no or no valid transition section, expecting this.trans={..}.');
  if (syms['on'] && syms['on'].type != 'ObjectExpression') 
    err('['+classname+'] Found invalid handler section, expecting this.on={..}.');
  if (!syms['on'] && verbose) 
    warn('['+classname+'] Found no handler section, expecting this.on={..}.');
  if (!nextVal) 
    err('['+classname+'] Found no next attribute, expecting  this.next="<nextact>".');
  if (!activities[nextVal])
    err('['+classname+'] Found invalid next attribute pointing to undefined activity '+nextVal+'.');
  
  // Phase 3 Function, Activity, and Transition properties check
  loop3A: for (ep in activities) {
    var elem=activities[ep];
    if (!transitions[ep] && verbose) warn('['+classname+'] No transition entry found for activity '+ep);
    switch (elem.type) {
      case 'FunctionExpression':
        options={funbody:true,this:true,syms:{}};
        check(elem.body,options); 
        elem.syms=syms;
        break;
      case 'ArrowFunctionExpression':
        options={funbody:true,this:true,syms:{}};
        check(elem.body,options); 
        elem.syms=syms;
        break;
      default:
        err('['+classname+'] Found invalid activity entry, expecting FunctionExpression or ArrowFunctionExpression, got '+
            elem.type+', at '+self.syntax.location(elem));
        
    }  
  }
  loop3B: for (ep in transitions) {
    var elem=transitions[ep],opt;
    if (!activities[ep])
        err('['+classname+'] Transition entry found referencing unknown activity: '+
            ep+', at '+self.syntax.location(elem));
    switch (elem.type) {
      case 'Identifier': 
        if (!activities[elem.name])
          err('['+classname+'] Unknown transition found: '+
            elem.name+', at '+self.syntax.location(elem));
        
        break;
      case 'Literal': 
        if (!activities[elem.value])
          err('['+classname+'] Unknown transition found: '+
            elem.value+', at '+self.syntax.location(elem));
        
        break;
      case 'FunctionExpression': 
        opt={trans:ep,ret:0};
        check(elem.body,opt);
        if (opt.ret==0 && verbose) 
           warn('['+classname+'] Missing return (undefined) in transition '+
                 opt.trans+', at '+self.syntax.location(elem)+'.');
        break;
      case 'ArrowFunctionExpression': 
        opt={trans:ep,ret:0};
        check(elem.body,opt);
        if (opt.ret==0 && verbose) 
           warn('['+classname+'] Missing return (undefined) in transition '+
                 opt.trans+', at '+self.syntax.location(elem)+'.');
        break;
      default:
        err('['+classname+'] Found invalid transition entry, expecting FunctionExpression or ArrowFunctionExpression, Identifier, or String Literal, got '+
            elem.type+', at '+self.syntax.location(elem));
        
    }  
  }

  if (verbose) out(classname+' passed check.');
  if (verbose) {
    out(classname+' has the following top-level object properties:');
    for (ep in syms) {
      var sym=syms[ep];
      if (!sym) continue;
      out('       '+ep+' : '+sym.type);
    }
    out(classname+' has the following activities:');
    for (ep in activities) {
      var elem=activities[ep];
      out('       '+ep);
    }
    out(classname+' next activity: '+nextVal);
    out(classname+' has the following transition entries:');
    for (ep in transitions) {
      var elem=transitions[ep];
      out('       '+ep);
    }
    if (subclasses) {
      out(classname+' has the following subclass entries:');
      for (ep in subclasses) {
        var elem=subclasses[ep];
        out('       '+ep);
      }
    }
  }
  if (verbose>1) {
    out(classname+' has the following top-level symbols:');
    for (ep in syms) {
      if (!syms[ep]) continue;
      out('       '+ep+':'+(verbose>2?Io.inspect(syms[ep]):syms[ep].type));
    }
  }
  return {
    activities:activities,
    transitions:transitions,
    subclasses:subclasses,
    symbols:syms,
    errors: errors
  }
}


module.exports = {
  corefuncs:corefuncs,
  /* Extend corefuncs */
  extend: function (funcs) {
    var p;
    for(p in funcs) {
      corefuncs[p]=funcs[p];
    }
  },
  jamc:jamc,
  options:options,
  current:function (module) { current=module.current; Aios=module; }
};
};
BundleModuleCode['shell/cluster']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2006-2020 bLAB
 **    $CREATED:     1-9-19 by sbosse.
 **    $VERSION:     1.3.3
 **
 **    $INFO: 
 **
 **    Automatic JAM Shell Cluster Manager
 **
 **  var workers = new Cluster(options);
 **  workers.start()
 **
 **  typeof @options = {
 **    cluster?:clusterdesc [],
 **    verbose?:number,
 **    connect?:boolean,
 **    rows?:number,
 **    cols?:number,
 **    port0?:number is private IP port base for grid links,
 **    port1?:number is public IP port base,
 **    portn?:number is public IP delta,
 **    proto?:string [] is public IP protocoll list,
 **    poll?: function (gridprocess),
 **    secure?:string is AMP security port,
 **    todo?: string is default command for worker,
 **  }
 **
 **  typeof clusterdesc = {
 **    id:string|number,
 **    todo;string,
 **    ports?:number|{port:number,proto:string}|dir [],
 **    connect?:{from:dir,to:dir} [],
 **    poll?:function|number
 **  }
 **  type gridprocess = {
 **  }
 ** 
 **  TODO:
 **   - Grid reconnect (pro.cluster.connect) after worker restart
 **   - Check internal links (satisfy above)
 **
 **    $ENDOFINFO
 **
 */
var Comp    = Require('com/compat');
var CP      = Require('child_process')
var osutils = Require('os/osutils');
var program = process.argv[1];
var Aios    = none;

var PS = {
  START   : 'PS.START',   // starting
  STOP    : 'PS.STOP',    // stopping
  RUN     : 'PS.RUN',     // running 
  RESTART : 'PS.RESTART', // restarting
  DEAD    : 'PS.DEAD',    // killed - non existing anymore
  EXIT    : 'PS.EXIT',    // on exit
  DEAF    : 'PS.DEAF',    // not responding
}



/** Create the CLUSTER (Grid of physical nodes)
 */
function Cluster(options,shell) {
  var i,j,n,m,self=this,poll;
  function pos2index(col,row) {
    return row*self.grid.cols+col;
  }
  function pos2ip(col,row,dir) {
    var off=0;
    switch (dir) {
      case DIR.NORTH: off=0; break; 
      case DIR.WEST:  off=1; break; 
      case DIR.EAST:  off=2; break; 
      case DIR.SOUTH: off=3; break; 
    }
    return self.options.port0+row*self.grid.cols*4+col*4+off;
  }
  function addr(port) {
    return 'localhost:'+port;
  }
  function ports(col,row) {
    // Create public ports if any
    var port;
    if (options.port1 && options.proto) {
      port=options.port1+pos2index(col,row)*(options.portn||options.proto.length)-1;
      return options.proto.map(function (proto) {
        port++;
        return DIR.IP(proto+'://'+addr(port)+
                      (options.secure?'?secure='+options.secure:''));
      })
    } else return [];
  }
  this.cluster  = options.cluster;
  this.options  = options;
  this.logging  = options.logging;  // messages from workers passed to outside!
  
  if ((options.poll==true||typeof options.poll=='number') && 
       options.port1 && options.proto &&
       options.proto.contains('http')) {
    // HTTP worker polling function using amp.scan  
    // -- requires http / no https with localhost DNS permitted!
    this.log('Creating AMP HTTP scanner on port '+(options.port0||(options.port1-1)));
    this.probe = shell.port(DIR.IP(options.port0||(options.port1-1)),{
      proto:'http',
    });
    poll = function poll(pro) {
      var port=(options.port1+options.proto.indexOf('http'))+pro.index*(options.portn||options.proto.length)
      self.probe.amp.scan(
        {address:'localhost',port:port,proto:'http'},
        null,
        function (reply) {
          pro.poll={
            time  : shell.time(), 
            state : reply!=undefined, 
            info  : reply&&reply.info,
            port  : (reply&&reply.port?shell.Port.toString(reply.port):null)
          };
        })
    }
    this.polltimer=setInterval(self.poll.bind(self),options.poll);
  } else if (typeof options.poll=='function') poll=options.poll;
  
  if (!this.cluster && this.options.rows && this.options.cols) {
    this.cluster=[];
    this.grid = {
      rows: this.options.rows,
      cols: this.options.cols
    }
    // Create cluster descriptor array
    for(j=0;j<this.grid.rows;j++)
      for(i=0;i<this.grid.cols;i++) 
        this.cluster.push({
          id:'node('+i+','+j+')',
          poll:poll,
          todo:this.options.todo||'',
          ports:ports(i,j)
        })
  } else if (this.cluster) {
    n=m=int(Math.log2(this.cluster.length));
    while (n*m<this.cluster.length) n++;
    this.grid = {
      rows: n,
      cols: m
    }
  } else return;
  if (this.options.verbose) this.log('Grid: '+this.grid.rows+','+this.grid.cols);
  this.cluster.forEach(function (node,i) {
    var row=int(i/self.grid.cols),
        col=i-row*self.grid.cols,
        ports,connects,ip;
    node.position=[col,row];
    if (!self.options.connect || !self.options.port0) return;
    
    // Create private P2P ports and connect them
    ip=pos2ip(col,row);
    if (!node.ports) node.ports=[];
    if (!node.connect) node.connect=[];
    // TODO: compact; iterative composition
    if (row==0) {
      if (col == 0) ports = [
        DIR.East  (ip+2),  
        DIR.South (ip+3)        
      ]; else if (col<(self.grid.cols-1)) ports = [
        DIR.West  (ip+1),
        DIR.East  (ip+2),  
        DIR.South (ip+3)        
      ]; else ports = [
        DIR.West  (ip+1),
        DIR.South (ip+3)        
      ]    
    } else if (row<(self.grid.rows-1)) {
      if (col == 0) ports = [
        DIR.North (ip),
        DIR.East  (ip+2),  
        DIR.South (ip+3)        
      ]; else if (col<(self.grid.cols-1)) ports = [
        DIR.North (ip),
        DIR.West  (ip+1),
        DIR.East  (ip+2),  
        DIR.South (ip+3)        
      ]; else ports = [
        DIR.North (ip),
        DIR.West  (ip+1),
        DIR.South (ip+3)        
      ]
    } else {
      if (col == 0) ports = [
        DIR.North (ip),
        DIR.East  (ip+2),  
      ]; else if (col<(self.grid.cols-1)) ports = [
        DIR.North (ip),
        DIR.West  (ip+1),
        DIR.East  (ip+2),  
      ]; else ports = [
        DIR.North (ip),
        DIR.West  (ip+1),
      ]
    }
    self.log('Creating cluster interconnect AMP UDP ports for node '+i+': '+
              ports.map(function (p) { return DIR.print(p) }).join(' '));

    node.ports=node.ports.concat(ports);
    if (row==0) {
      if (col==0) {
        connects = [
          DIR.East  (addr(pos2ip(col+1,row,DIR.WEST))),  
          DIR.South (addr(pos2ip(col,row+1,DIR.NORTH)))
        ]
      } else if (col<(self.grid.cols-1)) connects = [
          DIR.East  (addr(pos2ip(col+1,row,DIR.WEST))),  
          DIR.South (addr(pos2ip(col,row+1,DIR.NORTH)))
      ]; else connects = [
          DIR.South (addr(pos2ip(col,row+1,DIR.NORTH)))
      ]    
    } else if (row<(self.grid.rows-1)) {
      if (col==0) {
        connects = [
          DIR.East  (addr(pos2ip(col+1,row,DIR.WEST))),  
          DIR.South (addr(pos2ip(col,row+1,DIR.NORTH)))
        ]
      } else if (col<(self.grid.cols-1)) connects = [
          DIR.East  (addr(pos2ip(col+1,row,DIR.WEST))),  
          DIR.South (addr(pos2ip(col,row+1,DIR.NORTH)))
      ]; else connects = [
          DIR.South (addr(pos2ip(col,row+1,DIR.NORTH)))
      ]
    } else {
      if (col==0) {
        connects = [
          DIR.East  (addr(pos2ip(col+1,row,DIR.WEST))),  
        ]
      } else if (col<(self.grid.cols-1)) ports = [
          DIR.East(localhost+':'+pos2ip(col+1,row))
      ]; else connects = [
      ]
    }
    node.connect=node.connect.concat(connects);
    
  });
}

Cluster.prototype.log = function (msg,pid) {
  console.log('[CLU'+(pid||process.pid)+' '+Aios.clock()+'] '+msg);
}

Cluster.prototype.newVM = function(index,todo) {
  var argv = [],desc=this.cluster[index],self=this;
  if (this.options.verbose) argv.push('-v');
  if (this.options.verbose>1) argv.push('-v');
  if (todo && todo[todo.length-1]!=';') todo += ';';
  if (desc.ports) todo += desc.ports.map(function (port,index) {
    if (port.port != undefined || port.proto != undefined || port.address != undefined) 
      return ('var p'+index+'='+
              'port(DIR.IP('+port.port+'),{proto:"'+port.proto+'"'+
               (port.secure?',secure:"'+port.secure+'"':'')+
               '})');
    if (port.tag) 
      return ('var p'+index+'=port('+Aios.DIR.toString(port)+')');
  }).join(';');
  if (desc.connect) todo = todo +';'+desc.connect.map(function (port,index) {
    return 'var t'+index+'=setTimeout(function () { connect('+Aios.DIR.toString(port)+')},500)';
  }).join(';');
  todo = [
    'config({print:process.send.bind(process),printAgent:process.send.bind(process),printAsync:process.send.bind(process)})',
    ''
  ].join(';') + todo;
  argv=argv.concat(['-e',todo]);
  if (this.options.verbose>1) this.log(argv);
  var pro = CP.fork(program,argv);
  pro.on('exit',function (code,signal) {
    if (self.options.verbose) self.log('Process #'+pro.pid+' got signal '+code);
    pro.exited={code:code,signal:signal};
  });
  pro.on('message', function(msg) {
    if (self.logging) self.logging(msg,index);
    else self.log(msg,pro.pid);
  });
  if (this.options.verbose) this.log('Started worker #'+pro.pid+': '+todo);
  else this.log('Started worker #'+pro.pid);
  pro.argv=argv;
  pro.todo=todo;
  pro.state=PS.START;
  pro.index=index;
  pro.cluster=desc;
  pro.load=0;
  pro.mem=0;
  return pro;
}

// Check all worker processes
Cluster.prototype.check = function (index) {
  var self=this,stats=[];
  this.childs.forEach(function (pro,_index) {
    if (index != undefined && _index != index) return;
    if (pro.state != PS.DEAD) 
      pro.state=pro.exited?PS.STOP:PS.RUN;
    stats.push({pid:pro.pid,state:pro.state,poll:pro.poll,load:pro.load,mem:pro.mem})
    osutils.getProcess(pro.pid,function (res) {
        if (res) {
          pro.load=Math.floor(res.cpu)/100;
          pro.mem=Math.floor(res.mem);
        }
    });
  })
  return stats;
}

// Start polling - poll function must be provided by cluster descriptor
Cluster.prototype.poll = function (index) {
  var self=this;
  this.childs.forEach(function (pro,_index) {
    if (index != undefined && _index != index) return; 
    if (pro.cluster.poll) pro.cluster.poll(pro);
  })
}

// Restart all or one worker processs
Cluster.prototype.restart = function (index) {
  var self=this;
  this.childs=this.childs.map(function (pro,_index) {
    if (index != undefined && _index != index) return pro; 
    if (self.options.verbose) self.log('Restarting worker #'+index);
    if (pro.state == PS.RUN) pro.kill();
    return self.newVM(_index,self.cluster[_index].todo);
  })
}

Cluster.prototype.report = function (index) {
  var desc=this.cluster;
  function pn(proto) { 
    switch (proto) {
      case 'http':  return 'h';
      case 'https': return 'hs';
      case 'udp':   return 'u';
      case 'tcp':   return 't';
    }
  }
  function ports(index) {
    if (desc[index].ports) {
      var ports = desc[index].ports.filter(function (p) {
        return p.tag==DIR.tag.IP || p.port;
      });
      return ports.map(function(port) {
              if (port.proto) return pn(port.proto)+port.port+(port.secure?'S':'P');
              if (port.ip) return port.ip.replace(/localhost|ttp|dp|cp|:|\//g,'')
                                         .replace(/\?[^$]+$/,'')+
                                         (port.ip.match(/\?secure=/)?'S':'P')
             }).join(',');
    } else return '-';
  }
  var table='';
  // this.poll();
  var stats=this.check();
  table = '| Node | Ports | PID | Status | CPU% | Memory MB | Agents | Links |\n'+
          '|:---|:---|:---|:---|:---|:---|:---|:---|\n';
  table = table+stats.map(function (row,index) {
    if (!row || row.state!=Cluster.PS.RUN || !row.poll ||
        !row.poll.state || !row.poll.info)
      return '| - | '+ports(index)+' | -  | '+row.state+' | -  | - | - | - |'; 
    else
      return '| '+row.poll.info.world+' | '+ports(index)+' | '+
             row.pid+' | '+row.state + ' | '+row.load + ' | '+row.mem + ' | '+
             row.poll.info.stats.agents+' | '+
             row.poll.info.stats.links+' |';
  }).join('\n')
  return table;
}
// Start all or one worker processs
Cluster.prototype.start = function (index) {
  this.childs = [];
  for (var pi=0;pi<this.cluster.length;pi++) {
    if (index != undefined && index!=pi) continue;
    this.childs.push(this.newVM(pi,this.cluster[pi].todo));
  }
}

// Stop all or one worker processs
Cluster.prototype.stop = function (index) {
  var self=this;
  this.childs.forEach(function (pro,_index) {
    if (index != undefined && _index != index) return; 
    if (pro.state == PS.DEAD) return;
    self.log('Stopping worker #'+_index);
    pro.state=PS.DEAD;
    pro.kill();
  })
}


Cluster.PS=PS;
Cluster.current = function (module) { Aios=module };

module.exports = Cluster;
};
BundleModuleCode['os/osutils']=function (module,exports){
// https://github.com/oscmejia/os-utils
var _os   = require('os');
var _proc = require('child_process')
var _fs = require('fs')

exports.platform = function(){ 
    return process.platform;
}

exports.cpuCount = function(){ 
    return _os.cpus().length;
}

exports.sysUptime = function(){ 
    //seconds
    return _os.uptime();
}

exports.processUptime = function(){ 
    //seconds
    return process.uptime();
}



// Memory
exports.freemem = function(){
    return _os.freemem() / ( 1024 * 1024 );
}

exports.totalmem = function(){

    return _os.totalmem() / ( 1024 * 1024 );
}

exports.freememPercentage = function(){
    return _os.freemem() / _os.totalmem();
}

exports.freeCommand = function(callback){
    
    // Only Linux
    require('child_process').exec('free -m', function(error, stdout, stderr) {
       
       var lines = stdout.split("\n");
       
       
       var str_mem_info = lines[1].replace( /[\s\n\r]+/g,' ');
       
       var mem_info = str_mem_info.split(' ')
      
       total_mem    = parseFloat(mem_info[1])
       free_mem     = parseFloat(mem_info[3])
       buffers_mem  = parseFloat(mem_info[5])
       cached_mem   = parseFloat(mem_info[6])
       
       used_mem = total_mem - (free_mem + buffers_mem + cached_mem)
       
       callback(used_mem -2);
    });
}


// Hard Disk Drive
exports.harddrive = function(callback){
    
    require('child_process').exec('df -k', function(error, stdout, stderr) {
    
        var total = 0;
        var used = 0;
        var free = 0;
    
        var lines = stdout.split("\n");
    
        var str_disk_info = lines[1].replace( /[\s\n\r]+/g,' ');
    
        var disk_info = str_disk_info.split(' ');

        total = Math.ceil((disk_info[1] * 1024)/ Math.pow(1024,2));
        used = Math.ceil(disk_info[2] * 1024 / Math.pow(1024,2)) ;
        free = Math.ceil(disk_info[3] * 1024 / Math.pow(1024,2)) ;

        callback(total, free, used);
    });
}



// Return process running current 
exports.getProcesses = function(nProcess, callback){
    
    // if nprocess is undefined then is function
    if(typeof nProcess === 'function'){
        
        callback =nProcess; 
        nProcess = 0
    }   
    if (nProcess > 0)
      command = 'ps -eo pid,pcpu,pmem,time,args | sort -k 2 -r | head -n'+(nProcess + 1)
    else
      command = 'ps -eo pid,pcpu,pmem,time,args | sort -k 2 -r | head -n'+10
    
    
    _proc.exec(command, function(error, stdout, stderr) {
    
        var that = this
        
        var lines = stdout.split("\n");
        lines.shift()
        lines.pop()
       
        var result=[];
        
        lines.forEach(function(_item,_i){
            
            var _str = _item.replace( /[\s\n\r]+/g,' ');
            
            _str = _str.split(' ')
            result.push( {cpu:Number(_str[2]),
                        mem:Number(_str[3]),
                        time:_str[4],
                        pro:_str[5],
                        args:_str.slice(6,_str.length)});              
               
        });
        
        callback(result);
    }); 
}

// One process
exports.getProcess = function(pid, callback){
    if(typeof pid === 'function'){        
        callback = pid; 
        pid = process.pid
    }   
    
    command = 'ps -eo pid,pcpu,rss,time,args'
    
    _proc.exec(command, function(error, stdout, stderr) {
    
        var that = this
        var lines = stdout.split("\n");
        lines.shift()
        lines.pop()
  
        var result;
        lines.forEach(function(_item,_i){            
            var _str = _item.trim().replace( /[\s\n\r]+/g,' ');
            _str = _str.split(' ')
            if (Number(_str[0])==pid)
              result = {cpu:Number(_str[1]),
                        mem:Math.floor(Number(_str[2])/1024),
                        time:_str[3],
                        pro:_str[4],
                        args:_str.slice(5,_str.length)};              
               
        });
        
        callback(result);
    }); 
}



/*
* Returns All the load average usage for 1, 5 or 15 minutes.
*/
exports.allLoadavg = function(){ 
    
    var loads = _os.loadavg();
    		
    return loads[0].toFixed(4)+','+loads[1].toFixed(4)+','+loads[2].toFixed(4); 
}

/*
* Returns the load average usage for 1, 5 or 15 minutes.
*/
exports.loadavg = function(_time){ 

    if(_time === undefined || (_time !== 5 && _time !== 15) ) _time = 1;
	
    var loads = _os.loadavg();
    var v = 0;
    if(_time == 1) v = loads[0];
    if(_time == 5) v = loads[1];
    if(_time == 15) v = loads[2];
		
    return v; 
}


exports.cpuFree = function(callback){ 
    getCPUUsage(callback, true);
}

exports.cpuUsage = function(callback){ 
    getCPUUsage(callback, false);
}

if (process.platform=='linux')
  exports.load = function(pid,callback,time){
    var t0,t1;
    if (typeof pid == 'function') {
      time=callback;
      callback=pid;
      pid=process.pid;
    }
    getUsage(pid,function (t) {
      t0=t;
      if (!time) callback(t);
    })
    if (time) setTimeout(function () {
      getUsage(pid,function (t) {
        t1=t;
        callback((t1-t0)/time*1000);
      })
    },time);
  }

function getCPUUsage(callback, free){ 
	
    var stats1 = getCPUInfo();
    var startIdle = stats1.idle;
    var startTotal = stats1.total;
	
    setTimeout(function() {
        var stats2 = getCPUInfo();
        var endIdle = stats2.idle;
        var endTotal = stats2.total;
		
        var idle 	= endIdle - startIdle;
        var total 	= endTotal - startTotal;
        var perc	= idle / total;
	  	
        if(free === true)
            callback( perc );
        else
            callback( (1 - perc) );
	  		
    }, 1000 );
}

function getCPUInfo(callback){ 
    var cpus = _os.cpus();
	
    var user = 0;
    var nice = 0;
    var sys = 0;
    var idle = 0;
    var irq = 0;
    var total = 0;
	
    for(var cpu in cpus){
        if (!cpus.hasOwnProperty(cpu)) continue;	
        user += cpus[cpu].times.user;
        nice += cpus[cpu].times.nice;
        sys += cpus[cpu].times.sys;
        irq += cpus[cpu].times.irq;
        idle += cpus[cpu].times.idle;
    }
	
    var total = user + nice + sys + idle + irq;
	
    return {
        'idle': idle, 
        'total': total
    };
}

function getUsage(pid,cb){
  // Linux only
  print(pid)
  _fs.readFile("/proc/" + pid + "/stat", function(err, data){
        var elems = data.toString().split(' ');
        var utime = parseInt(elems[13]);
        var stime = parseInt(elems[14]);
        cb(utime + stime);
  });
}
};
BundleModuleCode['rpc/rpc']=function (module,exports){
var udprpc = Require('rpc/udp_rpc');
var version = "1.1.3"

function rpc(proto,port,arrayOfNamedFunctions,options) {
  options=options||{}
  switch (proto.toUpperCase()) {
    case 'UDP': 
      return new udprpc(options.dgramType||'udp4',port,
                        arrayOfNamedFunctions,options);
            
  }
}

var clientCache=[];
// server
function getreq (url, requestCallback, options) {
  var proto = url.match(/^([a-zA-Z]+):/),
      port  = url.match(/([0-9]+)$/);
  proto=(proto&&proto[1])||'udp';
  port=(port&&port[1]);
  if (!port) throw Error('getreq: invalid port');
  var myport = rpc(proto,port,[
    function request(source, data, callback) {
      callback(requestCallback(data,source))
    }
  ],options)
  return myport;
}
// client
function trans (url,data,replyCallback,options) {
  var proto = url.match(/^([a-zA-Z]+):/),
      destination = url.replace(/^([a-zA-Z]+):\/\//,''),
      port  = url.match(/([0-9]+)$/);
  proto=(proto&&proto[1])||'udp';
  port=(port&&port[1]);
  if (!port) throw Error('getreq: invalid port');
  if (clientCache[url]) {
    if (clientCache[url].state!='listening') {
      clientCache[url].queue.push([destination,data,replyCallback]);
    } else
      clientCache[url].request(destination,data,replyCallback||function(){});
    clientCache[url].timestamp=Date.now();
  } else {
    var myport = clientCache[url] = rpc(proto,0,[
      function request(source, data, callback) {}
    ],options);
    clientCache[url].timestamp=Date.now();
    clientCache[url].queue=[];
    clientCache[url].queue.push([destination,data,replyCallback]);
    myport.on('init',function () {
      while (clientCache[url].queue.length) {
        var next = clientCache[url].queue.shift();
        myport.request(next[0],next[1],next[2]||function(){});
      }
    });
    
  }
}

module.exports = {
  clientCache : clientCache,
  rpc    : rpc,
  getreq : getreq,
  trans  : trans,
  version : version
}
};
BundleModuleCode['rpc/udp_rpc']=function (module,exports){
// https://github.com/dfellis/node-udp-rpc

'use strict';
var dgram   = require('dgram');
var crypto  = Require('os/crypto.rand'); // var crypto  = require('crypto');
var util    = require('util');
var events  = require('events');
var async   = Require('rpc/async');
var version = "1.1.3";

// The header flag for udp-rpc packet reconstruction
var flags = {
    // Reserved: 1, 2, 4, 8, 16, 32, 64
    lastPacket: 128
};

// Helper function to generate Xor bytes
function bufXor(buf) {
    var out = 0;
    for (var i = 0; i < buf.length; i++) {
        out = out ^ buf.readUInt8(i, true);
    }
    return out;
}

// Helper function for generating a unique byte for the flag/id byte
function uniqId(usedList) {
    var id = 0;
    do {
        id = (Math.random() * (Math.pow(2, 8) - 1)) & 127; // Get a random byte and trim the top bit off
    } while (typeof usedList[id] != 'undefined')
    return id;
}

// Called when the client wants to call a remote rpc method
function execRpc(method, address) {
    if (!method || !address) {
        throw new Error("Did not receive a valid RPC request!");
    }
    // Construct the payload to send to the remote server
    var rpcParams = Array.prototype.slice.call(arguments, 2);
    var callback = rpcParams.length > 1 ? rpcParams.pop() : undefined;
    var addrArray = address.split(":");
    var messageId = this.genId();
    // var payload = new Buffer(([method].concat(messageId, rpcParams)).join(','));
    var payload = new Buffer(JSON.stringify([method,
        messageId,
    ].concat(rpcParams)));
    this.emit('execRpc', method, address, rpcParams, callback);
    var self = this;

    // we have to register callback handler in advance; maybe reply arrives earlier than
    // sendMessage callback is executed !?
    this.messages[messageId] = {
        callTs: new Date(),
        callback: callback,
        method: method,
        ip: addrArray[0],
        port: addrArray[1],
        rpcParams: rpcParams // This is for debugging purposes only
    };
    sendMessage.call(this, address, payload, function allSent(err) {
        self.emit('sentRpc', method, address, rpcParams, callback, err);
        if (err instanceof Error) {
          // delete callback handler
          delete self.messages[messageId];
          callback(err)
        } else {
          
          // nothing to do!?
        }
    });
}

function sendMessage(address, payload, callback) {
    var addrArray = address.split(":");

    // Construct the 6 byte header elements of the udp-rpc protocol for message verification/splitting
    if (!this.packets[address]) {
        this.packets[address] = {};
    }
    var currFlags = uniqId(this.packets[address]);
    var totXor = bufXor(payload);

    // Construct an array of payload fragments, each no larger than 494 bytes
    var payloads = [];
    if (payload.length > 494) {
        var i = 0;
        for (i = 0; i < payload.length; i += 494) {
            var newPayload = new Buffer(494);
            payload.copy(newPayload, 0, i, i + 493);
            payloads.push(newPayload);
        }
        if (i < payload.length) {
            var newPayload = new Buffer(payload.length - i);
            payload.copy(newPayload, 0, i, payload.length - 1);
            payloads.push(newPayload);
        }
    } else {
        payloads.push(payload);
    }
    var self = this;
    // For each payload fragment, generate the payload header, construct the output message, and send it
    async.forEach(payloads, function sendPayload(payload, callback) {
        var message = new Buffer(payload.length + 6);
        var packetNumber = payloads.indexOf(payload);
        currFlags &= 127;
        message.writeUInt32BE(packetNumber, 1, true);
        var curXor = bufXor(payload);
        if (packetNumber == payloads.length - 1) {
            currFlags |= flags.lastPacket;
            message.writeUInt8(totXor, 5, true);
        } else {
            message.writeUInt8(curXor, 5, true);
        }
        message.writeUInt8(currFlags, 0, true);
        payload.copy(message, 6);
        self.stats.send++;
        self.stats.sendData += message.length;
        self.dgram.send(message, 0, message.length, addrArray[1], addrArray[0], function(err) {
            if (err instanceof Error) {
                return callback(err);
            }
            return callback();
        });
        // When all are sent, or an error occurred, execute the callback
    }, callback);
}

// Called when a remote message is received. This could be an rpc request or response
function receiveRpc(message, info) {
    this.emit('receivedPacket', message, info);
    // Extract the header and body of the message
    var header = {
        flagid: message.readUInt8(0, true),
        packetNumber: message.readUInt32BE(1, true),
        xor: message.readUInt8(5, true)
    };
    var body = message.slice(6);
    this.stats.recv++;
    this.stats.recvData += message.length;
    var source = info.address + ":" + info.port;
    if (!this.packets[source]) {
        this.packets[source] = {};
    }
    if (!this.packets[source][header.flagid]) {
        this.packets[source][header.flagid] = [];
    }
    this.packets[source][header.flagid].push({
        header: header,
        body: body
    });
    attemptProcessing.call(this, source, header.flagid);
}

function attemptProcessing(source, id) {
    // Get the packet array to work on
    var packetArray = this.packets[source][id];
    // Tag the set of first and last packets in the packet array
    var integerSum = 0;
    var totalMessageLen = 0;
    var lastPacket = false;
    for (var i = 0; i < packetArray.length; i++) {
        integerSum += packetArray[i].header.packetNumber + 1;
        totalMessageLen += packetArray[i].body.length;
        if (!!(packetArray[i].header.flagid & flags.lastPacket)) {
            lastPacket = true;
        }
    }
    // If there is no last packet, processing impossible
    // Also, if the sum of packet numbers isn't n(n+1)/2, there must be a missing packet
    if (!lastPacket || integerSum != (packetArray.length * (packetArray.length + 1) / 2)) {
        return;
    }
    // Sort the array of packets based on packet number, since we can process this data
    packetArray = packetArray.sort(function(a, b) {
        return a.header.packetNumber - b.header.packetNumber;
    });
    // Build the full message buffer from the sorted packets
    var fullMessage = new Buffer(totalMessageLen);
    for (var i = 0, j = 0; i < packetArray.length; j += packetArray[i].body.length, i++) {
        packetArray[i].body.copy(fullMessage, 0, j);
    }
    // Remove the saved packets from the packets object and pass the message to the processMessage
    delete this.packets[source][id];
    processMessage.call(this, fullMessage, source);
}

function processMessage(message, source) {
    var sourceArr = source.split(':');
    // var messageArr = message.toString('utf8').split(',');
    try {
      var messageArr = JSON.parse(message.toString('utf8'));
    } catch (e) { console.log(e) }
    // console.log('processMessage',messageArr[0])
    // Determine type of message: RPC response, request
    if (!this.oneway && typeof this.messages[messageArr[0]] == 'object' &&
        sourceArr[1] == this.messages[messageArr[0]].port) {
        // If a response, the message array consists of the request id and response results
        // Find the appropriate callback
        this.emit('receivedRpcResponse', message, source);
        this.messages[messageArr[0]].callback.apply(this, messageArr.slice(1));
        delete this.messages[messageArr[0]];
    } else if (typeof this.methods[messageArr[0]] == 'function') {
        // If a request, the message array consists of the request method, id, and parameters, in that order
        this.emit('receivedRpcRequest', message, source);
        var messageId = messageArr[1];
        var params = messageArr.slice(2);
        // Parameters sent to the RPC method start with the source string, in case they need to know who is calling (simple state tracking)
        params.unshift(source);
        var self = this;
        // Automatically insert a callback to the params passed to the RPC method to handle the results
        if (!this.oneway) params.push(function rpcCallback() {
            // var payload = new Buffer(([messageId].concat(Array.prototype.slice.call(arguments, 0))).join(','));
            var payload = new Buffer(JSON.stringify([messageId].concat((Array.prototype.slice.call(arguments, 0)))));
            sendMessage.call(self, source, payload, function(err) {
                if (err instanceof Error) {
                    throw err;
                } // RPC server non-functional, this is fatal
            });
        });
        // Execute the requested RPC method
        this.methods[messageArr[0]].apply(this, params);
    } else {
        // If packet is not understood, ignore it
        console.log('receivedUnknownMessage',messageArr)
        this.emit('receivedUnknownMessage', message, source);
    }
}

// UDP-RPC object to provide a nice interface to the protocol and properly initialize/destroy it.
// I can't find a way to execute a function on ``delete`` like you can with get/set, so destroy this
// object with the ``die`` method.
function udpRpc(ipType, srvPort, methods, oneway) {
    var options = {};
    events.EventEmitter.call(this);
    if (typeof oneway == 'object') {
        options = oneway;
        oneway = options.oneway;
    }
    this.methods = {};
    this.messages = {};
    this.packets = {};
    this.state = 'starting';
    this.stats = {
        recv  : 0, // #msgs
        send  : 0,
        recvData : 0,   // #Bytes
        sendData : 0,
    }
    for (var i in methods) {
        Object.defineProperty(this, methods[i].name, {
            enumerable: true,
            configurable: false,
            writable: false,
            value: execRpc.bind(this, methods[i].name)
        });
        Object.defineProperty(this.methods, methods[i].name, {
            enumerable: true,
            configurable: false,
            writable: false,
            value: methods[i]
        });
    }
    this.genId = function() {
        var id = "";
        do {
            try {
                id = crypto.randomBytes(3).toString('base64');
            } catch (e) {
                id = new Buffer(Math.random() * (Math.pow(2, 24) - 1)).toString('base64');
            }
        } while (typeof this.messages[id] != 'undefined')
        return id;
    };
    this.srvPort = srvPort;
    this.address = undefined;
    var self = this;
    process.nextTick(function() {
        self.dgram = dgram.createSocket(ipType);
        self.dgram.on('listening', function udpRpcStart() {
            self.address = self.dgram.address();
            self.state = 'listening';
            if (options.verbose) console.log('udpRpc: listening on', self.address);
            self.emit('init');
        });
        self.dgram.on('message', receiveRpc.bind(self));
        self.dgram.bind(srvPort); // Currently hardwired; will look into alternatives
    });
    this.die = function die() {
        this.dgram.close();
        this.emit('death');
        delete this;
    };
    this.oneway = oneway || false;
    return this;
}
util.inherits(udpRpc, events.EventEmitter);

exports = module.exports = udpRpc;
};
BundleModuleCode['rpc/async']=function (module,exports){
(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
  typeof define === 'function' && define.amd ? define(['exports'], factory) :
  (factory((global.async = global.async || {})));
}(this, (function (exports) { 'use strict';

function slice(arrayLike, start) {
    start = start|0;
    var newLen = Math.max(arrayLike.length - start, 0);
    var newArr = Array(newLen);
    for(var idx = 0; idx < newLen; idx++)  {
        newArr[idx] = arrayLike[start + idx];
    }
    return newArr;
}

/**
 * Creates a continuation function with some arguments already applied.
 *
 * Useful as a shorthand when combined with other control flow functions. Any
 * arguments passed to the returned function are added to the arguments
 * originally passed to apply.
 *
 * @name apply
 * @static
 * @memberOf module:Utils
 * @method
 * @category Util
 * @param {Function} fn - The function you want to eventually apply all
 * arguments to. Invokes with (arguments...).
 * @param {...*} arguments... - Any number of arguments to automatically apply
 * when the continuation is called.
 * @returns {Function} the partially-applied function
 * @example
 *
 * // using apply
 * async.parallel([
 *     async.apply(fs.writeFile, 'testfile1', 'test1'),
 *     async.apply(fs.writeFile, 'testfile2', 'test2')
 * ]);
 *
 *
 * // the same process without using apply
 * async.parallel([
 *     function(callback) {
 *         fs.writeFile('testfile1', 'test1', callback);
 *     },
 *     function(callback) {
 *         fs.writeFile('testfile2', 'test2', callback);
 *     }
 * ]);
 *
 * // It's possible to pass any number of additional arguments when calling the
 * // continuation:
 *
 * node> var fn = async.apply(sys.puts, 'one');
 * node> fn('two', 'three');
 * one
 * two
 * three
 */
var apply = function(fn/*, ...args*/) {
    var args = slice(arguments, 1);
    return function(/*callArgs*/) {
        var callArgs = slice(arguments);
        return fn.apply(null, args.concat(callArgs));
    };
};

var initialParams = function (fn) {
    return function (/*...args, callback*/) {
        var args = slice(arguments);
        var callback = args.pop();
        fn.call(this, args, callback);
    };
};

/**
 * Checks if `value` is the
 * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types)
 * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
 *
 * @static
 * @memberOf _
 * @since 0.1.0
 * @category Lang
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is an object, else `false`.
 * @example
 *
 * _.isObject({});
 * // => true
 *
 * _.isObject([1, 2, 3]);
 * // => true
 *
 * _.isObject(_.noop);
 * // => true
 *
 * _.isObject(null);
 * // => false
 */
function isObject(value) {
  var type = typeof value;
  return value != null && (type == 'object' || type == 'function');
}

var hasSetImmediate = typeof setImmediate === 'function' && setImmediate;
var hasNextTick = typeof process === 'object' && typeof process.nextTick === 'function';

function fallback(fn) {
    setTimeout(fn, 0);
}

function wrap(defer) {
    return function (fn/*, ...args*/) {
        var args = slice(arguments, 1);
        defer(function () {
            fn.apply(null, args);
        });
    };
}

var _defer;

if (hasSetImmediate) {
    _defer = setImmediate;
} else if (hasNextTick) {
    _defer = process.nextTick;
} else {
    _defer = fallback;
}

var setImmediate$1 = wrap(_defer);

/**
 * Take a sync function and make it async, passing its return value to a
 * callback. This is useful for plugging sync functions into a waterfall,
 * series, or other async functions. Any arguments passed to the generated
 * function will be passed to the wrapped function (except for the final
 * callback argument). Errors thrown will be passed to the callback.
 *
 * If the function passed to `asyncify` returns a Promise, that promises's
 * resolved/rejected state will be used to call the callback, rather than simply
 * the synchronous return value.
 *
 * This also means you can asyncify ES2017 `async` functions.
 *
 * @name asyncify
 * @static
 * @memberOf module:Utils
 * @method
 * @alias wrapSync
 * @category Util
 * @param {Function} func - The synchronous function, or Promise-returning
 * function to convert to an {@link AsyncFunction}.
 * @returns {AsyncFunction} An asynchronous wrapper of the `func`. To be
 * invoked with `(args..., callback)`.
 * @example
 *
 * // passing a regular synchronous function
 * async.waterfall([
 *     async.apply(fs.readFile, filename, "utf8"),
 *     async.asyncify(JSON.parse),
 *     function (data, next) {
 *         // data is the result of parsing the text.
 *         // If there was a parsing error, it would have been caught.
 *     }
 * ], callback);
 *
 * // passing a function returning a promise
 * async.waterfall([
 *     async.apply(fs.readFile, filename, "utf8"),
 *     async.asyncify(function (contents) {
 *         return db.model.create(contents);
 *     }),
 *     function (model, next) {
 *         // `model` is the instantiated model object.
 *         // If there was an error, this function would be skipped.
 *     }
 * ], callback);
 *
 * // es2017 example, though `asyncify` is not needed if your JS environment
 * // supports async functions out of the box
 * var q = async.queue(async.asyncify(async function(file) {
 *     var intermediateStep = await processFile(file);
 *     return await somePromise(intermediateStep)
 * }));
 *
 * q.push(files);
 */
function asyncify(func) {
    return initialParams(function (args, callback) {
        var result;
        try {
            result = func.apply(this, args);
        } catch (e) {
            return callback(e);
        }
        // if result is Promise object
        if (isObject(result) && typeof result.then === 'function') {
            result.then(function(value) {
                invokeCallback(callback, null, value);
            }, function(err) {
                invokeCallback(callback, err.message ? err : new Error(err));
            });
        } else {
            callback(null, result);
        }
    });
}

function invokeCallback(callback, error, value) {
    try {
        callback(error, value);
    } catch (e) {
        setImmediate$1(rethrow, e);
    }
}

function rethrow(error) {
    throw error;
}

var supportsSymbol = typeof Symbol === 'function';

function isAsync(fn) {
    return supportsSymbol && fn[Symbol.toStringTag] === 'AsyncFunction';
}

function wrapAsync(asyncFn) {
    return isAsync(asyncFn) ? asyncify(asyncFn) : asyncFn;
}

function applyEach$1(eachfn) {
    return function(fns/*, ...args*/) {
        var args = slice(arguments, 1);
        var go = initialParams(function(args, callback) {
            var that = this;
            return eachfn(fns, function (fn, cb) {
                wrapAsync(fn).apply(that, args.concat(cb));
            }, callback);
        });
        if (args.length) {
            return go.apply(this, args);
        }
        else {
            return go;
        }
    };
}

/** Detect free variable `global` from Node.js. */
var freeGlobal = typeof global == 'object' && global && global.Object === Object && global;

/** Detect free variable `self`. */
var freeSelf = typeof self == 'object' && self && self.Object === Object && self;

/** Used as a reference to the global object. */
var root = freeGlobal || freeSelf || Function('return this')();

/** Built-in value references. */
var Symbol$1 = root.Symbol;

/** Used for built-in method references. */
var objectProto = Object.prototype;

/** Used to check objects for own properties. */
var hasOwnProperty = objectProto.hasOwnProperty;

/**
 * Used to resolve the
 * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
 * of values.
 */
var nativeObjectToString = objectProto.toString;

/** Built-in value references. */
var symToStringTag$1 = Symbol$1 ? Symbol$1.toStringTag : undefined;

/**
 * A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values.
 *
 * @private
 * @param {*} value The value to query.
 * @returns {string} Returns the raw `toStringTag`.
 */
function getRawTag(value) {
  var isOwn = hasOwnProperty.call(value, symToStringTag$1),
      tag = value[symToStringTag$1];

  try {
    value[symToStringTag$1] = undefined;
    var unmasked = true;
  } catch (e) {}

  var result = nativeObjectToString.call(value);
  if (unmasked) {
    if (isOwn) {
      value[symToStringTag$1] = tag;
    } else {
      delete value[symToStringTag$1];
    }
  }
  return result;
}

/** Used for built-in method references. */
var objectProto$1 = Object.prototype;

/**
 * Used to resolve the
 * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
 * of values.
 */
var nativeObjectToString$1 = objectProto$1.toString;

/**
 * Converts `value` to a string using `Object.prototype.toString`.
 *
 * @private
 * @param {*} value The value to convert.
 * @returns {string} Returns the converted string.
 */
function objectToString(value) {
  return nativeObjectToString$1.call(value);
}

/** `Object#toString` result references. */
var nullTag = '[object Null]';
var undefinedTag = '[object Undefined]';

/** Built-in value references. */
var symToStringTag = Symbol$1 ? Symbol$1.toStringTag : undefined;

/**
 * The base implementation of `getTag` without fallbacks for buggy environments.
 *
 * @private
 * @param {*} value The value to query.
 * @returns {string} Returns the `toStringTag`.
 */
function baseGetTag(value) {
  if (value == null) {
    return value === undefined ? undefinedTag : nullTag;
  }
  return (symToStringTag && symToStringTag in Object(value))
    ? getRawTag(value)
    : objectToString(value);
}

/** `Object#toString` result references. */
var asyncTag = '[object AsyncFunction]';
var funcTag = '[object Function]';
var genTag = '[object GeneratorFunction]';
var proxyTag = '[object Proxy]';

/**
 * Checks if `value` is classified as a `Function` object.
 *
 * @static
 * @memberOf _
 * @since 0.1.0
 * @category Lang
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is a function, else `false`.
 * @example
 *
 * _.isFunction(_);
 * // => true
 *
 * _.isFunction(/abc/);
 * // => false
 */
function isFunction(value) {
  if (!isObject(value)) {
    return false;
  }
  // The use of `Object#toString` avoids issues with the `typeof` operator
  // in Safari 9 which returns 'object' for typed arrays and other constructors.
  var tag = baseGetTag(value);
  return tag == funcTag || tag == genTag || tag == asyncTag || tag == proxyTag;
}

/** Used as references for various `Number` constants. */
var MAX_SAFE_INTEGER = 9007199254740991;

/**
 * Checks if `value` is a valid array-like length.
 *
 * **Note:** This method is loosely based on
 * [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength).
 *
 * @static
 * @memberOf _
 * @since 4.0.0
 * @category Lang
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is a valid length, else `false`.
 * @example
 *
 * _.isLength(3);
 * // => true
 *
 * _.isLength(Number.MIN_VALUE);
 * // => false
 *
 * _.isLength(Infinity);
 * // => false
 *
 * _.isLength('3');
 * // => false
 */
function isLength(value) {
  return typeof value == 'number' &&
    value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
}

/**
 * Checks if `value` is array-like. A value is considered array-like if it's
 * not a function and has a `value.length` that's an integer greater than or
 * equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`.
 *
 * @static
 * @memberOf _
 * @since 4.0.0
 * @category Lang
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is array-like, else `false`.
 * @example
 *
 * _.isArrayLike([1, 2, 3]);
 * // => true
 *
 * _.isArrayLike(document.body.children);
 * // => true
 *
 * _.isArrayLike('abc');
 * // => true
 *
 * _.isArrayLike(_.noop);
 * // => false
 */
function isArrayLike(value) {
  return value != null && isLength(value.length) && !isFunction(value);
}

// A temporary value used to identify if the loop should be broken.
// See #1064, #1293
var breakLoop = {};

/**
 * This method returns `undefined`.
 *
 * @static
 * @memberOf _
 * @since 2.3.0
 * @category Util
 * @example
 *
 * _.times(2, _.noop);
 * // => [undefined, undefined]
 */
function noop() {
  // No operation performed.
}

function once(fn) {
    return function () {
        if (fn === null) return;
        var callFn = fn;
        fn = null;
        callFn.apply(this, arguments);
    };
}

var iteratorSymbol = typeof Symbol === 'function' && Symbol.iterator;

var getIterator = function (coll) {
    return iteratorSymbol && coll[iteratorSymbol] && coll[iteratorSymbol]();
};

/**
 * The base implementation of `_.times` without support for iteratee shorthands
 * or max array length checks.
 *
 * @private
 * @param {number} n The number of times to invoke `iteratee`.
 * @param {Function} iteratee The function invoked per iteration.
 * @returns {Array} Returns the array of results.
 */
function baseTimes(n, iteratee) {
  var index = -1,
      result = Array(n);

  while (++index < n) {
    result[index] = iteratee(index);
  }
  return result;
}

/**
 * Checks if `value` is object-like. A value is object-like if it's not `null`
 * and has a `typeof` result of "object".
 *
 * @static
 * @memberOf _
 * @since 4.0.0
 * @category Lang
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is object-like, else `false`.
 * @example
 *
 * _.isObjectLike({});
 * // => true
 *
 * _.isObjectLike([1, 2, 3]);
 * // => true
 *
 * _.isObjectLike(_.noop);
 * // => false
 *
 * _.isObjectLike(null);
 * // => false
 */
function isObjectLike(value) {
  return value != null && typeof value == 'object';
}

/** `Object#toString` result references. */
var argsTag = '[object Arguments]';

/**
 * The base implementation of `_.isArguments`.
 *
 * @private
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is an `arguments` object,
 */
function baseIsArguments(value) {
  return isObjectLike(value) && baseGetTag(value) == argsTag;
}

/** Used for built-in method references. */
var objectProto$3 = Object.prototype;

/** Used to check objects for own properties. */
var hasOwnProperty$2 = objectProto$3.hasOwnProperty;

/** Built-in value references. */
var propertyIsEnumerable = objectProto$3.propertyIsEnumerable;

/**
 * Checks if `value` is likely an `arguments` object.
 *
 * @static
 * @memberOf _
 * @since 0.1.0
 * @category Lang
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is an `arguments` object,
 *  else `false`.
 * @example
 *
 * _.isArguments(function() { return arguments; }());
 * // => true
 *
 * _.isArguments([1, 2, 3]);
 * // => false
 */
var isArguments = baseIsArguments(function() { return arguments; }()) ? baseIsArguments : function(value) {
  return isObjectLike(value) && hasOwnProperty$2.call(value, 'callee') &&
    !propertyIsEnumerable.call(value, 'callee');
};

/**
 * Checks if `value` is classified as an `Array` object.
 *
 * @static
 * @memberOf _
 * @since 0.1.0
 * @category Lang
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is an array, else `false`.
 * @example
 *
 * _.isArray([1, 2, 3]);
 * // => true
 *
 * _.isArray(document.body.children);
 * // => false
 *
 * _.isArray('abc');
 * // => false
 *
 * _.isArray(_.noop);
 * // => false
 */
var isArray = Array.isArray;

/**
 * This method returns `false`.
 *
 * @static
 * @memberOf _
 * @since 4.13.0
 * @category Util
 * @returns {boolean} Returns `false`.
 * @example
 *
 * _.times(2, _.stubFalse);
 * // => [false, false]
 */
function stubFalse() {
  return false;
}

/** Detect free variable `exports`. */
var freeExports = typeof exports == 'object' && exports && !exports.nodeType && exports;

/** Detect free variable `module`. */
var freeModule = freeExports && typeof module == 'object' && module && !module.nodeType && module;

/** Detect the popular CommonJS extension `module.exports`. */
var moduleExports = freeModule && freeModule.exports === freeExports;

/** Built-in value references. */
var Buffer = moduleExports ? root.Buffer : undefined;

/* Built-in method references for those with the same name as other `lodash` methods. */
var nativeIsBuffer = Buffer ? Buffer.isBuffer : undefined;

/**
 * Checks if `value` is a buffer.
 *
 * @static
 * @memberOf _
 * @since 4.3.0
 * @category Lang
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is a buffer, else `false`.
 * @example
 *
 * _.isBuffer(new Buffer(2));
 * // => true
 *
 * _.isBuffer(new Uint8Array(2));
 * // => false
 */
var isBuffer = nativeIsBuffer || stubFalse;

/** Used as references for various `Number` constants. */
var MAX_SAFE_INTEGER$1 = 9007199254740991;

/** Used to detect unsigned integer values. */
var reIsUint = /^(?:0|[1-9]\d*)$/;

/**
 * Checks if `value` is a valid array-like index.
 *
 * @private
 * @param {*} value The value to check.
 * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index.
 * @returns {boolean} Returns `true` if `value` is a valid index, else `false`.
 */
function isIndex(value, length) {
  var type = typeof value;
  length = length == null ? MAX_SAFE_INTEGER$1 : length;

  return !!length &&
    (type == 'number' ||
      (type != 'symbol' && reIsUint.test(value))) &&
        (value > -1 && value % 1 == 0 && value < length);
}

/** `Object#toString` result references. */
var argsTag$1 = '[object Arguments]';
var arrayTag = '[object Array]';
var boolTag = '[object Boolean]';
var dateTag = '[object Date]';
var errorTag = '[object Error]';
var funcTag$1 = '[object Function]';
var mapTag = '[object Map]';
var numberTag = '[object Number]';
var objectTag = '[object Object]';
var regexpTag = '[object RegExp]';
var setTag = '[object Set]';
var stringTag = '[object String]';
var weakMapTag = '[object WeakMap]';

var arrayBufferTag = '[object ArrayBuffer]';
var dataViewTag = '[object DataView]';
var float32Tag = '[object Float32Array]';
var float64Tag = '[object Float64Array]';
var int8Tag = '[object Int8Array]';
var int16Tag = '[object Int16Array]';
var int32Tag = '[object Int32Array]';
var uint8Tag = '[object Uint8Array]';
var uint8ClampedTag = '[object Uint8ClampedArray]';
var uint16Tag = '[object Uint16Array]';
var uint32Tag = '[object Uint32Array]';

/** Used to identify `toStringTag` values of typed arrays. */
var typedArrayTags = {};
typedArrayTags[float32Tag] = typedArrayTags[float64Tag] =
typedArrayTags[int8Tag] = typedArrayTags[int16Tag] =
typedArrayTags[int32Tag] = typedArrayTags[uint8Tag] =
typedArrayTags[uint8ClampedTag] = typedArrayTags[uint16Tag] =
typedArrayTags[uint32Tag] = true;
typedArrayTags[argsTag$1] = typedArrayTags[arrayTag] =
typedArrayTags[arrayBufferTag] = typedArrayTags[boolTag] =
typedArrayTags[dataViewTag] = typedArrayTags[dateTag] =
typedArrayTags[errorTag] = typedArrayTags[funcTag$1] =
typedArrayTags[mapTag] = typedArrayTags[numberTag] =
typedArrayTags[objectTag] = typedArrayTags[regexpTag] =
typedArrayTags[setTag] = typedArrayTags[stringTag] =
typedArrayTags[weakMapTag] = false;

/**
 * The base implementation of `_.isTypedArray` without Node.js optimizations.
 *
 * @private
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is a typed array, else `false`.
 */
function baseIsTypedArray(value) {
  return isObjectLike(value) &&
    isLength(value.length) && !!typedArrayTags[baseGetTag(value)];
}

/**
 * The base implementation of `_.unary` without support for storing metadata.
 *
 * @private
 * @param {Function} func The function to cap arguments for.
 * @returns {Function} Returns the new capped function.
 */
function baseUnary(func) {
  return function(value) {
    return func(value);
  };
}

/** Detect free variable `exports`. */
var freeExports$1 = typeof exports == 'object' && exports && !exports.nodeType && exports;

/** Detect free variable `module`. */
var freeModule$1 = freeExports$1 && typeof module == 'object' && module && !module.nodeType && module;

/** Detect the popular CommonJS extension `module.exports`. */
var moduleExports$1 = freeModule$1 && freeModule$1.exports === freeExports$1;

/** Detect free variable `process` from Node.js. */
var freeProcess = moduleExports$1 && freeGlobal.process;

/** Used to access faster Node.js helpers. */
var nodeUtil = (function() {
  try {
    // Use `util.types` for Node.js 10+.
    var types = freeModule$1 && freeModule$1.require && freeModule$1.require('util').types;

    if (types) {
      return types;
    }

    // Legacy `process.binding('util')` for Node.js < 10.
    return freeProcess && freeProcess.binding && freeProcess.binding('util');
  } catch (e) {}
}());

/* Node.js helper references. */
var nodeIsTypedArray = nodeUtil && nodeUtil.isTypedArray;

/**
 * Checks if `value` is classified as a typed array.
 *
 * @static
 * @memberOf _
 * @since 3.0.0
 * @category Lang
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is a typed array, else `false`.
 * @example
 *
 * _.isTypedArray(new Uint8Array);
 * // => true
 *
 * _.isTypedArray([]);
 * // => false
 */
var isTypedArray = nodeIsTypedArray ? baseUnary(nodeIsTypedArray) : baseIsTypedArray;

/** Used for built-in method references. */
var objectProto$2 = Object.prototype;

/** Used to check objects for own properties. */
var hasOwnProperty$1 = objectProto$2.hasOwnProperty;

/**
 * Creates an array of the enumerable property names of the array-like `value`.
 *
 * @private
 * @param {*} value The value to query.
 * @param {boolean} inherited Specify returning inherited property names.
 * @returns {Array} Returns the array of property names.
 */
function arrayLikeKeys(value, inherited) {
  var isArr = isArray(value),
      isArg = !isArr && isArguments(value),
      isBuff = !isArr && !isArg && isBuffer(value),
      isType = !isArr && !isArg && !isBuff && isTypedArray(value),
      skipIndexes = isArr || isArg || isBuff || isType,
      result = skipIndexes ? baseTimes(value.length, String) : [],
      length = result.length;

  for (var key in value) {
    if ((inherited || hasOwnProperty$1.call(value, key)) &&
        !(skipIndexes && (
           // Safari 9 has enumerable `arguments.length` in strict mode.
           key == 'length' ||
           // Node.js 0.10 has enumerable non-index properties on buffers.
           (isBuff && (key == 'offset' || key == 'parent')) ||
           // PhantomJS 2 has enumerable non-index properties on typed arrays.
           (isType && (key == 'buffer' || key == 'byteLength' || key == 'byteOffset')) ||
           // Skip index properties.
           isIndex(key, length)
        ))) {
      result.push(key);
    }
  }
  return result;
}

/** Used for built-in method references. */
var objectProto$5 = Object.prototype;

/**
 * Checks if `value` is likely a prototype object.
 *
 * @private
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is a prototype, else `false`.
 */
function isPrototype(value) {
  var Ctor = value && value.constructor,
      proto = (typeof Ctor == 'function' && Ctor.prototype) || objectProto$5;

  return value === proto;
}

/**
 * Creates a unary function that invokes `func` with its argument transformed.
 *
 * @private
 * @param {Function} func The function to wrap.
 * @param {Function} transform The argument transform.
 * @returns {Function} Returns the new function.
 */
function overArg(func, transform) {
  return function(arg) {
    return func(transform(arg));
  };
}

/* Built-in method references for those with the same name as other `lodash` methods. */
var nativeKeys = overArg(Object.keys, Object);

/** Used for built-in method references. */
var objectProto$4 = Object.prototype;

/** Used to check objects for own properties. */
var hasOwnProperty$3 = objectProto$4.hasOwnProperty;

/**
 * The base implementation of `_.keys` which doesn't treat sparse arrays as dense.
 *
 * @private
 * @param {Object} object The object to query.
 * @returns {Array} Returns the array of property names.
 */
function baseKeys(object) {
  if (!isPrototype(object)) {
    return nativeKeys(object);
  }
  var result = [];
  for (var key in Object(object)) {
    if (hasOwnProperty$3.call(object, key) && key != 'constructor') {
      result.push(key);
    }
  }
  return result;
}

/**
 * Creates an array of the own enumerable property names of `object`.
 *
 * **Note:** Non-object values are coerced to objects. See the
 * [ES spec](http://ecma-international.org/ecma-262/7.0/#sec-object.keys)
 * for more details.
 *
 * @static
 * @since 0.1.0
 * @memberOf _
 * @category Object
 * @param {Object} object The object to query.
 * @returns {Array} Returns the array of property names.
 * @example
 *
 * function Foo() {
 *   this.a = 1;
 *   this.b = 2;
 * }
 *
 * Foo.prototype.c = 3;
 *
 * _.keys(new Foo);
 * // => ['a', 'b'] (iteration order is not guaranteed)
 *
 * _.keys('hi');
 * // => ['0', '1']
 */
function keys(object) {
  return isArrayLike(object) ? arrayLikeKeys(object) : baseKeys(object);
}

function createArrayIterator(coll) {
    var i = -1;
    var len = coll.length;
    return function next() {
        return ++i < len ? {value: coll[i], key: i} : null;
    }
}

function createES2015Iterator(iterator) {
    var i = -1;
    return function next() {
        var item = iterator.next();
        if (item.done)
            return null;
        i++;
        return {value: item.value, key: i};
    }
}

function createObjectIterator(obj) {
    var okeys = keys(obj);
    var i = -1;
    var len = okeys.length;
    return function next() {
        var key = okeys[++i];
        return i < len ? {value: obj[key], key: key} : null;
    };
}

function iterator(coll) {
    if (isArrayLike(coll)) {
        return createArrayIterator(coll);
    }

    var iterator = getIterator(coll);
    return iterator ? createES2015Iterator(iterator) : createObjectIterator(coll);
}

function onlyOnce(fn) {
    return function() {
        if (fn === null) throw new Error("Callback was already called.");
        var callFn = fn;
        fn = null;
        callFn.apply(this, arguments);
    };
}

function _eachOfLimit(limit) {
    return function (obj, iteratee, callback) {
        callback = once(callback || noop);
        if (limit <= 0 || !obj) {
            return callback(null);
        }
        var nextElem = iterator(obj);
        var done = false;
        var running = 0;
        var looping = false;

        function iterateeCallback(err, value) {
            running -= 1;
            if (err) {
                done = true;
                callback(err);
            }
            else if (value === breakLoop || (done && running <= 0)) {
                done = true;
                return callback(null);
            }
            else if (!looping) {
                replenish();
            }
        }

        function replenish () {
            looping = true;
            while (running < limit && !done) {
                var elem = nextElem();
                if (elem === null) {
                    done = true;
                    if (running <= 0) {
                        callback(null);
                    }
                    return;
                }
                running += 1;
                iteratee(elem.value, elem.key, onlyOnce(iterateeCallback));
            }
            looping = false;
        }

        replenish();
    };
}

/**
 * The same as [`eachOf`]{@link module:Collections.eachOf} but runs a maximum of `limit` async operations at a
 * time.
 *
 * @name eachOfLimit
 * @static
 * @memberOf module:Collections
 * @method
 * @see [async.eachOf]{@link module:Collections.eachOf}
 * @alias forEachOfLimit
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {number} limit - The maximum number of async operations at a time.
 * @param {AsyncFunction} iteratee - An async function to apply to each
 * item in `coll`. The `key` is the item's key, or index in the case of an
 * array.
 * Invoked with (item, key, callback).
 * @param {Function} [callback] - A callback which is called when all
 * `iteratee` functions have finished, or an error occurs. Invoked with (err).
 */
function eachOfLimit(coll, limit, iteratee, callback) {
    _eachOfLimit(limit)(coll, wrapAsync(iteratee), callback);
}

function doLimit(fn, limit) {
    return function (iterable, iteratee, callback) {
        return fn(iterable, limit, iteratee, callback);
    };
}

// eachOf implementation optimized for array-likes
function eachOfArrayLike(coll, iteratee, callback) {
    callback = once(callback || noop);
    var index = 0,
        completed = 0,
        length = coll.length;
    if (length === 0) {
        callback(null);
    }

    function iteratorCallback(err, value) {
        if (err) {
            callback(err);
        } else if ((++completed === length) || value === breakLoop) {
            callback(null);
        }
    }

    for (; index < length; index++) {
        iteratee(coll[index], index, onlyOnce(iteratorCallback));
    }
}

// a generic version of eachOf which can handle array, object, and iterator cases.
var eachOfGeneric = doLimit(eachOfLimit, Infinity);

/**
 * Like [`each`]{@link module:Collections.each}, except that it passes the key (or index) as the second argument
 * to the iteratee.
 *
 * @name eachOf
 * @static
 * @memberOf module:Collections
 * @method
 * @alias forEachOf
 * @category Collection
 * @see [async.each]{@link module:Collections.each}
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {AsyncFunction} iteratee - A function to apply to each
 * item in `coll`.
 * The `key` is the item's key, or index in the case of an array.
 * Invoked with (item, key, callback).
 * @param {Function} [callback] - A callback which is called when all
 * `iteratee` functions have finished, or an error occurs. Invoked with (err).
 * @example
 *
 * var obj = {dev: "/dev.json", test: "/test.json", prod: "/prod.json"};
 * var configs = {};
 *
 * async.forEachOf(obj, function (value, key, callback) {
 *     fs.readFile(__dirname + value, "utf8", function (err, data) {
 *         if (err) return callback(err);
 *         try {
 *             configs[key] = JSON.parse(data);
 *         } catch (e) {
 *             return callback(e);
 *         }
 *         callback();
 *     });
 * }, function (err) {
 *     if (err) console.error(err.message);
 *     // configs is now a map of JSON data
 *     doSomethingWith(configs);
 * });
 */
var eachOf = function(coll, iteratee, callback) {
    var eachOfImplementation = isArrayLike(coll) ? eachOfArrayLike : eachOfGeneric;
    eachOfImplementation(coll, wrapAsync(iteratee), callback);
};

function doParallel(fn) {
    return function (obj, iteratee, callback) {
        return fn(eachOf, obj, wrapAsync(iteratee), callback);
    };
}

function _asyncMap(eachfn, arr, iteratee, callback) {
    callback = callback || noop;
    arr = arr || [];
    var results = [];
    var counter = 0;
    var _iteratee = wrapAsync(iteratee);

    eachfn(arr, function (value, _, callback) {
        var index = counter++;
        _iteratee(value, function (err, v) {
            results[index] = v;
            callback(err);
        });
    }, function (err) {
        callback(err, results);
    });
}

/**
 * Produces a new collection of values by mapping each value in `coll` through
 * the `iteratee` function. The `iteratee` is called with an item from `coll`
 * and a callback for when it has finished processing. Each of these callback
 * takes 2 arguments: an `error`, and the transformed item from `coll`. If
 * `iteratee` passes an error to its callback, the main `callback` (for the
 * `map` function) is immediately called with the error.
 *
 * Note, that since this function applies the `iteratee` to each item in
 * parallel, there is no guarantee that the `iteratee` functions will complete
 * in order. However, the results array will be in the same order as the
 * original `coll`.
 *
 * If `map` is passed an Object, the results will be an Array.  The results
 * will roughly be in the order of the original Objects' keys (but this can
 * vary across JavaScript engines).
 *
 * @name map
 * @static
 * @memberOf module:Collections
 * @method
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {AsyncFunction} iteratee - An async function to apply to each item in
 * `coll`.
 * The iteratee should complete with the transformed item.
 * Invoked with (item, callback).
 * @param {Function} [callback] - A callback which is called when all `iteratee`
 * functions have finished, or an error occurs. Results is an Array of the
 * transformed items from the `coll`. Invoked with (err, results).
 * @example
 *
 * async.map(['file1','file2','file3'], fs.stat, function(err, results) {
 *     // results is now an array of stats for each file
 * });
 */
var map = doParallel(_asyncMap);

/**
 * Applies the provided arguments to each function in the array, calling
 * `callback` after all functions have completed. If you only provide the first
 * argument, `fns`, then it will return a function which lets you pass in the
 * arguments as if it were a single function call. If more arguments are
 * provided, `callback` is required while `args` is still optional.
 *
 * @name applyEach
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @category Control Flow
 * @param {Array|Iterable|Object} fns - A collection of {@link AsyncFunction}s
 * to all call with the same arguments
 * @param {...*} [args] - any number of separate arguments to pass to the
 * function.
 * @param {Function} [callback] - the final argument should be the callback,
 * called when all functions have completed processing.
 * @returns {Function} - If only the first argument, `fns`, is provided, it will
 * return a function which lets you pass in the arguments as if it were a single
 * function call. The signature is `(..args, callback)`. If invoked with any
 * arguments, `callback` is required.
 * @example
 *
 * async.applyEach([enableSearch, updateSchema], 'bucket', callback);
 *
 * // partial application example:
 * async.each(
 *     buckets,
 *     async.applyEach([enableSearch, updateSchema]),
 *     callback
 * );
 */
var applyEach = applyEach$1(map);

function doParallelLimit(fn) {
    return function (obj, limit, iteratee, callback) {
        return fn(_eachOfLimit(limit), obj, wrapAsync(iteratee), callback);
    };
}

/**
 * The same as [`map`]{@link module:Collections.map} but runs a maximum of `limit` async operations at a time.
 *
 * @name mapLimit
 * @static
 * @memberOf module:Collections
 * @method
 * @see [async.map]{@link module:Collections.map}
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {number} limit - The maximum number of async operations at a time.
 * @param {AsyncFunction} iteratee - An async function to apply to each item in
 * `coll`.
 * The iteratee should complete with the transformed item.
 * Invoked with (item, callback).
 * @param {Function} [callback] - A callback which is called when all `iteratee`
 * functions have finished, or an error occurs. Results is an array of the
 * transformed items from the `coll`. Invoked with (err, results).
 */
var mapLimit = doParallelLimit(_asyncMap);

/**
 * The same as [`map`]{@link module:Collections.map} but runs only a single async operation at a time.
 *
 * @name mapSeries
 * @static
 * @memberOf module:Collections
 * @method
 * @see [async.map]{@link module:Collections.map}
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {AsyncFunction} iteratee - An async function to apply to each item in
 * `coll`.
 * The iteratee should complete with the transformed item.
 * Invoked with (item, callback).
 * @param {Function} [callback] - A callback which is called when all `iteratee`
 * functions have finished, or an error occurs. Results is an array of the
 * transformed items from the `coll`. Invoked with (err, results).
 */
var mapSeries = doLimit(mapLimit, 1);

/**
 * The same as [`applyEach`]{@link module:ControlFlow.applyEach} but runs only a single async operation at a time.
 *
 * @name applyEachSeries
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @see [async.applyEach]{@link module:ControlFlow.applyEach}
 * @category Control Flow
 * @param {Array|Iterable|Object} fns - A collection of {@link AsyncFunction}s to all
 * call with the same arguments
 * @param {...*} [args] - any number of separate arguments to pass to the
 * function.
 * @param {Function} [callback] - the final argument should be the callback,
 * called when all functions have completed processing.
 * @returns {Function} - If only the first argument is provided, it will return
 * a function which lets you pass in the arguments as if it were a single
 * function call.
 */
var applyEachSeries = applyEach$1(mapSeries);

/**
 * A specialized version of `_.forEach` for arrays without support for
 * iteratee shorthands.
 *
 * @private
 * @param {Array} [array] The array to iterate over.
 * @param {Function} iteratee The function invoked per iteration.
 * @returns {Array} Returns `array`.
 */
function arrayEach(array, iteratee) {
  var index = -1,
      length = array == null ? 0 : array.length;

  while (++index < length) {
    if (iteratee(array[index], index, array) === false) {
      break;
    }
  }
  return array;
}

/**
 * Creates a base function for methods like `_.forIn` and `_.forOwn`.
 *
 * @private
 * @param {boolean} [fromRight] Specify iterating from right to left.
 * @returns {Function} Returns the new base function.
 */
function createBaseFor(fromRight) {
  return function(object, iteratee, keysFunc) {
    var index = -1,
        iterable = Object(object),
        props = keysFunc(object),
        length = props.length;

    while (length--) {
      var key = props[fromRight ? length : ++index];
      if (iteratee(iterable[key], key, iterable) === false) {
        break;
      }
    }
    return object;
  };
}

/**
 * The base implementation of `baseForOwn` which iterates over `object`
 * properties returned by `keysFunc` and invokes `iteratee` for each property.
 * Iteratee functions may exit iteration early by explicitly returning `false`.
 *
 * @private
 * @param {Object} object The object to iterate over.
 * @param {Function} iteratee The function invoked per iteration.
 * @param {Function} keysFunc The function to get the keys of `object`.
 * @returns {Object} Returns `object`.
 */
var baseFor = createBaseFor();

/**
 * The base implementation of `_.forOwn` without support for iteratee shorthands.
 *
 * @private
 * @param {Object} object The object to iterate over.
 * @param {Function} iteratee The function invoked per iteration.
 * @returns {Object} Returns `object`.
 */
function baseForOwn(object, iteratee) {
  return object && baseFor(object, iteratee, keys);
}

/**
 * The base implementation of `_.findIndex` and `_.findLastIndex` without
 * support for iteratee shorthands.
 *
 * @private
 * @param {Array} array The array to inspect.
 * @param {Function} predicate The function invoked per iteration.
 * @param {number} fromIndex The index to search from.
 * @param {boolean} [fromRight] Specify iterating from right to left.
 * @returns {number} Returns the index of the matched value, else `-1`.
 */
function baseFindIndex(array, predicate, fromIndex, fromRight) {
  var length = array.length,
      index = fromIndex + (fromRight ? 1 : -1);

  while ((fromRight ? index-- : ++index < length)) {
    if (predicate(array[index], index, array)) {
      return index;
    }
  }
  return -1;
}

/**
 * The base implementation of `_.isNaN` without support for number objects.
 *
 * @private
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is `NaN`, else `false`.
 */
function baseIsNaN(value) {
  return value !== value;
}

/**
 * A specialized version of `_.indexOf` which performs strict equality
 * comparisons of values, i.e. `===`.
 *
 * @private
 * @param {Array} array The array to inspect.
 * @param {*} value The value to search for.
 * @param {number} fromIndex The index to search from.
 * @returns {number} Returns the index of the matched value, else `-1`.
 */
function strictIndexOf(array, value, fromIndex) {
  var index = fromIndex - 1,
      length = array.length;

  while (++index < length) {
    if (array[index] === value) {
      return index;
    }
  }
  return -1;
}

/**
 * The base implementation of `_.indexOf` without `fromIndex` bounds checks.
 *
 * @private
 * @param {Array} array The array to inspect.
 * @param {*} value The value to search for.
 * @param {number} fromIndex The index to search from.
 * @returns {number} Returns the index of the matched value, else `-1`.
 */
function baseIndexOf(array, value, fromIndex) {
  return value === value
    ? strictIndexOf(array, value, fromIndex)
    : baseFindIndex(array, baseIsNaN, fromIndex);
}

/**
 * Determines the best order for running the {@link AsyncFunction}s in `tasks`, based on
 * their requirements. Each function can optionally depend on other functions
 * being completed first, and each function is run as soon as its requirements
 * are satisfied.
 *
 * If any of the {@link AsyncFunction}s pass an error to their callback, the `auto` sequence
 * will stop. Further tasks will not execute (so any other functions depending
 * on it will not run), and the main `callback` is immediately called with the
 * error.
 *
 * {@link AsyncFunction}s also receive an object containing the results of functions which
 * have completed so far as the first argument, if they have dependencies. If a
 * task function has no dependencies, it will only be passed a callback.
 *
 * @name auto
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @category Control Flow
 * @param {Object} tasks - An object. Each of its properties is either a
 * function or an array of requirements, with the {@link AsyncFunction} itself the last item
 * in the array. The object's key of a property serves as the name of the task
 * defined by that property, i.e. can be used when specifying requirements for
 * other tasks. The function receives one or two arguments:
 * * a `results` object, containing the results of the previously executed
 *   functions, only passed if the task has any dependencies,
 * * a `callback(err, result)` function, which must be called when finished,
 *   passing an `error` (which can be `null`) and the result of the function's
 *   execution.
 * @param {number} [concurrency=Infinity] - An optional `integer` for
 * determining the maximum number of tasks that can be run in parallel. By
 * default, as many as possible.
 * @param {Function} [callback] - An optional callback which is called when all
 * the tasks have been completed. It receives the `err` argument if any `tasks`
 * pass an error to their callback. Results are always returned; however, if an
 * error occurs, no further `tasks` will be performed, and the results object
 * will only contain partial results. Invoked with (err, results).
 * @returns undefined
 * @example
 *
 * async.auto({
 *     // this function will just be passed a callback
 *     readData: async.apply(fs.readFile, 'data.txt', 'utf-8'),
 *     showData: ['readData', function(results, cb) {
 *         // results.readData is the file's contents
 *         // ...
 *     }]
 * }, callback);
 *
 * async.auto({
 *     get_data: function(callback) {
 *         console.log('in get_data');
 *         // async code to get some data
 *         callback(null, 'data', 'converted to array');
 *     },
 *     make_folder: function(callback) {
 *         console.log('in make_folder');
 *         // async code to create a directory to store a file in
 *         // this is run at the same time as getting the data
 *         callback(null, 'folder');
 *     },
 *     write_file: ['get_data', 'make_folder', function(results, callback) {
 *         console.log('in write_file', JSON.stringify(results));
 *         // once there is some data and the directory exists,
 *         // write the data to a file in the directory
 *         callback(null, 'filename');
 *     }],
 *     email_link: ['write_file', function(results, callback) {
 *         console.log('in email_link', JSON.stringify(results));
 *         // once the file is written let's email a link to it...
 *         // results.write_file contains the filename returned by write_file.
 *         callback(null, {'file':results.write_file, 'email':'user@example.com'});
 *     }]
 * }, function(err, results) {
 *     console.log('err = ', err);
 *     console.log('results = ', results);
 * });
 */
var auto = function (tasks, concurrency, callback) {
    if (typeof concurrency === 'function') {
        // concurrency is optional, shift the args.
        callback = concurrency;
        concurrency = null;
    }
    callback = once(callback || noop);
    var keys$$1 = keys(tasks);
    var numTasks = keys$$1.length;
    if (!numTasks) {
        return callback(null);
    }
    if (!concurrency) {
        concurrency = numTasks;
    }

    var results = {};
    var runningTasks = 0;
    var hasError = false;

    var listeners = Object.create(null);

    var readyTasks = [];

    // for cycle detection:
    var readyToCheck = []; // tasks that have been identified as reachable
    // without the possibility of returning to an ancestor task
    var uncheckedDependencies = {};

    baseForOwn(tasks, function (task, key) {
        if (!isArray(task)) {
            // no dependencies
            enqueueTask(key, [task]);
            readyToCheck.push(key);
            return;
        }

        var dependencies = task.slice(0, task.length - 1);
        var remainingDependencies = dependencies.length;
        if (remainingDependencies === 0) {
            enqueueTask(key, task);
            readyToCheck.push(key);
            return;
        }
        uncheckedDependencies[key] = remainingDependencies;

        arrayEach(dependencies, function (dependencyName) {
            if (!tasks[dependencyName]) {
                throw new Error('async.auto task `' + key +
                    '` has a non-existent dependency `' +
                    dependencyName + '` in ' +
                    dependencies.join(', '));
            }
            addListener(dependencyName, function () {
                remainingDependencies--;
                if (remainingDependencies === 0) {
                    enqueueTask(key, task);
                }
            });
        });
    });

    checkForDeadlocks();
    processQueue();

    function enqueueTask(key, task) {
        readyTasks.push(function () {
            runTask(key, task);
        });
    }

    function processQueue() {
        if (readyTasks.length === 0 && runningTasks === 0) {
            return callback(null, results);
        }
        while(readyTasks.length && runningTasks < concurrency) {
            var run = readyTasks.shift();
            run();
        }

    }

    function addListener(taskName, fn) {
        var taskListeners = listeners[taskName];
        if (!taskListeners) {
            taskListeners = listeners[taskName] = [];
        }

        taskListeners.push(fn);
    }

    function taskComplete(taskName) {
        var taskListeners = listeners[taskName] || [];
        arrayEach(taskListeners, function (fn) {
            fn();
        });
        processQueue();
    }


    function runTask(key, task) {
        if (hasError) return;

        var taskCallback = onlyOnce(function(err, result) {
            runningTasks--;
            if (arguments.length > 2) {
                result = slice(arguments, 1);
            }
            if (err) {
                var safeResults = {};
                baseForOwn(results, function(val, rkey) {
                    safeResults[rkey] = val;
                });
                safeResults[key] = result;
                hasError = true;
                listeners = Object.create(null);

                callback(err, safeResults);
            } else {
                results[key] = result;
                taskComplete(key);
            }
        });

        runningTasks++;
        var taskFn = wrapAsync(task[task.length - 1]);
        if (task.length > 1) {
            taskFn(results, taskCallback);
        } else {
            taskFn(taskCallback);
        }
    }

    function checkForDeadlocks() {
        // Kahn's algorithm
        // https://en.wikipedia.org/wiki/Topological_sorting#Kahn.27s_algorithm
        // http://connalle.blogspot.com/2013/10/topological-sortingkahn-algorithm.html
        var currentTask;
        var counter = 0;
        while (readyToCheck.length) {
            currentTask = readyToCheck.pop();
            counter++;
            arrayEach(getDependents(currentTask), function (dependent) {
                if (--uncheckedDependencies[dependent] === 0) {
                    readyToCheck.push(dependent);
                }
            });
        }

        if (counter !== numTasks) {
            throw new Error(
                'async.auto cannot execute tasks due to a recursive dependency'
            );
        }
    }

    function getDependents(taskName) {
        var result = [];
        baseForOwn(tasks, function (task, key) {
            if (isArray(task) && baseIndexOf(task, taskName, 0) >= 0) {
                result.push(key);
            }
        });
        return result;
    }
};

/**
 * A specialized version of `_.map` for arrays without support for iteratee
 * shorthands.
 *
 * @private
 * @param {Array} [array] The array to iterate over.
 * @param {Function} iteratee The function invoked per iteration.
 * @returns {Array} Returns the new mapped array.
 */
function arrayMap(array, iteratee) {
  var index = -1,
      length = array == null ? 0 : array.length,
      result = Array(length);

  while (++index < length) {
    result[index] = iteratee(array[index], index, array);
  }
  return result;
}

/** `Object#toString` result references. */
var symbolTag = '[object Symbol]';

/**
 * Checks if `value` is classified as a `Symbol` primitive or object.
 *
 * @static
 * @memberOf _
 * @since 4.0.0
 * @category Lang
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is a symbol, else `false`.
 * @example
 *
 * _.isSymbol(Symbol.iterator);
 * // => true
 *
 * _.isSymbol('abc');
 * // => false
 */
function isSymbol(value) {
  return typeof value == 'symbol' ||
    (isObjectLike(value) && baseGetTag(value) == symbolTag);
}

/** Used as references for various `Number` constants. */
var INFINITY = 1 / 0;

/** Used to convert symbols to primitives and strings. */
var symbolProto = Symbol$1 ? Symbol$1.prototype : undefined;
var symbolToString = symbolProto ? symbolProto.toString : undefined;

/**
 * The base implementation of `_.toString` which doesn't convert nullish
 * values to empty strings.
 *
 * @private
 * @param {*} value The value to process.
 * @returns {string} Returns the string.
 */
function baseToString(value) {
  // Exit early for strings to avoid a performance hit in some environments.
  if (typeof value == 'string') {
    return value;
  }
  if (isArray(value)) {
    // Recursively convert values (susceptible to call stack limits).
    return arrayMap(value, baseToString) + '';
  }
  if (isSymbol(value)) {
    return symbolToString ? symbolToString.call(value) : '';
  }
  var result = (value + '');
  return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result;
}

/**
 * The base implementation of `_.slice` without an iteratee call guard.
 *
 * @private
 * @param {Array} array The array to slice.
 * @param {number} [start=0] The start position.
 * @param {number} [end=array.length] The end position.
 * @returns {Array} Returns the slice of `array`.
 */
function baseSlice(array, start, end) {
  var index = -1,
      length = array.length;

  if (start < 0) {
    start = -start > length ? 0 : (length + start);
  }
  end = end > length ? length : end;
  if (end < 0) {
    end += length;
  }
  length = start > end ? 0 : ((end - start) >>> 0);
  start >>>= 0;

  var result = Array(length);
  while (++index < length) {
    result[index] = array[index + start];
  }
  return result;
}

/**
 * Casts `array` to a slice if it's needed.
 *
 * @private
 * @param {Array} array The array to inspect.
 * @param {number} start The start position.
 * @param {number} [end=array.length] The end position.
 * @returns {Array} Returns the cast slice.
 */
function castSlice(array, start, end) {
  var length = array.length;
  end = end === undefined ? length : end;
  return (!start && end >= length) ? array : baseSlice(array, start, end);
}

/**
 * Used by `_.trim` and `_.trimEnd` to get the index of the last string symbol
 * that is not found in the character symbols.
 *
 * @private
 * @param {Array} strSymbols The string symbols to inspect.
 * @param {Array} chrSymbols The character symbols to find.
 * @returns {number} Returns the index of the last unmatched string symbol.
 */
function charsEndIndex(strSymbols, chrSymbols) {
  var index = strSymbols.length;

  while (index-- && baseIndexOf(chrSymbols, strSymbols[index], 0) > -1) {}
  return index;
}

/**
 * Used by `_.trim` and `_.trimStart` to get the index of the first string symbol
 * that is not found in the character symbols.
 *
 * @private
 * @param {Array} strSymbols The string symbols to inspect.
 * @param {Array} chrSymbols The character symbols to find.
 * @returns {number} Returns the index of the first unmatched string symbol.
 */
function charsStartIndex(strSymbols, chrSymbols) {
  var index = -1,
      length = strSymbols.length;

  while (++index < length && baseIndexOf(chrSymbols, strSymbols[index], 0) > -1) {}
  return index;
}

/**
 * Converts an ASCII `string` to an array.
 *
 * @private
 * @param {string} string The string to convert.
 * @returns {Array} Returns the converted array.
 */
function asciiToArray(string) {
  return string.split('');
}

/** Used to compose unicode character classes. */
var rsAstralRange = '\\ud800-\\udfff';
var rsComboMarksRange = '\\u0300-\\u036f';
var reComboHalfMarksRange = '\\ufe20-\\ufe2f';
var rsComboSymbolsRange = '\\u20d0-\\u20ff';
var rsComboRange = rsComboMarksRange + reComboHalfMarksRange + rsComboSymbolsRange;
var rsVarRange = '\\ufe0e\\ufe0f';

/** Used to compose unicode capture groups. */
var rsZWJ = '\\u200d';

/** Used to detect strings with [zero-width joiners or code points from the astral planes](http://eev.ee/blog/2015/09/12/dark-corners-of-unicode/). */
var reHasUnicode = RegExp('[' + rsZWJ + rsAstralRange  + rsComboRange + rsVarRange + ']');

/**
 * Checks if `string` contains Unicode symbols.
 *
 * @private
 * @param {string} string The string to inspect.
 * @returns {boolean} Returns `true` if a symbol is found, else `false`.
 */
function hasUnicode(string) {
  return reHasUnicode.test(string);
}

/** Used to compose unicode character classes. */
var rsAstralRange$1 = '\\ud800-\\udfff';
var rsComboMarksRange$1 = '\\u0300-\\u036f';
var reComboHalfMarksRange$1 = '\\ufe20-\\ufe2f';
var rsComboSymbolsRange$1 = '\\u20d0-\\u20ff';
var rsComboRange$1 = rsComboMarksRange$1 + reComboHalfMarksRange$1 + rsComboSymbolsRange$1;
var rsVarRange$1 = '\\ufe0e\\ufe0f';

/** Used to compose unicode capture groups. */
var rsAstral = '[' + rsAstralRange$1 + ']';
var rsCombo = '[' + rsComboRange$1 + ']';
var rsFitz = '\\ud83c[\\udffb-\\udfff]';
var rsModifier = '(?:' + rsCombo + '|' + rsFitz + ')';
var rsNonAstral = '[^' + rsAstralRange$1 + ']';
var rsRegional = '(?:\\ud83c[\\udde6-\\uddff]){2}';
var rsSurrPair = '[\\ud800-\\udbff][\\udc00-\\udfff]';
var rsZWJ$1 = '\\u200d';

/** Used to compose unicode regexes. */
var reOptMod = rsModifier + '?';
var rsOptVar = '[' + rsVarRange$1 + ']?';
var rsOptJoin = '(?:' + rsZWJ$1 + '(?:' + [rsNonAstral, rsRegional, rsSurrPair].join('|') + ')' + rsOptVar + reOptMod + ')*';
var rsSeq = rsOptVar + reOptMod + rsOptJoin;
var rsSymbol = '(?:' + [rsNonAstral + rsCombo + '?', rsCombo, rsRegional, rsSurrPair, rsAstral].join('|') + ')';

/** Used to match [string symbols](https://mathiasbynens.be/notes/javascript-unicode). */
var reUnicode = RegExp(rsFitz + '(?=' + rsFitz + ')|' + rsSymbol + rsSeq, 'g');

/**
 * Converts a Unicode `string` to an array.
 *
 * @private
 * @param {string} string The string to convert.
 * @returns {Array} Returns the converted array.
 */
function unicodeToArray(string) {
  return string.match(reUnicode) || [];
}

/**
 * Converts `string` to an array.
 *
 * @private
 * @param {string} string The string to convert.
 * @returns {Array} Returns the converted array.
 */
function stringToArray(string) {
  return hasUnicode(string)
    ? unicodeToArray(string)
    : asciiToArray(string);
}

/**
 * Converts `value` to a string. An empty string is returned for `null`
 * and `undefined` values. The sign of `-0` is preserved.
 *
 * @static
 * @memberOf _
 * @since 4.0.0
 * @category Lang
 * @param {*} value The value to convert.
 * @returns {string} Returns the converted string.
 * @example
 *
 * _.toString(null);
 * // => ''
 *
 * _.toString(-0);
 * // => '-0'
 *
 * _.toString([1, 2, 3]);
 * // => '1,2,3'
 */
function toString(value) {
  return value == null ? '' : baseToString(value);
}

/** Used to match leading and trailing whitespace. */
var reTrim = /^\s+|\s+$/g;

/**
 * Removes leading and trailing whitespace or specified characters from `string`.
 *
 * @static
 * @memberOf _
 * @since 3.0.0
 * @category String
 * @param {string} [string=''] The string to trim.
 * @param {string} [chars=whitespace] The characters to trim.
 * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
 * @returns {string} Returns the trimmed string.
 * @example
 *
 * _.trim('  abc  ');
 * // => 'abc'
 *
 * _.trim('-_-abc-_-', '_-');
 * // => 'abc'
 *
 * _.map(['  foo  ', '  bar  '], _.trim);
 * // => ['foo', 'bar']
 */
function trim(string, chars, guard) {
  string = toString(string);
  if (string && (guard || chars === undefined)) {
    return string.replace(reTrim, '');
  }
  if (!string || !(chars = baseToString(chars))) {
    return string;
  }
  var strSymbols = stringToArray(string),
      chrSymbols = stringToArray(chars),
      start = charsStartIndex(strSymbols, chrSymbols),
      end = charsEndIndex(strSymbols, chrSymbols) + 1;

  return castSlice(strSymbols, start, end).join('');
}

var FN_ARGS = /^(?:async\s+)?(function)?\s*[^\(]*\(\s*([^\)]*)\)/m;
var FN_ARG_SPLIT = /,/;
var FN_ARG = /(=.+)?(\s*)$/;
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;

function parseParams(func) {
    func = func.toString().replace(STRIP_COMMENTS, '');
    func = func.match(FN_ARGS)[2].replace(' ', '');
    func = func ? func.split(FN_ARG_SPLIT) : [];
    func = func.map(function (arg){
        return trim(arg.replace(FN_ARG, ''));
    });
    return func;
}

/**
 * A dependency-injected version of the [async.auto]{@link module:ControlFlow.auto} function. Dependent
 * tasks are specified as parameters to the function, after the usual callback
 * parameter, with the parameter names matching the names of the tasks it
 * depends on. This can provide even more readable task graphs which can be
 * easier to maintain.
 *
 * If a final callback is specified, the task results are similarly injected,
 * specified as named parameters after the initial error parameter.
 *
 * The autoInject function is purely syntactic sugar and its semantics are
 * otherwise equivalent to [async.auto]{@link module:ControlFlow.auto}.
 *
 * @name autoInject
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @see [async.auto]{@link module:ControlFlow.auto}
 * @category Control Flow
 * @param {Object} tasks - An object, each of whose properties is an {@link AsyncFunction} of
 * the form 'func([dependencies...], callback). The object's key of a property
 * serves as the name of the task defined by that property, i.e. can be used
 * when specifying requirements for other tasks.
 * * The `callback` parameter is a `callback(err, result)` which must be called
 *   when finished, passing an `error` (which can be `null`) and the result of
 *   the function's execution. The remaining parameters name other tasks on
 *   which the task is dependent, and the results from those tasks are the
 *   arguments of those parameters.
 * @param {Function} [callback] - An optional callback which is called when all
 * the tasks have been completed. It receives the `err` argument if any `tasks`
 * pass an error to their callback, and a `results` object with any completed
 * task results, similar to `auto`.
 * @example
 *
 * //  The example from `auto` can be rewritten as follows:
 * async.autoInject({
 *     get_data: function(callback) {
 *         // async code to get some data
 *         callback(null, 'data', 'converted to array');
 *     },
 *     make_folder: function(callback) {
 *         // async code to create a directory to store a file in
 *         // this is run at the same time as getting the data
 *         callback(null, 'folder');
 *     },
 *     write_file: function(get_data, make_folder, callback) {
 *         // once there is some data and the directory exists,
 *         // write the data to a file in the directory
 *         callback(null, 'filename');
 *     },
 *     email_link: function(write_file, callback) {
 *         // once the file is written let's email a link to it...
 *         // write_file contains the filename returned by write_file.
 *         callback(null, {'file':write_file, 'email':'user@example.com'});
 *     }
 * }, function(err, results) {
 *     console.log('err = ', err);
 *     console.log('email_link = ', results.email_link);
 * });
 *
 * // If you are using a JS minifier that mangles parameter names, `autoInject`
 * // will not work with plain functions, since the parameter names will be
 * // collapsed to a single letter identifier.  To work around this, you can
 * // explicitly specify the names of the parameters your task function needs
 * // in an array, similar to Angular.js dependency injection.
 *
 * // This still has an advantage over plain `auto`, since the results a task
 * // depends on are still spread into arguments.
 * async.autoInject({
 *     //...
 *     write_file: ['get_data', 'make_folder', function(get_data, make_folder, callback) {
 *         callback(null, 'filename');
 *     }],
 *     email_link: ['write_file', function(write_file, callback) {
 *         callback(null, {'file':write_file, 'email':'user@example.com'});
 *     }]
 *     //...
 * }, function(err, results) {
 *     console.log('err = ', err);
 *     console.log('email_link = ', results.email_link);
 * });
 */
function autoInject(tasks, callback) {
    var newTasks = {};

    baseForOwn(tasks, function (taskFn, key) {
        var params;
        var fnIsAsync = isAsync(taskFn);
        var hasNoDeps =
            (!fnIsAsync && taskFn.length === 1) ||
            (fnIsAsync && taskFn.length === 0);

        if (isArray(taskFn)) {
            params = taskFn.slice(0, -1);
            taskFn = taskFn[taskFn.length - 1];

            newTasks[key] = params.concat(params.length > 0 ? newTask : taskFn);
        } else if (hasNoDeps) {
            // no dependencies, use the function as-is
            newTasks[key] = taskFn;
        } else {
            params = parseParams(taskFn);
            if (taskFn.length === 0 && !fnIsAsync && params.length === 0) {
                throw new Error("autoInject task functions require explicit parameters.");
            }

            // remove callback param
            if (!fnIsAsync) params.pop();

            newTasks[key] = params.concat(newTask);
        }

        function newTask(results, taskCb) {
            var newArgs = arrayMap(params, function (name) {
                return results[name];
            });
            newArgs.push(taskCb);
            wrapAsync(taskFn).apply(null, newArgs);
        }
    });

    auto(newTasks, callback);
}

// Simple doubly linked list (https://en.wikipedia.org/wiki/Doubly_linked_list) implementation
// used for queues. This implementation assumes that the node provided by the user can be modified
// to adjust the next and last properties. We implement only the minimal functionality
// for queue support.
function DLL() {
    this.head = this.tail = null;
    this.length = 0;
}

function setInitial(dll, node) {
    dll.length = 1;
    dll.head = dll.tail = node;
}

DLL.prototype.removeLink = function(node) {
    if (node.prev) node.prev.next = node.next;
    else this.head = node.next;
    if (node.next) node.next.prev = node.prev;
    else this.tail = node.prev;

    node.prev = node.next = null;
    this.length -= 1;
    return node;
};

DLL.prototype.empty = function () {
    while(this.head) this.shift();
    return this;
};

DLL.prototype.insertAfter = function(node, newNode) {
    newNode.prev = node;
    newNode.next = node.next;
    if (node.next) node.next.prev = newNode;
    else this.tail = newNode;
    node.next = newNode;
    this.length += 1;
};

DLL.prototype.insertBefore = function(node, newNode) {
    newNode.prev = node.prev;
    newNode.next = node;
    if (node.prev) node.prev.next = newNode;
    else this.head = newNode;
    node.prev = newNode;
    this.length += 1;
};

DLL.prototype.unshift = function(node) {
    if (this.head) this.insertBefore(this.head, node);
    else setInitial(this, node);
};

DLL.prototype.push = function(node) {
    if (this.tail) this.insertAfter(this.tail, node);
    else setInitial(this, node);
};

DLL.prototype.shift = function() {
    return this.head && this.removeLink(this.head);
};

DLL.prototype.pop = function() {
    return this.tail && this.removeLink(this.tail);
};

DLL.prototype.toArray = function () {
    var arr = Array(this.length);
    var curr = this.head;
    for(var idx = 0; idx < this.length; idx++) {
        arr[idx] = curr.data;
        curr = curr.next;
    }
    return arr;
};

DLL.prototype.remove = function (testFn) {
    var curr = this.head;
    while(!!curr) {
        var next = curr.next;
        if (testFn(curr)) {
            this.removeLink(curr);
        }
        curr = next;
    }
    return this;
};

function queue(worker, concurrency, payload) {
    if (concurrency == null) {
        concurrency = 1;
    }
    else if(concurrency === 0) {
        throw new Error('Concurrency must not be zero');
    }

    var _worker = wrapAsync(worker);
    var numRunning = 0;
    var workersList = [];

    var processingScheduled = false;
    function _insert(data, insertAtFront, callback) {
        if (callback != null && typeof callback !== 'function') {
            throw new Error('task callback must be a function');
        }
        q.started = true;
        if (!isArray(data)) {
            data = [data];
        }
        if (data.length === 0 && q.idle()) {
            // call drain immediately if there are no tasks
            return setImmediate$1(function() {
                q.drain();
            });
        }

        for (var i = 0, l = data.length; i < l; i++) {
            var item = {
                data: data[i],
                callback: callback || noop
            };

            if (insertAtFront) {
                q._tasks.unshift(item);
            } else {
                q._tasks.push(item);
            }
        }

        if (!processingScheduled) {
            processingScheduled = true;
            setImmediate$1(function() {
                processingScheduled = false;
                q.process();
            });
        }
    }

    function _next(tasks) {
        return function(err){
            numRunning -= 1;

            for (var i = 0, l = tasks.length; i < l; i++) {
                var task = tasks[i];

                var index = baseIndexOf(workersList, task, 0);
                if (index === 0) {
                    workersList.shift();
                } else if (index > 0) {
                    workersList.splice(index, 1);
                }

                task.callback.apply(task, arguments);

                if (err != null) {
                    q.error(err, task.data);
                }
            }

            if (numRunning <= (q.concurrency - q.buffer) ) {
                q.unsaturated();
            }

            if (q.idle()) {
                q.drain();
            }
            q.process();
        };
    }

    var isProcessing = false;
    var q = {
        _tasks: new DLL(),
        concurrency: concurrency,
        payload: payload,
        saturated: noop,
        unsaturated:noop,
        buffer: concurrency / 4,
        empty: noop,
        drain: noop,
        error: noop,
        started: false,
        paused: false,
        push: function (data, callback) {
            _insert(data, false, callback);
        },
        kill: function () {
            q.drain = noop;
            q._tasks.empty();
        },
        unshift: function (data, callback) {
            _insert(data, true, callback);
        },
        remove: function (testFn) {
            q._tasks.remove(testFn);
        },
        process: function () {
            // Avoid trying to start too many processing operations. This can occur
            // when callbacks resolve synchronously (#1267).
            if (isProcessing) {
                return;
            }
            isProcessing = true;
            while(!q.paused && numRunning < q.concurrency && q._tasks.length){
                var tasks = [], data = [];
                var l = q._tasks.length;
                if (q.payload) l = Math.min(l, q.payload);
                for (var i = 0; i < l; i++) {
                    var node = q._tasks.shift();
                    tasks.push(node);
                    workersList.push(node);
                    data.push(node.data);
                }

                numRunning += 1;

                if (q._tasks.length === 0) {
                    q.empty();
                }

                if (numRunning === q.concurrency) {
                    q.saturated();
                }

                var cb = onlyOnce(_next(tasks));
                _worker(data, cb);
            }
            isProcessing = false;
        },
        length: function () {
            return q._tasks.length;
        },
        running: function () {
            return numRunning;
        },
        workersList: function () {
            return workersList;
        },
        idle: function() {
            return q._tasks.length + numRunning === 0;
        },
        pause: function () {
            q.paused = true;
        },
        resume: function () {
            if (q.paused === false) { return; }
            q.paused = false;
            setImmediate$1(q.process);
        }
    };
    return q;
}

/**
 * A cargo of tasks for the worker function to complete. Cargo inherits all of
 * the same methods and event callbacks as [`queue`]{@link module:ControlFlow.queue}.
 * @typedef {Object} CargoObject
 * @memberOf module:ControlFlow
 * @property {Function} length - A function returning the number of items
 * waiting to be processed. Invoke like `cargo.length()`.
 * @property {number} payload - An `integer` for determining how many tasks
 * should be process per round. This property can be changed after a `cargo` is
 * created to alter the payload on-the-fly.
 * @property {Function} push - Adds `task` to the `queue`. The callback is
 * called once the `worker` has finished processing the task. Instead of a
 * single task, an array of `tasks` can be submitted. The respective callback is
 * used for every task in the list. Invoke like `cargo.push(task, [callback])`.
 * @property {Function} saturated - A callback that is called when the
 * `queue.length()` hits the concurrency and further tasks will be queued.
 * @property {Function} empty - A callback that is called when the last item
 * from the `queue` is given to a `worker`.
 * @property {Function} drain - A callback that is called when the last item
 * from the `queue` has returned from the `worker`.
 * @property {Function} idle - a function returning false if there are items
 * waiting or being processed, or true if not. Invoke like `cargo.idle()`.
 * @property {Function} pause - a function that pauses the processing of tasks
 * until `resume()` is called. Invoke like `cargo.pause()`.
 * @property {Function} resume - a function that resumes the processing of
 * queued tasks when the queue is paused. Invoke like `cargo.resume()`.
 * @property {Function} kill - a function that removes the `drain` callback and
 * empties remaining tasks from the queue forcing it to go idle. Invoke like `cargo.kill()`.
 */

/**
 * Creates a `cargo` object with the specified payload. Tasks added to the
 * cargo will be processed altogether (up to the `payload` limit). If the
 * `worker` is in progress, the task is queued until it becomes available. Once
 * the `worker` has completed some tasks, each callback of those tasks is
 * called. Check out [these](https://camo.githubusercontent.com/6bbd36f4cf5b35a0f11a96dcd2e97711ffc2fb37/68747470733a2f2f662e636c6f75642e6769746875622e636f6d2f6173736574732f313637363837312f36383130382f62626330636662302d356632392d313165322d393734662d3333393763363464633835382e676966) [animations](https://camo.githubusercontent.com/f4810e00e1c5f5f8addbe3e9f49064fd5d102699/68747470733a2f2f662e636c6f75642e6769746875622e636f6d2f6173736574732f313637363837312f36383130312f38346339323036362d356632392d313165322d383134662d3964336430323431336266642e676966)
 * for how `cargo` and `queue` work.
 *
 * While [`queue`]{@link module:ControlFlow.queue} passes only one task to one of a group of workers
 * at a time, cargo passes an array of tasks to a single worker, repeating
 * when the worker is finished.
 *
 * @name cargo
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @see [async.queue]{@link module:ControlFlow.queue}
 * @category Control Flow
 * @param {AsyncFunction} worker - An asynchronous function for processing an array
 * of queued tasks. Invoked with `(tasks, callback)`.
 * @param {number} [payload=Infinity] - An optional `integer` for determining
 * how many tasks should be processed per round; if omitted, the default is
 * unlimited.
 * @returns {module:ControlFlow.CargoObject} A cargo object to manage the tasks. Callbacks can
 * attached as certain properties to listen for specific events during the
 * lifecycle of the cargo and inner queue.
 * @example
 *
 * // create a cargo object with payload 2
 * var cargo = async.cargo(function(tasks, callback) {
 *     for (var i=0; i<tasks.length; i++) {
 *         console.log('hello ' + tasks[i].name);
 *     }
 *     callback();
 * }, 2);
 *
 * // add some items
 * cargo.push({name: 'foo'}, function(err) {
 *     console.log('finished processing foo');
 * });
 * cargo.push({name: 'bar'}, function(err) {
 *     console.log('finished processing bar');
 * });
 * cargo.push({name: 'baz'}, function(err) {
 *     console.log('finished processing baz');
 * });
 */
function cargo(worker, payload) {
    return queue(worker, 1, payload);
}

/**
 * The same as [`eachOf`]{@link module:Collections.eachOf} but runs only a single async operation at a time.
 *
 * @name eachOfSeries
 * @static
 * @memberOf module:Collections
 * @method
 * @see [async.eachOf]{@link module:Collections.eachOf}
 * @alias forEachOfSeries
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {AsyncFunction} iteratee - An async function to apply to each item in
 * `coll`.
 * Invoked with (item, key, callback).
 * @param {Function} [callback] - A callback which is called when all `iteratee`
 * functions have finished, or an error occurs. Invoked with (err).
 */
var eachOfSeries = doLimit(eachOfLimit, 1);

/**
 * Reduces `coll` into a single value using an async `iteratee` to return each
 * successive step. `memo` is the initial state of the reduction. This function
 * only operates in series.
 *
 * For performance reasons, it may make sense to split a call to this function
 * into a parallel map, and then use the normal `Array.prototype.reduce` on the
 * results. This function is for situations where each step in the reduction
 * needs to be async; if you can get the data before reducing it, then it's
 * probably a good idea to do so.
 *
 * @name reduce
 * @static
 * @memberOf module:Collections
 * @method
 * @alias inject
 * @alias foldl
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {*} memo - The initial state of the reduction.
 * @param {AsyncFunction} iteratee - A function applied to each item in the
 * array to produce the next step in the reduction.
 * The `iteratee` should complete with the next state of the reduction.
 * If the iteratee complete with an error, the reduction is stopped and the
 * main `callback` is immediately called with the error.
 * Invoked with (memo, item, callback).
 * @param {Function} [callback] - A callback which is called after all the
 * `iteratee` functions have finished. Result is the reduced value. Invoked with
 * (err, result).
 * @example
 *
 * async.reduce([1,2,3], 0, function(memo, item, callback) {
 *     // pointless async:
 *     process.nextTick(function() {
 *         callback(null, memo + item)
 *     });
 * }, function(err, result) {
 *     // result is now equal to the last value of memo, which is 6
 * });
 */
function reduce(coll, memo, iteratee, callback) {
    callback = once(callback || noop);
    var _iteratee = wrapAsync(iteratee);
    eachOfSeries(coll, function(x, i, callback) {
        _iteratee(memo, x, function(err, v) {
            memo = v;
            callback(err);
        });
    }, function(err) {
        callback(err, memo);
    });
}

/**
 * Version of the compose function that is more natural to read. Each function
 * consumes the return value of the previous function. It is the equivalent of
 * [compose]{@link module:ControlFlow.compose} with the arguments reversed.
 *
 * Each function is executed with the `this` binding of the composed function.
 *
 * @name seq
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @see [async.compose]{@link module:ControlFlow.compose}
 * @category Control Flow
 * @param {...AsyncFunction} functions - the asynchronous functions to compose
 * @returns {Function} a function that composes the `functions` in order
 * @example
 *
 * // Requires lodash (or underscore), express3 and dresende's orm2.
 * // Part of an app, that fetches cats of the logged user.
 * // This example uses `seq` function to avoid overnesting and error
 * // handling clutter.
 * app.get('/cats', function(request, response) {
 *     var User = request.models.User;
 *     async.seq(
 *         _.bind(User.get, User),  // 'User.get' has signature (id, callback(err, data))
 *         function(user, fn) {
 *             user.getCats(fn);      // 'getCats' has signature (callback(err, data))
 *         }
 *     )(req.session.user_id, function (err, cats) {
 *         if (err) {
 *             console.error(err);
 *             response.json({ status: 'error', message: err.message });
 *         } else {
 *             response.json({ status: 'ok', message: 'Cats found', data: cats });
 *         }
 *     });
 * });
 */
function seq(/*...functions*/) {
    var _functions = arrayMap(arguments, wrapAsync);
    return function(/*...args*/) {
        var args = slice(arguments);
        var that = this;

        var cb = args[args.length - 1];
        if (typeof cb == 'function') {
            args.pop();
        } else {
            cb = noop;
        }

        reduce(_functions, args, function(newargs, fn, cb) {
            fn.apply(that, newargs.concat(function(err/*, ...nextargs*/) {
                var nextargs = slice(arguments, 1);
                cb(err, nextargs);
            }));
        },
        function(err, results) {
            cb.apply(that, [err].concat(results));
        });
    };
}

/**
 * Creates a function which is a composition of the passed asynchronous
 * functions. Each function consumes the return value of the function that
 * follows. Composing functions `f()`, `g()`, and `h()` would produce the result
 * of `f(g(h()))`, only this version uses callbacks to obtain the return values.
 *
 * Each function is executed with the `this` binding of the composed function.
 *
 * @name compose
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @category Control Flow
 * @param {...AsyncFunction} functions - the asynchronous functions to compose
 * @returns {Function} an asynchronous function that is the composed
 * asynchronous `functions`
 * @example
 *
 * function add1(n, callback) {
 *     setTimeout(function () {
 *         callback(null, n + 1);
 *     }, 10);
 * }
 *
 * function mul3(n, callback) {
 *     setTimeout(function () {
 *         callback(null, n * 3);
 *     }, 10);
 * }
 *
 * var add1mul3 = async.compose(mul3, add1);
 * add1mul3(4, function (err, result) {
 *     // result now equals 15
 * });
 */
var compose = function(/*...args*/) {
    return seq.apply(null, slice(arguments).reverse());
};

var _concat = Array.prototype.concat;

/**
 * The same as [`concat`]{@link module:Collections.concat} but runs a maximum of `limit` async operations at a time.
 *
 * @name concatLimit
 * @static
 * @memberOf module:Collections
 * @method
 * @see [async.concat]{@link module:Collections.concat}
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {number} limit - The maximum number of async operations at a time.
 * @param {AsyncFunction} iteratee - A function to apply to each item in `coll`,
 * which should use an array as its result. Invoked with (item, callback).
 * @param {Function} [callback] - A callback which is called after all the
 * `iteratee` functions have finished, or an error occurs. Results is an array
 * containing the concatenated results of the `iteratee` function. Invoked with
 * (err, results).
 */
var concatLimit = function(coll, limit, iteratee, callback) {
    callback = callback || noop;
    var _iteratee = wrapAsync(iteratee);
    mapLimit(coll, limit, function(val, callback) {
        _iteratee(val, function(err /*, ...args*/) {
            if (err) return callback(err);
            return callback(null, slice(arguments, 1));
        });
    }, function(err, mapResults) {
        var result = [];
        for (var i = 0; i < mapResults.length; i++) {
            if (mapResults[i]) {
                result = _concat.apply(result, mapResults[i]);
            }
        }

        return callback(err, result);
    });
};

/**
 * Applies `iteratee` to each item in `coll`, concatenating the results. Returns
 * the concatenated list. The `iteratee`s are called in parallel, and the
 * results are concatenated as they return. There is no guarantee that the
 * results array will be returned in the original order of `coll` passed to the
 * `iteratee` function.
 *
 * @name concat
 * @static
 * @memberOf module:Collections
 * @method
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {AsyncFunction} iteratee - A function to apply to each item in `coll`,
 * which should use an array as its result. Invoked with (item, callback).
 * @param {Function} [callback(err)] - A callback which is called after all the
 * `iteratee` functions have finished, or an error occurs. Results is an array
 * containing the concatenated results of the `iteratee` function. Invoked with
 * (err, results).
 * @example
 *
 * async.concat(['dir1','dir2','dir3'], fs.readdir, function(err, files) {
 *     // files is now a list of filenames that exist in the 3 directories
 * });
 */
var concat = doLimit(concatLimit, Infinity);

/**
 * The same as [`concat`]{@link module:Collections.concat} but runs only a single async operation at a time.
 *
 * @name concatSeries
 * @static
 * @memberOf module:Collections
 * @method
 * @see [async.concat]{@link module:Collections.concat}
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {AsyncFunction} iteratee - A function to apply to each item in `coll`.
 * The iteratee should complete with an array an array of results.
 * Invoked with (item, callback).
 * @param {Function} [callback(err)] - A callback which is called after all the
 * `iteratee` functions have finished, or an error occurs. Results is an array
 * containing the concatenated results of the `iteratee` function. Invoked with
 * (err, results).
 */
var concatSeries = doLimit(concatLimit, 1);

/**
 * Returns a function that when called, calls-back with the values provided.
 * Useful as the first function in a [`waterfall`]{@link module:ControlFlow.waterfall}, or for plugging values in to
 * [`auto`]{@link module:ControlFlow.auto}.
 *
 * @name constant
 * @static
 * @memberOf module:Utils
 * @method
 * @category Util
 * @param {...*} arguments... - Any number of arguments to automatically invoke
 * callback with.
 * @returns {AsyncFunction} Returns a function that when invoked, automatically
 * invokes the callback with the previous given arguments.
 * @example
 *
 * async.waterfall([
 *     async.constant(42),
 *     function (value, next) {
 *         // value === 42
 *     },
 *     //...
 * ], callback);
 *
 * async.waterfall([
 *     async.constant(filename, "utf8"),
 *     fs.readFile,
 *     function (fileData, next) {
 *         //...
 *     }
 *     //...
 * ], callback);
 *
 * async.auto({
 *     hostname: async.constant("https://server.net/"),
 *     port: findFreePort,
 *     launchServer: ["hostname", "port", function (options, cb) {
 *         startServer(options, cb);
 *     }],
 *     //...
 * }, callback);
 */
var constant = function(/*...values*/) {
    var values = slice(arguments);
    var args = [null].concat(values);
    return function (/*...ignoredArgs, callback*/) {
        var callback = arguments[arguments.length - 1];
        return callback.apply(this, args);
    };
};

/**
 * This method returns the first argument it receives.
 *
 * @static
 * @since 0.1.0
 * @memberOf _
 * @category Util
 * @param {*} value Any value.
 * @returns {*} Returns `value`.
 * @example
 *
 * var object = { 'a': 1 };
 *
 * console.log(_.identity(object) === object);
 * // => true
 */
function identity(value) {
  return value;
}

function _createTester(check, getResult) {
    return function(eachfn, arr, iteratee, cb) {
        cb = cb || noop;
        var testPassed = false;
        var testResult;
        eachfn(arr, function(value, _, callback) {
            iteratee(value, function(err, result) {
                if (err) {
                    callback(err);
                } else if (check(result) && !testResult) {
                    testPassed = true;
                    testResult = getResult(true, value);
                    callback(null, breakLoop);
                } else {
                    callback();
                }
            });
        }, function(err) {
            if (err) {
                cb(err);
            } else {
                cb(null, testPassed ? testResult : getResult(false));
            }
        });
    };
}

function _findGetResult(v, x) {
    return x;
}

/**
 * Returns the first value in `coll` that passes an async truth test. The
 * `iteratee` is applied in parallel, meaning the first iteratee to return
 * `true` will fire the detect `callback` with that result. That means the
 * result might not be the first item in the original `coll` (in terms of order)
 * that passes the test.

 * If order within the original `coll` is important, then look at
 * [`detectSeries`]{@link module:Collections.detectSeries}.
 *
 * @name detect
 * @static
 * @memberOf module:Collections
 * @method
 * @alias find
 * @category Collections
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {AsyncFunction} iteratee - A truth test to apply to each item in `coll`.
 * The iteratee must complete with a boolean value as its result.
 * Invoked with (item, callback).
 * @param {Function} [callback] - A callback which is called as soon as any
 * iteratee returns `true`, or after all the `iteratee` functions have finished.
 * Result will be the first item in the array that passes the truth test
 * (iteratee) or the value `undefined` if none passed. Invoked with
 * (err, result).
 * @example
 *
 * async.detect(['file1','file2','file3'], function(filePath, callback) {
 *     fs.access(filePath, function(err) {
 *         callback(null, !err)
 *     });
 * }, function(err, result) {
 *     // result now equals the first file in the list that exists
 * });
 */
var detect = doParallel(_createTester(identity, _findGetResult));

/**
 * The same as [`detect`]{@link module:Collections.detect} but runs a maximum of `limit` async operations at a
 * time.
 *
 * @name detectLimit
 * @static
 * @memberOf module:Collections
 * @method
 * @see [async.detect]{@link module:Collections.detect}
 * @alias findLimit
 * @category Collections
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {number} limit - The maximum number of async operations at a time.
 * @param {AsyncFunction} iteratee - A truth test to apply to each item in `coll`.
 * The iteratee must complete with a boolean value as its result.
 * Invoked with (item, callback).
 * @param {Function} [callback] - A callback which is called as soon as any
 * iteratee returns `true`, or after all the `iteratee` functions have finished.
 * Result will be the first item in the array that passes the truth test
 * (iteratee) or the value `undefined` if none passed. Invoked with
 * (err, result).
 */
var detectLimit = doParallelLimit(_createTester(identity, _findGetResult));

/**
 * The same as [`detect`]{@link module:Collections.detect} but runs only a single async operation at a time.
 *
 * @name detectSeries
 * @static
 * @memberOf module:Collections
 * @method
 * @see [async.detect]{@link module:Collections.detect}
 * @alias findSeries
 * @category Collections
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {AsyncFunction} iteratee - A truth test to apply to each item in `coll`.
 * The iteratee must complete with a boolean value as its result.
 * Invoked with (item, callback).
 * @param {Function} [callback] - A callback which is called as soon as any
 * iteratee returns `true`, or after all the `iteratee` functions have finished.
 * Result will be the first item in the array that passes the truth test
 * (iteratee) or the value `undefined` if none passed. Invoked with
 * (err, result).
 */
var detectSeries = doLimit(detectLimit, 1);

function consoleFunc(name) {
    return function (fn/*, ...args*/) {
        var args = slice(arguments, 1);
        args.push(function (err/*, ...args*/) {
            var args = slice(arguments, 1);
            if (typeof console === 'object') {
                if (err) {
                    if (console.error) {
                        console.error(err);
                    }
                } else if (console[name]) {
                    arrayEach(args, function (x) {
                        console[name](x);
                    });
                }
            }
        });
        wrapAsync(fn).apply(null, args);
    };
}

/**
 * Logs the result of an [`async` function]{@link AsyncFunction} to the
 * `console` using `console.dir` to display the properties of the resulting object.
 * Only works in Node.js or in browsers that support `console.dir` and
 * `console.error` (such as FF and Chrome).
 * If multiple arguments are returned from the async function,
 * `console.dir` is called on each argument in order.
 *
 * @name dir
 * @static
 * @memberOf module:Utils
 * @method
 * @category Util
 * @param {AsyncFunction} function - The function you want to eventually apply
 * all arguments to.
 * @param {...*} arguments... - Any number of arguments to apply to the function.
 * @example
 *
 * // in a module
 * var hello = function(name, callback) {
 *     setTimeout(function() {
 *         callback(null, {hello: name});
 *     }, 1000);
 * };
 *
 * // in the node repl
 * node> async.dir(hello, 'world');
 * {hello: 'world'}
 */
var dir = consoleFunc('dir');

/**
 * The post-check version of [`during`]{@link module:ControlFlow.during}. To reflect the difference in
 * the order of operations, the arguments `test` and `fn` are switched.
 *
 * Also a version of [`doWhilst`]{@link module:ControlFlow.doWhilst} with asynchronous `test` function.
 * @name doDuring
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @see [async.during]{@link module:ControlFlow.during}
 * @category Control Flow
 * @param {AsyncFunction} fn - An async function which is called each time
 * `test` passes. Invoked with (callback).
 * @param {AsyncFunction} test - asynchronous truth test to perform before each
 * execution of `fn`. Invoked with (...args, callback), where `...args` are the
 * non-error args from the previous callback of `fn`.
 * @param {Function} [callback] - A callback which is called after the test
 * function has failed and repeated execution of `fn` has stopped. `callback`
 * will be passed an error if one occurred, otherwise `null`.
 */
function doDuring(fn, test, callback) {
    callback = onlyOnce(callback || noop);
    var _fn = wrapAsync(fn);
    var _test = wrapAsync(test);

    function next(err/*, ...args*/) {
        if (err) return callback(err);
        var args = slice(arguments, 1);
        args.push(check);
        _test.apply(this, args);
    }

    function check(err, truth) {
        if (err) return callback(err);
        if (!truth) return callback(null);
        _fn(next);
    }

    check(null, true);

}

/**
 * The post-check version of [`whilst`]{@link module:ControlFlow.whilst}. To reflect the difference in
 * the order of operations, the arguments `test` and `iteratee` are switched.
 *
 * `doWhilst` is to `whilst` as `do while` is to `while` in plain JavaScript.
 *
 * @name doWhilst
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @see [async.whilst]{@link module:ControlFlow.whilst}
 * @category Control Flow
 * @param {AsyncFunction} iteratee - A function which is called each time `test`
 * passes. Invoked with (callback).
 * @param {Function} test - synchronous truth test to perform after each
 * execution of `iteratee`. Invoked with any non-error callback results of
 * `iteratee`.
 * @param {Function} [callback] - A callback which is called after the test
 * function has failed and repeated execution of `iteratee` has stopped.
 * `callback` will be passed an error and any arguments passed to the final
 * `iteratee`'s callback. Invoked with (err, [results]);
 */
function doWhilst(iteratee, test, callback) {
    callback = onlyOnce(callback || noop);
    var _iteratee = wrapAsync(iteratee);
    var next = function(err/*, ...args*/) {
        if (err) return callback(err);
        var args = slice(arguments, 1);
        if (test.apply(this, args)) return _iteratee(next);
        callback.apply(null, [null].concat(args));
    };
    _iteratee(next);
}

/**
 * Like ['doWhilst']{@link module:ControlFlow.doWhilst}, except the `test` is inverted. Note the
 * argument ordering differs from `until`.
 *
 * @name doUntil
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @see [async.doWhilst]{@link module:ControlFlow.doWhilst}
 * @category Control Flow
 * @param {AsyncFunction} iteratee - An async function which is called each time
 * `test` fails. Invoked with (callback).
 * @param {Function} test - synchronous truth test to perform after each
 * execution of `iteratee`. Invoked with any non-error callback results of
 * `iteratee`.
 * @param {Function} [callback] - A callback which is called after the test
 * function has passed and repeated execution of `iteratee` has stopped. `callback`
 * will be passed an error and any arguments passed to the final `iteratee`'s
 * callback. Invoked with (err, [results]);
 */
function doUntil(iteratee, test, callback) {
    doWhilst(iteratee, function() {
        return !test.apply(this, arguments);
    }, callback);
}

/**
 * Like [`whilst`]{@link module:ControlFlow.whilst}, except the `test` is an asynchronous function that
 * is passed a callback in the form of `function (err, truth)`. If error is
 * passed to `test` or `fn`, the main callback is immediately called with the
 * value of the error.
 *
 * @name during
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @see [async.whilst]{@link module:ControlFlow.whilst}
 * @category Control Flow
 * @param {AsyncFunction} test - asynchronous truth test to perform before each
 * execution of `fn`. Invoked with (callback).
 * @param {AsyncFunction} fn - An async function which is called each time
 * `test` passes. Invoked with (callback).
 * @param {Function} [callback] - A callback which is called after the test
 * function has failed and repeated execution of `fn` has stopped. `callback`
 * will be passed an error, if one occurred, otherwise `null`.
 * @example
 *
 * var count = 0;
 *
 * async.during(
 *     function (callback) {
 *         return callback(null, count < 5);
 *     },
 *     function (callback) {
 *         count++;
 *         setTimeout(callback, 1000);
 *     },
 *     function (err) {
 *         // 5 seconds have passed
 *     }
 * );
 */
function during(test, fn, callback) {
    callback = onlyOnce(callback || noop);
    var _fn = wrapAsync(fn);
    var _test = wrapAsync(test);

    function next(err) {
        if (err) return callback(err);
        _test(check);
    }

    function check(err, truth) {
        if (err) return callback(err);
        if (!truth) return callback(null);
        _fn(next);
    }

    _test(check);
}

function _withoutIndex(iteratee) {
    return function (value, index, callback) {
        return iteratee(value, callback);
    };
}

/**
 * Applies the function `iteratee` to each item in `coll`, in parallel.
 * The `iteratee` is called with an item from the list, and a callback for when
 * it has finished. If the `iteratee` passes an error to its `callback`, the
 * main `callback` (for the `each` function) is immediately called with the
 * error.
 *
 * Note, that since this function applies `iteratee` to each item in parallel,
 * there is no guarantee that the iteratee functions will complete in order.
 *
 * @name each
 * @static
 * @memberOf module:Collections
 * @method
 * @alias forEach
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {AsyncFunction} iteratee - An async function to apply to
 * each item in `coll`. Invoked with (item, callback).
 * The array index is not passed to the iteratee.
 * If you need the index, use `eachOf`.
 * @param {Function} [callback] - A callback which is called when all
 * `iteratee` functions have finished, or an error occurs. Invoked with (err).
 * @example
 *
 * // assuming openFiles is an array of file names and saveFile is a function
 * // to save the modified contents of that file:
 *
 * async.each(openFiles, saveFile, function(err){
 *   // if any of the saves produced an error, err would equal that error
 * });
 *
 * // assuming openFiles is an array of file names
 * async.each(openFiles, function(file, callback) {
 *
 *     // Perform operation on file here.
 *     console.log('Processing file ' + file);
 *
 *     if( file.length > 32 ) {
 *       console.log('This file name is too long');
 *       callback('File name too long');
 *     } else {
 *       // Do work to process file here
 *       console.log('File processed');
 *       callback();
 *     }
 * }, function(err) {
 *     // if any of the file processing produced an error, err would equal that error
 *     if( err ) {
 *       // One of the iterations produced an error.
 *       // All processing will now stop.
 *       console.log('A file failed to process');
 *     } else {
 *       console.log('All files have been processed successfully');
 *     }
 * });
 */
function eachLimit(coll, iteratee, callback) {
    eachOf(coll, _withoutIndex(wrapAsync(iteratee)), callback);
}

/**
 * The same as [`each`]{@link module:Collections.each} but runs a maximum of `limit` async operations at a time.
 *
 * @name eachLimit
 * @static
 * @memberOf module:Collections
 * @method
 * @see [async.each]{@link module:Collections.each}
 * @alias forEachLimit
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {number} limit - The maximum number of async operations at a time.
 * @param {AsyncFunction} iteratee - An async function to apply to each item in
 * `coll`.
 * The array index is not passed to the iteratee.
 * If you need the index, use `eachOfLimit`.
 * Invoked with (item, callback).
 * @param {Function} [callback] - A callback which is called when all
 * `iteratee` functions have finished, or an error occurs. Invoked with (err).
 */
function eachLimit$1(coll, limit, iteratee, callback) {
    _eachOfLimit(limit)(coll, _withoutIndex(wrapAsync(iteratee)), callback);
}

/**
 * The same as [`each`]{@link module:Collections.each} but runs only a single async operation at a time.
 *
 * @name eachSeries
 * @static
 * @memberOf module:Collections
 * @method
 * @see [async.each]{@link module:Collections.each}
 * @alias forEachSeries
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {AsyncFunction} iteratee - An async function to apply to each
 * item in `coll`.
 * The array index is not passed to the iteratee.
 * If you need the index, use `eachOfSeries`.
 * Invoked with (item, callback).
 * @param {Function} [callback] - A callback which is called when all
 * `iteratee` functions have finished, or an error occurs. Invoked with (err).
 */
var eachSeries = doLimit(eachLimit$1, 1);

/**
 * Wrap an async function and ensure it calls its callback on a later tick of
 * the event loop.  If the function already calls its callback on a next tick,
 * no extra deferral is added. This is useful for preventing stack overflows
 * (`RangeError: Maximum call stack size exceeded`) and generally keeping
 * [Zalgo](http://blog.izs.me/post/59142742143/designing-apis-for-asynchrony)
 * contained. ES2017 `async` functions are returned as-is -- they are immune
 * to Zalgo's corrupting influences, as they always resolve on a later tick.
 *
 * @name ensureAsync
 * @static
 * @memberOf module:Utils
 * @method
 * @category Util
 * @param {AsyncFunction} fn - an async function, one that expects a node-style
 * callback as its last argument.
 * @returns {AsyncFunction} Returns a wrapped function with the exact same call
 * signature as the function passed in.
 * @example
 *
 * function sometimesAsync(arg, callback) {
 *     if (cache[arg]) {
 *         return callback(null, cache[arg]); // this would be synchronous!!
 *     } else {
 *         doSomeIO(arg, callback); // this IO would be asynchronous
 *     }
 * }
 *
 * // this has a risk of stack overflows if many results are cached in a row
 * async.mapSeries(args, sometimesAsync, done);
 *
 * // this will defer sometimesAsync's callback if necessary,
 * // preventing stack overflows
 * async.mapSeries(args, async.ensureAsync(sometimesAsync), done);
 */
function ensureAsync(fn) {
    if (isAsync(fn)) return fn;
    return initialParams(function (args, callback) {
        var sync = true;
        args.push(function () {
            var innerArgs = arguments;
            if (sync) {
                setImmediate$1(function () {
                    callback.apply(null, innerArgs);
                });
            } else {
                callback.apply(null, innerArgs);
            }
        });
        fn.apply(this, args);
        sync = false;
    });
}

function notId(v) {
    return !v;
}

/**
 * Returns `true` if every element in `coll` satisfies an async test. If any
 * iteratee call returns `false`, the main `callback` is immediately called.
 *
 * @name every
 * @static
 * @memberOf module:Collections
 * @method
 * @alias all
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {AsyncFunction} iteratee - An async truth test to apply to each item
 * in the collection in parallel.
 * The iteratee must complete with a boolean result value.
 * Invoked with (item, callback).
 * @param {Function} [callback] - A callback which is called after all the
 * `iteratee` functions have finished. Result will be either `true` or `false`
 * depending on the values of the async tests. Invoked with (err, result).
 * @example
 *
 * async.every(['file1','file2','file3'], function(filePath, callback) {
 *     fs.access(filePath, function(err) {
 *         callback(null, !err)
 *     });
 * }, function(err, result) {
 *     // if result is true then every file exists
 * });
 */
var every = doParallel(_createTester(notId, notId));

/**
 * The same as [`every`]{@link module:Collections.every} but runs a maximum of `limit` async operations at a time.
 *
 * @name everyLimit
 * @static
 * @memberOf module:Collections
 * @method
 * @see [async.every]{@link module:Collections.every}
 * @alias allLimit
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {number} limit - The maximum number of async operations at a time.
 * @param {AsyncFunction} iteratee - An async truth test to apply to each item
 * in the collection in parallel.
 * The iteratee must complete with a boolean result value.
 * Invoked with (item, callback).
 * @param {Function} [callback] - A callback which is called after all the
 * `iteratee` functions have finished. Result will be either `true` or `false`
 * depending on the values of the async tests. Invoked with (err, result).
 */
var everyLimit = doParallelLimit(_createTester(notId, notId));

/**
 * The same as [`every`]{@link module:Collections.every} but runs only a single async operation at a time.
 *
 * @name everySeries
 * @static
 * @memberOf module:Collections
 * @method
 * @see [async.every]{@link module:Collections.every}
 * @alias allSeries
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {AsyncFunction} iteratee - An async truth test to apply to each item
 * in the collection in series.
 * The iteratee must complete with a boolean result value.
 * Invoked with (item, callback).
 * @param {Function} [callback] - A callback which is called after all the
 * `iteratee` functions have finished. Result will be either `true` or `false`
 * depending on the values of the async tests. Invoked with (err, result).
 */
var everySeries = doLimit(everyLimit, 1);

/**
 * The base implementation of `_.property` without support for deep paths.
 *
 * @private
 * @param {string} key The key of the property to get.
 * @returns {Function} Returns the new accessor function.
 */
function baseProperty(key) {
  return function(object) {
    return object == null ? undefined : object[key];
  };
}

function filterArray(eachfn, arr, iteratee, callback) {
    var truthValues = new Array(arr.length);
    eachfn(arr, function (x, index, callback) {
        iteratee(x, function (err, v) {
            truthValues[index] = !!v;
            callback(err);
        });
    }, function (err) {
        if (err) return callback(err);
        var results = [];
        for (var i = 0; i < arr.length; i++) {
            if (truthValues[i]) results.push(arr[i]);
        }
        callback(null, results);
    });
}

function filterGeneric(eachfn, coll, iteratee, callback) {
    var results = [];
    eachfn(coll, function (x, index, callback) {
        iteratee(x, function (err, v) {
            if (err) {
                callback(err);
            } else {
                if (v) {
                    results.push({index: index, value: x});
                }
                callback();
            }
        });
    }, function (err) {
        if (err) {
            callback(err);
        } else {
            callback(null, arrayMap(results.sort(function (a, b) {
                return a.index - b.index;
            }), baseProperty('value')));
        }
    });
}

function _filter(eachfn, coll, iteratee, callback) {
    var filter = isArrayLike(coll) ? filterArray : filterGeneric;
    filter(eachfn, coll, wrapAsync(iteratee), callback || noop);
}

/**
 * Returns a new array of all the values in `coll` which pass an async truth
 * test. This operation is performed in parallel, but the results array will be
 * in the same order as the original.
 *
 * @name filter
 * @static
 * @memberOf module:Collections
 * @method
 * @alias select
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {Function} iteratee - A truth test to apply to each item in `coll`.
 * The `iteratee` is passed a `callback(err, truthValue)`, which must be called
 * with a boolean argument once it has completed. Invoked with (item, callback).
 * @param {Function} [callback] - A callback which is called after all the
 * `iteratee` functions have finished. Invoked with (err, results).
 * @example
 *
 * async.filter(['file1','file2','file3'], function(filePath, callback) {
 *     fs.access(filePath, function(err) {
 *         callback(null, !err)
 *     });
 * }, function(err, results) {
 *     // results now equals an array of the existing files
 * });
 */
var filter = doParallel(_filter);

/**
 * The same as [`filter`]{@link module:Collections.filter} but runs a maximum of `limit` async operations at a
 * time.
 *
 * @name filterLimit
 * @static
 * @memberOf module:Collections
 * @method
 * @see [async.filter]{@link module:Collections.filter}
 * @alias selectLimit
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {number} limit - The maximum number of async operations at a time.
 * @param {Function} iteratee - A truth test to apply to each item in `coll`.
 * The `iteratee` is passed a `callback(err, truthValue)`, which must be called
 * with a boolean argument once it has completed. Invoked with (item, callback).
 * @param {Function} [callback] - A callback which is called after all the
 * `iteratee` functions have finished. Invoked with (err, results).
 */
var filterLimit = doParallelLimit(_filter);

/**
 * The same as [`filter`]{@link module:Collections.filter} but runs only a single async operation at a time.
 *
 * @name filterSeries
 * @static
 * @memberOf module:Collections
 * @method
 * @see [async.filter]{@link module:Collections.filter}
 * @alias selectSeries
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {Function} iteratee - A truth test to apply to each item in `coll`.
 * The `iteratee` is passed a `callback(err, truthValue)`, which must be called
 * with a boolean argument once it has completed. Invoked with (item, callback).
 * @param {Function} [callback] - A callback which is called after all the
 * `iteratee` functions have finished. Invoked with (err, results)
 */
var filterSeries = doLimit(filterLimit, 1);

/**
 * Calls the asynchronous function `fn` with a callback parameter that allows it
 * to call itself again, in series, indefinitely.

 * If an error is passed to the callback then `errback` is called with the
 * error, and execution stops, otherwise it will never be called.
 *
 * @name forever
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @category Control Flow
 * @param {AsyncFunction} fn - an async function to call repeatedly.
 * Invoked with (next).
 * @param {Function} [errback] - when `fn` passes an error to it's callback,
 * this function will be called, and execution stops. Invoked with (err).
 * @example
 *
 * async.forever(
 *     function(next) {
 *         // next is suitable for passing to things that need a callback(err [, whatever]);
 *         // it will result in this function being called again.
 *     },
 *     function(err) {
 *         // if next is called with a value in its first parameter, it will appear
 *         // in here as 'err', and execution will stop.
 *     }
 * );
 */
function forever(fn, errback) {
    var done = onlyOnce(errback || noop);
    var task = wrapAsync(ensureAsync(fn));

    function next(err) {
        if (err) return done(err);
        task(next);
    }
    next();
}

/**
 * The same as [`groupBy`]{@link module:Collections.groupBy} but runs a maximum of `limit` async operations at a time.
 *
 * @name groupByLimit
 * @static
 * @memberOf module:Collections
 * @method
 * @see [async.groupBy]{@link module:Collections.groupBy}
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {number} limit - The maximum number of async operations at a time.
 * @param {AsyncFunction} iteratee - An async function to apply to each item in
 * `coll`.
 * The iteratee should complete with a `key` to group the value under.
 * Invoked with (value, callback).
 * @param {Function} [callback] - A callback which is called when all `iteratee`
 * functions have finished, or an error occurs. Result is an `Object` whoses
 * properties are arrays of values which returned the corresponding key.
 */
var groupByLimit = function(coll, limit, iteratee, callback) {
    callback = callback || noop;
    var _iteratee = wrapAsync(iteratee);
    mapLimit(coll, limit, function(val, callback) {
        _iteratee(val, function(err, key) {
            if (err) return callback(err);
            return callback(null, {key: key, val: val});
        });
    }, function(err, mapResults) {
        var result = {};
        // from MDN, handle object having an `hasOwnProperty` prop
        var hasOwnProperty = Object.prototype.hasOwnProperty;

        for (var i = 0; i < mapResults.length; i++) {
            if (mapResults[i]) {
                var key = mapResults[i].key;
                var val = mapResults[i].val;

                if (hasOwnProperty.call(result, key)) {
                    result[key].push(val);
                } else {
                    result[key] = [val];
                }
            }
        }

        return callback(err, result);
    });
};

/**
 * Returns a new object, where each value corresponds to an array of items, from
 * `coll`, that returned the corresponding key. That is, the keys of the object
 * correspond to the values passed to the `iteratee` callback.
 *
 * Note: Since this function applies the `iteratee` to each item in parallel,
 * there is no guarantee that the `iteratee` functions will complete in order.
 * However, the values for each key in the `result` will be in the same order as
 * the original `coll`. For Objects, the values will roughly be in the order of
 * the original Objects' keys (but this can vary across JavaScript engines).
 *
 * @name groupBy
 * @static
 * @memberOf module:Collections
 * @method
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {AsyncFunction} iteratee - An async function to apply to each item in
 * `coll`.
 * The iteratee should complete with a `key` to group the value under.
 * Invoked with (value, callback).
 * @param {Function} [callback] - A callback which is called when all `iteratee`
 * functions have finished, or an error occurs. Result is an `Object` whoses
 * properties are arrays of values which returned the corresponding key.
 * @example
 *
 * async.groupBy(['userId1', 'userId2', 'userId3'], function(userId, callback) {
 *     db.findById(userId, function(err, user) {
 *         if (err) return callback(err);
 *         return callback(null, user.age);
 *     });
 * }, function(err, result) {
 *     // result is object containing the userIds grouped by age
 *     // e.g. { 30: ['userId1', 'userId3'], 42: ['userId2']};
 * });
 */
var groupBy = doLimit(groupByLimit, Infinity);

/**
 * The same as [`groupBy`]{@link module:Collections.groupBy} but runs only a single async operation at a time.
 *
 * @name groupBySeries
 * @static
 * @memberOf module:Collections
 * @method
 * @see [async.groupBy]{@link module:Collections.groupBy}
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {number} limit - The maximum number of async operations at a time.
 * @param {AsyncFunction} iteratee - An async function to apply to each item in
 * `coll`.
 * The iteratee should complete with a `key` to group the value under.
 * Invoked with (value, callback).
 * @param {Function} [callback] - A callback which is called when all `iteratee`
 * functions have finished, or an error occurs. Result is an `Object` whoses
 * properties are arrays of values which returned the corresponding key.
 */
var groupBySeries = doLimit(groupByLimit, 1);

/**
 * Logs the result of an `async` function to the `console`. Only works in
 * Node.js or in browsers that support `console.log` and `console.error` (such
 * as FF and Chrome). If multiple arguments are returned from the async
 * function, `console.log` is called on each argument in order.
 *
 * @name log
 * @static
 * @memberOf module:Utils
 * @method
 * @category Util
 * @param {AsyncFunction} function - The function you want to eventually apply
 * all arguments to.
 * @param {...*} arguments... - Any number of arguments to apply to the function.
 * @example
 *
 * // in a module
 * var hello = function(name, callback) {
 *     setTimeout(function() {
 *         callback(null, 'hello ' + name);
 *     }, 1000);
 * };
 *
 * // in the node repl
 * node> async.log(hello, 'world');
 * 'hello world'
 */
var log = consoleFunc('log');

/**
 * The same as [`mapValues`]{@link module:Collections.mapValues} but runs a maximum of `limit` async operations at a
 * time.
 *
 * @name mapValuesLimit
 * @static
 * @memberOf module:Collections
 * @method
 * @see [async.mapValues]{@link module:Collections.mapValues}
 * @category Collection
 * @param {Object} obj - A collection to iterate over.
 * @param {number} limit - The maximum number of async operations at a time.
 * @param {AsyncFunction} iteratee - A function to apply to each value and key
 * in `coll`.
 * The iteratee should complete with the transformed value as its result.
 * Invoked with (value, key, callback).
 * @param {Function} [callback] - A callback which is called when all `iteratee`
 * functions have finished, or an error occurs. `result` is a new object consisting
 * of each key from `obj`, with each transformed value on the right-hand side.
 * Invoked with (err, result).
 */
function mapValuesLimit(obj, limit, iteratee, callback) {
    callback = once(callback || noop);
    var newObj = {};
    var _iteratee = wrapAsync(iteratee);
    eachOfLimit(obj, limit, function(val, key, next) {
        _iteratee(val, key, function (err, result) {
            if (err) return next(err);
            newObj[key] = result;
            next();
        });
    }, function (err) {
        callback(err, newObj);
    });
}

/**
 * A relative of [`map`]{@link module:Collections.map}, designed for use with objects.
 *
 * Produces a new Object by mapping each value of `obj` through the `iteratee`
 * function. The `iteratee` is called each `value` and `key` from `obj` and a
 * callback for when it has finished processing. Each of these callbacks takes
 * two arguments: an `error`, and the transformed item from `obj`. If `iteratee`
 * passes an error to its callback, the main `callback` (for the `mapValues`
 * function) is immediately called with the error.
 *
 * Note, the order of the keys in the result is not guaranteed.  The keys will
 * be roughly in the order they complete, (but this is very engine-specific)
 *
 * @name mapValues
 * @static
 * @memberOf module:Collections
 * @method
 * @category Collection
 * @param {Object} obj - A collection to iterate over.
 * @param {AsyncFunction} iteratee - A function to apply to each value and key
 * in `coll`.
 * The iteratee should complete with the transformed value as its result.
 * Invoked with (value, key, callback).
 * @param {Function} [callback] - A callback which is called when all `iteratee`
 * functions have finished, or an error occurs. `result` is a new object consisting
 * of each key from `obj`, with each transformed value on the right-hand side.
 * Invoked with (err, result).
 * @example
 *
 * async.mapValues({
 *     f1: 'file1',
 *     f2: 'file2',
 *     f3: 'file3'
 * }, function (file, key, callback) {
 *   fs.stat(file, callback);
 * }, function(err, result) {
 *     // result is now a map of stats for each file, e.g.
 *     // {
 *     //     f1: [stats for file1],
 *     //     f2: [stats for file2],
 *     //     f3: [stats for file3]
 *     // }
 * });
 */

var mapValues = doLimit(mapValuesLimit, Infinity);

/**
 * The same as [`mapValues`]{@link module:Collections.mapValues} but runs only a single async operation at a time.
 *
 * @name mapValuesSeries
 * @static
 * @memberOf module:Collections
 * @method
 * @see [async.mapValues]{@link module:Collections.mapValues}
 * @category Collection
 * @param {Object} obj - A collection to iterate over.
 * @param {AsyncFunction} iteratee - A function to apply to each value and key
 * in `coll`.
 * The iteratee should complete with the transformed value as its result.
 * Invoked with (value, key, callback).
 * @param {Function} [callback] - A callback which is called when all `iteratee`
 * functions have finished, or an error occurs. `result` is a new object consisting
 * of each key from `obj`, with each transformed value on the right-hand side.
 * Invoked with (err, result).
 */
var mapValuesSeries = doLimit(mapValuesLimit, 1);

function has(obj, key) {
    return key in obj;
}

/**
 * Caches the results of an async function. When creating a hash to store
 * function results against, the callback is omitted from the hash and an
 * optional hash function can be used.
 *
 * If no hash function is specified, the first argument is used as a hash key,
 * which may work reasonably if it is a string or a data type that converts to a
 * distinct string. Note that objects and arrays will not behave reasonably.
 * Neither will cases where the other arguments are significant. In such cases,
 * specify your own hash function.
 *
 * The cache of results is exposed as the `memo` property of the function
 * returned by `memoize`.
 *
 * @name memoize
 * @static
 * @memberOf module:Utils
 * @method
 * @category Util
 * @param {AsyncFunction} fn - The async function to proxy and cache results from.
 * @param {Function} hasher - An optional function for generating a custom hash
 * for storing results. It has all the arguments applied to it apart from the
 * callback, and must be synchronous.
 * @returns {AsyncFunction} a memoized version of `fn`
 * @example
 *
 * var slow_fn = function(name, callback) {
 *     // do something
 *     callback(null, result);
 * };
 * var fn = async.memoize(slow_fn);
 *
 * // fn can now be used as if it were slow_fn
 * fn('some name', function() {
 *     // callback
 * });
 */
function memoize(fn, hasher) {
    var memo = Object.create(null);
    var queues = Object.create(null);
    hasher = hasher || identity;
    var _fn = wrapAsync(fn);
    var memoized = initialParams(function memoized(args, callback) {
        var key = hasher.apply(null, args);
        if (has(memo, key)) {
            setImmediate$1(function() {
                callback.apply(null, memo[key]);
            });
        } else if (has(queues, key)) {
            queues[key].push(callback);
        } else {
            queues[key] = [callback];
            _fn.apply(null, args.concat(function(/*args*/) {
                var args = slice(arguments);
                memo[key] = args;
                var q = queues[key];
                delete queues[key];
                for (var i = 0, l = q.length; i < l; i++) {
                    q[i].apply(null, args);
                }
            }));
        }
    });
    memoized.memo = memo;
    memoized.unmemoized = fn;
    return memoized;
}

/**
 * Calls `callback` on a later loop around the event loop. In Node.js this just
 * calls `process.nextTick`.  In the browser it will use `setImmediate` if
 * available, otherwise `setTimeout(callback, 0)`, which means other higher
 * priority events may precede the execution of `callback`.
 *
 * This is used internally for browser-compatibility purposes.
 *
 * @name nextTick
 * @static
 * @memberOf module:Utils
 * @method
 * @see [async.setImmediate]{@link module:Utils.setImmediate}
 * @category Util
 * @param {Function} callback - The function to call on a later loop around
 * the event loop. Invoked with (args...).
 * @param {...*} args... - any number of additional arguments to pass to the
 * callback on the next tick.
 * @example
 *
 * var call_order = [];
 * async.nextTick(function() {
 *     call_order.push('two');
 *     // call_order now equals ['one','two']
 * });
 * call_order.push('one');
 *
 * async.setImmediate(function (a, b, c) {
 *     // a, b, and c equal 1, 2, and 3
 * }, 1, 2, 3);
 */
var _defer$1;

if (hasNextTick) {
    _defer$1 = process.nextTick;
} else if (hasSetImmediate) {
    _defer$1 = setImmediate;
} else {
    _defer$1 = fallback;
}

var nextTick = wrap(_defer$1);

function _parallel(eachfn, tasks, callback) {
    callback = callback || noop;
    var results = isArrayLike(tasks) ? [] : {};

    eachfn(tasks, function (task, key, callback) {
        wrapAsync(task)(function (err, result) {
            if (arguments.length > 2) {
                result = slice(arguments, 1);
            }
            results[key] = result;
            callback(err);
        });
    }, function (err) {
        callback(err, results);
    });
}

/**
 * Run the `tasks` collection of functions in parallel, without waiting until
 * the previous function has completed. If any of the functions pass an error to
 * its callback, the main `callback` is immediately called with the value of the
 * error. Once the `tasks` have completed, the results are passed to the final
 * `callback` as an array.
 *
 * **Note:** `parallel` is about kicking-off I/O tasks in parallel, not about
 * parallel execution of code.  If your tasks do not use any timers or perform
 * any I/O, they will actually be executed in series.  Any synchronous setup
 * sections for each task will happen one after the other.  JavaScript remains
 * single-threaded.
 *
 * **Hint:** Use [`reflect`]{@link module:Utils.reflect} to continue the
 * execution of other tasks when a task fails.
 *
 * It is also possible to use an object instead of an array. Each property will
 * be run as a function and the results will be passed to the final `callback`
 * as an object instead of an array. This can be a more readable way of handling
 * results from {@link async.parallel}.
 *
 * @name parallel
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @category Control Flow
 * @param {Array|Iterable|Object} tasks - A collection of
 * [async functions]{@link AsyncFunction} to run.
 * Each async function can complete with any number of optional `result` values.
 * @param {Function} [callback] - An optional callback to run once all the
 * functions have completed successfully. This function gets a results array
 * (or object) containing all the result arguments passed to the task callbacks.
 * Invoked with (err, results).
 *
 * @example
 * async.parallel([
 *     function(callback) {
 *         setTimeout(function() {
 *             callback(null, 'one');
 *         }, 200);
 *     },
 *     function(callback) {
 *         setTimeout(function() {
 *             callback(null, 'two');
 *         }, 100);
 *     }
 * ],
 * // optional callback
 * function(err, results) {
 *     // the results array will equal ['one','two'] even though
 *     // the second function had a shorter timeout.
 * });
 *
 * // an example using an object instead of an array
 * async.parallel({
 *     one: function(callback) {
 *         setTimeout(function() {
 *             callback(null, 1);
 *         }, 200);
 *     },
 *     two: function(callback) {
 *         setTimeout(function() {
 *             callback(null, 2);
 *         }, 100);
 *     }
 * }, function(err, results) {
 *     // results is now equals to: {one: 1, two: 2}
 * });
 */
function parallelLimit(tasks, callback) {
    _parallel(eachOf, tasks, callback);
}

/**
 * The same as [`parallel`]{@link module:ControlFlow.parallel} but runs a maximum of `limit` async operations at a
 * time.
 *
 * @name parallelLimit
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @see [async.parallel]{@link module:ControlFlow.parallel}
 * @category Control Flow
 * @param {Array|Iterable|Object} tasks - A collection of
 * [async functions]{@link AsyncFunction} to run.
 * Each async function can complete with any number of optional `result` values.
 * @param {number} limit - The maximum number of async operations at a time.
 * @param {Function} [callback] - An optional callback to run once all the
 * functions have completed successfully. This function gets a results array
 * (or object) containing all the result arguments passed to the task callbacks.
 * Invoked with (err, results).
 */
function parallelLimit$1(tasks, limit, callback) {
    _parallel(_eachOfLimit(limit), tasks, callback);
}

/**
 * A queue of tasks for the worker function to complete.
 * @typedef {Object} QueueObject
 * @memberOf module:ControlFlow
 * @property {Function} length - a function returning the number of items
 * waiting to be processed. Invoke with `queue.length()`.
 * @property {boolean} started - a boolean indicating whether or not any
 * items have been pushed and processed by the queue.
 * @property {Function} running - a function returning the number of items
 * currently being processed. Invoke with `queue.running()`.
 * @property {Function} workersList - a function returning the array of items
 * currently being processed. Invoke with `queue.workersList()`.
 * @property {Function} idle - a function returning false if there are items
 * waiting or being processed, or true if not. Invoke with `queue.idle()`.
 * @property {number} concurrency - an integer for determining how many `worker`
 * functions should be run in parallel. This property can be changed after a
 * `queue` is created to alter the concurrency on-the-fly.
 * @property {Function} push - add a new task to the `queue`. Calls `callback`
 * once the `worker` has finished processing the task. Instead of a single task,
 * a `tasks` array can be submitted. The respective callback is used for every
 * task in the list. Invoke with `queue.push(task, [callback])`,
 * @property {Function} unshift - add a new task to the front of the `queue`.
 * Invoke with `queue.unshift(task, [callback])`.
 * @property {Function} remove - remove items from the queue that match a test
 * function.  The test function will be passed an object with a `data` property,
 * and a `priority` property, if this is a
 * [priorityQueue]{@link module:ControlFlow.priorityQueue} object.
 * Invoked with `queue.remove(testFn)`, where `testFn` is of the form
 * `function ({data, priority}) {}` and returns a Boolean.
 * @property {Function} saturated - a callback that is called when the number of
 * running workers hits the `concurrency` limit, and further tasks will be
 * queued.
 * @property {Function} unsaturated - a callback that is called when the number
 * of running workers is less than the `concurrency` & `buffer` limits, and
 * further tasks will not be queued.
 * @property {number} buffer - A minimum threshold buffer in order to say that
 * the `queue` is `unsaturated`.
 * @property {Function} empty - a callback that is called when the last item
 * from the `queue` is given to a `worker`.
 * @property {Function} drain - a callback that is called when the last item
 * from the `queue` has returned from the `worker`.
 * @property {Function} error - a callback that is called when a task errors.
 * Has the signature `function(error, task)`.
 * @property {boolean} paused - a boolean for determining whether the queue is
 * in a paused state.
 * @property {Function} pause - a function that pauses the processing of tasks
 * until `resume()` is called. Invoke with `queue.pause()`.
 * @property {Function} resume - a function that resumes the processing of
 * queued tasks when the queue is paused. Invoke with `queue.resume()`.
 * @property {Function} kill - a function that removes the `drain` callback and
 * empties remaining tasks from the queue forcing it to go idle. No more tasks
 * should be pushed to the queue after calling this function. Invoke with `queue.kill()`.
 */

/**
 * Creates a `queue` object with the specified `concurrency`. Tasks added to the
 * `queue` are processed in parallel (up to the `concurrency` limit). If all
 * `worker`s are in progress, the task is queued until one becomes available.
 * Once a `worker` completes a `task`, that `task`'s callback is called.
 *
 * @name queue
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @category Control Flow
 * @param {AsyncFunction} worker - An async function for processing a queued task.
 * If you want to handle errors from an individual task, pass a callback to
 * `q.push()`. Invoked with (task, callback).
 * @param {number} [concurrency=1] - An `integer` for determining how many
 * `worker` functions should be run in parallel.  If omitted, the concurrency
 * defaults to `1`.  If the concurrency is `0`, an error is thrown.
 * @returns {module:ControlFlow.QueueObject} A queue object to manage the tasks. Callbacks can
 * attached as certain properties to listen for specific events during the
 * lifecycle of the queue.
 * @example
 *
 * // create a queue object with concurrency 2
 * var q = async.queue(function(task, callback) {
 *     console.log('hello ' + task.name);
 *     callback();
 * }, 2);
 *
 * // assign a callback
 * q.drain = function() {
 *     console.log('all items have been processed');
 * };
 *
 * // add some items to the queue
 * q.push({name: 'foo'}, function(err) {
 *     console.log('finished processing foo');
 * });
 * q.push({name: 'bar'}, function (err) {
 *     console.log('finished processing bar');
 * });
 *
 * // add some items to the queue (batch-wise)
 * q.push([{name: 'baz'},{name: 'bay'},{name: 'bax'}], function(err) {
 *     console.log('finished processing item');
 * });
 *
 * // add some items to the front of the queue
 * q.unshift({name: 'bar'}, function (err) {
 *     console.log('finished processing bar');
 * });
 */
var queue$1 = function (worker, concurrency) {
    var _worker = wrapAsync(worker);
    return queue(function (items, cb) {
        _worker(items[0], cb);
    }, concurrency, 1);
};

/**
 * The same as [async.queue]{@link module:ControlFlow.queue} only tasks are assigned a priority and
 * completed in ascending priority order.
 *
 * @name priorityQueue
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @see [async.queue]{@link module:ControlFlow.queue}
 * @category Control Flow
 * @param {AsyncFunction} worker - An async function for processing a queued task.
 * If you want to handle errors from an individual task, pass a callback to
 * `q.push()`.
 * Invoked with (task, callback).
 * @param {number} concurrency - An `integer` for determining how many `worker`
 * functions should be run in parallel.  If omitted, the concurrency defaults to
 * `1`.  If the concurrency is `0`, an error is thrown.
 * @returns {module:ControlFlow.QueueObject} A priorityQueue object to manage the tasks. There are two
 * differences between `queue` and `priorityQueue` objects:
 * * `push(task, priority, [callback])` - `priority` should be a number. If an
 *   array of `tasks` is given, all tasks will be assigned the same priority.
 * * The `unshift` method was removed.
 */
var priorityQueue = function(worker, concurrency) {
    // Start with a normal queue
    var q = queue$1(worker, concurrency);

    // Override push to accept second parameter representing priority
    q.push = function(data, priority, callback) {
        if (callback == null) callback = noop;
        if (typeof callback !== 'function') {
            throw new Error('task callback must be a function');
        }
        q.started = true;
        if (!isArray(data)) {
            data = [data];
        }
        if (data.length === 0) {
            // call drain immediately if there are no tasks
            return setImmediate$1(function() {
                q.drain();
            });
        }

        priority = priority || 0;
        var nextNode = q._tasks.head;
        while (nextNode && priority >= nextNode.priority) {
            nextNode = nextNode.next;
        }

        for (var i = 0, l = data.length; i < l; i++) {
            var item = {
                data: data[i],
                priority: priority,
                callback: callback
            };

            if (nextNode) {
                q._tasks.insertBefore(nextNode, item);
            } else {
                q._tasks.push(item);
            }
        }
        setImmediate$1(q.process);
    };

    // Remove unshift function
    delete q.unshift;

    return q;
};

/**
 * Runs the `tasks` array of functions in parallel, without waiting until the
 * previous function has completed. Once any of the `tasks` complete or pass an
 * error to its callback, the main `callback` is immediately called. It's
 * equivalent to `Promise.race()`.
 *
 * @name race
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @category Control Flow
 * @param {Array} tasks - An array containing [async functions]{@link AsyncFunction}
 * to run. Each function can complete with an optional `result` value.
 * @param {Function} callback - A callback to run once any of the functions have
 * completed. This function gets an error or result from the first function that
 * completed. Invoked with (err, result).
 * @returns undefined
 * @example
 *
 * async.race([
 *     function(callback) {
 *         setTimeout(function() {
 *             callback(null, 'one');
 *         }, 200);
 *     },
 *     function(callback) {
 *         setTimeout(function() {
 *             callback(null, 'two');
 *         }, 100);
 *     }
 * ],
 * // main callback
 * function(err, result) {
 *     // the result will be equal to 'two' as it finishes earlier
 * });
 */
function race(tasks, callback) {
    callback = once(callback || noop);
    if (!isArray(tasks)) return callback(new TypeError('First argument to race must be an array of functions'));
    if (!tasks.length) return callback();
    for (var i = 0, l = tasks.length; i < l; i++) {
        wrapAsync(tasks[i])(callback);
    }
}

/**
 * Same as [`reduce`]{@link module:Collections.reduce}, only operates on `array` in reverse order.
 *
 * @name reduceRight
 * @static
 * @memberOf module:Collections
 * @method
 * @see [async.reduce]{@link module:Collections.reduce}
 * @alias foldr
 * @category Collection
 * @param {Array} array - A collection to iterate over.
 * @param {*} memo - The initial state of the reduction.
 * @param {AsyncFunction} iteratee - A function applied to each item in the
 * array to produce the next step in the reduction.
 * The `iteratee` should complete with the next state of the reduction.
 * If the iteratee complete with an error, the reduction is stopped and the
 * main `callback` is immediately called with the error.
 * Invoked with (memo, item, callback).
 * @param {Function} [callback] - A callback which is called after all the
 * `iteratee` functions have finished. Result is the reduced value. Invoked with
 * (err, result).
 */
function reduceRight (array, memo, iteratee, callback) {
    var reversed = slice(array).reverse();
    reduce(reversed, memo, iteratee, callback);
}

/**
 * Wraps the async function in another function that always completes with a
 * result object, even when it errors.
 *
 * The result object has either the property `error` or `value`.
 *
 * @name reflect
 * @static
 * @memberOf module:Utils
 * @method
 * @category Util
 * @param {AsyncFunction} fn - The async function you want to wrap
 * @returns {Function} - A function that always passes null to it's callback as
 * the error. The second argument to the callback will be an `object` with
 * either an `error` or a `value` property.
 * @example
 *
 * async.parallel([
 *     async.reflect(function(callback) {
 *         // do some stuff ...
 *         callback(null, 'one');
 *     }),
 *     async.reflect(function(callback) {
 *         // do some more stuff but error ...
 *         callback('bad stuff happened');
 *     }),
 *     async.reflect(function(callback) {
 *         // do some more stuff ...
 *         callback(null, 'two');
 *     })
 * ],
 * // optional callback
 * function(err, results) {
 *     // values
 *     // results[0].value = 'one'
 *     // results[1].error = 'bad stuff happened'
 *     // results[2].value = 'two'
 * });
 */
function reflect(fn) {
    var _fn = wrapAsync(fn);
    return initialParams(function reflectOn(args, reflectCallback) {
        args.push(function callback(error, cbArg) {
            if (error) {
                reflectCallback(null, { error: error });
            } else {
                var value;
                if (arguments.length <= 2) {
                    value = cbArg;
                } else {
                    value = slice(arguments, 1);
                }
                reflectCallback(null, { value: value });
            }
        });

        return _fn.apply(this, args);
    });
}

/**
 * A helper function that wraps an array or an object of functions with `reflect`.
 *
 * @name reflectAll
 * @static
 * @memberOf module:Utils
 * @method
 * @see [async.reflect]{@link module:Utils.reflect}
 * @category Util
 * @param {Array|Object|Iterable} tasks - The collection of
 * [async functions]{@link AsyncFunction} to wrap in `async.reflect`.
 * @returns {Array} Returns an array of async functions, each wrapped in
 * `async.reflect`
 * @example
 *
 * let tasks = [
 *     function(callback) {
 *         setTimeout(function() {
 *             callback(null, 'one');
 *         }, 200);
 *     },
 *     function(callback) {
 *         // do some more stuff but error ...
 *         callback(new Error('bad stuff happened'));
 *     },
 *     function(callback) {
 *         setTimeout(function() {
 *             callback(null, 'two');
 *         }, 100);
 *     }
 * ];
 *
 * async.parallel(async.reflectAll(tasks),
 * // optional callback
 * function(err, results) {
 *     // values
 *     // results[0].value = 'one'
 *     // results[1].error = Error('bad stuff happened')
 *     // results[2].value = 'two'
 * });
 *
 * // an example using an object instead of an array
 * let tasks = {
 *     one: function(callback) {
 *         setTimeout(function() {
 *             callback(null, 'one');
 *         }, 200);
 *     },
 *     two: function(callback) {
 *         callback('two');
 *     },
 *     three: function(callback) {
 *         setTimeout(function() {
 *             callback(null, 'three');
 *         }, 100);
 *     }
 * };
 *
 * async.parallel(async.reflectAll(tasks),
 * // optional callback
 * function(err, results) {
 *     // values
 *     // results.one.value = 'one'
 *     // results.two.error = 'two'
 *     // results.three.value = 'three'
 * });
 */
function reflectAll(tasks) {
    var results;
    if (isArray(tasks)) {
        results = arrayMap(tasks, reflect);
    } else {
        results = {};
        baseForOwn(tasks, function(task, key) {
            results[key] = reflect.call(this, task);
        });
    }
    return results;
}

function reject$1(eachfn, arr, iteratee, callback) {
    _filter(eachfn, arr, function(value, cb) {
        iteratee(value, function(err, v) {
            cb(err, !v);
        });
    }, callback);
}

/**
 * The opposite of [`filter`]{@link module:Collections.filter}. Removes values that pass an `async` truth test.
 *
 * @name reject
 * @static
 * @memberOf module:Collections
 * @method
 * @see [async.filter]{@link module:Collections.filter}
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {Function} iteratee - An async truth test to apply to each item in
 * `coll`.
 * The should complete with a boolean value as its `result`.
 * Invoked with (item, callback).
 * @param {Function} [callback] - A callback which is called after all the
 * `iteratee` functions have finished. Invoked with (err, results).
 * @example
 *
 * async.reject(['file1','file2','file3'], function(filePath, callback) {
 *     fs.access(filePath, function(err) {
 *         callback(null, !err)
 *     });
 * }, function(err, results) {
 *     // results now equals an array of missing files
 *     createFiles(results);
 * });
 */
var reject = doParallel(reject$1);

/**
 * The same as [`reject`]{@link module:Collections.reject} but runs a maximum of `limit` async operations at a
 * time.
 *
 * @name rejectLimit
 * @static
 * @memberOf module:Collections
 * @method
 * @see [async.reject]{@link module:Collections.reject}
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {number} limit - The maximum number of async operations at a time.
 * @param {Function} iteratee - An async truth test to apply to each item in
 * `coll`.
 * The should complete with a boolean value as its `result`.
 * Invoked with (item, callback).
 * @param {Function} [callback] - A callback which is called after all the
 * `iteratee` functions have finished. Invoked with (err, results).
 */
var rejectLimit = doParallelLimit(reject$1);

/**
 * The same as [`reject`]{@link module:Collections.reject} but runs only a single async operation at a time.
 *
 * @name rejectSeries
 * @static
 * @memberOf module:Collections
 * @method
 * @see [async.reject]{@link module:Collections.reject}
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {Function} iteratee - An async truth test to apply to each item in
 * `coll`.
 * The should complete with a boolean value as its `result`.
 * Invoked with (item, callback).
 * @param {Function} [callback] - A callback which is called after all the
 * `iteratee` functions have finished. Invoked with (err, results).
 */
var rejectSeries = doLimit(rejectLimit, 1);

/**
 * Creates a function that returns `value`.
 *
 * @static
 * @memberOf _
 * @since 2.4.0
 * @category Util
 * @param {*} value The value to return from the new function.
 * @returns {Function} Returns the new constant function.
 * @example
 *
 * var objects = _.times(2, _.constant({ 'a': 1 }));
 *
 * console.log(objects);
 * // => [{ 'a': 1 }, { 'a': 1 }]
 *
 * console.log(objects[0] === objects[1]);
 * // => true
 */
function constant$1(value) {
  return function() {
    return value;
  };
}

/**
 * Attempts to get a successful response from `task` no more than `times` times
 * before returning an error. If the task is successful, the `callback` will be
 * passed the result of the successful task. If all attempts fail, the callback
 * will be passed the error and result (if any) of the final attempt.
 *
 * @name retry
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @category Control Flow
 * @see [async.retryable]{@link module:ControlFlow.retryable}
 * @param {Object|number} [opts = {times: 5, interval: 0}| 5] - Can be either an
 * object with `times` and `interval` or a number.
 * * `times` - The number of attempts to make before giving up.  The default
 *   is `5`.
 * * `interval` - The time to wait between retries, in milliseconds.  The
 *   default is `0`. The interval may also be specified as a function of the
 *   retry count (see example).
 * * `errorFilter` - An optional synchronous function that is invoked on
 *   erroneous result. If it returns `true` the retry attempts will continue;
 *   if the function returns `false` the retry flow is aborted with the current
 *   attempt's error and result being returned to the final callback.
 *   Invoked with (err).
 * * If `opts` is a number, the number specifies the number of times to retry,
 *   with the default interval of `0`.
 * @param {AsyncFunction} task - An async function to retry.
 * Invoked with (callback).
 * @param {Function} [callback] - An optional callback which is called when the
 * task has succeeded, or after the final failed attempt. It receives the `err`
 * and `result` arguments of the last attempt at completing the `task`. Invoked
 * with (err, results).
 *
 * @example
 *
 * // The `retry` function can be used as a stand-alone control flow by passing
 * // a callback, as shown below:
 *
 * // try calling apiMethod 3 times
 * async.retry(3, apiMethod, function(err, result) {
 *     // do something with the result
 * });
 *
 * // try calling apiMethod 3 times, waiting 200 ms between each retry
 * async.retry({times: 3, interval: 200}, apiMethod, function(err, result) {
 *     // do something with the result
 * });
 *
 * // try calling apiMethod 10 times with exponential backoff
 * // (i.e. intervals of 100, 200, 400, 800, 1600, ... milliseconds)
 * async.retry({
 *   times: 10,
 *   interval: function(retryCount) {
 *     return 50 * Math.pow(2, retryCount);
 *   }
 * }, apiMethod, function(err, result) {
 *     // do something with the result
 * });
 *
 * // try calling apiMethod the default 5 times no delay between each retry
 * async.retry(apiMethod, function(err, result) {
 *     // do something with the result
 * });
 *
 * // try calling apiMethod only when error condition satisfies, all other
 * // errors will abort the retry control flow and return to final callback
 * async.retry({
 *   errorFilter: function(err) {
 *     return err.message === 'Temporary error'; // only retry on a specific error
 *   }
 * }, apiMethod, function(err, result) {
 *     // do something with the result
 * });
 *
 * // to retry individual methods that are not as reliable within other
 * // control flow functions, use the `retryable` wrapper:
 * async.auto({
 *     users: api.getUsers.bind(api),
 *     payments: async.retryable(3, api.getPayments.bind(api))
 * }, function(err, results) {
 *     // do something with the results
 * });
 *
 */
function retry(opts, task, callback) {
    var DEFAULT_TIMES = 5;
    var DEFAULT_INTERVAL = 0;

    var options = {
        times: DEFAULT_TIMES,
        intervalFunc: constant$1(DEFAULT_INTERVAL)
    };

    function parseTimes(acc, t) {
        if (typeof t === 'object') {
            acc.times = +t.times || DEFAULT_TIMES;

            acc.intervalFunc = typeof t.interval === 'function' ?
                t.interval :
                constant$1(+t.interval || DEFAULT_INTERVAL);

            acc.errorFilter = t.errorFilter;
        } else if (typeof t === 'number' || typeof t === 'string') {
            acc.times = +t || DEFAULT_TIMES;
        } else {
            throw new Error("Invalid arguments for async.retry");
        }
    }

    if (arguments.length < 3 && typeof opts === 'function') {
        callback = task || noop;
        task = opts;
    } else {
        parseTimes(options, opts);
        callback = callback || noop;
    }

    if (typeof task !== 'function') {
        throw new Error("Invalid arguments for async.retry");
    }

    var _task = wrapAsync(task);

    var attempt = 1;
    function retryAttempt() {
        _task(function(err) {
            if (err && attempt++ < options.times &&
                (typeof options.errorFilter != 'function' ||
                    options.errorFilter(err))) {
                setTimeout(retryAttempt, options.intervalFunc(attempt));
            } else {
                callback.apply(null, arguments);
            }
        });
    }

    retryAttempt();
}

/**
 * A close relative of [`retry`]{@link module:ControlFlow.retry}.  This method
 * wraps a task and makes it retryable, rather than immediately calling it
 * with retries.
 *
 * @name retryable
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @see [async.retry]{@link module:ControlFlow.retry}
 * @category Control Flow
 * @param {Object|number} [opts = {times: 5, interval: 0}| 5] - optional
 * options, exactly the same as from `retry`
 * @param {AsyncFunction} task - the asynchronous function to wrap.
 * This function will be passed any arguments passed to the returned wrapper.
 * Invoked with (...args, callback).
 * @returns {AsyncFunction} The wrapped function, which when invoked, will
 * retry on an error, based on the parameters specified in `opts`.
 * This function will accept the same parameters as `task`.
 * @example
 *
 * async.auto({
 *     dep1: async.retryable(3, getFromFlakyService),
 *     process: ["dep1", async.retryable(3, function (results, cb) {
 *         maybeProcessData(results.dep1, cb);
 *     })]
 * }, callback);
 */
var retryable = function (opts, task) {
    if (!task) {
        task = opts;
        opts = null;
    }
    var _task = wrapAsync(task);
    return initialParams(function (args, callback) {
        function taskFn(cb) {
            _task.apply(null, args.concat(cb));
        }

        if (opts) retry(opts, taskFn, callback);
        else retry(taskFn, callback);

    });
};

/**
 * Run the functions in the `tasks` collection in series, each one running once
 * the previous function has completed. If any functions in the series pass an
 * error to its callback, no more functions are run, and `callback` is
 * immediately called with the value of the error. Otherwise, `callback`
 * receives an array of results when `tasks` have completed.
 *
 * It is also possible to use an object instead of an array. Each property will
 * be run as a function, and the results will be passed to the final `callback`
 * as an object instead of an array. This can be a more readable way of handling
 *  results from {@link async.series}.
 *
 * **Note** that while many implementations preserve the order of object
 * properties, the [ECMAScript Language Specification](http://www.ecma-international.org/ecma-262/5.1/#sec-8.6)
 * explicitly states that
 *
 * > The mechanics and order of enumerating the properties is not specified.
 *
 * So if you rely on the order in which your series of functions are executed,
 * and want this to work on all platforms, consider using an array.
 *
 * @name series
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @category Control Flow
 * @param {Array|Iterable|Object} tasks - A collection containing
 * [async functions]{@link AsyncFunction} to run in series.
 * Each function can complete with any number of optional `result` values.
 * @param {Function} [callback] - An optional callback to run once all the
 * functions have completed. This function gets a results array (or object)
 * containing all the result arguments passed to the `task` callbacks. Invoked
 * with (err, result).
 * @example
 * async.series([
 *     function(callback) {
 *         // do some stuff ...
 *         callback(null, 'one');
 *     },
 *     function(callback) {
 *         // do some more stuff ...
 *         callback(null, 'two');
 *     }
 * ],
 * // optional callback
 * function(err, results) {
 *     // results is now equal to ['one', 'two']
 * });
 *
 * async.series({
 *     one: function(callback) {
 *         setTimeout(function() {
 *             callback(null, 1);
 *         }, 200);
 *     },
 *     two: function(callback){
 *         setTimeout(function() {
 *             callback(null, 2);
 *         }, 100);
 *     }
 * }, function(err, results) {
 *     // results is now equal to: {one: 1, two: 2}
 * });
 */
function series(tasks, callback) {
    _parallel(eachOfSeries, tasks, callback);
}

/**
 * Returns `true` if at least one element in the `coll` satisfies an async test.
 * If any iteratee call returns `true`, the main `callback` is immediately
 * called.
 *
 * @name some
 * @static
 * @memberOf module:Collections
 * @method
 * @alias any
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {AsyncFunction} iteratee - An async truth test to apply to each item
 * in the collections in parallel.
 * The iteratee should complete with a boolean `result` value.
 * Invoked with (item, callback).
 * @param {Function} [callback] - A callback which is called as soon as any
 * iteratee returns `true`, or after all the iteratee functions have finished.
 * Result will be either `true` or `false` depending on the values of the async
 * tests. Invoked with (err, result).
 * @example
 *
 * async.some(['file1','file2','file3'], function(filePath, callback) {
 *     fs.access(filePath, function(err) {
 *         callback(null, !err)
 *     });
 * }, function(err, result) {
 *     // if result is true then at least one of the files exists
 * });
 */
var some = doParallel(_createTester(Boolean, identity));

/**
 * The same as [`some`]{@link module:Collections.some} but runs a maximum of `limit` async operations at a time.
 *
 * @name someLimit
 * @static
 * @memberOf module:Collections
 * @method
 * @see [async.some]{@link module:Collections.some}
 * @alias anyLimit
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {number} limit - The maximum number of async operations at a time.
 * @param {AsyncFunction} iteratee - An async truth test to apply to each item
 * in the collections in parallel.
 * The iteratee should complete with a boolean `result` value.
 * Invoked with (item, callback).
 * @param {Function} [callback] - A callback which is called as soon as any
 * iteratee returns `true`, or after all the iteratee functions have finished.
 * Result will be either `true` or `false` depending on the values of the async
 * tests. Invoked with (err, result).
 */
var someLimit = doParallelLimit(_createTester(Boolean, identity));

/**
 * The same as [`some`]{@link module:Collections.some} but runs only a single async operation at a time.
 *
 * @name someSeries
 * @static
 * @memberOf module:Collections
 * @method
 * @see [async.some]{@link module:Collections.some}
 * @alias anySeries
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {AsyncFunction} iteratee - An async truth test to apply to each item
 * in the collections in series.
 * The iteratee should complete with a boolean `result` value.
 * Invoked with (item, callback).
 * @param {Function} [callback] - A callback which is called as soon as any
 * iteratee returns `true`, or after all the iteratee functions have finished.
 * Result will be either `true` or `false` depending on the values of the async
 * tests. Invoked with (err, result).
 */
var someSeries = doLimit(someLimit, 1);

/**
 * Sorts a list by the results of running each `coll` value through an async
 * `iteratee`.
 *
 * @name sortBy
 * @static
 * @memberOf module:Collections
 * @method
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {AsyncFunction} iteratee - An async function to apply to each item in
 * `coll`.
 * The iteratee should complete with a value to use as the sort criteria as
 * its `result`.
 * Invoked with (item, callback).
 * @param {Function} callback - A callback which is called after all the
 * `iteratee` functions have finished, or an error occurs. Results is the items
 * from the original `coll` sorted by the values returned by the `iteratee`
 * calls. Invoked with (err, results).
 * @example
 *
 * async.sortBy(['file1','file2','file3'], function(file, callback) {
 *     fs.stat(file, function(err, stats) {
 *         callback(err, stats.mtime);
 *     });
 * }, function(err, results) {
 *     // results is now the original array of files sorted by
 *     // modified date
 * });
 *
 * // By modifying the callback parameter the
 * // sorting order can be influenced:
 *
 * // ascending order
 * async.sortBy([1,9,3,5], function(x, callback) {
 *     callback(null, x);
 * }, function(err,result) {
 *     // result callback
 * });
 *
 * // descending order
 * async.sortBy([1,9,3,5], function(x, callback) {
 *     callback(null, x*-1);    //<- x*-1 instead of x, turns the order around
 * }, function(err,result) {
 *     // result callback
 * });
 */
function sortBy (coll, iteratee, callback) {
    var _iteratee = wrapAsync(iteratee);
    map(coll, function (x, callback) {
        _iteratee(x, function (err, criteria) {
            if (err) return callback(err);
            callback(null, {value: x, criteria: criteria});
        });
    }, function (err, results) {
        if (err) return callback(err);
        callback(null, arrayMap(results.sort(comparator), baseProperty('value')));
    });

    function comparator(left, right) {
        var a = left.criteria, b = right.criteria;
        return a < b ? -1 : a > b ? 1 : 0;
    }
}

/**
 * Sets a time limit on an asynchronous function. If the function does not call
 * its callback within the specified milliseconds, it will be called with a
 * timeout error. The code property for the error object will be `'ETIMEDOUT'`.
 *
 * @name timeout
 * @static
 * @memberOf module:Utils
 * @method
 * @category Util
 * @param {AsyncFunction} asyncFn - The async function to limit in time.
 * @param {number} milliseconds - The specified time limit.
 * @param {*} [info] - Any variable you want attached (`string`, `object`, etc)
 * to timeout Error for more information..
 * @returns {AsyncFunction} Returns a wrapped function that can be used with any
 * of the control flow functions.
 * Invoke this function with the same parameters as you would `asyncFunc`.
 * @example
 *
 * function myFunction(foo, callback) {
 *     doAsyncTask(foo, function(err, data) {
 *         // handle errors
 *         if (err) return callback(err);
 *
 *         // do some stuff ...
 *
 *         // return processed data
 *         return callback(null, data);
 *     });
 * }
 *
 * var wrapped = async.timeout(myFunction, 1000);
 *
 * // call `wrapped` as you would `myFunction`
 * wrapped({ bar: 'bar' }, function(err, data) {
 *     // if `myFunction` takes < 1000 ms to execute, `err`
 *     // and `data` will have their expected values
 *
 *     // else `err` will be an Error with the code 'ETIMEDOUT'
 * });
 */
function timeout(asyncFn, milliseconds, info) {
    var fn = wrapAsync(asyncFn);

    return initialParams(function (args, callback) {
        var timedOut = false;
        var timer;

        function timeoutCallback() {
            var name = asyncFn.name || 'anonymous';
            var error  = new Error('Callback function "' + name + '" timed out.');
            error.code = 'ETIMEDOUT';
            if (info) {
                error.info = info;
            }
            timedOut = true;
            callback(error);
        }

        args.push(function () {
            if (!timedOut) {
                callback.apply(null, arguments);
                clearTimeout(timer);
            }
        });

        // setup timer and call original function
        timer = setTimeout(timeoutCallback, milliseconds);
        fn.apply(null, args);
    });
}

/* Built-in method references for those with the same name as other `lodash` methods. */
var nativeCeil = Math.ceil;
var nativeMax = Math.max;

/**
 * The base implementation of `_.range` and `_.rangeRight` which doesn't
 * coerce arguments.
 *
 * @private
 * @param {number} start The start of the range.
 * @param {number} end The end of the range.
 * @param {number} step The value to increment or decrement by.
 * @param {boolean} [fromRight] Specify iterating from right to left.
 * @returns {Array} Returns the range of numbers.
 */
function baseRange(start, end, step, fromRight) {
  var index = -1,
      length = nativeMax(nativeCeil((end - start) / (step || 1)), 0),
      result = Array(length);

  while (length--) {
    result[fromRight ? length : ++index] = start;
    start += step;
  }
  return result;
}

/**
 * The same as [times]{@link module:ControlFlow.times} but runs a maximum of `limit` async operations at a
 * time.
 *
 * @name timesLimit
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @see [async.times]{@link module:ControlFlow.times}
 * @category Control Flow
 * @param {number} count - The number of times to run the function.
 * @param {number} limit - The maximum number of async operations at a time.
 * @param {AsyncFunction} iteratee - The async function to call `n` times.
 * Invoked with the iteration index and a callback: (n, next).
 * @param {Function} callback - see [async.map]{@link module:Collections.map}.
 */
function timeLimit(count, limit, iteratee, callback) {
    var _iteratee = wrapAsync(iteratee);
    mapLimit(baseRange(0, count, 1), limit, _iteratee, callback);
}

/**
 * Calls the `iteratee` function `n` times, and accumulates results in the same
 * manner you would use with [map]{@link module:Collections.map}.
 *
 * @name times
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @see [async.map]{@link module:Collections.map}
 * @category Control Flow
 * @param {number} n - The number of times to run the function.
 * @param {AsyncFunction} iteratee - The async function to call `n` times.
 * Invoked with the iteration index and a callback: (n, next).
 * @param {Function} callback - see {@link module:Collections.map}.
 * @example
 *
 * // Pretend this is some complicated async factory
 * var createUser = function(id, callback) {
 *     callback(null, {
 *         id: 'user' + id
 *     });
 * };
 *
 * // generate 5 users
 * async.times(5, function(n, next) {
 *     createUser(n, function(err, user) {
 *         next(err, user);
 *     });
 * }, function(err, users) {
 *     // we should now have 5 users
 * });
 */
var times = doLimit(timeLimit, Infinity);

/**
 * The same as [times]{@link module:ControlFlow.times} but runs only a single async operation at a time.
 *
 * @name timesSeries
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @see [async.times]{@link module:ControlFlow.times}
 * @category Control Flow
 * @param {number} n - The number of times to run the function.
 * @param {AsyncFunction} iteratee - The async function to call `n` times.
 * Invoked with the iteration index and a callback: (n, next).
 * @param {Function} callback - see {@link module:Collections.map}.
 */
var timesSeries = doLimit(timeLimit, 1);

/**
 * A relative of `reduce`.  Takes an Object or Array, and iterates over each
 * element in series, each step potentially mutating an `accumulator` value.
 * The type of the accumulator defaults to the type of collection passed in.
 *
 * @name transform
 * @static
 * @memberOf module:Collections
 * @method
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {*} [accumulator] - The initial state of the transform.  If omitted,
 * it will default to an empty Object or Array, depending on the type of `coll`
 * @param {AsyncFunction} iteratee - A function applied to each item in the
 * collection that potentially modifies the accumulator.
 * Invoked with (accumulator, item, key, callback).
 * @param {Function} [callback] - A callback which is called after all the
 * `iteratee` functions have finished. Result is the transformed accumulator.
 * Invoked with (err, result).
 * @example
 *
 * async.transform([1,2,3], function(acc, item, index, callback) {
 *     // pointless async:
 *     process.nextTick(function() {
 *         acc.push(item * 2)
 *         callback(null)
 *     });
 * }, function(err, result) {
 *     // result is now equal to [2, 4, 6]
 * });
 *
 * @example
 *
 * async.transform({a: 1, b: 2, c: 3}, function (obj, val, key, callback) {
 *     setImmediate(function () {
 *         obj[key] = val * 2;
 *         callback();
 *     })
 * }, function (err, result) {
 *     // result is equal to {a: 2, b: 4, c: 6}
 * })
 */
function transform (coll, accumulator, iteratee, callback) {
    if (arguments.length <= 3) {
        callback = iteratee;
        iteratee = accumulator;
        accumulator = isArray(coll) ? [] : {};
    }
    callback = once(callback || noop);
    var _iteratee = wrapAsync(iteratee);

    eachOf(coll, function(v, k, cb) {
        _iteratee(accumulator, v, k, cb);
    }, function(err) {
        callback(err, accumulator);
    });
}

/**
 * It runs each task in series but stops whenever any of the functions were
 * successful. If one of the tasks were successful, the `callback` will be
 * passed the result of the successful task. If all tasks fail, the callback
 * will be passed the error and result (if any) of the final attempt.
 *
 * @name tryEach
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @category Control Flow
 * @param {Array|Iterable|Object} tasks - A collection containing functions to
 * run, each function is passed a `callback(err, result)` it must call on
 * completion with an error `err` (which can be `null`) and an optional `result`
 * value.
 * @param {Function} [callback] - An optional callback which is called when one
 * of the tasks has succeeded, or all have failed. It receives the `err` and
 * `result` arguments of the last attempt at completing the `task`. Invoked with
 * (err, results).
 * @example
 * async.tryEach([
 *     function getDataFromFirstWebsite(callback) {
 *         // Try getting the data from the first website
 *         callback(err, data);
 *     },
 *     function getDataFromSecondWebsite(callback) {
 *         // First website failed,
 *         // Try getting the data from the backup website
 *         callback(err, data);
 *     }
 * ],
 * // optional callback
 * function(err, results) {
 *     Now do something with the data.
 * });
 *
 */
function tryEach(tasks, callback) {
    var error = null;
    var result;
    callback = callback || noop;
    eachSeries(tasks, function(task, callback) {
        wrapAsync(task)(function (err, res/*, ...args*/) {
            if (arguments.length > 2) {
                result = slice(arguments, 1);
            } else {
                result = res;
            }
            error = err;
            callback(!err);
        });
    }, function () {
        callback(error, result);
    });
}

/**
 * Undoes a [memoize]{@link module:Utils.memoize}d function, reverting it to the original,
 * unmemoized form. Handy for testing.
 *
 * @name unmemoize
 * @static
 * @memberOf module:Utils
 * @method
 * @see [async.memoize]{@link module:Utils.memoize}
 * @category Util
 * @param {AsyncFunction} fn - the memoized function
 * @returns {AsyncFunction} a function that calls the original unmemoized function
 */
function unmemoize(fn) {
    return function () {
        return (fn.unmemoized || fn).apply(null, arguments);
    };
}

/**
 * Repeatedly call `iteratee`, while `test` returns `true`. Calls `callback` when
 * stopped, or an error occurs.
 *
 * @name whilst
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @category Control Flow
 * @param {Function} test - synchronous truth test to perform before each
 * execution of `iteratee`. Invoked with ().
 * @param {AsyncFunction} iteratee - An async function which is called each time
 * `test` passes. Invoked with (callback).
 * @param {Function} [callback] - A callback which is called after the test
 * function has failed and repeated execution of `iteratee` has stopped. `callback`
 * will be passed an error and any arguments passed to the final `iteratee`'s
 * callback. Invoked with (err, [results]);
 * @returns undefined
 * @example
 *
 * var count = 0;
 * async.whilst(
 *     function() { return count < 5; },
 *     function(callback) {
 *         count++;
 *         setTimeout(function() {
 *             callback(null, count);
 *         }, 1000);
 *     },
 *     function (err, n) {
 *         // 5 seconds have passed, n = 5
 *     }
 * );
 */
function whilst(test, iteratee, callback) {
    callback = onlyOnce(callback || noop);
    var _iteratee = wrapAsync(iteratee);
    if (!test()) return callback(null);
    var next = function(err/*, ...args*/) {
        if (err) return callback(err);
        if (test()) return _iteratee(next);
        var args = slice(arguments, 1);
        callback.apply(null, [null].concat(args));
    };
    _iteratee(next);
}

/**
 * Repeatedly call `iteratee` until `test` returns `true`. Calls `callback` when
 * stopped, or an error occurs. `callback` will be passed an error and any
 * arguments passed to the final `iteratee`'s callback.
 *
 * The inverse of [whilst]{@link module:ControlFlow.whilst}.
 *
 * @name until
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @see [async.whilst]{@link module:ControlFlow.whilst}
 * @category Control Flow
 * @param {Function} test - synchronous truth test to perform before each
 * execution of `iteratee`. Invoked with ().
 * @param {AsyncFunction} iteratee - An async function which is called each time
 * `test` fails. Invoked with (callback).
 * @param {Function} [callback] - A callback which is called after the test
 * function has passed and repeated execution of `iteratee` has stopped. `callback`
 * will be passed an error and any arguments passed to the final `iteratee`'s
 * callback. Invoked with (err, [results]);
 */
function until(test, iteratee, callback) {
    whilst(function() {
        return !test.apply(this, arguments);
    }, iteratee, callback);
}

/**
 * Runs the `tasks` array of functions in series, each passing their results to
 * the next in the array. However, if any of the `tasks` pass an error to their
 * own callback, the next function is not executed, and the main `callback` is
 * immediately called with the error.
 *
 * @name waterfall
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @category Control Flow
 * @param {Array} tasks - An array of [async functions]{@link AsyncFunction}
 * to run.
 * Each function should complete with any number of `result` values.
 * The `result` values will be passed as arguments, in order, to the next task.
 * @param {Function} [callback] - An optional callback to run once all the
 * functions have completed. This will be passed the results of the last task's
 * callback. Invoked with (err, [results]).
 * @returns undefined
 * @example
 *
 * async.waterfall([
 *     function(callback) {
 *         callback(null, 'one', 'two');
 *     },
 *     function(arg1, arg2, callback) {
 *         // arg1 now equals 'one' and arg2 now equals 'two'
 *         callback(null, 'three');
 *     },
 *     function(arg1, callback) {
 *         // arg1 now equals 'three'
 *         callback(null, 'done');
 *     }
 * ], function (err, result) {
 *     // result now equals 'done'
 * });
 *
 * // Or, with named functions:
 * async.waterfall([
 *     myFirstFunction,
 *     mySecondFunction,
 *     myLastFunction,
 * ], function (err, result) {
 *     // result now equals 'done'
 * });
 * function myFirstFunction(callback) {
 *     callback(null, 'one', 'two');
 * }
 * function mySecondFunction(arg1, arg2, callback) {
 *     // arg1 now equals 'one' and arg2 now equals 'two'
 *     callback(null, 'three');
 * }
 * function myLastFunction(arg1, callback) {
 *     // arg1 now equals 'three'
 *     callback(null, 'done');
 * }
 */
var waterfall = function(tasks, callback) {
    callback = once(callback || noop);
    if (!isArray(tasks)) return callback(new Error('First argument to waterfall must be an array of functions'));
    if (!tasks.length) return callback();
    var taskIndex = 0;

    function nextTask(args) {
        var task = wrapAsync(tasks[taskIndex++]);
        args.push(onlyOnce(next));
        task.apply(null, args);
    }

    function next(err/*, ...args*/) {
        if (err || taskIndex === tasks.length) {
            return callback.apply(null, arguments);
        }
        nextTask(slice(arguments, 1));
    }

    nextTask([]);
};

/**
 * An "async function" in the context of Async is an asynchronous function with
 * a variable number of parameters, with the final parameter being a callback.
 * (`function (arg1, arg2, ..., callback) {}`)
 * The final callback is of the form `callback(err, results...)`, which must be
 * called once the function is completed.  The callback should be called with a
 * Error as its first argument to signal that an error occurred.
 * Otherwise, if no error occurred, it should be called with `null` as the first
 * argument, and any additional `result` arguments that may apply, to signal
 * successful completion.
 * The callback must be called exactly once, ideally on a later tick of the
 * JavaScript event loop.
 *
 * This type of function is also referred to as a "Node-style async function",
 * or a "continuation passing-style function" (CPS). Most of the methods of this
 * library are themselves CPS/Node-style async functions, or functions that
 * return CPS/Node-style async functions.
 *
 * Wherever we accept a Node-style async function, we also directly accept an
 * [ES2017 `async` function]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function}.
 * In this case, the `async` function will not be passed a final callback
 * argument, and any thrown error will be used as the `err` argument of the
 * implicit callback, and the return value will be used as the `result` value.
 * (i.e. a `rejected` of the returned Promise becomes the `err` callback
 * argument, and a `resolved` value becomes the `result`.)
 *
 * Note, due to JavaScript limitations, we can only detect native `async`
 * functions and not transpilied implementations.
 * Your environment must have `async`/`await` support for this to work.
 * (e.g. Node > v7.6, or a recent version of a modern browser).
 * If you are using `async` functions through a transpiler (e.g. Babel), you
 * must still wrap the function with [asyncify]{@link module:Utils.asyncify},
 * because the `async function` will be compiled to an ordinary function that
 * returns a promise.
 *
 * @typedef {Function} AsyncFunction
 * @static
 */

/**
 * Async is a utility module which provides straight-forward, powerful functions
 * for working with asynchronous JavaScript. Although originally designed for
 * use with [Node.js](http://nodejs.org) and installable via
 * `npm install --save async`, it can also be used directly in the browser.
 * @module async
 * @see AsyncFunction
 */


/**
 * A collection of `async` functions for manipulating collections, such as
 * arrays and objects.
 * @module Collections
 */

/**
 * A collection of `async` functions for controlling the flow through a script.
 * @module ControlFlow
 */

/**
 * A collection of `async` utility functions.
 * @module Utils
 */

var index = {
    apply: apply,
    applyEach: applyEach,
    applyEachSeries: applyEachSeries,
    asyncify: asyncify,
    auto: auto,
    autoInject: autoInject,
    cargo: cargo,
    compose: compose,
    concat: concat,
    concatLimit: concatLimit,
    concatSeries: concatSeries,
    constant: constant,
    detect: detect,
    detectLimit: detectLimit,
    detectSeries: detectSeries,
    dir: dir,
    doDuring: doDuring,
    doUntil: doUntil,
    doWhilst: doWhilst,
    during: during,
    each: eachLimit,
    eachLimit: eachLimit$1,
    eachOf: eachOf,
    eachOfLimit: eachOfLimit,
    eachOfSeries: eachOfSeries,
    eachSeries: eachSeries,
    ensureAsync: ensureAsync,
    every: every,
    everyLimit: everyLimit,
    everySeries: everySeries,
    filter: filter,
    filterLimit: filterLimit,
    filterSeries: filterSeries,
    forever: forever,
    groupBy: groupBy,
    groupByLimit: groupByLimit,
    groupBySeries: groupBySeries,
    log: log,
    map: map,
    mapLimit: mapLimit,
    mapSeries: mapSeries,
    mapValues: mapValues,
    mapValuesLimit: mapValuesLimit,
    mapValuesSeries: mapValuesSeries,
    memoize: memoize,
    nextTick: nextTick,
    parallel: parallelLimit,
    parallelLimit: parallelLimit$1,
    priorityQueue: priorityQueue,
    queue: queue$1,
    race: race,
    reduce: reduce,
    reduceRight: reduceRight,
    reflect: reflect,
    reflectAll: reflectAll,
    reject: reject,
    rejectLimit: rejectLimit,
    rejectSeries: rejectSeries,
    retry: retry,
    retryable: retryable,
    seq: seq,
    series: series,
    setImmediate: setImmediate$1,
    some: some,
    someLimit: someLimit,
    someSeries: someSeries,
    sortBy: sortBy,
    timeout: timeout,
    times: times,
    timesLimit: timeLimit,
    timesSeries: timesSeries,
    transform: transform,
    tryEach: tryEach,
    unmemoize: unmemoize,
    until: until,
    waterfall: waterfall,
    whilst: whilst,

    // aliases
    all: every,
    allLimit: everyLimit,
    allSeries: everySeries,
    any: some,
    anyLimit: someLimit,
    anySeries: someSeries,
    find: detect,
    findLimit: detectLimit,
    findSeries: detectSeries,
    forEach: eachLimit,
    forEachSeries: eachSeries,
    forEachLimit: eachLimit$1,
    forEachOf: eachOf,
    forEachOfSeries: eachOfSeries,
    forEachOfLimit: eachOfLimit,
    inject: reduce,
    foldl: reduce,
    foldr: reduceRight,
    select: filter,
    selectLimit: filterLimit,
    selectSeries: filterSeries,
    wrapSync: asyncify
};

exports['default'] = index;
exports.apply = apply;
exports.applyEach = applyEach;
exports.applyEachSeries = applyEachSeries;
exports.asyncify = asyncify;
exports.auto = auto;
exports.autoInject = autoInject;
exports.cargo = cargo;
exports.compose = compose;
exports.concat = concat;
exports.concatLimit = concatLimit;
exports.concatSeries = concatSeries;
exports.constant = constant;
exports.detect = detect;
exports.detectLimit = detectLimit;
exports.detectSeries = detectSeries;
exports.dir = dir;
exports.doDuring = doDuring;
exports.doUntil = doUntil;
exports.doWhilst = doWhilst;
exports.during = during;
exports.each = eachLimit;
exports.eachLimit = eachLimit$1;
exports.eachOf = eachOf;
exports.eachOfLimit = eachOfLimit;
exports.eachOfSeries = eachOfSeries;
exports.eachSeries = eachSeries;
exports.ensureAsync = ensureAsync;
exports.every = every;
exports.everyLimit = everyLimit;
exports.everySeries = everySeries;
exports.filter = filter;
exports.filterLimit = filterLimit;
exports.filterSeries = filterSeries;
exports.forever = forever;
exports.groupBy = groupBy;
exports.groupByLimit = groupByLimit;
exports.groupBySeries = groupBySeries;
exports.log = log;
exports.map = map;
exports.mapLimit = mapLimit;
exports.mapSeries = mapSeries;
exports.mapValues = mapValues;
exports.mapValuesLimit = mapValuesLimit;
exports.mapValuesSeries = mapValuesSeries;
exports.memoize = memoize;
exports.nextTick = nextTick;
exports.parallel = parallelLimit;
exports.parallelLimit = parallelLimit$1;
exports.priorityQueue = priorityQueue;
exports.queue = queue$1;
exports.race = race;
exports.reduce = reduce;
exports.reduceRight = reduceRight;
exports.reflect = reflect;
exports.reflectAll = reflectAll;
exports.reject = reject;
exports.rejectLimit = rejectLimit;
exports.rejectSeries = rejectSeries;
exports.retry = retry;
exports.retryable = retryable;
exports.seq = seq;
exports.series = series;
exports.setImmediate = setImmediate$1;
exports.some = some;
exports.someLimit = someLimit;
exports.someSeries = someSeries;
exports.sortBy = sortBy;
exports.timeout = timeout;
exports.times = times;
exports.timesLimit = timeLimit;
exports.timesSeries = timesSeries;
exports.transform = transform;
exports.tryEach = tryEach;
exports.unmemoize = unmemoize;
exports.until = until;
exports.waterfall = waterfall;
exports.whilst = whilst;
exports.all = every;
exports.allLimit = everyLimit;
exports.allSeries = everySeries;
exports.any = some;
exports.anyLimit = someLimit;
exports.anySeries = someSeries;
exports.find = detect;
exports.findLimit = detectLimit;
exports.findSeries = detectSeries;
exports.forEach = eachLimit;
exports.forEachSeries = eachSeries;
exports.forEachLimit = eachLimit$1;
exports.forEachOf = eachOf;
exports.forEachOfSeries = eachOfSeries;
exports.forEachOfLimit = eachOfLimit;
exports.inject = reduce;
exports.foldl = reduce;
exports.foldr = reduceRight;
exports.select = filter;
exports.selectLimit = filterLimit;
exports.selectSeries = filterSeries;
exports.wrapSync = asyncify;

Object.defineProperty(exports, '__esModule', { value: true });

})));
};
BundleModuleCode['nlp/nlp']=function (module,exports){
var nlp = Require('nlp/compromise');
nlp.extend(Require('nlp/compromise-adjectives'));
nlp.extend(Require('nlp/compromise-dates'));
nlp.extend(Require('nlp/compromise-numbers'));
nlp.extend(Require('nlp/compromise-sentences'));
var efrt = Require('nlp/efrt');
nlp.lexer = efrt.lexer;
nlp.pack = efrt.pack;
nlp.unpack = efrt.unpack;
nlp.version = '1.2.2';
module.exports=nlp;
};
BundleModuleCode['nlp/compromise']=function (module,exports){
/* compromise 13.5.0X004 MIT/blab */
/* https://github.com/spencermountain/compromise */

/* polyfills */
Object.addProperty(Array,'findIndex', function(callback) {
  if (this === null) {
    throw new TypeError('Array.prototype.findIndex called on null or undefined');
  } else if (typeof callback !== 'function') {
    throw new TypeError('callback must be a function');
  }
  var list = Object(this);
  // Makes sures is always has an positive integer as length.
  var length = list.length >>> 0;
  var thisArg = arguments[1];
  for (var i = 0; i < length; i++) {
    if ( callback.call(thisArg, list[i], i, list) ) {
      return i;
    }
  }
  return -1;
});

/* NLP module */
(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  typeof define === 'function' && define.amd ? define(factory) :
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.nlp = factory());
}(this, (function () { 'use strict';
  function similar_text (first, second, percent) {
    // http://kevin.vanzonneveld.net
    // +   original by: Rafał Kukawski (http://blog.kukawski.pl)
    // +   bugfixed by: Chris McMacken
    // +   added percent parameter by: Markus Padourek (taken from http://www.kevinhq.com/2012/06/php-similartext-function-in-javascript_16.html)
    // *     example 1: similar_text('Hello World!', 'Hello phpjs!');
    // *     returns 1: 7
    // *     example 2: similar_text('Hello World!', null);
    // *     returns 2: 0
    // *     example 3: similar_text('Hello World!', null, 1);
    // *     returns 3: 58.33
    if (first === null || second === null || typeof first === 'undefined' || typeof second === 'undefined') {
      return 0;
    }

    first += '';
    second += '';

    var pos1 = 0,
      pos2 = 0,
      max = 0,
      firstLength = first.length,
      secondLength = second.length,
      p, q, l, sum;

    max = 0;

    for (p = 0; p < firstLength; p++) {
      for (q = 0; q < secondLength; q++) {
        for (l = 0;
        (p + l < firstLength) && (q + l < secondLength) && (first.charAt(p + l) === second.charAt(q + l)); l++);
        if (l > max) {
          max = l;
          pos1 = p;
          pos2 = q;
        }
      }
    }

    sum = max;

    if (sum) {
      if (pos1 && pos2) {
        sum += similar_text(first.substr(0, pos2), second.substr(0, pos2));
      }

      if ((pos1 + max < firstLength) && (pos2 + max < secondLength)) {
        sum += similar_text(first.substr(pos1 + max, firstLength - pos1 - max), second.substr(pos2 + max, secondLength - pos2 - max));
      }
    }

    if (!percent) {
      return sum;
    } else {
      return (sum * 200) / (firstLength + secondLength);
    }
  }

  function _typeof(obj) {
    "@babel/helpers - typeof";

    if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
      _typeof = function (obj) {
        return typeof obj;
      };
    } else {
      _typeof = function (obj) {
        return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
      };
    }

    return _typeof(obj);
  }

  function _classCallCheck(instance, Constructor) {
    if (!(instance instanceof Constructor)) {
      throw new TypeError("Cannot call a class as a function");
    }
  }

  function _defineProperties(target, props) {
    for (var i = 0; i < props.length; i++) {
      var descriptor = props[i];
      descriptor.enumerable = descriptor.enumerable || false;
      descriptor.configurable = true;
      if ("value" in descriptor) descriptor.writable = true;
      Object.defineProperty(target, descriptor.key, descriptor);
    }
  }

  function _createClass(Constructor, protoProps, staticProps) {
    if (protoProps) _defineProperties(Constructor.prototype, protoProps);
    if (staticProps) _defineProperties(Constructor, staticProps);
    return Constructor;
  }

  function _inherits(subClass, superClass) {
    if (typeof superClass !== "function" && superClass !== null) {
      throw new TypeError("Super expression must either be null or a function");
    }

    subClass.prototype = Object.create(superClass && superClass.prototype, {
      constructor: {
        value: subClass,
        writable: true,
        configurable: true
      }
    });
    if (superClass) _setPrototypeOf(subClass, superClass);
  }

  function _getPrototypeOf(o) {
    _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
      return o.__proto__ || Object.getPrototypeOf(o);
    };
    return _getPrototypeOf(o);
  }

  function _setPrototypeOf(o, p) {
    _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
      o.__proto__ = p;
      return o;
    };

    return _setPrototypeOf(o, p);
  }

  function _isNativeReflectConstruct() {
    if (typeof Reflect === "undefined" || !Reflect.construct) return false;
    if (Reflect.construct.sham) return false;
    if (typeof Proxy === "function") return true;

    try {
      Date.prototype.toString.call(Reflect.construct(Date, [], function () {}));
      return true;
    } catch (e) {
      return false;
    }
  }

  function _assertThisInitialized(self) {
    if (self === void 0) {
      throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
    }

    return self;
  }

  function _possibleConstructorReturn(self, call) {
    if (call && (typeof call === "object" || typeof call === "function")) {
      return call;
    }

    return _assertThisInitialized(self);
  }

  function _createSuper(Derived) {
    var hasNativeReflectConstruct = _isNativeReflectConstruct();

    return function _createSuperInternal() {
      var Super = _getPrototypeOf(Derived),
          result;

      if (hasNativeReflectConstruct) {
        var NewTarget = _getPrototypeOf(this).constructor;

        result = Reflect.construct(Super, arguments, NewTarget);
      } else {
        result = Super.apply(this, arguments);
      }

      return _possibleConstructorReturn(this, result);
    };
  }

  function _slicedToArray(arr, i) {
    return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();
  }

  function _arrayWithHoles(arr) {
    if (Array.isArray(arr)) return arr;
  }

  function _iterableToArrayLimit(arr, i) {
    if (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr))) return;
    var _arr = [];
    var _n = true;
    var _d = false;
    var _e = undefined;

    try {
      for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
        _arr.push(_s.value);

        if (i && _arr.length === i) break;
      }
    } catch (err) {
      _d = true;
      _e = err;
    } finally {
      try {
        if (!_n && _i["return"] != null) _i["return"]();
      } finally {
        if (_d) throw _e;
      }
    }

    return _arr;
  }

  function _unsupportedIterableToArray(o, minLen) {
    if (!o) return;
    if (typeof o === "string") return _arrayLikeToArray(o, minLen);
    var n = Object.prototype.toString.call(o).slice(8, -1);
    if (n === "Object" && o.constructor) n = o.constructor.name;
    if (n === "Map" || n === "Set") return Array.from(o);
    if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
  }

  function _arrayLikeToArray(arr, len) {
    if (len == null || len > arr.length) len = arr.length;

    for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];

    return arr2;
  }

  function _nonIterableRest() {
    throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
  }

  //this is a not-well-thought-out way to reduce our dependence on `object===object` stuff
  var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'.split(''); //generates a unique id for this term

  function makeId(str) {
    str = str || '_';
    var text = str + '-';

    for (var i = 0; i < 7; i++) {
      text += chars[Math.floor(Math.random() * chars.length)];
    }

    return text;
  }

  var _id = makeId;

  //a hugely-ignorant, and widely subjective transliteration of latin, cryllic, greek unicode characters to english ascii.
  //approximate visual (not semantic or phonetic) relationship between unicode and ascii characters
  //http://en.wikipedia.org/wiki/List_of_Unicode_characters
  //https://docs.google.com/spreadsheet/ccc?key=0Ah46z755j7cVdFRDM1A2YVpwa1ZYWlpJM2pQZ003M0E
  var compact = {
    '!': '¡',
    '?': '¿Ɂ',
    '"': '“”"❝❞',
    "'": '‘‛❛❜',
    '-': '—–',
    a: 'ªÀÁÂÃÄÅàáâãäåĀāĂ㥹ǍǎǞǟǠǡǺǻȀȁȂȃȦȧȺΆΑΔΛάαλАадѦѧӐӑӒӓƛɅæ',
    b: 'ßþƀƁƂƃƄƅɃΒβϐϦБВЪЬвъьѢѣҌҍ',
    c: '¢©ÇçĆćĈĉĊċČčƆƇƈȻȼͻͼͽϲϹϽϾСсєҀҁҪҫ',
    d: 'ÐĎďĐđƉƊȡƋƌǷ',
    e: 'ÈÉÊËèéêëĒēĔĕĖėĘęĚěƎƏƐǝȄȅȆȇȨȩɆɇΈΕΞΣέεξϱϵ϶ЀЁЕЭеѐёҼҽҾҿӖӗӘәӚӛӬӭ',
    f: 'ƑƒϜϝӺӻҒғſ',
    g: 'ĜĝĞğĠġĢģƓǤǥǦǧǴǵ',
    h: 'ĤĥĦħƕǶȞȟΉΗЂЊЋНнђћҢңҤҥҺһӉӊ',
    I: 'ÌÍÎÏ',
    i: 'ìíîïĨĩĪīĬĭĮįİıƖƗȈȉȊȋΊΐΪίιϊІЇії',
    j: 'ĴĵǰȷɈɉϳЈј',
    k: 'ĶķĸƘƙǨǩΚκЌЖКжкќҚқҜҝҞҟҠҡ',
    l: 'ĹĺĻļĽľĿŀŁłƚƪǀǏǐȴȽΙӀӏ',
    m: 'ΜϺϻМмӍӎ',
    n: 'ÑñŃńŅņŇňʼnŊŋƝƞǸǹȠȵΝΠήηϞЍИЙЛПийлпѝҊҋӅӆӢӣӤӥπ',
    o: 'ÒÓÔÕÖØðòóôõöøŌōŎŏŐőƟƠơǑǒǪǫǬǭǾǿȌȍȎȏȪȫȬȭȮȯȰȱΌΘΟθοσόϕϘϙϬϭϴОФоѲѳӦӧӨөӪӫ',
    p: 'ƤƿΡρϷϸϼРрҎҏÞ',
    q: 'Ɋɋ',
    r: 'ŔŕŖŗŘřƦȐȑȒȓɌɍЃГЯгяѓҐґ',
    s: 'ŚśŜŝŞşŠšƧƨȘșȿЅѕ',
    t: 'ŢţŤťŦŧƫƬƭƮȚțȶȾΓΤτϮТт',
    u: 'µÙÚÛÜùúûüŨũŪūŬŭŮůŰűŲųƯưƱƲǓǔǕǖǗǘǙǚǛǜȔȕȖȗɄΰμυϋύ',
    v: 'νѴѵѶѷ',
    w: 'ŴŵƜωώϖϢϣШЩшщѡѿ',
    x: '×ΧχϗϰХхҲҳӼӽӾӿ',
    y: 'ÝýÿŶŷŸƳƴȲȳɎɏΎΥΫγψϒϓϔЎУучўѰѱҮүҰұӮӯӰӱӲӳ',
    z: 'ŹźŻżŽžƩƵƶȤȥɀΖζ'
  }; //decompress data into two hashes

  var unicode = {};
  Object.keys(compact).forEach(function (k) {
    compact[k].split('').forEach(function (s) {
      unicode[s] = k;
    });
  });

  var killUnicode = function killUnicode(str) {
    var chars = str.split('');
    chars.forEach(function (s, i) {
      if (unicode[s]) {
        chars[i] = unicode[s];
      }
    });
    return chars.join('');
  };

  var unicode_1 = killUnicode; // console.log(killUnicode('bjŏȒk—Ɏó'));

  var periodAcronym = /([A-Z]\.)+[A-Z]?,?$/;
  var oneLetterAcronym = /^[A-Z]\.,?$/;
  var noPeriodAcronym = /[A-Z]{2,}('s|,)?$/;
  var lowerCaseAcronym = /([a-z]\.){2,}[a-z]\.?$/;

  var isAcronym = function isAcronym(str) {
    //like N.D.A
    if (periodAcronym.test(str) === true) {
      return true;
    } //like c.e.o


    if (lowerCaseAcronym.test(str) === true) {
      return true;
    } //like 'F.'


    if (oneLetterAcronym.test(str) === true) {
      return true;
    } //like NDA


    if (noPeriodAcronym.test(str) === true) {
      return true;
    }

    return false;
  };

  var isAcronym_1 = isAcronym;

  var hasSlash = /[a-z\u00C0-\u00FF] ?\/ ?[a-z\u00C0-\u00FF]/;
  /** some basic operations on a string to reduce noise */

  var clean = function clean(str) {
    str = str || '';
    str = str.toLowerCase();
    str = str.trim();
    var original = str; //(very) rough ASCII transliteration -  bjŏrk -> bjork

    str = unicode_1(str); //rough handling of slashes - 'see/saw'

    if (hasSlash.test(str) === true) {
      str = str.replace(/\/.*/, '');
    } //#tags, @mentions


    str = str.replace(/^[#@]/, ''); //punctuation

    str = str.replace(/[,;.!?]+$/, ''); // coerce single curly quotes

    str = str.replace(/[\u0027\u0060\u00B4\u2018\u2019\u201A\u201B\u2032\u2035\u2039\u203A]+/g, "'"); // coerce double curly quotes

    str = str.replace(/[\u0022\u00AB\u00BB\u201C\u201D\u201E\u201F\u2033\u2034\u2036\u2037\u2E42\u301D\u301E\u301F\uFF02]+/g, '"'); //coerce Unicode ellipses

    str = str.replace(/\u2026/g, '...'); //en-dash

    str = str.replace(/\u2013/g, '-'); //lookin'->looking (make it easier for conjugation)

    str = str.replace(/([aeiou][ktrp])in$/, '$1ing'); //turn re-enactment to reenactment

    if (/^(re|un)-?[^aeiou]./.test(str) === true) {
      str = str.replace('-', '');
    } //strip leading & trailing grammatical punctuation


    if (/^[:;]/.test(str) === false) {
      str = str.replace(/\.{3,}$/g, '');
      str = str.replace(/[",\.!:;\?\)]+$/g, '');
      str = str.replace(/^['"\(]+/g, '');
    } //do this again..


    str = str.trim(); //oh shucks,

    if (str === '') {
      str = original;
    } //compact acronyms


    if (isAcronym_1(str)) {
      str = str.replace(/\./g, '');
    } //nice-numbers


    str = str.replace(/([0-9]),([0-9])/g, '$1$2');
    return str;
  };

  var clean_1 = clean; // console.log(normalize('Dr. V Cooper'));

  /** reduced is one step further than clean */
  var reduced = function reduced(str) {
    // remove apostrophes
    str = str.replace(/['’]s$/, '');
    str = str.replace(/s['’]$/, 's');
    return str;
  };

  var reduce = reduced;

  //all punctuation marks, from https://en.wikipedia.org/wiki/Punctuation
  //we have slightly different rules for start/end - like #hashtags.

  var startings = /^[ \n\t\.’'\[\](){}⟨⟩:,،、‒–—―…!.‹›«»‐\-?‘’;\/⁄·&*•^†‡°¡¿※№÷׺ª%‰+−=‱¶′″‴§~|‖¦©℗®℠™¤₳฿\u0022|\uFF02|\u0027|\u201C|\u2018|\u201F|\u201B|\u201E|\u2E42|\u201A|\u00AB|\u2039|\u2035|\u2036|\u2037|\u301D|\u0060|\u301F]+/;
  var endings = /[ \n\t\.’'\[\](){}⟨⟩:,،、‒–—―…!.‹›«»‐\-?‘’;\/⁄·&*@•^†‡°¡¿※#№÷׺ª‰+−=‱¶′″‴§~|‖¦©℗®℠™¤₳฿\u0022|\uFF02|\u0027|\u201D|\u2019|\u201D|\u2019|\u201D|\u201D|\u2019|\u00BB|\u203A|\u2032|\u2033|\u2034|\u301E|\u00B4|\u301E]+$/; //money = ₵¢₡₢$₫₯֏₠€ƒ₣₲₴₭₺₾ℳ₥₦₧₱₰£៛₽₹₨₪৳₸₮₩¥

  var hasSlash$1 = /\//;
  var hasApostrophe = /['’]/;
  var hasAcronym = /^[a-z]\.([a-z]\.)+/i;
  var minusNumber = /^[-+\.][0-9]/;
  /** turn given text into a parsed-up object
   * seperate the 'meat' of the word from the whitespace+punctuation
   */

  var parseTerm = function parseTerm(str) {
    var original = str;
    var pre = '';
    var post = '';
    str = str.replace(startings, function (found) {
      pre = found; // support '-40'

      if ((pre === '-' || pre === '+' || pre === '.') && minusNumber.test(str)) {
        pre = '';
        return found;
      }

      return '';
    });
    str = str.replace(endings, function (found) {
      post = found; // keep s-apostrophe - "flanders'" or "chillin'"

      if (hasApostrophe.test(found) && /[sn]['’]$/.test(original) && hasApostrophe.test(pre) === false) {
        post = post.replace(hasApostrophe, '');
        return "'";
      } //keep end-period in acronym


      if (hasAcronym.test(str) === true) {
        post = post.replace(/\./, '');
        return '.';
      }

      return '';
    }); //we went too far..

    if (str === '') {
      // do a very mild parse, and hope for the best.
      original = original.replace(/ *$/, function (after) {
        post = after || '';
        return '';
      });
      str = original;
      pre = '';
      post = post;
    } // create the various forms of our text,


    var clean = clean_1(str);
    var parsed = {
      text: str,
      clean: clean,
      reduced: reduce(clean),
      pre: pre,
      post: post
    }; // support aliases for slashes

    if (hasSlash$1.test(str)) {
      str.split(hasSlash$1).forEach(function (word) {
        parsed.alias = parsed.alias || {};
        parsed.alias[word.trim()] = true;
      });
    }

    return parsed;
  };

  var parse = parseTerm;

  function createCommonjsModule(fn, basedir, module) {
  	return module = {
  	  path: basedir,
  	  exports: {},
  	  require: function (path, base) {
        return commonjsRequire(path, (base === undefined || base === null) ? module.path : base);
      }
  	}, fn(module, module.exports), module.exports;
  }

  function commonjsRequire () {
  	throw new Error('Dynamic requires are not currently supported by @rollup/plugin-commonjs');
  }

  var _01Case = createCommonjsModule(function (module, exports) {
    var titleCase = /^[A-Z][a-z'\u00C0-\u00FF]/;
    var upperCase = /^[A-Z]+s?$/;
    /** convert all text to uppercase */

    exports.toUpperCase = function () {
      this.text = this.text.toUpperCase();
      return this;
    };
    /** convert all text to lowercase */


    exports.toLowerCase = function () {
      this.text = this.text.toLowerCase();
      return this;
    };
    /** only set the first letter to uppercase
     * leave any existing uppercase alone
     */


    exports.toTitleCase = function () {
      this.text = this.text.replace(/^ *[a-z\u00C0-\u00FF]/, function (x) {
        return x.toUpperCase();
      }); //support unicode?

      return this;
    };
    /** if all letters are uppercase */


    exports.isUpperCase = function () {
      return upperCase.test(this.text);
    };
    /** if the first letter is uppercase, and the rest are lowercase */


    exports.isTitleCase = function () {
      return titleCase.test(this.text);
    };

    exports.titleCase = exports.isTitleCase;
  });

  var _02Punctuation = createCommonjsModule(function (module, exports) {
    // these methods are called with '@hasComma' in the match syntax
    // various unicode quotation-mark formats
    var startQuote = /(\u0022|\uFF02|\u0027|\u201C|\u2018|\u201F|\u201B|\u201E|\u2E42|\u201A|\u00AB|\u2039|\u2035|\u2036|\u2037|\u301D|\u0060|\u301F)/;
    var endQuote = /(\u0022|\uFF02|\u0027|\u201D|\u2019|\u201D|\u2019|\u201D|\u201D|\u2019|\u00BB|\u203A|\u2032|\u2033|\u2034|\u301E|\u00B4|\u301E)/;
    /** search the term's 'post' punctuation  */

    exports.hasPost = function (punct) {
      return this.post.indexOf(punct) !== -1;
    };
    /** search the term's 'pre' punctuation  */


    exports.hasPre = function (punct) {
      return this.pre.indexOf(punct) !== -1;
    };
    /** does it have a quotation symbol?  */


    exports.hasQuote = function () {
      return startQuote.test(this.pre) || endQuote.test(this.post);
    };

    exports.hasQuotation = exports.hasQuote;
    /** does it have a comma?  */

    exports.hasComma = function () {
      return this.hasPost(',');
    };
    /** does it end in a period? */


    exports.hasPeriod = function () {
      return this.hasPost('.') === true && this.hasPost('...') === false;
    };
    /** does it end in an exclamation */


    exports.hasExclamation = function () {
      return this.hasPost('!');
    };
    /** does it end with a question mark? */


    exports.hasQuestionMark = function () {
      return this.hasPost('?') || this.hasPost('¿');
    };
    /** is there a ... at the end? */


    exports.hasEllipses = function () {
      return this.hasPost('..') || this.hasPost('…') || this.hasPre('..') || this.hasPre('…');
    };
    /** is there a semicolon after this word? */


    exports.hasSemicolon = function () {
      return this.hasPost(';');
    };
    /** is there a slash '/' in this word? */


    exports.hasSlash = function () {
      var slash = /\//;
      return slash.test(this.text);
    };
    /** a hyphen connects two words like-this */


    exports.hasHyphen = function () {
      var hyphen = /(-|–|—)/;
      return hyphen.test(this.post) || hyphen.test(this.pre);
    };
    /** a dash separates words - like that */


    exports.hasDash = function () {
      var hyphen = / (-|–|—) /;
      return hyphen.test(this.post) || hyphen.test(this.pre);
    };
    /** is it multiple words combinded */


    exports.hasContraction = function () {
      return Boolean(this.implicit);
    };
    /** try to sensibly put this punctuation mark into the term */


    exports.addPunctuation = function (punct) {
      // dont add doubles
      if (punct === ',' || punct === ';') {
        this.post = this.post.replace(punct, '');
      }

      this.post = punct + this.post;
      return this;
    };
  });

  //declare it up here
  var wrapMatch = function wrapMatch() {};
  /** ignore optional/greedy logic, straight-up term match*/


  var doesMatch = function doesMatch(t, reg, index, length) {
    // support id matches
    if (reg.id === t.id) {
      return true;
    } // support '.'


    if (reg.anything === true) {
      return true;
    } // support '^' (in parentheses)


    if (reg.start === true && index !== 0) {
      return false;
    } // support '$' (in parentheses)


    if (reg.end === true && index !== length - 1) {
      return false;
    } //support a text match


    if (reg.word !== undefined) {
      //match contractions
      if (t.implicit !== null && t.implicit === reg.word) {
        return true;
      } // term aliases for slashes and things


      if (t.alias !== undefined && t.alias.hasOwnProperty(reg.word)) {
        return true;
      } // support ~ match


      if (reg.soft === true && reg.word === t.root) {
        return true;
      } //match either .clean or .text


      return reg.word === t.clean || reg.word === t.text || reg.word === t.reduced;
    } //support #Tag


    if (reg.tag !== undefined) {
      return t.tags[reg.tag] === true;
    } //support @method


    if (reg.method !== undefined) {
      if (typeof t[reg.method] === 'function' && t[reg.method]() === true) {
        return true;
      }

      return false;
    } //support /reg/


    if (reg.regex !== undefined) {
      return reg.regex.test(t.clean);
    } // support optimized (one|two)


    if (reg.oneOf !== undefined) {
      return reg.oneOf.hasOwnProperty(t.reduced) || reg.oneOf.hasOwnProperty(t.text);
    } //support (one|two)


    if (reg.choices !== undefined) {
      // try to support && operator
      if (reg.operator === 'and') {
        // must match them all
        return reg.choices.every(function (r) {
          return wrapMatch(t, r, index, length);
        });
      } // or must match one


      return reg.choices.some(function (r) {
        return wrapMatch(t, r, index, length);
      });
    }

    return false;
  }; // wrap result for !negative match logic


  wrapMatch = function wrapMatch(t, reg, index, length) {
    var result = doesMatch(t, reg, index, length);

    if (reg.negative === true) {
      return !result;
    }

    return result;
  };

  var _doesMatch = wrapMatch;

  var boring = {};
  /** check a match object against this term */

  var doesMatch_1 = function doesMatch_1(reg, index, length) {
    return _doesMatch(this, reg, index, length);
  };
  /** does this term look like an acronym? */


  var isAcronym_1$1 = function isAcronym_1$1() {
    return isAcronym_1(this.text);
  };
  /** is this term implied by a contraction? */


  var isImplicit = function isImplicit() {
    return this.text === '' && Boolean(this.implicit);
  };
  /** does the term have at least one good tag? */


  var isKnown = function isKnown() {
    return Object.keys(this.tags).some(function (t) {
      return boring[t] !== true;
    });
  };
  /** cache the root property of the term */


  var setRoot = function setRoot(world) {
    var transform = world.transforms;
    var str = this.implicit || this.clean;

    if (this.tags.Plural) {
      str = transform.toSingular(str, world);
    }

    if (this.tags.Verb && !this.tags.Negative && !this.tags.Infinitive) {
      var tense = null;

      if (this.tags.PastTense) {
        tense = 'PastTense';
      } else if (this.tags.Gerund) {
        tense = 'Gerund';
      } else if (this.tags.PresentTense) {
        tense = 'PresentTense';
      } else if (this.tags.Participle) {
        tense = 'Participle';
      } else if (this.tags.Actor) {
        tense = 'Actor';
      }

      str = transform.toInfinitive(str, world, tense);
    }

    this.root = str;
  };

  var _03Misc = {
    doesMatch: doesMatch_1,
    isAcronym: isAcronym_1$1,
    isImplicit: isImplicit,
    isKnown: isKnown,
    setRoot: setRoot
  };

  var hasSpace = /[\s-]/;
  var isUpperCase = /^[A-Z-]+$/; // const titleCase = str => {
  //   return str.charAt(0).toUpperCase() + str.substr(1)
  // }

  /** return various text formats of this term */

  var textOut = function textOut(options, showPre, showPost) {
    options = options || {};
    var word = this.text;
    var before = this.pre;
    var after = this.post; // -word-

    if (options.reduced === true) {
      word = this.reduced || '';
    }

    if (options.root === true) {
      word = this.root || '';
    }

    if (options.implicit === true && this.implicit) {
      word = this.implicit || '';
    }

    if (options.normal === true) {
      word = this.clean || this.text || '';
    }

    if (options.root === true) {
      word = this.root || this.reduced || '';
    }

    if (options.unicode === true) {
      word = unicode_1(word);
    } // cleanup case


    if (options.titlecase === true) {
      if (this.tags.ProperNoun && !this.titleCase()) ; else if (this.tags.Acronym) {
        word = word.toUpperCase(); //uppercase acronyms
      } else if (isUpperCase.test(word) && !this.tags.Acronym) {
        // lowercase everything else
        word = word.toLowerCase();
      }
    }

    if (options.lowercase === true) {
      word = word.toLowerCase();
    } // remove the '.'s from 'F.B.I.' (safely)


    if (options.acronyms === true && this.tags.Acronym) {
      word = word.replace(/\./g, '');
    } // -before/after-


    if (options.whitespace === true || options.root === true) {
      before = '';
      after = ' ';

      if ((hasSpace.test(this.post) === false || options.last) && !this.implicit) {
        after = '';
      }
    }

    if (options.punctuation === true && !options.root) {
      //normalized end punctuation
      if (this.hasPost('.') === true) {
        after = '.' + after;
      } else if (this.hasPost('?') === true) {
        after = '?' + after;
      } else if (this.hasPost('!') === true) {
        after = '!' + after;
      } else if (this.hasPost(',') === true) {
        after = ',' + after;
      } else if (this.hasEllipses() === true) {
        after = '...' + after;
      }
    }

    if (showPre !== true) {
      before = '';
    }

    if (showPost !== true) {
      // let keep = after.match(/\)/) || ''
      after = ''; //keep //after.replace(/[ .?!,]+/, '')
    } // remove the '.' from 'Mrs.' (safely)


    if (options.abbreviations === true && this.tags.Abbreviation) {
      after = after.replace(/^\./, '');
    }

    return before + word + after;
  };

  var _04Text = {
    textOut: textOut
  };

  var boringTags = {
    Auxiliary: 1,
    Possessive: 1
  };
  /** a subjective ranking of tags kinda tfidf-based */

  var rankTags = function rankTags(term, world) {
    var tags = Object.keys(term.tags);
    var tagSet = world.tags;
    tags = tags.sort(function (a, b) {
      //bury the tags we dont want
      if (boringTags[b] || !tagSet[b]) {
        return -1;
      } // unknown tags are interesting


      if (!tagSet[b]) {
        return 1;
      }

      if (!tagSet[a]) {
        return 0;
      } // then sort by #of parent tags (most-specific tags first)


      if (tagSet[a].lineage.length > tagSet[b].lineage.length) {
        return 1;
      }

      if (tagSet[a].isA.length > tagSet[b].isA.length) {
        return -1;
      }

      return 0;
    });
    return tags;
  };

  var _bestTag = rankTags;

  var jsonDefault = {
    text: true,
    tags: true,
    implicit: true,
    whitespace: true,
    clean: false,
    id: false,
    index: false,
    offset: false,
    bestTag: false
  };
  /** return various metadata for this term */

  var json = function json(options, world) {
    options = options || {};
    options = Object.assign({}, jsonDefault, options);
    var result = {}; // default on

    if (options.text) {
      result.text = this.text;
    }

    if (options.normal) {
      result.normal = this.normal;
    }

    if (options.tags) {
      result.tags = Object.keys(this.tags);
    } // default off


    if (options.clean) {
      result.clean = this.clean;
    }

    if (options.id || options.offset) {
      result.id = this.id;
    }

    if (options.implicit && this.implicit !== null) {
      result.implicit = this.implicit;
    }

    if (options.whitespace) {
      result.pre = this.pre;
      result.post = this.post;
    }

    if (options.bestTag) {
      result.bestTag = _bestTag(this, world)[0];
    }

    return result;
  };

  var _05Json = {
    json: json
  };

  var methods = Object.assign({}, _01Case, _02Punctuation, _03Misc, _04Text, _05Json);

  function isClientSide() {
    return typeof window !== 'undefined' && window.document;
  }
  /** add spaces at the end */


  var padEnd = function padEnd(str, width) {
    str = str.toString();

    while (str.length < width) {
      str += ' ';
    }

    return str;
  };
  /** output for verbose-mode */


  var logTag = function logTag(t, tag, reason) {
    if (isClientSide()) {
      console.log('%c' + padEnd(t.clean, 3) + '  + ' + tag + ' ', 'color: #6accb2;');
      return;
    } //server-side


    var log = '\x1b[33m' + padEnd(t.clean, 15) + '\x1b[0m + \x1b[32m' + tag + '\x1b[0m ';

    if (reason) {
      log = padEnd(log, 35) + ' ' + reason + '';
    }

    console.log(log);
  };
  /** output for verbose mode  */


  var logUntag = function logUntag(t, tag, reason) {
    if (isClientSide()) {
      console.log('%c' + padEnd(t.clean, 3) + '  - ' + tag + ' ', 'color: #AB5850;');
      return;
    } //server-side


    var log = '\x1b[33m' + padEnd(t.clean, 3) + ' \x1b[31m - #' + tag + '\x1b[0m ';

    if (reason) {
      log = padEnd(log, 35) + ' ' + reason;
    }

    console.log(log);
  };

  var isArray = function isArray(arr) {
    return Object.prototype.toString.call(arr) === '[object Array]';
  };

  var titleCase = function titleCase(str) {
    return str.charAt(0).toUpperCase() + str.substr(1);
  };

  var fns = {
    logTag: logTag,
    logUntag: logUntag,
    isArray: isArray,
    titleCase: titleCase
  };

  /** add a tag, and its descendents, to a term */

  var addTag = function addTag(t, tag, reason, world) {
    var tagset = world.tags; //support '.' or '-' notation for skipping the tag

    if (tag === '' || tag === '.' || tag === '-') {
      return;
    }

    if (tag[0] === '#') {
      tag = tag.replace(/^#/, '');
    }

    tag = fns.titleCase(tag); //if we already got this one

    if (t.tags[tag] === true) {
      return;
    } // log it?


    var isVerbose = world.isVerbose();

    if (isVerbose === true) {
      fns.logTag(t, tag, reason);
    } //add tag


    t.tags[tag] = true; //whee!
    //check tagset for any additional things to do...

    if (tagset.hasOwnProperty(tag) === true) {
      //add parent Tags
      tagset[tag].isA.forEach(function (down) {
        t.tags[down] = true;

        if (isVerbose === true) {
          fns.logTag(t, '→ ' + down);
        }
      }); //remove any contrary tags

      t.unTag(tagset[tag].notA, '←', world);
    }
  };
  /** support an array of tags */


  var addTags = function addTags(term, tags, reason, world) {
    if (typeof tags !== 'string') {
      for (var i = 0; i < tags.length; i++) {
        addTag(term, tags[i], reason, world);
      } // tags.forEach(tag => addTag(term, tag, reason, world))

    } else {
      addTag(term, tags, reason, world);
    }
  };

  var add = addTags;

  var lowerCase = /^[a-z]/;

  var titleCase$1 = function titleCase(str) {
    return str.charAt(0).toUpperCase() + str.substr(1);
  };
  /** remove this tag, and its descentents from the term */


  var unTag = function unTag(t, tag, reason, world) {
    var isVerbose = world.isVerbose(); //support '*' for removing all tags

    if (tag === '*') {
      t.tags = {};
      return t;
    }

    tag = tag.replace(/^#/, '');

    if (lowerCase.test(tag) === true) {
      tag = titleCase$1(tag);
    } // remove the tag


    if (t.tags[tag] === true) {
      delete t.tags[tag]; //log in verbose-mode

      if (isVerbose === true) {
        fns.logUntag(t, tag, reason);
      }
    } //delete downstream tags too


    var tagset = world.tags;

    if (tagset[tag]) {
      var lineage = tagset[tag].lineage;

      for (var i = 0; i < lineage.length; i++) {
        if (t.tags[lineage[i]] === true) {
          delete t.tags[lineage[i]];

          if (isVerbose === true) {
            fns.logUntag(t, ' - ' + lineage[i]);
          }
        }
      }
    }

    return t;
  }; //handle an array of tags


  var untagAll = function untagAll(term, tags, reason, world) {
    if (typeof tags !== 'string' && tags) {
      for (var i = 0; i < tags.length; i++) {
        unTag(term, tags[i], reason, world);
      }

      return;
    }

    unTag(term, tags, reason, world);
  };

  var unTag_1 = untagAll;

  var canBe = function canBe(term, tag, world) {
    var tagset = world.tags; // cleanup tag

    if (tag[0] === '#') {
      tag = tag.replace(/^#/, '');
    } //fail-fast


    if (tagset[tag] === undefined) {
      return true;
    } //loop through tag's contradictory tags


    var enemies = tagset[tag].notA || [];

    for (var i = 0; i < enemies.length; i++) {
      if (term.tags[enemies[i]] === true) {
        return false;
      }
    }

    if (tagset[tag].isA !== undefined) {
      return canBe(term, tagset[tag].isA, world); //recursive
    }

    return true;
  };

  var canBe_1 = canBe;

  /** add a tag or tags, and their descendents to this term
   * @param  {string | string[]} tags - a tag or tags
   * @param {string?} [reason] a clue for debugging
   */

  var tag_1 = function tag_1(tags, reason, world) {
    add(this, tags, reason, world);
    return this;
  };
  /** only tag this term if it's consistent with it's current tags */


  var tagSafe = function tagSafe(tags, reason, world) {
    if (canBe_1(this, tags, world)) {
      add(this, tags, reason, world);
    }

    return this;
  };
  /** remove a tag or tags, and their descendents from this term
   * @param {string | string[]} tags  - a tag or tags
   * @param {string?} [reason] a clue for debugging
   */


  var unTag_1$1 = function unTag_1$1(tags, reason, world) {
    unTag_1(this, tags, reason, world);
    return this;
  };
  /** is this tag consistent with the word's current tags?
   * @param {string | string[]} tags - a tag or tags
   * @returns {boolean}
   */


  var canBe_1$1 = function canBe_1$1(tags, world) {
    return canBe_1(this, tags, world);
  };

  var tag = {
    tag: tag_1,
    tagSafe: tagSafe,
    unTag: unTag_1$1,
    canBe: canBe_1$1
  };

  var Term = /*#__PURE__*/function () {
    function Term() {
      var text = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';

      _classCallCheck(this, Term);

      text = String(text);
      var obj = parse(text); // the various forms of our text

      this.text = obj.text || '';
      this.clean = obj.clean;
      this.reduced = obj.reduced;
      this.root =  null;
      this.implicit =  null;
      this.pre = obj.pre || '';
      this.post = obj.post || '';
      this.tags = {};
      this.prev = null;
      this.next = null;
      this.id = _id(obj.clean);
      this.isA = 'Term'; // easier than .constructor...
      // support alternative matches

      if (obj.alias) {
        this.alias = obj.alias;
      }
    }
    /** set the text of the Term to something else*/


    _createClass(Term, [{
      key: "set",
      value: function set(str) {
        var obj = parse(str);
        this.text = obj.text;
        this.clean = obj.clean;
        return this;
      }
    }]);

    return Term;
  }();
  /** create a deep-copy of this term */


  Term.prototype.clone = function () {
    var term = new Term(this.text);
    term.pre = this.pre;
    term.post = this.post;
    term.clean = this.clean;
    term.reduced = this.reduced;
    term.root = this.root;
    term.implicit = this.implicit;
    term.tags = Object.assign({}, this.tags); //use the old id, so it can be matched with .match(doc)
    // term.id = this.id

    return term;
  };

  Object.assign(Term.prototype, methods);
  Object.assign(Term.prototype, tag);
  var Term_1 = Term;

  /** return a flat array of Term objects */
  var terms = function terms(n) {
    if (this.length === 0) {
      return [];
    } // use cache, if it exists


    if (this.cache.terms) {
      if (n !== undefined) {
        return this.cache.terms[n];
      }

      return this.cache.terms;
    }

    var terms = [this.pool.get(this.start)];

    for (var i = 0; i < this.length - 1; i += 1) {
      var id = terms[terms.length - 1].next;

      if (id === null) {
        // throw new Error('linked-list broken')
        console.error("Compromise error: Linked list broken in phrase '" + this.start + "'");
        break;
      }

      var term = this.pool.get(id);
      terms.push(term); //return this one?

      if (n !== undefined && n === i) {
        return terms[n];
      }
    }

    if (n === undefined) {
      this.cache.terms = terms;
    }

    if (n !== undefined) {
      return terms[n];
    }

    return terms;
  };
  /** return a shallow or deep copy of this phrase  */


  var clone = function clone(isShallow) {
    var _this = this;

    if (isShallow) {
      var p = this.buildFrom(this.start, this.length);
      p.cache = this.cache;
      return p;
    } //how do we clone part of the pool?


    var terms = this.terms();
    var newTerms = terms.map(function (t) {
      return t.clone();
    }); // console.log(newTerms)
    //connect these new ids up

    newTerms.forEach(function (t, i) {
      //add it to the pool..
      _this.pool.add(t);

      if (newTerms[i + 1]) {
        t.next = newTerms[i + 1].id;
      }

      if (newTerms[i - 1]) {
        t.prev = newTerms[i - 1].id;
      }
    });
    return this.buildFrom(newTerms[0].id, newTerms.length);
  };
  /** return last term object */


  var lastTerm = function lastTerm() {
    var terms = this.terms();
    return terms[terms.length - 1];
  };
  /** quick lookup for a term id */


  var hasId = function hasId(wantId) {
    if (this.length === 0 || !wantId) {
      return false;
    }

    if (this.start === wantId) {
      return true;
    } // use cache, if available


    if (this.cache.terms) {
      var _terms = this.cache.terms;

      for (var i = 0; i < _terms.length; i++) {
        if (_terms[i].id === wantId) {
          return true;
        }
      }

      return false;
    } // otherwise, go through each term


    var lastId = this.start;

    for (var _i = 0; _i < this.length - 1; _i += 1) {
      var term = this.pool.get(lastId);

      if (term === undefined) {
        console.error("Compromise error: Linked list broken. Missing term '".concat(lastId, "' in phrase '").concat(this.start, "'\n")); // throw new Error('linked List error')

        return false;
      }

      if (term.next === wantId) {
        return true;
      }

      lastId = term.next;
    }

    return false;
  };
  /** how many seperate, non-empty words is it? */


  var wordCount = function wordCount() {
    return this.terms().filter(function (t) {
      return t.text !== '';
    }).length;
  };
  /** get the full-sentence this phrase belongs to */


  var fullSentence = function fullSentence() {
    var t = this.terms(0); //find first term in sentence

    while (t.prev) {
      t = this.pool.get(t.prev);
    }

    var start = t.id;
    var len = 1; //go to end of sentence

    while (t.next) {
      t = this.pool.get(t.next);
      len += 1;
    }

    return this.buildFrom(start, len);
  };

  var _01Utils = {
    terms: terms,
    clone: clone,
    lastTerm: lastTerm,
    hasId: hasId,
    wordCount: wordCount,
    fullSentence: fullSentence
  };

  var trimEnd = function trimEnd(str) {
    return str.replace(/ +$/, '');
  };
  /** produce output in the given format */


  var text = function text() {
    var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
    var isFirst = arguments.length > 1 ? arguments[1] : undefined;
    var isLast = arguments.length > 2 ? arguments[2] : undefined;

    if (typeof options === 'string') {
      if (options === 'normal') {
        options = {
          whitespace: true,
          unicode: true,
          lowercase: true,
          punctuation: true,
          acronyms: true,
          abbreviations: true,
          implicit: true,
          normal: true
        };
      } else if (options === 'clean') {
        options = {
          titlecase: false,
          lowercase: true,
          punctuation: true,
          whitespace: true,
          unicode: true,
          implicit: true
        };
      } else if (options === 'reduced') {
        options = {
          titlecase: false,
          lowercase: true,
          punctuation: false,
          //FIXME: reversed?
          whitespace: true,
          unicode: true,
          implicit: true,
          reduced: true
        };
      } else if (options === 'root') {
        options = {
          titlecase: false,
          lowercase: true,
          punctuation: true,
          whitespace: true,
          unicode: true,
          implicit: true,
          root: true
        };
      } else {
        options = {};
      }
    }

    var terms = this.terms(); //this this phrase a complete sentence?

    var isFull = false;

    if (terms[0] && terms[0].prev === null && terms[terms.length - 1].next === null) {
      isFull = true;
    }

    var text = terms.reduce(function (str, t, i) {
      options.last = isLast && i === terms.length - 1;
      var showPre = true;
      var showPost = true;

      if (isFull === false) {
        // dont show beginning whitespace
        if (i === 0 && isFirst) {
          showPre = false;
        } // dont show end-whitespace


        if (i === terms.length - 1 && isLast) {
          showPost = false;
        }
      }

      var txt = t.textOut(options, showPre, showPost); // if (options.titlecase && i === 0) {
      // txt = titleCase(txt)
      // }

      return str + txt;
    }, ''); //full-phrases show punctuation, but not whitespace

    if (isFull === true && isLast) {
      text = trimEnd(text);
    }

    if (options.trim === true) {
      text = text.trim();
    }

    return text;
  };

  var _02Text = {
    text: text
  };

  /** remove start and end whitespace */
  var trim = function trim() {
    var terms = this.terms();

    if (terms.length > 0) {
      //trim starting
      terms[0].pre = terms[0].pre.replace(/^\s+/, ''); //trim ending

      var lastTerm = terms[terms.length - 1];
      lastTerm.post = lastTerm.post.replace(/\s+$/, '');
    }

    return this;
  };

  var _03Change = {
    trim: trim
  };

  var endOfSentence = /[.?!]\s*$/; // replacing a 'word.' with a 'word!'

  var combinePost = function combinePost(before, after) {
    //only transfer the whitespace
    if (endOfSentence.test(after)) {
      var whitespace = before.match(/\s*$/);
      return after + whitespace;
    }

    return before;
  }; //add whitespace to the start of the second bit


  var addWhitespace = function addWhitespace(beforeTerms, newTerms) {
    // add any existing pre-whitespace to beginning
    newTerms[0].pre = beforeTerms[0].pre;
    var lastTerm = beforeTerms[beforeTerms.length - 1]; //add any existing punctuation to end of our new terms

    var newTerm = newTerms[newTerms.length - 1];
    newTerm.post = combinePost(lastTerm.post, newTerm.post); // remove existing punctuation

    lastTerm.post = ''; //before ←[space]  - after

    if (lastTerm.post === '') {
      lastTerm.post += ' ';
    }
  }; //insert this segment into the linked-list


  var stitchIn = function stitchIn(beforeTerms, newTerms, pool) {
    var lastBefore = beforeTerms[beforeTerms.length - 1];
    var lastNew = newTerms[newTerms.length - 1];
    var afterId = lastBefore.next; //connect ours in (main → newPhrase)

    lastBefore.next = newTerms[0].id; //stich the end in  (newPhrase → after)

    lastNew.next = afterId; //do it backwards, too

    if (afterId) {
      // newPhrase ← after
      var afterTerm = pool.get(afterId);
      afterTerm.prev = lastNew.id;
    } // before ← newPhrase


    var beforeId = beforeTerms[0].id;

    if (beforeId) {
      var newTerm = newTerms[0];
      newTerm.prev = beforeId;
    }
  }; // avoid stretching a phrase twice.


  var unique = function unique(list) {
    return list.filter(function (o, i) {
      return list.indexOf(o) === i;
    });
  }; //append one phrase onto another.


  var appendPhrase = function appendPhrase(before, newPhrase, doc) {
    var beforeTerms = before.terms();
    var newTerms = newPhrase.terms(); //spruce-up the whitespace issues

    addWhitespace(beforeTerms, newTerms); //insert this segment into the linked-list

    stitchIn(beforeTerms, newTerms, before.pool); // stretch!
    // make each effected phrase longer

    var toStretch = [before];
    var hasId = before.start;
    var docs = [doc];
    docs = docs.concat(doc.parents()); // find them all!

    docs.forEach(function (parent) {
      // only the phrases that should change
      var shouldChange = parent.list.filter(function (p) {
        return p.hasId(hasId);
      });
      toStretch = toStretch.concat(shouldChange);
    }); // don't double-count a phrase

    toStretch = unique(toStretch);
    toStretch.forEach(function (p) {
      p.length += newPhrase.length;
    });
    before.cache = {};
    return before;
  };

  var append = appendPhrase;

  var hasSpace$1 = / /; //a new space needs to be added, either on the new phrase, or the old one
  // '[new] [◻old]'   -or-   '[old] [◻new] [old]'

  var addWhitespace$1 = function addWhitespace(newTerms) {
    //add a space before our new text?
    // add a space after our text
    var lastTerm = newTerms[newTerms.length - 1];

    if (hasSpace$1.test(lastTerm.post) === false) {
      lastTerm.post += ' ';
    }

    return;
  }; //insert this segment into the linked-list


  var stitchIn$1 = function stitchIn(main, newPhrase, newTerms) {
    // [newPhrase] → [main]
    var lastTerm = newTerms[newTerms.length - 1];
    lastTerm.next = main.start; // [before] → [main]

    var pool = main.pool;
    var start = pool.get(main.start);

    if (start.prev) {
      var before = pool.get(start.prev);
      before.next = newPhrase.start;
    } //do it backwards, too
    // before ← newPhrase


    newTerms[0].prev = main.terms(0).prev; // newPhrase ← main

    main.terms(0).prev = lastTerm.id;
  };

  var unique$1 = function unique(list) {
    return list.filter(function (o, i) {
      return list.indexOf(o) === i;
    });
  }; //append one phrase onto another


  var joinPhrase = function joinPhrase(original, newPhrase, doc) {
    var starterId = original.start;
    var newTerms = newPhrase.terms(); //spruce-up the whitespace issues

    addWhitespace$1(newTerms); //insert this segment into the linked-list

    stitchIn$1(original, newPhrase, newTerms); //increase the length of our phrases

    var toStretch = [original];
    var docs = [doc];
    docs = docs.concat(doc.parents());
    docs.forEach(function (d) {
      // only the phrases that should change
      var shouldChange = d.list.filter(function (p) {
        return p.hasId(starterId) || p.hasId(newPhrase.start);
      });
      toStretch = toStretch.concat(shouldChange);
    }); // don't double-count

    toStretch = unique$1(toStretch); // stretch these phrases

    toStretch.forEach(function (p) {
      p.length += newPhrase.length; // change the start too, if necessary

      if (p.start === starterId) {
        p.start = newPhrase.start;
      }

      p.cache = {};
    });
    return original;
  };

  var prepend = joinPhrase;

  //recursively decrease the length of all the parent phrases
  var shrinkAll = function shrinkAll(doc, id, deleteLength, after) {
    var arr = doc.parents();
    arr.push(doc);
    arr.forEach(function (d) {
      //find our phrase to shrink
      var phrase = d.list.find(function (p) {
        return p.hasId(id);
      });

      if (!phrase) {
        return;
      }

      phrase.length -= deleteLength; // does it start with this soon-removed word?

      if (phrase.start === id) {
        phrase.start = after.id;
      }

      phrase.cache = {};
    }); // cleanup empty phrase objects

    doc.list = doc.list.filter(function (p) {
      if (!p.start || !p.length) {
        return false;
      }

      return true;
    });
  };
  /** wrap the linked-list around these terms
   * so they don't appear any more
   */


  var deletePhrase = function deletePhrase(phrase, doc) {
    var pool = doc.pool();
    var terms = phrase.terms(); //grab both sides of the chain,

    var prev = pool.get(terms[0].prev) || {};
    var after = pool.get(terms[terms.length - 1].next) || {};

    if (terms[0].implicit && prev.implicit) {
      prev.set(prev.implicit);
      prev.post += ' ';
    } // //first, change phrase lengths


    shrinkAll(doc, phrase.start, phrase.length, after); // connect [prev]->[after]

    if (prev) {
      prev.next = after.id;
    } // connect [prev]<-[after]


    if (after) {
      after.prev = prev.id;
    } // lastly, actually delete the terms from the pool?
    // for (let i = 0; i < terms.length; i++) {
    //   pool.remove(terms[i].id)
    // }

  };

  var _delete = deletePhrase;

  /** put this text at the end */

  var append_1 = function append_1(newPhrase, doc) {
    append(this, newPhrase, doc);
    return this;
  };
  /** add this text to the beginning */


  var prepend_1 = function prepend_1(newPhrase, doc) {
    prepend(this, newPhrase, doc);
    return this;
  };

  var _delete$1 = function _delete$1(doc) {
    _delete(this, doc);
    return this;
  }; // stich-in newPhrase, stretch 'doc' + parents


  var replace = function replace(newPhrase, doc) {
    //add it do the end
    var firstLength = this.length;
    append(this, newPhrase, doc); //delete original terms

    var tmp = this.buildFrom(this.start, this.length);
    tmp.length = firstLength;
    _delete(tmp, doc);
  };
  /**
   * Turn this phrase object into 3 phrase objects
   */


  var splitOn = function splitOn(p) {
    var terms = this.terms();
    var result = {
      before: null,
      match: null,
      after: null
    };
    var index = terms.findIndex(function (t) {
      return t.id === p.start;
    });

    if (index === -1) {
      return result;
    } //make all three sections into phrase-objects


    var start = terms.slice(0, index);

    if (start.length > 0) {
      result.before = this.buildFrom(start[0].id, start.length);
    }

    var match = terms.slice(index, index + p.length);

    if (match.length > 0) {
      result.match = this.buildFrom(match[0].id, match.length);
    }

    var end = terms.slice(index + p.length, terms.length);

    if (end.length > 0) {
      result.after = this.buildFrom(end[0].id, end.length, this.pool);
    }

    return result;
  };

  var _04Insert = {
    append: append_1,
    prepend: prepend_1,
    "delete": _delete$1,
    replace: replace,
    splitOn: splitOn
  };

  /** return json metadata for this phrase */
  var json$1 = function json() {
    var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
    var world = arguments.length > 1 ? arguments[1] : undefined;
    var res = {}; // text data

    if (options.text) {
      res.text = this.text();
    }

    if (options.normal) {
      res.normal = this.text('normal');
    }

    if (options.clean) {
      res.clean = this.text('clean');
    }

    if (options.reduced) {
      res.reduced = this.text('reduced');
    }

    if (options.root) {
      res.root = this.text('root');
    }

    if (options.trim) {
      if (res.text) {
        res.text = res.text.trim();
      }

      if (res.normal) {
        res.normal = res.normal.trim();
      }

      if (res.reduced) {
        res.reduced = res.reduced.trim();
      }
    } // terms data


    if (options.terms) {
      if (options.terms === true) {
        options.terms = {};
      }

      res.terms = this.terms().map(function (t) {
        return t.json(options.terms, world);
      });
    }

    return res;
  };

  var _05Json$1 = {
    json: json$1
  };

  /** match any terms after this phrase */
  var lookAhead = function lookAhead(regs) {
    // if empty match string, return everything after
    if (!regs) {
      regs = '.*';
    }

    var pool = this.pool; // get a list of all terms preceding our start

    var terms = [];

    var getAfter = function getAfter(id) {
      var term = pool.get(id);

      if (!term) {
        return;
      }

      terms.push(term);

      if (term.prev) {
        getAfter(term.next); //recursion
      }
    };

    var all = this.terms();
    var lastTerm = all[all.length - 1];
    getAfter(lastTerm.next);

    if (terms.length === 0) {
      return [];
    } // got the terms, make a phrase from them


    var p = this.buildFrom(terms[0].id, terms.length);
    return p.match(regs);
  };
  /** match any terms before this phrase */


  var lookBehind = function lookBehind(regs) {
    // if empty match string, return everything before
    if (!regs) {
      regs = '.*';
    }

    var pool = this.pool; // get a list of all terms preceding our start

    var terms = [];

    var getBefore = function getBefore(id) {
      var term = pool.get(id);

      if (!term) {
        return;
      }

      terms.push(term);

      if (term.prev) {
        getBefore(term.prev); //recursion
      }
    };

    var term = pool.get(this.start);
    getBefore(term.prev);

    if (terms.length === 0) {
      return [];
    } // got the terms, make a phrase from them


    var p = this.buildFrom(terms[terms.length - 1].id, terms.length);
    return p.match(regs);
  };

  var _06Lookahead = {
    lookAhead: lookAhead,
    lookBehind: lookBehind
  };

  var methods$1 = Object.assign({}, _01Utils, _02Text, _03Change, _04Insert, _05Json$1, _06Lookahead);

  // try to avoid doing the match
  var failFast = function failFast(p, regs) {
    if (regs.length === 0) {
      return true;
    }

    for (var i = 0; i < regs.length; i += 1) {
      var reg = regs[i]; //logical quick-ones

      if (reg.optional !== true && reg.negative !== true) {
        //start/end impossibilites
        if (reg.start === true && i > 0) {
          return true;
        }
      } //this is not possible


      if (reg.anything === true && reg.negative === true) {
        return true;
      }
    }

    return false;
  };

  var _02FailFast = failFast;

  //found a match? it's greedy? keep going!

  var getGreedy = function getGreedy(terms, t, reg, until, index, length) {
    var start = t;

    for (; t < terms.length; t += 1) {
      //stop for next-reg match
      if (until && terms[t].doesMatch(until, index + t, length)) {
        return t;
      }

      var count = t - start + 1; // is it max-length now?

      if (reg.max !== undefined && count === reg.max) {
        return t;
      } //stop here


      if (terms[t].doesMatch(reg, index + t, length) === false) {
        // is it too short?
        if (reg.min !== undefined && count < reg.min) {
          return null;
        }

        return t;
      }
    }

    return t;
  }; //'unspecific greedy' is a weird situation.


  var greedyTo = function greedyTo(terms, t, nextReg, index, length) {
    //if there's no next one, just go off the end!
    if (!nextReg) {
      return terms.length;
    } //otherwise, we're looking for the next one


    for (; t < terms.length; t += 1) {
      if (terms[t].doesMatch(nextReg, index + t, length) === true) {
        return t;
      }
    } //guess it doesn't exist, then.


    return null;
  }; // get or create named group


  var getOrCreateGroup = function getOrCreateGroup(namedGroups, namedGroupId, terms, startIndex, group) {
    var g = namedGroups[namedGroupId];

    if (g) {
      return g;
    }

    var id = terms[startIndex].id;
    namedGroups[namedGroupId] = {
      group: String(group),
      start: id,
      length: 0
    };
    return namedGroups[namedGroupId];
  };
  /** tries to match a sequence of terms, starting from here */


  var tryHere = function tryHere(terms, regs, index, length) {
    var namedGroups = {};
    var previousGroupId = null;
    var t = 0; // we must satisfy each rule in 'regs'

    for (var r = 0; r < regs.length; r += 1) {
      var reg = regs[r]; // Check if this reg has a named capture group

      var isNamedGroup = typeof reg.named === 'string' || typeof reg.named === 'number';
      var namedGroupId = null; // Reuse previous capture group if same

      if (isNamedGroup) {
        var prev = regs[r - 1];

        if (prev && prev.named === reg.named && previousGroupId) {
          namedGroupId = previousGroupId;
        } else {
          namedGroupId = _id(reg.named);
          previousGroupId = namedGroupId;
        }
      } //should we fail here?


      if (!terms[t]) {
        //are all remaining regs optional?
        var hasNeeds = regs.slice(r).some(function (remain) {
          return !remain.optional;
        });

        if (hasNeeds === false) {
          break;
        } // have unmet needs


        return [false, null];
      } //support 'unspecific greedy' .* properly


      if (reg.anything === true && reg.greedy === true) {
        var skipto = greedyTo(terms, t, regs[r + 1], reg, index); // ensure it's long enough

        if (reg.min !== undefined && skipto - t < reg.min) {
          return [false, null];
        } // reduce it back, if it's too long


        if (reg.max !== undefined && skipto - t > reg.max) {
          t = t + reg.max;
          continue;
        }

        if (skipto === null) {
          return [false, null]; //couldn't find it
        } // is it really this easy?....


        if (isNamedGroup) {
          var g = getOrCreateGroup(namedGroups, namedGroupId, terms, t, reg.named); // Update group

          g.length = skipto - t;
        }

        t = skipto;
        continue;
      } //if it looks like a match, continue
      //we have a special case where an end-anchored greedy match may need to
      //start matching before the actual end; we do this by (temporarily!)
      //removing the "end" property from the matching token... since this is
      //very situation-specific, we *only* do this when we really need to.


      if (reg.anything === true || reg.end === true && reg.greedy === true && index + t < length - 1 && terms[t].doesMatch(Object.assign({}, reg, {
        end: false
      }), index + t, length) === true || terms[t].doesMatch(reg, index + t, length) === true) {
        var startAt = t; // okay, it was a match, but if it optional too,
        // we should check the next reg too, to skip it?

        if (reg.optional && regs[r + 1]) {
          // does the next reg match it too?
          if (terms[t].doesMatch(regs[r + 1], index + t, length) === true) {
            // but does the next reg match the next term??
            // only skip if it doesn't
            if (!terms[t + 1] || terms[t + 1].doesMatch(regs[r + 1], index + t, length) === false) {
              r += 1;
            }
          }
        } //advance to the next term!


        t += 1; //check any ending '$' flags

        if (reg.end === true) {
          //if this isn't the last term, refuse the match
          if (t !== terms.length && reg.greedy !== true) {
            return [false, null];
          }
        } //try keep it going!


        if (reg.greedy === true) {
          // for greedy checking, we no longer care about the reg.start
          // value, and leaving it can cause failures for anchored greedy
          // matches.  ditto for end-greedy matches: we need an earlier non-
          // ending match to succceed until we get to the actual end.
          t = getGreedy(terms, t, Object.assign({}, reg, {
            start: false,
            end: false
          }), regs[r + 1], index, length);

          if (t === null) {
            return [false, null]; //greedy was too short
          }

          if (reg.min && reg.min > t) {
            return [false, null]; //greedy was too short
          } // if this was also an end-anchor match, check to see we really
          // reached the end


          if (reg.end === true && index + t !== length) {
            return [false, null]; //greedy didn't reach the end
          }
        }

        if (isNamedGroup) {
          // Get or create capture group
          var _g = getOrCreateGroup(namedGroups, namedGroupId, terms, startAt, reg.named); // Update group - add greedy or increment length


          if (t > 1 && reg.greedy) {
            _g.length += t - startAt;
          } else {
            _g.length++;
          }
        }

        continue;
      } //bah, who cares, keep going


      if (reg.optional === true) {
        continue;
      } // should we skip-over an implicit word?


      if (terms[t].isImplicit() && regs[r - 1] && terms[t + 1]) {
        // does the next one match?
        if (terms[t + 1].doesMatch(reg, index + t, length)) {
          t += 2;
          continue;
        }
      } // console.log('   ❌\n\n')


      return [false, null];
    } //return our result


    return [terms.slice(0, t), namedGroups];
  };

  var _03TryMatch = tryHere;

  var postProcess = function postProcess(terms, regs, matches) {
    if (!matches || matches.length === 0) {
      return matches;
    } // ensure end reg has the end term


    var atEnd = regs.some(function (r) {
      return r.end;
    });

    if (atEnd) {
      var lastTerm = terms[terms.length - 1];
      matches = matches.filter(function (_ref) {
        var arr = _ref.match;
        return arr.indexOf(lastTerm) !== -1;
      });
    }

    return matches;
  };

  var _04PostProcess = postProcess;

  /* break-down a match expression into this:
  {
    word:'',
    tag:'',
    regex:'',

    start:false,
    end:false,
    negative:false,
    anything:false,
    greedy:false,
    optional:false,

    named:'',
    choices:[],
  }
  */
  var hasMinMax = /\{([0-9]+,?[0-9]*)\}/;
  var andSign = /&&/;
  var captureName = new RegExp(/^<(\S+)>/);

  var titleCase$2 = function titleCase(str) {
    return str.charAt(0).toUpperCase() + str.substr(1);
  };

  var end = function end(str) {
    return str[str.length - 1];
  };

  var start = function start(str) {
    return str[0];
  };

  var stripStart = function stripStart(str) {
    return str.substr(1);
  };

  var stripEnd = function stripEnd(str) {
    return str.substr(0, str.length - 1);
  };

  var stripBoth = function stripBoth(str) {
    str = stripStart(str);
    str = stripEnd(str);
    return str;
  }; //


  var parseToken = function parseToken(w) {
    var obj = {}; //collect any flags (do it twice)

    for (var i = 0; i < 2; i += 1) {
      //end-flag
      if (end(w) === '$') {
        obj.end = true;
        w = stripEnd(w);
      } //front-flag


      if (start(w) === '^') {
        obj.start = true;
        w = stripStart(w);
      } //capture group (this one can span multiple-terms)


      if (start(w) === '[' || end(w) === ']') {
        obj.named = true;

        if (start(w) === '[') {
          obj.groupType = end(w) === ']' ? 'single' : 'start';
        } else {
          obj.groupType = 'end';
        }

        w = w.replace(/^\[/, '');
        w = w.replace(/\]$/, ''); // Use capture group name

        if (start(w) === '<') {
          var res = captureName.exec(w);

          if (res.length >= 2) {
            obj.named = res[1];
            w = w.replace(res[0], '');
          }
        }
      } //back-flags


      if (end(w) === '+') {
        obj.greedy = true;
        w = stripEnd(w);
      }

      if (w !== '*' && end(w) === '*' && w !== '\\*') {
        obj.greedy = true;
        w = stripEnd(w);
      }

      if (end(w) === '?') {
        obj.optional = true;
        w = stripEnd(w);
      }

      if (start(w) === '!') {
        obj.negative = true;
        w = stripStart(w);
      } //wrapped-flags


      if (start(w) === '(' && end(w) === ')') {
        // support (one && two)
        if (andSign.test(w)) {
          obj.choices = w.split(andSign);
          obj.operator = 'and';
        } else {
          obj.choices = w.split('|');
          obj.operator = 'or';
        } //remove '(' and ')'


        obj.choices[0] = stripStart(obj.choices[0]);
        var last = obj.choices.length - 1;
        obj.choices[last] = stripEnd(obj.choices[last]); // clean up the results

        obj.choices = obj.choices.map(function (s) {
          return s.trim();
        });
        obj.choices = obj.choices.filter(function (s) {
          return s;
        }); //recursion alert!

        obj.choices = obj.choices.map(parseToken);
        w = '';
      } //regex


      if (start(w) === '/' && end(w) === '/') {
        w = stripBoth(w);
        obj.regex = new RegExp(w); //potential vuln - security/detect-non-literal-regexp

        return obj;
      } //soft-match


      if (start(w) === '~' && end(w) === '~') {
        w = stripBoth(w);
        obj.soft = true;
        obj.word = w;
        return obj;
      }
    } // support #Tag{0,9}


    if (hasMinMax.test(w) === true) {
      w = w.replace(hasMinMax, function (a, b) {
        var arr = b.split(/,/g);

        if (arr.length === 1) {
          // '{3}'	Exactly three times
          obj.min = Number(arr[0]);
          obj.max = Number(arr[0]);
        } else {
          // '{2,4}' Two to four times
          // '{3,}' Three or more times
          obj.min = Number(arr[0]);
          obj.max = Number(arr[1] || 999);
        }

        obj.greedy = true;
        return '';
      });
    } //do the actual token content


    if (start(w) === '#') {
      obj.tag = stripStart(w);
      obj.tag = titleCase$2(obj.tag);
      return obj;
    } //dynamic function on a term object


    if (start(w) === '@') {
      obj.method = stripStart(w);
      return obj;
    }

    if (w === '.') {
      obj.anything = true;
      return obj;
    } //support alone-astrix


    if (w === '*') {
      obj.anything = true;
      obj.greedy = true;
      obj.optional = true;
      return obj;
    }

    if (w) {
      //somehow handle encoded-chars?
      w = w.replace('\\*', '*');
      w = w.replace('\\.', '.');
      obj.word = w.toLowerCase();
    }

    return obj;
  };

  var parseToken_1 = parseToken;

  var isNamed = function isNamed(capture) {
    return typeof capture === 'string' || typeof capture === 'number';
  };

  var fillGroups = function fillGroups(tokens) {
    var convert = false;
    var index = -1;
    var current; //'fill in' capture groups between start-end

    for (var i = 0; i < tokens.length; i++) {
      var n = tokens[i]; // Give name to un-named single tokens

      if (n.groupType === 'single' && n.named === true) {
        index += 1;
        n.named = index;
        continue;
      } // Start converting tokens


      if (n.groupType === 'start') {
        convert = true;

        if (isNamed(n.named)) {
          current = n.named;
        } else {
          index += 1;
          current = index;
        }
      } // Ensure this token has the right name


      if (convert) {
        n.named = current;
      } // Stop converting tokens


      if (n.groupType === 'end') {
        convert = false;
      }
    }

    return tokens;
  };

  var useOneOf = function useOneOf(tokens) {
    return tokens.map(function (token) {
      if (token.choices !== undefined) {
        // are they all straight non-optional words?
        var shouldPack = token.choices.every(function (c) {
          return c.optional !== true && c.negative !== true && c.word !== undefined;
        });

        if (shouldPack === true) {
          var oneOf = {};
          token.choices.forEach(function (c) {
            return oneOf[c.word] = true;
          });
          token.oneOf = oneOf;
          delete token.choices;
        }
      }

      return token;
    });
  };

  var postProcess$1 = function postProcess(tokens) {
    // ensure all capture groups are filled between start and end
    // give all capture groups names
    var count = tokens.filter(function (t) {
      return t.groupType;
    }).length;

    if (count > 0) {
      tokens = fillGroups(tokens);
    } // convert 'choices' format to 'oneOf' format


    tokens = useOneOf(tokens); // console.log(tokens)

    return tokens;
  };

  var postProcess_1 = postProcess$1;

  var isArray$1 = function isArray(arr) {
    return Object.prototype.toString.call(arr) === '[object Array]';
  }; //split-up by (these things)


  var byParentheses = function byParentheses(str) {
    var arr = str.split(/([\^\[\!]*(?:<\S+>)?\(.*?\)[?+*]*\]?\$?)/);
    arr = arr.map(function (s) {
      return s.trim();
    });
    return arr;
  };

  var byWords = function byWords(arr) {
    var words = [];
    arr.forEach(function (a) {
      //keep brackets lumped together
      if (/^[[^_/]?\(/.test(a[0])) {
        words.push(a);
        return;
      }

      var list = a.split(' ');
      list = list.filter(function (w) {
        return w;
      });
      words = words.concat(list);
    });
    return words;
  }; //turn an array into a 'choices' list


  var byArray = function byArray(arr) {
    return [{
      choices: arr.map(function (s) {
        return {
          word: s
        };
      })
    }];
  };

  var fromDoc = function fromDoc(doc) {
    if (!doc || !doc.list || !doc.list[0]) {
      return [];
    }

    var ids = [];
    doc.list.forEach(function (p) {
      p.terms().forEach(function (t) {
        ids.push({
          id: t.id
        });
      });
    });
    return [{
      choices: ids,
      greedy: true
    }];
  };
  /** parse a match-syntax string into json */


  var syntax = function syntax(input) {
    // fail-fast
    if (input === null || input === undefined || input === '') {
      return [];
    } //try to support a ton of different formats:


    if (_typeof(input) === 'object') {
      if (isArray$1(input)) {
        if (input.length === 0 || !input[0]) {
          return [];
        } //is it a pre-parsed reg-list?


        if (_typeof(input[0]) === 'object') {
          return input;
        } //support a flat array of normalized words


        if (typeof input[0] === 'string') {
          return byArray(input);
        }
      } //support passing-in a compromise object as a match


      if (input && input.isA === 'Doc') {
        return fromDoc(input);
      }

      return [];
    }

    if (typeof input === 'number') {
      input = String(input); //go for it?
    }

    var tokens = byParentheses(input);
    tokens = byWords(tokens);
    tokens = tokens.map(parseToken_1); //clean up anything weird

    tokens = postProcess_1(tokens); // console.log(JSON.stringify(tokens, null, 2))

    return tokens;
  };

  var syntax_1 = syntax;

  /**  returns a simple array of arrays */

  var matchAll = function matchAll(p, regs) {
    var matchOne = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;

    //if we forgot to parse it..
    if (typeof regs === 'string') {
      regs = syntax_1(regs);
    } //try to dismiss it, at-once


    if (_02FailFast(p, regs) === true) {
      return [];
    } //any match needs to be this long, at least


    var minLength = regs.filter(function (r) {
      return r.optional !== true;
    }).length;
    var terms = p.terms();
    var matches = []; //optimisation for '^' start logic

    if (regs[0].start === true) {
      var _tryMatch = _03TryMatch(terms, regs, 0, terms.length),
          _tryMatch2 = _slicedToArray(_tryMatch, 2),
          match = _tryMatch2[0],
          groups = _tryMatch2[1];

      if (match !== false && match.length > 0) {
        match = match.filter(function (m) {
          return m;
        });
        matches.push({
          match: match,
          groups: groups
        });
      }

      return _04PostProcess(terms, regs, matches);
    } //try starting, from every term


    for (var i = 0; i < terms.length; i += 1) {
      // slice may be too short
      if (i + minLength > terms.length) {
        break;
      } //try it!


      var _tryMatch3 = _03TryMatch(terms.slice(i), regs, i, terms.length),
          _tryMatch4 = _slicedToArray(_tryMatch3, 2),
          _match = _tryMatch4[0],
          _groups = _tryMatch4[1];

      if (_match !== false && _match.length > 0) {
        //zoom forward!
        i += _match.length - 1; //[capture-groups] return some null responses

        _match = _match.filter(function (m) {
          return m;
        });
        matches.push({
          match: _match,
          groups: _groups
        }); //ok, maybe that's enough?

        if (matchOne === true) {
          return _04PostProcess(terms, regs, matches);
        }
      }
    }

    return _04PostProcess(terms, regs, matches);
  };

  var _01MatchAll = matchAll;

  /** return anything that doesn't match.
   * returns a simple array of arrays
   */

  var notMatch = function notMatch(p, regs) {
    var found = {};
    var arr = _01MatchAll(p, regs);
    arr.forEach(function (_ref) {
      var ts = _ref.match;
      ts.forEach(function (t) {
        found[t.id] = true;
      });
    }); //return anything not found

    var terms = p.terms();
    var result = [];
    var current = [];
    terms.forEach(function (t) {
      if (found[t.id] === true) {
        if (current.length > 0) {
          result.push(current);
          current = [];
        }

        return;
      }

      current.push(t);
    });

    if (current.length > 0) {
      result.push(current);
    }

    return result;
  };

  var not = notMatch;

  /** return an array of matching phrases */

  var match_1 = function match_1(regs) {
    var _this = this;

    var justOne = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
    var matches = _01MatchAll(this, regs, justOne); //make them phrase objects

    matches = matches.map(function (_ref) {
      var match = _ref.match,
          groups = _ref.groups;

      var p = _this.buildFrom(match[0].id, match.length, groups);

      p.cache.terms = match;
      return p;
    });
    return matches;
  };
  /** return boolean if one match is found */


  var has = function has(regs) {
    var matches = _01MatchAll(this, regs, true);
    return matches.length > 0;
  };
  /** remove all matches from the result */


  var not$1 = function not$1(regs) {
    var _this2 = this;

    var matches = not(this, regs); //make them phrase objects

    matches = matches.map(function (list) {
      return _this2.buildFrom(list[0].id, list.length);
    });
    return matches;
  };
  /** return a list of phrases that can have this tag */


  var canBe$1 = function canBe(tag, world) {
    var _this3 = this;

    var results = [];
    var terms = this.terms();
    var previous = false;

    for (var i = 0; i < terms.length; i += 1) {
      var can = terms[i].canBe(tag, world);

      if (can === true) {
        if (previous === true) {
          //add it to the end
          results[results.length - 1].push(terms[i]);
        } else {
          results.push([terms[i]]); //make a new one
        }

        previous = can;
      }
    } //turn them into Phrase objects


    results = results.filter(function (a) {
      return a.length > 0;
    }).map(function (arr) {
      return _this3.buildFrom(arr[0].id, arr.length);
    });
    return results;
  };

  var match = {
    match: match_1,
    has: has,
    not: not$1,
    canBe: canBe$1
  };

  var Phrase = function Phrase(id, length, pool) {
    _classCallCheck(this, Phrase);

    this.start = id;
    this.length = length;
    this.isA = 'Phrase'; // easier than .constructor...

    Object.defineProperty(this, 'pool', {
      enumerable: false,
      writable: true,
      value: pool
    });
    Object.defineProperty(this, 'cache', {
      enumerable: false,
      writable: true,
      value: {}
    });
    Object.defineProperty(this, 'groups', {
      enumerable: false,
      writable: true,
      value: {}
    });
  };
  /** create a new Phrase object from an id and length */


  Phrase.prototype.buildFrom = function (id, length, groups) {
    var p = new Phrase(id, length, this.pool); //copy-over or replace capture-groups too

    if (groups && Object.keys(groups).length > 0) {
      p.groups = groups;
    } else {
      p.groups = this.groups;
    }

    return p;
  }; //apply methods


  Object.assign(Phrase.prototype, match);
  Object.assign(Phrase.prototype, methods$1); //apply aliases

  var aliases = {
    term: 'terms'
  };
  Object.keys(aliases).forEach(function (k) {
    return Phrase.prototype[k] = Phrase.prototype[aliases[k]];
  });
  var Phrase_1 = Phrase;

  /** a key-value store of all terms in our Document */
  var Pool = /*#__PURE__*/function () {
    function Pool() {
      var words = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};

      _classCallCheck(this, Pool);

      //quiet this property in console.logs
      Object.defineProperty(this, 'words', {
        enumerable: false,
        value: words
      });
    }
    /** throw a new term object in */


    _createClass(Pool, [{
      key: "add",
      value: function add(term) {
        this.words[term.id] = term;
        return this;
      }
      /** find a term by it's id */

    }, {
      key: "get",
      value: function get(id) {
        return this.words[id];
      }
      /** find a term by it's id */

    }, {
      key: "remove",
      value: function remove(id) {
        delete this.words[id];
      }
    }, {
      key: "merge",
      value: function merge(pool) {
        Object.assign(this.words, pool.words);
        return this;
      }
      /** helper method */

    }, {
      key: "stats",
      value: function stats() {
        return {
          words: Object.keys(this.words).length
        };
      }
    }]);

    return Pool;
  }();
  /** make a deep-copy of all terms */


  Pool.prototype.clone = function () {
    var _this = this;

    var keys = Object.keys(this.words);
    var words = keys.reduce(function (h, k) {
      var t = _this.words[k].clone();

      h[t.id] = t;
      return h;
    }, {});
    return new Pool(words);
  };

  var Pool_1 = Pool;

  //add forward/backward 'linked-list' prev/next ids
  var linkTerms = function linkTerms(terms) {
    terms.forEach(function (term, i) {
      if (i > 0) {
        term.prev = terms[i - 1].id;
      }

      if (terms[i + 1]) {
        term.next = terms[i + 1].id;
      }
    });
  };

  var _linkTerms = linkTerms;

  //(Rule-based sentence boundary segmentation) - chop given text into its proper sentences.
  // Ignore periods/questions/exclamations used in acronyms/abbreviations/numbers, etc.
  // @spencermountain 2017 MIT
  //proper nouns with exclamation marks
  // const blacklist = {
  //   yahoo: true,
  //   joomla: true,
  //   jeopardy: true,
  // }
  //regs-
  var initSplit = /(\S.+?[.!?\u203D\u2E18\u203C\u2047-\u2049])(?=\s+|$)/g;
  var hasSomething = /\S/;
  var isAcronym$1 = /[ .][A-Z]\.? *$/i;
  var hasEllipse = /(?:\u2026|\.{2,}) *$/;
  var newLine = /((?:\r?\n|\r)+)/; // Match different new-line formats

  var hasLetter = /[a-z0-9\u00C0-\u00FF\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff]/i;
  var startWhitespace = /^\s+/; // Start with a regex:

  var naiive_split = function naiive_split(text) {
    var all = []; //first, split by newline

    var lines = text.split(newLine);

    for (var i = 0; i < lines.length; i++) {
      //split by period, question-mark, and exclamation-mark
      var arr = lines[i].split(initSplit);

      for (var o = 0; o < arr.length; o++) {
        all.push(arr[o]);
      }
    }

    return all;
  };
  /** does this look like a sentence? */


  var isSentence = function isSentence(str, abbrevs) {
    // check for 'F.B.I.'
    if (isAcronym$1.test(str) === true) {
      return false;
    } //check for '...'


    if (hasEllipse.test(str) === true) {
      return false;
    } // must have a letter


    if (hasLetter.test(str) === false) {
      return false;
    }

    var txt = str.replace(/[.!?\u203D\u2E18\u203C\u2047-\u2049] *$/, '');
    var words = txt.split(' ');
    var lastWord = words[words.length - 1].toLowerCase(); // check for 'Mr.'

    if (abbrevs.hasOwnProperty(lastWord)) {
      return false;
    } // //check for jeopardy!
    // if (blacklist.hasOwnProperty(lastWord)) {
    //   return false
    // }


    return true;
  };

  var splitSentences = function splitSentences(text, world) {
    var abbrevs = world.cache.abbreviations;
    text = text || '';
    text = String(text);
    var sentences = []; // First do a greedy-split..

    var chunks = []; // Ensure it 'smells like' a sentence

    if (!text || typeof text !== 'string' || hasSomething.test(text) === false) {
      return sentences;
    } // cleanup unicode-spaces


    text = text.replace('\xa0', ' '); // Start somewhere:

    var splits = naiive_split(text); // Filter-out the crap ones

    for (var i = 0; i < splits.length; i++) {
      var s = splits[i];

      if (s === undefined || s === '') {
        continue;
      } //this is meaningful whitespace


      if (hasSomething.test(s) === false) {
        //add it to the last one
        if (chunks[chunks.length - 1]) {
          chunks[chunks.length - 1] += s;
          continue;
        } else if (splits[i + 1]) {
          //add it to the next one
          splits[i + 1] = s + splits[i + 1];
          continue;
        }
      } //else, only whitespace, no terms, no sentence


      chunks.push(s);
    } //detection of non-sentence chunks:
    //loop through these chunks, and join the non-sentence chunks back together..


    for (var _i = 0; _i < chunks.length; _i++) {
      var c = chunks[_i]; //should this chunk be combined with the next one?

      if (chunks[_i + 1] && isSentence(c, abbrevs) === false) {
        chunks[_i + 1] = c + (chunks[_i + 1] || '');
      } else if (c && c.length > 0) {
        //&& hasLetter.test(c)
        //this chunk is a proper sentence..
        sentences.push(c);
        chunks[_i] = '';
      }
    } //if we never got a sentence, return the given text


    if (sentences.length === 0) {
      return [text];
    } //move whitespace to the ends of sentences, when possible
    //['hello',' world'] -> ['hello ','world']


    for (var _i2 = 1; _i2 < sentences.length; _i2 += 1) {
      var ws = sentences[_i2].match(startWhitespace);

      if (ws !== null) {
        sentences[_i2 - 1] += ws[0];
        sentences[_i2] = sentences[_i2].replace(startWhitespace, '');
      }
    }

    return sentences;
  };

  var _01Sentences = splitSentences; // console.log(sentence_parser('john f. kennedy'));

  var wordlike = /\S/;
  var isBoundary = /^[!?.]+$/;
  var naiiveSplit = /(\S+)/;
  var isSlash = /[a-z] ?\/ ?[a-z]*$/;
  var notWord = {
    '.': true,
    '-': true,
    //dash
    '–': true,
    //en-dash
    '—': true,
    //em-dash
    '--': true,
    '...': true // '/': true, // 'one / two'

  };

  var hasHyphen = function hasHyphen(str) {
    //dont split 're-do'
    if (/^(re|un)-?[^aeiou]./.test(str) === true) {
      return false;
    } //letter-number


    var reg = /^([a-z\u00C0-\u00FF`"'/]+)(-|–|—)([a-z0-9\u00C0-\u00FF].*)/i;

    if (reg.test(str) === true) {
      return true;
    } //support weird number-emdash combo '2010–2011'
    // let reg2 = /^([0-9]+)(–|—)([0-9].*)/i
    // if (reg2.test(str)) {
    //   return true
    // }


    return false;
  }; // 'he / she' should be one word


  var combineSlashes = function combineSlashes(arr) {
    for (var i = 1; i < arr.length - 1; i++) {
      if (isSlash.test(arr[i])) {
        arr[i - 1] += arr[i] + arr[i + 1];
        arr[i] = null;
        arr[i + 1] = null;
      }
    }

    return arr;
  };

  var splitHyphens = function splitHyphens(word) {
    var arr = []; //support multiple-hyphenated-terms

    var hyphens = word.split(/[-–—]/);
    var whichDash = '-';
    var found = word.match(/[-–—]/);

    if (found && found[0]) {
      whichDash = found;
    }

    for (var o = 0; o < hyphens.length; o++) {
      if (o === hyphens.length - 1) {
        arr.push(hyphens[o]);
      } else {
        arr.push(hyphens[o] + whichDash);
      }
    }

    return arr;
  };

  var isArray$2 = function isArray(arr) {
    return Object.prototype.toString.call(arr) === '[object Array]';
  }; //turn a string into an array of strings (naiive for now, lumped later)


  var splitWords = function splitWords(str) {
    var result = [];
    var arr = []; //start with a naiive split

    str = str || '';

    if (typeof str === 'number') {
      str = String(str);
    }

    if (isArray$2(str)) {
      return str;
    }

    var words = str.split(naiiveSplit);

    for (var i = 0; i < words.length; i++) {
      //split 'one-two'
      if (hasHyphen(words[i]) === true) {
        arr = arr.concat(splitHyphens(words[i]));
        continue;
      }

      arr.push(words[i]);
    } //greedy merge whitespace+arr to the right


    var carry = '';

    for (var _i = 0; _i < arr.length; _i++) {
      var word = arr[_i]; //if it's more than a whitespace

      if (wordlike.test(word) === true && notWord.hasOwnProperty(word) === false && isBoundary.test(word) === false) {
        //put whitespace on end of previous term, if possible
        if (result.length > 0) {
          result[result.length - 1] += carry;
          result.push(word);
        } else {
          //otherwise, but whitespace before
          result.push(carry + word);
        }

        carry = '';
      } else {
        carry += word;
      }
    } //handle last one


    if (carry) {
      if (result.length === 0) {
        result[0] = '';
      }

      result[result.length - 1] += carry; //put it on the end
    } // combine 'one / two'


    result = combineSlashes(result); // remove empty results

    result = result.filter(function (s) {
      return s;
    });
    return result;
  };

  var _02Words = splitWords;

  var isArray$3 = function isArray(arr) {
    return Object.prototype.toString.call(arr) === '[object Array]';
  };
  /** turn a string into an array of Phrase objects */


  var fromText = function fromText() {
    var text = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
    var world = arguments.length > 1 ? arguments[1] : undefined;
    var pool = arguments.length > 2 ? arguments[2] : undefined;
    var sentences = null; //a bit of validation, first

    if (typeof text !== 'string') {
      if (typeof text === 'number') {
        text = String(text);
      } else if (isArray$3(text)) {
        sentences = text;
      }
    } //tokenize into words


    sentences = sentences || _01Sentences(text, world);
    sentences = sentences.map(function (str) {
      return _02Words(str);
    }); //turn them into proper objects

    pool = pool || new Pool_1();
    var phrases = sentences.map(function (terms) {
      terms = terms.map(function (str) {
        var term = new Term_1(str);
        pool.add(term);
        return term;
      }); //add next/previous ids

      _linkTerms(terms); //return phrase objects

      var p = new Phrase_1(terms[0].id, terms.length, pool);
      p.cache.terms = terms;
      return p;
    }); //return them ready for a Document object

    return phrases;
  };

  var _01Tokenizer = fromText;

  var fromJSON = function fromJSON(json, world) {
    var pool = new Pool_1();
    var phrases = json.map(function (p, k) {
      var terms = p.terms.map(function (o, i) {
        var term = new Term_1(o.text);
        term.pre = o.pre !== undefined ? o.pre : '';

        if (o.post === undefined) {
          o.post = ' '; //no given space for very last term

          if (i >= p.terms.length - 1) {
            o.post = '. ';

            if (k >= p.terms.length - 1) {
              o.post = '.';
            }
          }
        }

        term.post = o.post !== undefined ? o.post : ' ';

        if (o.tags) {
          o.tags.forEach(function (tag) {
            return term.tag(tag, '', world);
          });
        }

        pool.add(term);
        return term;
      }); //add prev/next links

      _linkTerms(terms); // return a proper Phrase object

      return new Phrase_1(terms[0].id, terms.length, pool);
    });
    return phrases;
  };

  var fromJSON_1 = fromJSON;

  var _version = '13.5.0X003';

  var _data = {
    "Comparative": "true¦better",
    "Superlative": "true¦earlier",
    "PresentTense": "true¦is,sounds",
    "Value": "true¦a few",
    "Noun": "true¦a5b4c2f1here,ie,lit,m0no doubt,pd,tce;a,d;t,y;a,ca,o0;l,rp;a,l;d,l,rc",
    "Copula": "true¦a1is,w0;as,ere;m,re",
    "PastTense": "true¦be3came,d2had,lied,meant,sa2taken,w0;as,e0;nt,re;id;en,gan",
    "Condition": "true¦if,lest,unless",
    "Gerund": "true¦accord0be0develop0go0result0stain0;ing",
    "Negative": "true¦n0;ever,o0;!n,t",
    "QuestionWord": "true¦how3wh0;at,e1ich,o0y;!m,se;n,re; come,'s",
    "Plural": "true¦records",
    "Conjunction": "true¦&,aEbAcuz,how8in caDno7o6p4supposing,t1vers5wh0yet;eth8ile;h0o;eref9o0;!uC;l0rovided that;us;r,therwi6; matt1r;!ev0;er;e0ut;cau1f0;ore;se;lthou1nd,s 0;far as,if;gh",
    "Pronoun": "true¦'em,elle,h4i3me,ourselves,she5th1us,we,you0;!rself;e0ou;m,y;!l,t;e0im;!'s",
    "Singular": "true¦0:0Z;1:12;a0Yb0Mc0Dd06e04fZgUhQiPjel0kitty,lOmKnJoIpEquestion mark,rCs7t4u2womY;nc0Ts 2;doll0Fst0H; rex,a3h2ic,ragedy,v show;ere,i1;l0x return;i5ky,omeone,t2uper bowl,yst0Y;ep3ri1u2;de0Rff;faOmoO;st0Nze;al0i1o2;om,se;a4i0Kl06r3u2;dMrpoE;erogaWobl0P;rt,te0J;bjTceHthers;othi1umb0F;a4ee05o2;del,m2nopo0th0D;!my;n,yf0;i0unch;ci1nsect;ead start,o2;l0me3u2;se;! run;adf0entlem5irlZlaci04od,rand3u2;l0y; slam,fa2mo2;th01;an;a5ella,ly,ol0r3un2;di1;iTo2;ntiWsN;mi0thV;conomy,gg,ner5veWx2;ampQecu7;ad7e4innSo2ragonf0ude;cumentFg2i0l0or;gy;ath,t2;ec2;tive;!dy;a8eili1h6i4o2redit card;ttage,u2;riJsin;ty,vil w2;ar;andeliGocol2;ate;n2rD;ary;aAel0lesHo6r4u2;n2tterf0;ti1;eakfast,o2;!th8;dy,tt4y2;!fri2;end;le;nki1r2;ri2;er;d4l0noma0u2;nt;ly; homin4verti2;si1;ng;em",
    "Actor": "true¦aJbGcFdCengineIfAgardenIh9instructPjournalLlawyIm8nurse,opeOp5r3s1t0;echnCherapK;ailNcientJoldiGu0;pervKrgeon;e0oofE;ceptionGsearC;hotographClumbColi1r0sychologF;actitionBogrammB;cem6t5;echanic,inist9us4;airdress8ousekeep8;arm7ire0;fight6m2;eputy,iet0;ici0;an;arpent2lerk;ricklay1ut0;ch0;er;ccoun6d2ge7r0ssis6ttenda7;chitect,t0;ist;minist1v0;is1;rat0;or;ta0;nt",
    "Honorific": "true¦a03b00cSdReQfiLgKhon,jr,king,lJmEoDp8queen,r4s0taoiseach,vice7;e1fc,gt,ir,r,u0;ltTpt,rg;c0nDrgeaL;ond liJretary;abbi,e0;ar1pAs,v0;!erend; admirY;astPhd,r0vt;esideEi1of0;!essN;me mini5nce0;!ss;fficOp,rd;a3essrs,i2lle,me,r1s0;!tr;!s;stK;gistrate,j,r6yF;i3lb,t;en,ov;eld mar3rst l0;ady,i0;eutena0;nt;shG;sq,xcellency;et,oct6r,utchess;apt6hance4mdr,o0pl;lonel,m2ngress0unci3;m0wom0;an;dr,mand5;ll0;or;!ain;ldg,rig0;!adi0;er;d0sst,tty,yatullah;j,m0v;!ir0;al",
    "SportsTeam": "true¦0:1A;1:1H;2:1G;a1Eb16c0Td0Kfc dallas,g0Ihouston 0Hindiana0Gjacksonville jagua0k0El0Bm01newToQpJqueens parkIreal salt lake,sAt5utah jazz,vancouver whitecaps,w3yW;ashington 3est ham0Rh10;natio1Oredski2wizar0W;ampa bay 6e5o3;ronto 3ttenham hotspur;blue ja0Mrapto0;nnessee tita2xasC;buccanee0ra0K;a7eattle 5heffield0Kporting kansas0Wt3;. louis 3oke0V;c1Frams;marine0s3;eah15ounG;cramento Rn 3;antonio spu0diego 3francisco gJjose earthquak1;char08paA; ran07;a8h5ittsburgh 4ortland t3;imbe0rail blaze0;pirat1steele0;il3oenix su2;adelphia 3li1;eagl1philNunE;dr1;akland 3klahoma city thunder,rlando magic;athle0Mrai3;de0; 3castle01;england 7orleans 6york 3;city fc,g4je0FknXme0Fred bul0Yy3;anke1;ian0D;pelica2sain0C;patrio0Brevolut3;ion;anchester Be9i3ontreal impact;ami 7lwaukee b6nnesota 3;t4u0Fvi3;kings;imberwolv1wi2;rewe0uc0K;dolphi2heat,marli2;mphis grizz3ts;li1;cXu08;a4eicesterVos angeles 3;clippe0dodDla9; galaxy,ke0;ansas city 3nE;chiefs,roya0E; pace0polis colU;astr06dynamo,rockeTtexa2;olden state warrio0reen bay pac3;ke0;.c.Aallas 7e3i05od5;nver 5troit 3;lio2pisto2ti3;ge0;broncZnuggeM;cowbo4maver3;ic00;ys; uQ;arCelKh8incinnati 6leveland 5ol3;orado r3umbus crew sc;api5ocki1;brow2cavalie0india2;bengaWre3;ds;arlotte horAicago 3;b4cubs,fire,wh3;iteB;ea0ulR;diff3olina panthe0; c3;ity;altimore 9lackburn rove0oston 5rooklyn 3uffalo bilN;ne3;ts;cel4red3; sox;tics;rs;oriol1rave2;rizona Ast8tlanta 3;brav1falco2h4u3;nited;aw9;ns;es;on villa,r3;os;c5di3;amondbac3;ks;ardi3;na3;ls",
    "Uncountable": "true¦a1Ib1Ac11d0Ye0Rf0Lg0Hh0Ci08j07knowled1Hl02mUnews,oTpQrLsAt5vi4w0;a2ea05i1oo0;d,l;ldlife,ne;rmth,t17;neg0Yol06tae;e3h2oothpaste,r0una;affPou0;ble,sers,t;ermod1Eund12;a,nnis;a8cene04eri0Oh7il6kittl0Onow,o5p3t1u0;g0Rnshi0H;ati1De0;am,el;ace16e0;ci0Jed;ap,cc0U;k,v0T;eep,ingl0G;d04fe10l0nd;m0St;a3e1ic0;e,ke0D;c0laxa09search;ogni08rea08;bi09in;aJe1hys10last5o0ressV;lit0Zrk,w0J;a0Vtrol;bstetr0Xil,xygen;a5e3ilk,o2u0;mps,s0;ic;nGo0A;a0chan0S;slZt;chine0il,themat0Q; learn05ry;aught08e2i1ogi0Nu0;ck,g0C;ce,ghtn02ngui0LteratH;a0isG;th04;ewel7usti0G;ce,mp0nformaOtself;a0ortan0E;ti0;en0C;a3isto2o0;ck0mework,n0spitali06;ey;ry;ir,libut,ppi7;en01o1r0um,ymna08;a6ound;l0ssip;d,f;i4lour,o1urnit0;ure;od,rgive0uriNwl;ne0;ss;c6sh;conomZduca5lectr4n2quip3thZvery0;body,o0thE;ne;joy0tertain0;ment;iciNonU;tiF;ar1iabet0raugh1;es;ts;a7elcius,h3ivPl2o0urrency;al,ld w0nfusiAttA;ar;assMoth2;aos,e0;e1w0;ing;se;r4sh;a4eef,i1lood,owls,read,utt0;er;lliar1s0;on;ds;g0ss;ga0;ge;c6dvi5ero3ir2mnes1rt,thl0;et7;ty;craft;b4d0naut4;ynam3;ce;id,ou0;st0;ics",
    "Infinitive": "true¦0:6K;1:6Y;2:57;3:6W;4:6V;5:5Z;6:67;7:6U;8:6Q;9:6I;A:6S;B:6P;C:6Z;D:6D;E:56;F:5P;a6Cb61c52d4Ae3Uf3Hg3Bh34i2Rj2Pk2Nl2Fm25n22o1Xp1Iques3Ir0Qs05tXuSvOwHyG;awn,ield;aJe1Yhist6iIoGre65;nd0rG;k,ry;pe,sh,th0;lk,nHrGsh,tDve;n,raC;d0t;aIiGo7;eGsB;!w;l6Cry;nHpGr4se;gra4Mli3Z;dGi7lo5Spub3O;erGo;mi58w1I;aMeLhKoJrHuGwi8;ne,rn;aGe0Mi5Nu8y;de,in,nsf0p,v5F;r2XuD;ank,reat2N;nd,st;lk,rg1Ps7;aZcWeVhTi4Akip,lSmRnee3Jo4YpQtJuGwitD;bmBck,ff0gge8ppHrGspe5;ge,pri1rou4Vvi3;ly,o34;aLeKoJrHuG;dy,mb6;aEeGi3;ngth2Dss,tD;p,re;m,p;in,ke,r0Qy;laFoil,rink6;e1Xi6o3H;am,ip;a2iv0oG;ck,ut;arDem,le5n1r3tt6;aHo2rG;atDew;le,re;il,ve;a05eIisk,oHuG;in,le,sh;am,ll;a01cZdu9fYgXje5lUmTnt,pQquPsKtJvGwa5O;eGiew,o34;al,l,rG;se,t;aEi2u40;eJi8oItG;!o2rG;i5uc1Y;l3rt;mb6nt,r3;e8i2;air,eHlGo3ZreseC;a9y;at;aEemb0i3Vo3;aHeGi3y;a1nt;te,x;a56r0I;act1Wer,le5u1;a11ei3k5IoGyc6;gni2Anci6rd;ch,li29s5G;i1nG;ge,k;aTerSiRlOoMrIuG;b1Zll,mp,rGsh;cha1s4J;ai1eIiCoG;cGdu9greAhibBmi1te8vi2T;eAlaim;di5pa2ss,veC;iCp,rtr3ZsGur;e,t;aHuG;g,n4;n,y;ck,le;fo30mBsi8;ck,iCrt4Fss,u1;bJccur,ff0pera7utweIverGwe;co40lap,ta20u1wG;helm;igh;ser3taE;eHotG;e,i9;ed,gle5;aLeKiIoHuG;ltip3Crd0;nit11ve;nGrr10;d,g6us;asu2lt,n0Nr4;intaEna4rHtG;ch,t0;ch,kGry;et;aLeKiIoGu1B;aGck,ok,ve;d,n;ft,ke,mBnGst2Wve;e,k;a2Dc0Et;b0Nck,uG;gh,nD;iGno2Z;ck,ll,ss;am,oEuG;d4mp;gno2mQnGss3C;cOdica7flu0MhNsKtIvG;eGol3;nt,st;erGrodu9;a5fe2;i8tG;aGru5;ll;abBibB;lu1Er1C;agi22pG;lemeCo20ro3;aKeIi2oHuG;nt,rry;n02pe,st;aGlp;d,t;nd6ppGrm,te;en;aKloAove1MrIuG;arGeAi13;ant33d;aGip,umb6;b,sp;in,th0ze;aQeaPiNlLoIracHuncG;ti3D;tu2;cus,lHrG;ce,eca8m,s2V;d,l1Z;aFoG;at,od,w;gu2lGniFx;e,l;r,tu2;il,vG;or;a13cho,le5mSnPstNvalua7xG;a0AcLerKi8pGte17;a16eHi2laEoGreA;rt,se;ct,riG;en9;ci1t;el,han4;abGima7;liF;ab6couXdHfor9ga4han9j03riDsu2t0vG;isi2Qy;!u2;body,er4pG;hasiGow0;ze;a06eUiLoKrHuG;mp;aHeAiG;ft;g,in;d4ubt;ff0p,re5sHvG;iYor9;aKcHliGmiApl16tinguiF;ke;oGuA;uGv0;ra4;gr1TppG;ear,ro3;cNem,fLliv0ma0Dny,pKsHterG;mi0E;cribe,er3iHtrG;oy;gn,re;a09e08i5osB;eGi09y;at,ct;iIlHrG;ea1;a2i05;de;ma4n9re,te;a0Ae09h06i7l04oJrG;aHeGoAuFy;a7dB;ck,ve;llZmSnHok,py,uGv0;gh,nt;cePdu5fMsKtIvG;eGin9;rt,y;aEin0SrG;a8ibu7ol;iGtitu7;d0st;iHoGroC;rm;gu2rm;rn;biLfoKmaJpG;a2laE;in;re;nd;rt;ne;ap1e5;aGip,o1;im,w;aHeG;at,ck,w;llen4n4r4se;a1nt0;ll,ncIrGt0u1;eGry;!en;el;aPeMloLoJruFuG;lGry;ly;sh;a8mb,o8rrGth0un9;ow;ck;ar,lHnefBtrG;ay;ie3ong;ng,se;band0Jc0Bd06ffo05gr04id,l01mu1nYppTrQsKttGvoid,waB;acIeHra5;ct;m0Fnd;h,k;k,sG;eIiHocia7uG;me;gn,st;mb6rt;le;chHgGri3;ue;!i3;eaJlIroG;aDve;ch;aud,y;l,r;noun9sw0tG;icipa7;ce;lHt0;er;e4ow;ee;rd;aRdIju8mBoR;it;st;!reA;ss;cJhie3knowled4tiva7;te;ge;ve;eIouCu1;se;nt;pt;on",
    "Unit": "true¦0:19;a14b12c0Od0Ne0Lf0Gg0Ch09in0Hjoule0k02l00mNnMoLpIqHsqCt7volts,w6y4z3°2µ1;g,s;c,f,n;b,e2;a0Nb,d0Dears old,o1;tt0H;att0b;able4b3d,e2on1sp;!ne0;a2r0D;!l,sp;spo04; ft,uare 1;c0Id0Hf3i0Fkilo0Jm1ya0E;e0Mil1;e0li0H;eet0o0D;t,uart0;ascals,e2i1ou0Pt;c0Mnt0;rcent,t02;hms,uYz;an0JewtT;/s,b,e9g,i3l,m2p1²,³;h,s;!²;!/h,cro5l1;e1li08;! pFs1²;! 1;anEpD;g06s0B;gQter1;! 2s1;! 1;per second;b,i00m,u1x;men0x0;b,elvin0g,ilo2m1nR;!/h,ph,²;byZgXmeter1;! p2s1;! p1;er1; hour;e1g,r0z;ct1rtz0;aXogQ;al2b,igAra1;in0m0;!l1;on0;a4emtPl2t1;²,³; oz,uid ou1;nce0;hrenheit0rad0;b,x1;abyH;eciCg,l,mA;arat0eAg,m9oulomb0u1;bic 1p0;c5d4fo3i2meAya1;rd0;nch0;ot0;eci2;enti1;me4;!²,³;lsius0nti1;g2li1me1;ter0;ram0;bl,y1;te0;c4tt1;os1;eco1;nd0;re0;!s",
    "Organization": "true¦0:46;a3Ab2Qc2Ad21e1Xf1Tg1Lh1Gi1Dj19k17l13m0Sn0Go0Dp07qu06rZsStFuBv8w3y1;amaha,m0Xou1w0X;gov,tu2S;a3e1orld trade organizati41;lls fargo,st1;fie22inghou16;l1rner br3D;-m11gree31l street journ25m11;an halNeriz3Wisa,o1;dafo2Gl1;kswagLvo;bs,kip,n2ps,s1;a tod2Rps;es35i1;lev2Xted natio2Uv; mobi2Kaco bePd bMeAgi frida9h3im horto2Tmz,o1witt2W;shiba,y1;ota,s r Y;e 1in lizzy;b3carpen33daily ma2Xguess w2holli0rolling st1Ms1w2;mashing pumpki2Ouprem0;ho;ea1lack eyed pe3Fyrds;ch bo1tl0;ys;l2s1;co,la m12;efoni07us;a6e4ieme2Gnp,o2pice gir5ta1ubaru;rbucks,to2N;ny,undgard1;en;a2Rx pisto1;ls;few25insbu26msu1X;.e.m.,adiohead,b6e3oyal 1yan2X;b1dutch she4;ank;/max,aders dige1Ed 1vl32;bu1c1Uhot chili peppe2Klobst28;ll;c,s;ant2Vizno2F;an5bs,e3fiz24hilip morrBi2r1;emier27octer & gamb1Rudenti14;nk floyd,zza hut;psi28tro1uge08;br2Qchina,n2Q; 2ason1Xda2G;ld navy,pec,range juli2xf1;am;us;a9b8e5fl,h4i3o1sa,wa;kia,tre dame,vart1;is;ke,ntendo,ss0K;l,s;c,st1Etflix,w1; 1sweek;kids on the block,york08;a,c;nd1Us2t1;ional aca2Fo,we0Q;a,cYd0O;aAcdonald9e5i3lb,o1tv,yspace;b1Nnsanto,ody blu0t1;ley crue,or0O;crosoft,t1;as,subisO;dica3rcedes2talli1;ca;!-benz;id,re;'s,s;c's milk,tt13z1Y;'ore09a3e1g,ittle caesa1Ktd;novo,x1;is,mark; pres5-z-boy,bour party;atv,fc,kk,m1od1K;art;iffy lu0Lo3pmorgan1sa;! cha1;se;hnson & johns1Sy d1R;bm,hop,n1tv;c,g,te1;l,rpol; & m,asbro,ewlett-packaTi3o1sbc,yundai;me dep1n1J;ot;tac1zbollah;hi;eneral 6hq,l5mb,o2reen d0Iu1;cci,ns n ros0;ldman sachs,o1;dye1g0B;ar;axo smith kliZencore;electr0Im1;oto0V;a3bi,da,edex,i1leetwood mac,oGrito-l0A;at,nancial1restoV; tim0;cebook,nnie mae;b06sa,u3xxon1; m1m1;ob0H;!rosceptics;aiml0Ae5isney,o3u1;nkin donuts,po0Wran dur1;an;j,w j1;on0;a,f leppa3ll,p2r spiegZstiny's chi1;ld;eche mode,t;rd;aEbc,hBi9nn,o3r1;aigsli5eedence clearwater reviv1ossra05;al;!ca c5l4m1o0Ast05;ca2p1;aq;st;dplMgate;ola;a,sco1tigroup;! systems;ev2i1;ck fil-a,na daily;r0Hy;dbury,pital o1rl's jr;ne;aGbc,eCfAl6mw,ni,o2p,r1;exiteeWos;ei3mbardiJston 1;glo1pizza;be;ng;ack & deckFo2ue c1;roX;ckbuster video,omingda1;le; g1g1;oodriN;cht3e ge0n & jer2rkshire hathaw1;ay;ryH;el;nana republ3s1xt5y5;f,kin robbi1;ns;ic;bXcSdidRerosmith,ig,lLmFnheuser-busEol,ppleAr7s3t&t,v2y1;er;is,on;hland2s1;n,ociated F; o1;il;by4g2m1;co;os; compu2bee1;'s;te1;rs;ch;c,d,erican3t1;!r1;ak; ex1;pre1;ss; 4catel2t1;air;!-luce1;nt;jazeera,qae1;da;as;/dc,a3er,t1;ivisi1;on;demy of scienc0;es;ba,c",
    "Demonym": "true¦0:16;1:13;a0Wb0Nc0Cd0Ae09f07g04h02iYjVkTlPmLnIomHpDqatari,rBs7t5u4v3wel0Rz2;am0Fimbabwe0;enezuel0ietnam0H;g9krai1;aiwThai,rinida0Iu2;ni0Qrkmen;a4cot0Ke3ingapoOlovak,oma0Tpa05udRw2y0X;edi0Kiss;negal0Br08;mo0uU;o6us0Lw2;and0;a3eru0Hhilipp0Po2;li0Ertugu06;kist3lesti1na2raguay0;ma1;ani;amiZi2orweP;caragu0geri2;an,en;a3ex0Mo2;ngo0Erocc0;cedo1la2;gasy,y08;a4eb9i2;b2thua1;e0Dy0;o,t02;azakh,eny0o2uwaiti;re0;a2orda1;ma0Bp2;anN;celandic,nd4r2sraeli,ta02vo06;a2iT;ni0qi;i0oneV;aiDin2ondur0unN;di;amDe2hanai0reek,uatemal0;or2rm0;gi0;i2ren7;lipino,n4;cuadoVgyp6ngliJsto1thiopi0urope0;a2ominXut4;niH;a9h6o4roa3ub0ze2;ch;ti0;lom2ngol5;bi0;a6i2;le0n2;ese;lifor1m2na3;bo2eroo1;di0;angladeshi,el8o6r3ul2;gaG;aziBi2;ti2;sh;li2s1;vi0;aru2gi0;si0;fAl7merBngol0r5si0us2;sie,tr2;a2i0;li0;gent2me1;ine;ba1ge2;ri0;ni0;gh0r2;ic0;an",
    "Possessive": "true¦anyAh5its,m3noCo1sometBthe0yo1;ir1mselves;ur0;!s;i8y0;!se4;er1i0;mse2s;!s0;!e0;lf;o1t0;hing;ne",
    "Currency": "true¦$,aud,bScQdLeurKfJgbp,hkd,iIjpy,kGlEp8r7s3usd,x2y1z0¢,£,¥,ден,лв,руб,฿,₡,₨,€,₭,﷼;lotySł;en,uanR;af,of;h0t5;e0il5;k0q0;elM;iel,oubleLp,upeeL;e2ound st0;er0;lingI;n0soH;ceGn0;ies,y;e0i8;i,mpi7;n,r0wanzaCyatC;!onaBw;ls,nr;ori7ranc9;!o8;en3i2kk,o0;b0ll2;ra5;me4n0rham4;ar3;ad,e0ny;nt1;aht,itcoin0;!s",
    "City": "true¦a2Wb26c1Wd1Re1Qf1Og1Ih1Ai18jakar2Hk0Zl0Tm0Gn0Co0ApZquiYrVsLtCuBv8w3y1z0;agreb,uri1Z;ang1Te0okohama;katerin1Hrev34;ars3e2i0rocl3;ckl0Vn0;nipeg,terth0W;llingt1Oxford;aw;a1i0;en2Hlni2Z;lenc2Uncouv0Gr2G;lan bat0Dtrecht;a6bilisi,e5he4i3o2rondheim,u0;nVr0;in,ku;kyo,ronIulouC;anj23l13miso2Jra2A; haJssaloni0X;gucigalpa,hr2Ol av0L;i0llinn,mpe2Bngi07rtu;chu22n2MpT;a3e2h1kopje,t0ydney;ockholm,uttga12;angh1Fenzh1X;o0KvZ;int peters0Ul3n0ppo1F; 0ti1B;jo0salv2;se;v0z0Q;adU;eykjavik,i1o0;me,sario,t25;ga,o de janei17;to;a8e6h5i4o2r0ueb1Qyongya1N;a0etor24;gue;rt0zn24; elizabe3o;ls1Grae24;iladelph1Znom pe07oenix;r0tah tik19;th;lerJr0tr10;is;dessa,s0ttawa;a1Hlo;a2ew 0is;delTtaip0york;ei;goya,nt0Upl0Uv1R;a5e4i3o1u0;mb0Lni0I;nt0scH;evideo,real;l1Mn01skolc;dellín,lbour0S;drid,l5n3r0;ib1se0;ille;or;chest0dalWi0Z;er;mo;a4i1o0vAy01;nd00s angel0F;ege,ma0nz,sbZverpo1;!ss0;ol; pla0Iusan0F;a5hark4i3laipeda,o1rak0uala lump2;ow;be,pavog0sice;ur;ev,ng8;iv;b3mpa0Kndy,ohsiu0Hra0un03;c0j;hi;ncheMstanb0̇zmir;ul;a5e3o0; chi mi1ms,u0;stI;nh;lsin0rakliG;ki;ifa,m0noi,va0A;bu0SiltD;alw4dan3en2hent,iza,othen1raz,ua0;dalaj0Gngzhou;bu0P;eUoa;sk;ay;es,rankfu0;rt;dmont4indhovU;a1ha01oha,u0;blRrb0Eshanbe;e0kar,masc0FugavpiJ;gu,je0;on;a7ebu,h2o0raioJuriti01;lo0nstanJpenhagNrk;gFmbo;enn3i1ristchur0;ch;ang m1c0ttagoL;ago;ai;i0lgary,pe town,rac4;ro;aHeBirminghWogoAr5u0;char3dap3enos air2r0sZ;g0sa;as;es;est;a2isba1usse0;ls;ne;silPtisla0;va;ta;i3lgrade,r0;g1l0n;in;en;ji0rut;ng;ku,n3r0sel;celo1ranquil0;la;na;g1ja lu0;ka;alo0kok;re;aBb9hmedabad,l7m4n2qa1sh0thens,uckland;dod,gabat;ba;k0twerp;ara;m5s0;terd0;am;exandr0maty;ia;idj0u dhabi;an;lbo1rh0;us;rg",
    "Abbreviation": "true¦a0Tb0Qc0Kd0Ie0Ff0Cg0Ah08i06j04k02l00mRnOoNpIqHrFs9t6u5v2w0yb,µg;is0r,y0L;!c;a,b,e1i0ol,s,t;tro,vo;r,t;niv,safa,t;b1ce,d,e0sp;l,mp,nn,x;!l,sp;ask,e3fc,gt,i2q1r,s,t,u0;pt,rg;! ft;r,tu;c,nVp0;!t;b,d,e0;pSs,v;t,ue;a,d,enn3hd,l,p,r1s0t,vt;!eud;ef,o0;b,f,n;!a;ct,kla,nt,p,rd,z;e0ov;b0e;!r;a7b,d,essrs,g,i4l3m2p1rHs0t;!tr;h,s;!e;!le;!n1s0;c,ter;!n;!j,r,sc;at,b,it,lb,m,ng,t0x;!d;an6b,g,m0;!ph;an,d,r,u0;l,n;a,da,e,n0;c,f;g,on,r0wy,z;!s;a0b,en,ov;!l;e1ig,l0m,r,t,y;! oz,a;b,m;a,g,ng,s1tc,x0;!p;p,q,t;ak,e0g,ist,l,m,r;c,f,pt,t;a3ca,g,l,m2o0pl,res,t,yn;!l0mdr,nn,rp;!o;!dr;!l0pt;!if;a,c,l1r0;ig,os;!dg,vd;d4l3p2r1ss0tty,ug,ve;n,t;c,iz;prox,r,t;!ta;!j,m,v",
    "Country": "true¦0:38;1:2L;a2Wb2Dc21d1Xe1Rf1Lg1Bh19i13j11k0Zl0Um0Gn05om3CpZqat1JrXsKtCu6v4wal3yemTz2;a24imbabwe;es,lis and futu2X;a2enezue31ietnam;nuatu,tican city;.5gTkraiZnited 3ruXs2zbeE;a,sr;arab emirat0Kkingdom,states2;! of am2X;k.,s.2; 27a.;a7haBimor-les0Bo6rinidad4u2;nis0rk2valu;ey,me2Xs and caic1T; and 2-2;toba1J;go,kel0Ynga;iw2Vji2nz2R;ki2T;aCcotl1eBi8lov7o5pa2Bri lanka,u4w2yr0;az2ed9itzerl1;il1;d2Qriname;lomon1Vmal0uth 2;afr2IkLsud2O;ak0en0;erra leoEn2;gapo1Wt maart2;en;negKrb0ychellY;int 2moa,n marino,udi arab0;hele24luc0mart1Z;epublic of ir0Com2Cuss0w2;an25;a3eHhilippinTitcairn1Ko2uerto riM;l1rtugE;ki2Bl3nama,pua new0Tra2;gu6;au,esti2;ne;aAe8i6or2;folk1Gth3w2;ay; k2ern mariana1B;or0M;caragua,ger2ue;!ia;p2ther18w zeal1;al;mib0u2;ru;a6exi5icro09o2yanm04;ldova,n2roc4zamb9;a3gol0t2;enegro,serrat;co;c9dagascZl6r4urit3yot2;te;an0i14;shall0Vtin2;ique;a3div2i,ta;es;wi,ys0;ao,ed00;a5e4i2uxembourg;b2echtenste10thu1E;er0ya;ban0Gsotho;os,tv0;azakh1De2iriba02osovo,uwait,yrgyz1D;eling0Jnya;a2erF;ma15p1B;c6nd5r3s2taly,vory coast;le of m19rael;a2el1;n,q;ia,oI;el1;aiSon2ungary;dur0Mg kong;aAermany,ha0Pibralt9re7u2;a5ern4inea2ya0O;!-biss2;au;sey;deloupe,m,tema0P;e2na0M;ce,nl1;ar;bTmb0;a6i5r2;ance,ench 2;guia0Dpoly2;nes0;ji,nl1;lklandTroeT;ast tim6cu5gypt,l salv5ngl1quatorial3ritr4st2thiop0;on0; guin2;ea;ad2;or;enmark,jibou4ominica3r con2;go;!n B;ti;aAentral african 9h7o4roat0u3yprQzech2; 8ia;ba,racao;c3lo2morPngo-brazzaville,okFsta r03te d'ivoiK;mb0;osD;i2ristmasF;le,na;republic;m2naTpe verde,yman9;bod0ero2;on;aFeChut00o8r4u2;lgar0r2;kina faso,ma,undi;azil,itish 2unei;virgin2; is2;lands;liv0nai4snia and herzegoviGtswaGuvet2; isl1;and;re;l2n7rmuF;ar2gium,ize;us;h3ngladesh,rbad2;os;am3ra2;in;as;fghaFlCmAn5r3ustr2zerbaijH;al0ia;genti2men0uba;na;dorra,g4t2;arct6igua and barbu2;da;o2uil2;la;er2;ica;b2ger0;an0;ia;ni2;st2;an",
    "Region": "true¦0:1U;a20b1Sc1Id1Des1Cf19g13h10i0Xj0Vk0Tl0Qm0FnZoXpSqPrMsDtAut9v6w3y1zacatec22;o05u1;cat18kZ;a1est vi4isconsin,yomi14;rwick0shington1;! dc;er2i1;rgin1S;acruz,mont;ah,tar pradesh;a2e1laxca1DuscaA;nnessee,x1R;bas0Kmaulip1QsmJ;a6i4o2taf0Ou1ylh13;ffVrr00s0Y;me10no1Auth 1;cSdR;ber1Ic1naloa;hu0Sily;n2skatchew0Rxo1;ny; luis potosi,ta catari1I;a1hode7;j1ngp02;asth0Mshahi;inghai,u1;e1intana roo;bec,ensWreta0E;ara4e2rince edward1; isU;i,nnsylv1rnambu02;an14;!na;axa0Ndisha,h1klaho1Bntar1reg4x04;io;ayarit,eBo3u1;evo le1nav0L;on;r1tt0Rva scot0X;f6mandy,th1; 1ampton0;c3d2yo1;rk0;ako0Y;aroli0V;olk;bras0Xva01w1; 2foundland1;! and labrador;brunswick,hamp0jers1mexiJyork state;ey;a6i2o1;nta0Nrelos;ch3dlanBn2ss1;issippi,ouri;as geraGneso0M;igQoacQ;dhya,harasht04ine,ni3r1ssachusetts;anhao,y1;land;p1toba;ur;anca0e1incoln0ouis8;e1iH;ds;a1entucky,hul0A;ns08rnata0Dshmir;alis1iangxi;co;daho,llino2nd1owa;ia05;is;a2ert1idalEunA;ford0;mp0waii;ansu,eorgWlou5u1;an2erre1izhou,jarat;ro;ajuato,gdo1;ng;cester0;lori2uji1;an;da;sex;e4o2uran1;go;rs1;et;lawaErby0;a8ea7hi6o1umbrH;ahui4l3nnectic2rsi1ventry;ca;ut;iMorado;la;apEhuahua;ra;l8m1;bridge0peche;a5r4uck1;ingham0;shi1;re;emen,itish columb3;h2ja cal1sque,var2;iforn1;ia;guascalientes,l4r1;izo2kans1;as;na;a2ber1;ta;ba2s1;ka;ma",
    "FemaleName": "true¦0:FY;1:G2;2:FR;3:FD;4:FC;5:FS;6:ER;7:EP;8:GF;9:EZ;A:GB;B:E5;C:G8;D:FO;E:FL;F:EG;aE2bD4cB8dAIe9Gf91g8Hh83i7Sj6Uk60l4Om38n2To2Qp2Fqu2Er1Os0Qt04ursu6vUwOyLzG;aJeHoG;e,la,ra;lGna;da,ma;da,ra;as7EeHol1TvG;et7onB9;le0sen3;an9endBNhiB4iG;lInG;if3AniGo0;e,f39;a,helmi0lGma;a,ow;aMeJiG;cHviG;an9XenG1;kCZtor3;da,l8Vnus,rG;a,nGoniD2;a,iDC;leGnesEC;nDLrG;i1y;aSePhNiMoJrGu6y4;acG3iGu0E;c3na,sG;h9Mta;nHrG;a,i;i9Jya;a5IffaCGna,s5;al3eGomasi0;a,l8Go6Xres1;g7Uo6WrHssG;!a,ie;eFi,ri8;bNliMmKnIrHs5tGwa0;ia0um;a,yn;iGya;a,ka,s5;a4e4iGmCAra;!ka;a,t5;at5it5;a05carlet2Ye04hUiSkye,oQtMuHyG;bFJlvi1;e,sHzG;an2Tet7ie,y;anGi8;!a,e,nG;aEe;aIeG;fGl3DphG;an2;cF8r6;f3nGphi1;d4ia,ja,ya;er4lv3mon1nGobh75;dy;aKeGirlBLo0y6;ba,e0i6lIrG;iGrBPyl;!d70;ia,lBV;ki4nIrHu0w0yG;la,na;i,leAon,ron;a,da,ia,nGon;a,on;l5Yre0;bMdLi9lKmIndHrGs5vannaE;aEi0;ra,y;aGi4;nt5ra;lBNome;e,ie;in1ri0;a02eXhViToHuG;by,thBK;bQcPlOnNsHwe0xG;an94ie,y;aHeGie,lC;ann8ll1marBFtB;!lGnn1;iGyn;e,nG;a,d7W;da,i,na;an9;hel53io;bin,erByn;a,cGkki,na,ta;helBZki;ea,iannDXoG;da,n12;an0bIgi0i0nGta,y0;aGee;!e,ta;a,eG;cARkaE;chGe,i0mo0n5EquCDvDy0;aCCelGi9;!e,le;een2ia0;aMeLhJoIrG;iGudenAW;scil1Uyamva9;lly,rt3;ilome0oebe,ylG;is,lis;arl,ggy,nelope,r6t4;ige,m0Fn4Oo6rvaBBtHulG;a,et7in1;ricGsy,tA8;a,e,ia;ctav3deHfAWlGphAW;a,ga,iv3;l3t7;aQePiJoGy6;eHrG;aEeDma;ll1mi;aKcIkGla,na,s5ta;iGki;!ta;hoB2k8BolG;a,eBH;!mh;l7Tna,risF;dIi5PnHo23taG;li1s5;cy,et7;eAiCO;a01ckenz2eViLoIrignayani,uriBGyG;a,rG;a,na,tAS;i4ll9XnG;a,iG;ca,ka,qB4;a,chOkaNlJmi,nIrGtzi;aGiam;!n9;a,dy,erva,h,n2;a,dIi9JlG;iGy;cent,e;red;!e6;ae6el3G;ag4KgKi,lHrG;edi61isFyl;an2iGliF;nGsAM;a,da;!an,han;b08c9Ed06e,g04i03l01nZrKtJuHv6Sx87yGz2;a,bell,ra;de,rG;a,eD;h75il9t2;a,cSgOiJjor2l6In2s5tIyG;!aGbe5QjaAlou;m,n9S;a,ha,i0;!aIbALeHja,lCna,sGt53;!a,ol,sa;!l06;!h,m,nG;!a,e,n1;arIeHie,oGr3Kueri7;!t;!ry;et3IiB;elGi61y;a,l1;dGon,ue6;akranBy;iGlo36;a,ka,n9;a,re,s2;daGg2;!l2W;alCd2elGge,isBGon0;eiAin1yn;el,le;a0Ie08iWoQuKyG;d3la,nG;!a,dHe9SnGsAQ;!a,e9R;a,sAO;aB1cJelIiFlHna,pGz;e,iB;a,u;a,la;iGy;a2Ae,l25n9;is,l1GrHtt2uG;el6is1;aIeHi8na,rG;a6Zi8;lei,n1tB;!in1;aQbPd3lLnIsHv3zG;!a,be4Ket7z2;a,et7;a,dG;a,sGy;ay,ey,i,y;a,iaIlG;iGy;a8Ge;!n4F;b7Terty;!n5R;aNda,e0iLla,nKoIslARtGx2;iGt2;c3t3;la,nGra;a,ie,o4;a,or1;a,gh,laG;!ni;!h,nG;a,d4e,n4N;cNdon7Si6kes5na,rMtKurIvHxGy6;mi;ern1in3;a,eGie,yn;l,n;as5is5oG;nya,ya;a,isF;ey,ie,y;aZeUhadija,iMoLrIyG;lGra;a,ee,ie;istGy5B;a,en,iGy;!e,n48;ri,urtn9A;aMerLl99mIrGzzy;a,stG;en,in;!berlG;eGi,y;e,y;a,stD;!na,ra;el6PiJlInHrG;a,i,ri;d4na;ey,i,l9Qs2y;ra,s5;c8Wi5XlOma6nyakumari,rMss5LtJviByG;!e,lG;a,eG;e,i78;a5EeHhGi3PlCri0y;ar5Cer5Cie,leDr9Fy;!lyn73;a,en,iGl4Uyn;!ma,n31sF;ei72i,l2;a04eVilToMuG;anKdJliGst56;aHeGsF;!nAt0W;!n8X;i2Ry;a,iB;!anLcelCd5Vel71han6IlJni,sHva0yG;a,ce;eGie;fi0lCph4X;eGie;en,n1;!a,e,n36;!i10lG;!i0Z;anLle0nIrHsG;i5Qsi5Q;i,ri;!a,el6Pif1RnG;a,et7iGy;!e,f1P;a,e72iHnG;a,e71iG;e,n1;cLd1mi,nHqueliAsmin2Uvie4yAzG;min8;a8eHiG;ce,e,n1s;!lGsFt06;e,le;inHk2lCquelG;in1yn;da,ta;lPmNnMo0rLsHvaG;!na;aHiGob6U;do4;!belGdo4;!a,e,l2G;en1i0ma;a,di4es,gr5R;el9ogG;en1;a,eAia0o0se;aNeKilHoGyacin1N;ll2rten1H;aHdGlaH;a,egard;ry;ath0WiHlGnrietBrmiAst0W;en24ga;di;il75lKnJrGtt2yl75z6D;iGmo4Fri4G;etG;!te;aEnaE;ey,l2;aYeTiOlMold12rIwG;enGyne18;!dolC;acHetGisel9;a,chD;e,ieG;!la;adys,enGor3yn1Y;a,da,na;aJgi,lHna,ov71selG;a,e,le;da,liG;an;!n0;mYnIorgHrG;ald35i,m2Stru73;et7i5T;a,eGna;s1Nvieve;briel3Fil,le,rnet,yle;aReOio0loMrG;anHe9iG;da,e9;!cG;esHiGoi0G;n1s3V;!ca;!rG;a,en43;lHrnG;!an9;ec3ic3;rHtiGy8;ma;ah,rah;d0FileDkBl00mUn4ArRsMtLuKvG;aIelHiG;e,ta;in0Ayn;!ngel2H;geni1la,ni3R;h52ta;meral9peranJtG;eHhGrel6;er;l2Pr;za;iGma,nest29yn;cGka,n;a,ka;eJilImG;aGie,y;!liA;ee,i1y;lGrald;da,y;aTeRiMlLma,no4oJsIvG;a,iG;na,ra;a,ie;iGuiG;se;a,en,ie,y;a0c3da,nJsGzaH;aGe;!beG;th;!a,or;anor,nG;!a;in1na;en,iGna,wi0;e,th;aWeKiJoGul2U;lor51miniq3Yn30rGtt2;a,eDis,la,othGthy;ea,y;an09naEonAx2;anPbOde,eNiLja,lImetr3nGsir4U;a,iG;ce,se;a,iHla,orGphiA;es,is;a,l5J;dGrdG;re;!d4Mna;!b2CoraEra;a,d4nG;!a,e;hl3i0mMnKphn1rHvi1WyG;le,na;a,by,cHia,lG;a,en1;ey,ie;a,et7iG;!ca,el1Aka;arGia;is;a0Qe0Mh04i02lUoJrHynG;di,th3;istGy04;al,i0;lOnLrHurG;tn1D;aId28iGn28riA;!nG;a,e,n1;!l1S;n2sG;tanGuelo;ce,za;eGleD;en,t7;aIeoHotG;il4B;!pat4;ir8rIudG;et7iG;a,ne;a,e,iG;ce,sX;a4er4ndG;i,y;aPeMloe,rG;isHyG;stal;sy,tG;aHen,iGy;!an1e,n1;!l;lseHrG;!i8yl;a,y;nLrG;isJlHmG;aiA;a,eGot7;n1t7;!sa;d4el1PtG;al,el1O;cHlG;es7i3F;el3ilG;e,ia,y;iYlXmilWndVrNsLtGy6;aJeIhGri0;erGleDrCy;in1;ri0;li0ri0;a2GsG;a2Fie;a,iMlKmeIolHrG;ie,ol;!e,in1yn;lGn;!a,la;a,eGie,y;ne,y;na,sF;a0Di0D;a,e,l1;isBl2;tlG;in,yn;arb0CeYianXlVoTrG;andRePiIoHyG;an0nn;nwCok8;an2NdgKg0ItG;n27tG;!aHnG;ey,i,y;ny;etG;!t8;an0e,nG;da,na;i8y;bbi8nG;iBn2;ancGossom,ythe;a,he;ca;aRcky,lin9niBrNssMtIulaEvG;!erlG;ey,y;hHsy,tG;e,i0Zy8;!anG;ie,y;!ie;nGt5yl;adHiG;ce;et7iA;!triG;ce,z;a4ie,ra;aliy29b24d1Lg1Hi19l0Sm0Nn01rWsNthe0uJvIyG;anGes5;a,na;a,r25;drIgusHrG;el3;ti0;a,ey,i,y;hHtrG;id;aKlGt1P;eHi8yG;!n;e,iGy;gh;!nG;ti;iIleHpiB;ta;en,n1t7;an19elG;le;aYdWeUgQiOja,nHtoGya;inet7n3;!aJeHiGmI;e,ka;!mGt7;ar2;!belHliFmT;sa;!le;ka,sGta;a,sa;elGie;a,iG;a,ca,n1qG;ue;!t7;te;je6rea;la;!bHmGstas3;ar3;el;aIberHel3iGy;e,na;!ly;l3n9;da;aTba,eNiKlIma,yG;a,c3sG;a,on,sa;iGys0J;e,s0I;a,cHna,sGza;a,ha,on,sa;e,ia;c3is5jaIna,ssaIxG;aGia;!nd4;nd4;ra;ia;i0nHyG;ah,na;a,is,naE;c5da,leDmLnslKsG;haElG;inGyW;g,n;!h;ey;ee;en;at5g2nG;es;ie;ha;aVdiSelLrG;eIiG;anLenG;a,e,ne;an0;na;aKeJiHyG;nn;a,n1;a,e;!ne;!iG;de;e,lCsG;on;yn;!lG;iAyn;ne;agaJbHiG;!gaI;ey,i8y;!e;il;ah",
    "Place": "true¦a07b05cZdYeXfVgRhQiOjfk,kMlKmHneEoDp9que,rd,s8t5u4v3w0yyz;is1y0;!o;!c;a,t;pYsafa,t;e1he 0;bronx,hamptons;nn,x;ask,fo,oho,t,under6yd;a2e1h0;l,x;k,nnK;!cifX;kla,nt;b1w eng0;land;!r;a1co,i0t,uc;dKnn;libu,nhattS;a0gw,hr;s,x;an0ul;!s;a0cn,da,ndianMst;!x;arlem,kg,nd,wy;a2re0;at 0enwich;britain,lak6;!y village;co,l0ra;!a;urope,verglad2;ak,en,fw,ist,own4xb;al4dg,gk,hina3l2o1r0t;es;lo,nn;!t;town;!if;cn,e0kk,lvd,rooklyn;l air,verly hills;frica,lta,m5ntarct2r1sia,tl0ve;!ant1;ct0iz;ic0; oce0;an;ericas,s",
    "WeekDay": "true¦fri2mon2s1t0wednesd3;hurs1ues1;aturd1und1;!d0;ay0;!s",
    "Month": "true¦aBdec9feb7j2mar,nov9oct1sep0;!t8;!o8;an3u0;l1n0;!e;!y;!u1;!ru0;ary;!em0;ber;pr1ug0;!ust;!il",
    "Date": "true¦ago,t0weekend,yesterd2;mr2o0;d0morrow;ay;!w",
    "FirstName": "true¦aEblair,cCdevBj8k6lashawn,m3nelly,quinn,re2sh0;ay,e0iloh;a,lby;g1ne;ar1el,org0;an;ion,lo;as8e0r9;ls7nyatta,rry;am0ess1ude;ie,m0;ie;an,on;as0heyenne;ey,sidy;lex1ndra,ubr0;ey;is",
    "LastName": "true¦0:34;1:3B;2:39;3:2Y;4:2E;5:30;a3Bb31c2Od2Ee2Bf25g1Zh1Pi1Kj1Ek17l0Zm0Nn0Jo0Gp05rYsMtHvFwCxBy8zh6;a6ou,u;ng,o;a6eun2Uoshi1Kun;ma6ng;da,guc1Zmo27sh21zaR;iao,u;a7eb0il6o3right,u;li3Bs2;gn0lk0ng,tanabe;a6ivaldi;ssilj37zqu1;a9h8i2Go7r6sui,urn0;an,ynisJ;lst0Prr1Uth;at1Uomps2;kah0Vnaka,ylor;aEchDeChimizu,iBmiAo9t7u6zabo;ar1lliv2AzuE;a6ein0;l23rm0;sa,u3;rn4th;lva,mmo24ngh;mjon4rrano;midt,neid0ulz;ito,n7sa6to;ki;ch1dLtos,z;amBeag1Zi9o7u6;bio,iz,sD;b6dri1MgIj0Tme24osevelt,ssi,ux;erts,ins2;c6ve0F;ci,hards2;ir1os;aEeAh8ic6ow20ut1N;as6hl0;so;a6illips;m,n1T;ders5et8r7t6;e0Nr4;ez,ry;ers;h21rk0t6vl4;el,te0J;baBg0Blivei01r6;t6w1O;ega,iz;a6eils2guy5ix2owak,ym1E;gy,ka6var1K;ji6muW;ma;aEeCiBo8u6;ll0n6rr0Bssolini,ñ6;oz;lina,oKr6zart;al0Me6r0U;au,no;hhail4ll0;rci0ssi6y0;!er;eWmmad4r6tsu07;in6tin1;!o;aCe8i6op1uo;!n6u;coln,dholm;fe7n0Qr6w0J;oy;bv6v6;re;mmy,rs5u;aBennedy,imuAle0Lo8u7wo6;k,n;mar,znets4;bay6vacs;asY;ra;hn,rl9to,ur,zl4;aAen9ha3imen1o6u3;h6nYu3;an6ns2;ss2;ki0Es5;cks2nsse0D;glesi9ke8noue,shik7to,vano6;u,v;awa;da;as;aBe8itchcock,o7u6;!a3b0ghNynh;a3ffmann,rvat;mingw7nde6rN;rs2;ay;ns5rrQs7y6;asDes;an4hi6;moJ;a9il,o8r7u6;o,tierr1;ayli3ub0;m1nzal1;nd6o,rcia;hi;erAis9lor8o7uj6;ita;st0urni0;es;ch0;nand1;d7insteHsposi6vaL;to;is2wards;aCeBi9omin8u6;bo6rand;is;gu1;az,mitr4;ov;lgado,vi;nkula,rw7vi6;es,s;in;aFhBlarkAo6;h5l6op0rbyn,x;em7li6;ns;an;!e;an8e7iu,o6ristens5u3we;i,ng,u3w,y;!n,on6u3;!g;mpb7rt0st6;ro;ell;aBe8ha3lanco,oyko,r6yrne;ooks,yant;ng;ck7ethov5nnett;en;er,ham;ch,h8iley,rn6;es,i0;er;k,ng;dDl9nd6;ers6rA;en,on,s2;on;eks7iy8var1;ez;ej6;ev;ams",
    "MaleName": "true¦0:CE;1:BL;2:C2;3:BT;4:B5;5:BZ;6:AT;7:9V;8:BD;9:AX;A:AO;aB4bA8c97d87e7Gf6Yg6Gh5Wi5Ij4Lk4Bl3Rm2Pn2Eo28p22qu20r1As0Qt06u05v00wNxavi3yGzB;aBor0;cBh8Ine;hCkB;!aB1;ar51eB0;ass2i,oCuB;sDu25;nEsDusB;oBsC;uf;ef;at0g;aJeHiCoByaAP;lfgang,odrow;lBn1O;bDey,frBJlB;aA5iB;am,e,s;e89ur;i,nde7sB;!l6t1;de,lCrr5yB;l1ne;lBt3;a93y;aEern1iBladimir;cCha0kt5CnceBrg9Bva0;!nt;ente,t5A;lentin49n8Yughn;lyss4Msm0;aTeOhKiIoErCyB;!l3ro8s1;av9QeBist0oy,um0;nt9Iv54y;bDd7XmBny;!as,mBoharu;aAYie,y;i83y;mBt9;!my,othy;adDeoCia7DomB;!as;!do7M;!de9;dErB;en8HrB;an8GeBy;ll,n8F;!dy;dgh,ic9Tnn3req,ts45;aRcotPeNhJiHoFpenc3tBur1Oylve8Hzym1;anDeBua7B;f0phAFvBwa7A;e57ie;!islaw,l6;lom1nA3uB;leyma8ta;dBl7Jm1;!n6;aDeB;lBrm0;d1t1;h6Sne,qu0Uun,wn,y8;aBbasti0k1Xl41rg40th,ymo9I;m9n;!tB;!ie,y;lCmBnti21q4Iul;!mAu4;ik,vato6V;aWeShe92iOoFuCyB;an,ou;b6LdCf9pe6QssB;!elAI;ol2Uy;an,bIcHdGel,geFh0landA9mEnDry,sCyB;!ce;coe,s;!a95nA;an,eo;l3Jr;e4Qg3n6olfo,ri68;co,ky;bAe9U;cBl6;ar5Oc5NhCkBo;!ey,ie,y;a85ie;gCid,ub5x,yBza;ansh,nS;g8WiB;na8Ss;ch5Yfa4lDmCndBpha4sh6Uul,ymo70;al9Yol2By;i9Ion;f,ph;ent2inB;cy,t1;aFeDhilCier62ol,reB;st1;!ip,lip;d9Brcy,tB;ar,e2V;b3Sdra6Ft44ul;ctav2Vliv3m96rFsCtBum8Uw5;is,to;aCc8SvB;al52;ma;i,l49vJ;athJeHiDoB;aBel,l0ma0r2X;h,m;cCg4i3IkB;h6Uola;hol5XkBol5X;!ol5W;al,d,il,ls1vB;il50;anBy;!a4i4;aWeTiKoFuCyB;l21r1;hamCr5ZstaB;fa,p4G;ed,mF;dibo,e,hamDis1XntCsBussa;es,he;e,y;ad,ed,mB;ad,ed;cGgu4kElDnCtchB;!e7;a78ik;house,o03t1;e,olB;aj;ah,hBk6;a4eB;al,l;hClv2rB;le,ri7v2;di,met;ck,hNlLmOnu4rHs1tDuricCxB;!imilian8Cwe7;e,io;eo,hCi52tB;!eo,hew,ia;eBis;us,w;cDio,k86lCqu6Gsha7tBv2;i2Hy;in,on;!el,oKus;achBcolm,ik;ai,y;amBdi,moud;adB;ou;aReNiMlo2RoIuCyB;le,nd1;cEiDkBth3;aBe;!s;gi,s;as,iaB;no;g0nn6RrenDuBwe7;!iB;e,s;!zo;am,on4;a7Bevi,la4SnDoBst3vi;!nB;!a60el;!ny;mCnBr67ur4Twr4T;ce,d1;ar,o4N;aIeDhaled,iBrist4Vu48y3B;er0p,rB;by,k,ollos;en0iEnBrmit,v2;!dCnBt5C;e0Yy;a7ri4N;r,th;na68rBthem;im,l;aYeQiOoDuB;an,liBst2;an,o,us;aqu2eJhnInGrEsB;eChBi7Bue;!ua;!ph;dBge;an,i,on;!aBny;h,s,th4X;!ath4Wie,nA;!l,sBy;ph;an,e,mB;!mA;d,ffGrDsB;sBus;!e;a5JemCmai8oBry;me,ni0O;i6Uy;!e58rB;ey,y;cHd5kGmFrDsCvi3yB;!d5s1;on,p3;ed,od,rBv4M;e4Zod;al,es,is1;e,ob,ub;k,ob,quB;es;aNbrahMchika,gKkeJlija,nuIrGsDtBv0;ai,sB;uki;aBha0i6Fma4sac;ac,iaB;h,s;a,vinBw2;!g;k,nngu52;!r;nacBor;io;im;in,n;aJeFina4VoDuByd56;be25gBmber4CsD;h,o;m3ra33sBwa3X;se2;aDctCitCn4ErB;be20m0;or;th;bKlJmza,nIo,rDsCyB;a43d5;an,s0;lEo4FrDuBv6;hi40ki,tB;a,o;is1y;an,ey;k,s;!im;ib;aQeMiLlenKoIrEuB;illerCsB;!tavo;mo;aDegBov3;!g,orB;io,y;dy,h57nt;nzaBrd1;lo;!n;lbe4Qno,ovan4R;ne,oDrB;aBry;ld,rd4U;ffr6rge;bri4l5rBv2;la1Zr3Eth,y;aReNiLlJorr0IrB;anDedBitz;!dAeBri24;ri23;cDkB;!ie,lB;in,yn;esJisB;!co,zek;etch3oB;yd;d4lBonn;ip;deriDliCng,rnB;an01;pe,x;co;bi0di;arZdUfrTit0lNmGnFo2rCsteb0th0uge8vBym5zra;an,ere2V;gi,iCnBrol,v2w2;est45ie;c07k;och,rique,zo;aGerFiCmB;aFe2P;lCrB;!h0;!io;s1y;nu4;be09d1iEliDmCt1viBwood;n,s;er,o;ot1Ts;!as,j43sB;ha;a2en;!dAg32mEuCwB;a25in;arB;do;o0Su0S;l,nB;est;aYeOiLoErDuCwByl0;ay8ight;a8dl6nc0st2;ag0ew;minFnDri0ugCyB;le;!l03;!a29nBov0;e7ie,y;go,icB;!k;armuCeBll1on,rk;go;id;anIj0lbeHmetri9nFon,rEsDvCwBxt3;ay8ey;en,in;hawn,mo08;ek,ri0F;is,nBv3;is,y;rt;!dB;re;lKmInHrDvB;e,iB;!d;en,iDne7rByl;eBin,yl;l2Vn;n,o,us;!e,i4ny;iBon;an,en,on;e,lB;as;a06e04hWiar0lLoGrEuCyrB;il,us;rtB;!is;aBistobal;ig;dy,lEnCrB;ey,neli9y;or,rB;ad;by,e,in,l2t1;aGeDiByI;fBnt;fo0Ct1;meCt9velaB;nd;nt;rDuCyB;!t1;de;enB;ce;aFeErisCuB;ck;!tB;i0oph3;st3;d,rlBs;eBie;s,y;cBdric,s11;il;lEmer1rB;ey,lCro7y;ll;!os,t1;eb,v2;ar02eUilTlaSoPrCuByr1;ddy,rtI;aJeEiDuCyB;an,ce,on;ce,no;an,ce;nCtB;!t;dCtB;!on;an,on;dCndB;en,on;!foBl6y;rd;bCrByd;is;!by;i8ke;al,lA;nFrBshoi;at,nCtB;!r10;aBie;rd0S;!edict,iCjam2nA;ie,y;to;n6rBt;eBy;tt;ey;ar0Xb0Nd0Jgust2hm0Gid5ja0ElZmXnPputsiOrFsaEuCveBya0ziz;ry;gust9st2;us;hi;aIchHi4jun,maFnDon,tBy0;hBu06;ur;av,oB;ld;an,nd0A;el;ie;ta;aq;dGgel05tB;hoEoB;i8nB;!i02y;ne;ny;reBy;!as,s,w;ir,mBos;ar;an,beOd5eIfFi,lEonDphonHt1vB;aMin;on;so,zo;an,en;onCrB;edP;so;c,jaEksandDssaExB;!and3;er;ar,er;ndB;ro;rtH;ni;en;ad,eB;d,t;in;aColfBri0vik;!o;mBn;!a;dFeEraCuB;!bakr,lfazl;hBm;am;!l;allEel,oulaye,ulB;!lCrahm0;an;ah,o;ah;av,on",
    "Person": "true¦ashton kutchSbRcMdKeIgastNhGinez,jEkDleCmBnettJoAp8r4s3t2v0;a0irgin maG;lentino rossi,n go3;heresa may,iger woods,yra banks;addam hussain,carlett johanssJlobodan milosevic,uB;ay romano,eese witherspoIo1ush limbau0;gh;d stewart,nald0;inho,o;a0ipJ;lmIris hiltD;prah winfrFra;essiaen,itt romnEubarek;bron james,e;anye west,iefer sutherland,obe bryant;aime,effers8k rowli0;ng;alle ber0itlBulk hogan;ry;ff0meril lagasse,zekiel;ie;a0enzel washingt2ick wolf;lt1nte;ar1lint0ruz;on;dinal wols1son0;! palm2;ey;arack obama,rock;er",
    "Verb": "true¦awak9born,cannot,fr8g7h5k3le2m1s0wors9;e8h3;ake sure,sg;ngth6ss6;eep tabs,n0;own;as0e2;!t2;iv1onna;ight0;en",
    "PhrasalVerb": "true¦0:72;1:6Q;2:7E;3:74;4:6J;5:7H;6:76;7:6P;8:6C;9:6D;A:5I;B:71;C:70;a7Hb63c5Dd5Ae58f46g3Oh38iron0j34k2Zl2Km2Bn29o27p1Pr1Es09tQuOvacuum 1wGyammerCzD;eroAip EonD;e0k0;by,up;aJeGhFiEorDrit53;d 1k2R;mp0n4Ape0r8s8;eel Bip 7L;aEiD;gh 06rd0;n Br 3D;it 5Kk8lk6rm 0Qsh 74t67v4P;rgeCsD;e 9herA;aRePhNiJoHrFuDype 0N;ckArn D;d2in,o3Gup;ade YiDot0y 28;ckle68p 7A;ne67p Ds4D;d2o6Lup;ck FdEe Dgh5Tme0p o0Dre0;aw3ba4d2in,up;e5Ky 1;by,o6V;ink Drow 5V;ba4ov7up;aDe 4Ill4O;m 1r W;ckCke Elk D;ov7u4O;aDba4d2in,o31up;ba4ft7p4Tw3;a0Gc0Fe09h05i02lYmXnWoVpSquare RtJuHwD;earFiD;ngEtch D;aw3ba4o6P; by;ck Dit 1m 1ss0;in,up;aIe0RiHoFrD;aigh1MiD;ke 5Yn2Y;p Drm1P;by,in,o6B;n2Zr 1tc3I;c2Ymp0nd Dr6Hve6y 1;ba4d2up;d2o67up;ar2Vell0ill4UlErDurC;ingCuc8;a33it 3U;be4Crt0;ap 4Eow B;ash 4Zoke0;eep EiDow 9;c3Np 1;in,oD;ff,v7;gn Eng2Zt Dz8;d2o5up;in,o5up;aFoDu4F;ot Dut0w 5X;aw3ba4f37o5R;c2FdeAk4Sve6;e Hll0nd GtD; Dtl43;d2in,o5upD;!on;aw3ba4d2in,o1Yup;o5to;al4Lout0rap4L;il6v8;at0eKiJoGuD;b 4Ele0n Dstl8;aDba4d2in53o3Gt30u3E;c1Xw3;ot EuD;g2Knd6;a1Xf2Ro5;ng 4Op6;aDel6inAnt0;c4Yd D;o2Tu0C;aQePiOlMoKrHsyc2AuD;ll Ft D;aDba4d2in,o1Ht34up;p39w3;ap38d2in,o5t32up;attleCess EiGoD;p 1;ah1Hon;iDp 53re3Mur45wer 53;nt0;ay3ZuD;gAmp 9;ck 53g0leCn 9p3W;el 47ncilA;c3Pir 2In0ss FtEy D;ba4o4R; d2c1Y;aw3ba4o12;pDw3K;e3Jt B;arrow3Terd0oD;d6te3S;aJeHiGoEuD;ddl8ll37;c17p 1uth6ve D;al3Bd2in,o5up;ss0x 1;asur8lt 9ss D;a1Aup;ke Dn 9r30s1Lx0;do,o3Yup;aPeNiIoDuck0;a17c37g GoDse0;k Dse35;aft7ba4d2forw2Bin3Wov7uD;nd7p;in,o0J;e GghtFnEsDv1T;ten 4D;e 1k 1; 1e2Y;ar43d2;av1Ht 2YvelD; o3L;p 1sh DtchCugh6y1U;in3Lo5;eEick6nock D;d2o3H;eDyA;l2Hp D;aw3ba4d2fSin,o05to,up;aFoEuD;ic8mpA;ke2St2W;c31zz 1;aPeKiHoEuD;nker2Ts0U;lDneArse2O;d De 1;ba4d2fast,oZup;de Et D;ba4on,up;aw3o5;aDlp0;d Fl22r Dt 1;fDof;rom;in,oRu1A;cZm 1nDve it,ze1Y;d Dg 27kerF;d2in,o5;aReLive Jloss1VoFrEunD; f0M;in39ow 23; Dof 0U;aEb17it,oDr35t0Ou12;ff,n,v7;bo5ft7hJw3;aw3ba4d2in,oDup,w3;ff,n,ut;a17ek0t D;aEb11d2oDr2Zup;ff,n,ut,v7;cEhDl1Pr2Xt,w3;ead;ross;d aEnD;g 1;bo5;a08e01iRlNoJrFuD;cDel 1;k 1;eEighten DownCy 1;aw3o2L;eDshe1G; 1z8;lFol D;aDwi19;bo5r2I;d 9;aEeDip0;sh0;g 9ke0mDrD;e 2K;gLlJnHrFsEzzD;le0;h 2H;e Dm 1;aw3ba4up;d0isD;h 1;e Dl 11;aw3fI;ht ba4ure0;eInEsD;s 1;cFd D;fDo1X;or;e B;dQl 1;cHll Drm0t0O;apYbFd2in,oEtD;hrough;ff,ut,v7;a4ehi1S;e E;at0dge0nd Dy8;o1Mup;o09rD;ess 9op D;aw3bNin,o15;aShPlean 9oDross But 0T;me FoEuntD; o1M;k 1l6;aJbIforGin,oFtEuD;nd7;ogeth7;ut,v7;th,wD;ard;a4y;pDr19w3;art;eDipA;ck BeD;r 1;lJncel0rGsFtch EveA; in;o16up;h Bt6;ry EvD;e V;aw3o12;l Dm02;aDba4d2o10up;r0Vw3;a0He08l01oSrHuD;bbleFcklTilZlEndlTrn 05tDy 10zz6;t B;k 9; ov7;anMeaKiDush6;ghHng D;aEba4d2forDin,o5up;th;bo5lDr0Lw3;ong;teD;n 1;k D;d2in,o5up;ch0;arKgJil 9n8oGssFttlEunce Dx B;aw3ba4;e 9; ar0B;k Bt 1;e 1;d2up; d2;d 1;aIeed0oDurt0;cFw D;aw3ba4d2o5up;ck;k D;in,oK;ck0nk0st6; oJaGef 1nd D;d2ov7up;er;up;r0t D;d2in,oDup;ff,ut;ff,nD;to;ck Jil0nFrgEsD;h B;ainCe B;g BkC; on;in,o5; o5;aw3d2o5up;ay;cMdIsk Fuction6; oD;ff;arDo5;ouD;nd;d D;d2oDup;ff,n;own;t D;o5up;ut",
    "Modal": "true¦c5lets,m4ought3sh1w0;ill,o5;a0o4;ll,nt;! to;ay,ight,ust;an,o0;uld",
    "Adjective": "true¦0:73;1:7I;2:7O;3:7H;4:7A;5:5B;6:4R;7:49;8:48;9:7F;A:60;a6Eb60c5Md52e4Pf45g3Xh3Mi31j2Zk2Yl2Nm2Cn23o1Np16quack,r0Ws0Ct05uMvJwByear5;arp0eFholeEiDoB;man5oBu67;d69zy;despr6Zs5B;!sa7;eClBste22;co1El o4H;!k5;aCiBola47;b7Nce versa,ol50;ca2gabo5Ynilla;ltSnFpCrb55su4tterB;!mo6U; f30b1KpCsBti1D;ca7et,ide dItairs;er,i3J;aLbeco6Lconvin23deIeHfair,ivers4knGprecedUrEsCwB;iel1Writt5U;i1RuB;pervis0specti3;eBu5;cognHgul6Bl6B;own;ndi3v5Oxpect0;cid0rB;!grou5JsB;iz0tood;b7ppeaHssu6AuthorB;iz0;i20ra;aFeDhough4KoCrB;i1oubl0;geth6p,rp6B;en5LlBm4Vrr2Q;li3;boo,lBn;ent0;aTcSeQhPiNmug,nobbi3AoLpKqueami3AtFuBymb5Y;bDi gener50pBrprisi3;erBre0H;! dup6b,i25;du0seq4P;anda6OeEi0LrBy34;aightBip0; fBfB;or56;adfa5Wreotyp0;a4Uec2Cir1Flend5Wot on; call0le,mb6phist1TrBu0Tvi3X;d5Ury;gnifica2nB;ce4Qg7;am2Le6ocki3ut;cBda1em5lfi2Uni1Spa63re8;o1Cr3R;at53ient24reec53;cr0me,ns serif;aIeEiCoB;bu5Ktt4PuOy4;ghtBv4;!-25fA;ar,bel,condi1du5Xfres4XlDpublic3RsBtard0;is43oB;lu1na2;e1Auc41;b5EciB;al,st;aMeKicayu8lac5Copuli5BrCuB;bl54mp0;eFiCoB;!b06fu5Cmi2Xp6;mCor,sBva1;ti8;a4Re;ci58mB;a0EiB;er,um;ac1WrBti1;fe9ma2Pplexi3v2Z;rBst;allelDtB;-tiBi4;me;!ed;bMffKkJld fashion0nIpHrg1Dth6utGvB;al,erB;!aDniCt,wB;eiBrouB;ght;ll;do0Rer,g2Hsi41;en,posi1; boa5Ag2Fli8;!ay; gua58bBli8;eat;eDsB;cBer0Dole1;e8u3F;d2Ose;ak0eIiHoBua4J;nFrCtB;ab7;thB;!eB;rn;chala2descri4Ustop;ght5;arby,cessa3Sighbor5xt;aJeHiEoBultip7;bi7derClBnth5ot,st;dy;a1n;nBx0;iaBor;tu2Y;di49naBre;ci3;cBgenta,in,jZkeshift,le,mmoth,ny,sculi8;ab2Uho;aKeFiCoBu0Z;uti0Yvi3;mCteraB;l,te;it0;ftEgBth4;al,eCitiB;ma1;nda38;!-08;ngu3Lst,tt6;ap1Oind5no06;agg0uB;niKstifi0veni7;de4gno46lleg4mOnDpso 1RrB;a1releB;va2; JaIbr0corHdFfluenPiPnEsDtB;a9en3GoxB;ic31;a8i2N;a1er,oce2;iCoB;or;re9;deq3Eppr2T;fBsitu,vitro;ro2;mFpB;arDerfe9oBrop6;li1rtB;a2ed;ti4;eBi0M;d2Ln30;aGelFiDoBumdr36;ne2Uok0rrBs03ur5;if2N;ghfalut1KspB;an2L;liVpfA;lEnDrB;d01roB;wi3;dy,gi3;f,low0;ainfAener2Eiga1YlHoGraDuB;ilBng ho;ty;cCtB;efAis;efA;ne,od;ea28ob4;aQeKinJlIoDrB;a1PeBoz1G;e28q0YtfA;oDrB; keeps,eBm6tuna1;g00ign;liB;sh;ag2Uue2;al,i1;dFmCrB;ti7;a7ini8;ne;le; up;bl0i2l20r Cux,voB;ri1uri1;oBreac1A;ff;aJfficie2lImiHnFre9there4veExB;a9cess,pe1JtraCuB;be2Gl0D;!va19;n,ryday; Bcouragi3ti0M;rou1sui1;ne2;abo1YdMe14i1;g6sB;t,ygB;oi3;er;aReJiDoBrea11ue;mina2ne,ubB;le,tfA;dact16fficu1JsCvB;er1F;creDeas0gruntl0hone1AordCtB;a2ress0;er5;et; HadpGfFgene1KliDrang0spe1KtCvoB;ut;ail0ermin0;be1Hca1ghB;tfA;ia2;an;facto;i5magBngeroVs0E;ed,i3;ly;ertaNhief,ivil,oDrB;aBowd0u0D;mp0vYz0;loJmHnCoi3rrBve0K;e9u1D;cre1grEsDtB;emBra0B;po09;ta2;ue2;mer04pleB;te,x;ni4ss4;in;aLeHizarGlFoCrB;and new,isk,okL;gCna fiSttom,urgeoB;is;us;ank,iE;re;autifAhiClov0nBst,yoC;eRt;nd;ul;ckCnkru0SrrB;en;!wards; priori,b0Ic0Fd05fra04g00hZlUma01ntiquTppQrKsIttracti02utheHvEwB;aCkB;wa0P;ke,re;ant garCerB;age;de;ntQ;leep,tonisB;hi3;ab,bitEroDtiB;fiB;ci4;ga2;raB;ry;are2etiLrB;oprB;ia1;at0;arEcohCeBiIl,oof;rt;olB;ic;mi3;ead;ainDgressiConiB;zi3;ve;st;id; IeGuFvB;aCerB;se;nc0;ed;lt;pt,qB;ua1;hoc,infinitB;um;cuCtu4u1;al;ra1;erLlKoIruHsCuB;nda2;e2oCtra9;ct;lu1rbi3;ng;te;pt;aBve;rd;aze,e;ra2;nt",
    "Comparable": "true¦0:3Z;1:4G;2:43;3:49;4:3V;5:2W;a4Mb42c3Md3Be33f2Pg2Dh22i1Tj1Sk1Pl1Hm1Bn15o13p0Tqu0Rr0IsRtKuIvFw7y6za11;ell25ou3;aBe9hi1Wi7r6;o3y;ck0Mde,l6n1ry,se;d,y;a6i4Kt;k,ry;n1Rr6sI;m,y;a7e6ulgar;nge4rda2xi3;gue,in,st;g0n6pco3Kse4;like0ti1;aAen9hi8i7ough,r6;anqu2Oen1ue;dy,g3Sme0ny,r09;ck,n,rs2P;d40se;ll,me,rt,s6wd45;te4;aVcarUeThRiQkin0FlMmKoHpGqua1FtAu7w6;eet,ift;b7dd13per0Gr6;e,re2H;sta2Ft5;aAe9iff,r7u6;pXr1;a6ict,o3;ig3Fn0U;a1ep,rn;le,rk;e22i3Fright0;ci28ft,l7o6re,ur;n,thi3;emn,id;a6el0ooth;ll,rt;e8i6ow,y;ck,g35m6;!y;ek,nd3D;ck,l0mp5;a6iTort,rill,y;dy,ll0Xrp;cu0Rve0Rxy;ce,ed,y;d,fe,int0l1Vv14;aBe9i8o6ude;mantic,o1Isy,u6;gh,nd;ch,pe,tzy;a6d,mo0H;dy,l;gg7ndom,p6re,w;id;ed;ai2i6;ck,et;aEhoDi1QlCoBr8u6;ny,r6;e,p5;egna2ic7o6;fouYud;ey,k0;li04or,te1B;ain,easa2;ny;in4le;dd,f6i0ld,ranQ;fi10;aAe8i7o6;b5isy,rm15sy;ce,mb5;a6w;r,t;ive,rr01;aAe8ild,o7u6;nda19te;ist,o1;a6ek,llX;n,s0ty;d,tuQ;aBeAi9o6ucky;f0Un7o1Du6ve0w17y0T;d,sy;e0g;g1Tke0tt5ve0;an,wd;me,r6te;ge;e7i6;nd;en;ol0ui1P;cy,ll,n6;sBt6;e6ima8;llege2r6;es7media6;te;ti3;ecu6ta2;re;aEeBiAo8u6;ge,m6ng1R;b5id;ll6me0t;ow;gh,l0;a6f04sita2;dy,v6;en0y;nd1Hppy,r6te4;d,sh;aGenFhDiClBoofy,r6;a9e8is0o6ue1E;o6ss;vy;at,en,y;nd,y;ad,ib,ooI;a2d1;a6o6;st0;t5uiY;u1y;aIeeb5iDlat,oAr8u6;ll,n6r14;!ny;aHe6iend0;e,sh;a7r6ul;get4mG;my;erce8n6rm,t;an6e;ciC;! ;le;ir,ke,n0Fr,st,t,ulA;aAerie,mp9sse7v6xtre0Q;il;nti6;al;ty;r7s6;tern,y;ly,th0;aFeCi9r7u6;ll,mb;u6y;nk;r7vi6;ne;e,ty;a6ep,nD;d6f,r;!ly;mp,pp03rk;aHhDlAo8r7u6;dd0r0te;isp,uel;ar6ld,mmon,ol,st0ward0zy;se;e6ou1;a6vW;n,r;ar8e6il0;ap,e6;sy;mi3;gey,lm8r6;e4i3;ful;!i3;aNiLlIoEr8u6;r0sy;ly;aAi7o6;ad,wn;ef,g7llia2;nt;ht;sh,ve;ld,r7un6;cy;ed,i3;ng;a7o6ue;nd,o1;ck,nd;g,tt6;er;d,ld,w1;dy;bsu9ng8we6;so6;me;ry;rd",
    "TextValue": "true¦bOeJfDhundredNmOninAone,qu8s6t0zeroN;enMh3rNw0;e0o;l0ntD;fHve;ir0ousandKree;d,t6;e0ix8;cond,pt1ven7xt1;adr0int0;illionD;e0th;!t0;e9ie8y;i3o0;rt1ur0;!t2;ie4y;ft0rst,ve;e3h,ie2y;ight0lev2;!e1h,ie0y;th;en0;!th;illion0;!s,th",
    "Ordinal": "true¦bGeDf9hundredHmGnin7qu6s4t0zeroH;enGh1rFwe0;lfFn9;ir0ousandE;d,t4;e0ixt9;cond,ptAvent8xtA;adr9int9;et0th;e6ie8;i2o0;r0urt3;tie5;ft1rst;ight0lev1;e0h,ie2;en1;illion0;th",
    "Cardinal": "true¦bHeEf8hundred,mHnineAone,qu6s4t0zero;en,h2rGw0;e0o;lve,n8;irt9ousandEree;e0ix5;pt1ven4xt1;adr0int0;illion;i3o0;r1ur0;!t2;ty;ft0ve;e2y;ight0lev1;!e0y;en;illion0;!s",
    "Expression": "true¦a02b01dXeVfuck,gShLlImHnGoDpBshAtsk,u7voi04w3y0;a1eLu0;ck,p;!a,hoo,y;h1ow,t0;af,f;e0oa;e,w;gh,h0;! 0h,m;huh,oh;eesh,hh,it;ff,hew,l0sst;ease,z;h1o0w,y;h,o,ps;!h;ah,ope;eh,mm;m1ol0;!s;ao,fao;a4e2i,mm,oly1urr0;ah;! mo6;e,ll0y;!o;ha0i;!ha;ah,ee,o0rr;l0odbye;ly;e0h,t cetera,ww;k,p;'oh,a0uh;m0ng;mit,n0;!it;ah,oo,ye; 1h0rgh;!em;la",
    "Adverb": "true¦a07by 05d01eYfShQinPjustOkinda,mMnJoEpCquite,r9s5t2up1very,w0Bye0;p,s; to,wards5;h1o0wiO;o,t6ward;en,us;everal,o0uch;!me1rt0; of;hXtimes,w07;a1e0;alS;ndomRthN;ar excellDer0oint blank; Mhaps;f3n0;ce0ly;! 0;ag00moU; courHten;ewJo0; longEt 0;onHwithstanding;aybe,eanwhiAore0;!ovB;! aboS;deed,steT;en0;ce;or2u0;l9rther0;!moH; 0ev3;examp0good,suF;le;n mas1v0;er;se;e0irect1; 1finite0;ly;ju7trop;far,n0;ow; CbroBd nauseam,gAl5ny2part,side,t 0w3;be5l0mo5wor5;arge,ea4;mo1w0;ay;re;l 1mo0one,ready,so,ways;st;b1t0;hat;ut;ain;ad;lot,posteriori",
    "Preposition": "true¦'o,-,aKbHcGdFexcept,fEinDmidPnotwithstandiQoBpRqua,sAt6u3vi2w0;/o,hereMith0;!in,oQ;a,s-a-vis;n1p0;!on;like,til;h0ill,owards;an,r0;ough0u;!oI;ans,ince,o that;',f0n1ut;!f;!to;or,rom;espite,own,u3;hez,irca;ar1e0oAy;low,sides,tween;ri6;',bo7cross,ft6lo5m3propos,round,s1t0;!op;! long 0;as;id0ong0;!st;ng;er;ut",
    "Determiner": "true¦aAboth,d8e5few,l3mu7neiCown,plenty,some,th2various,wh0;at0ich0;evB;at,e3is,ose;a,e0;!ast,s;a1i6l0nough,very;!se;ch;e0u;!s;!n0;!o0y;th0;er"
  };

  var entity = ['Person', 'Place', 'Organization'];
  var nouns = {
    Noun: {
      notA: ['Verb', 'Adjective', 'Adverb']
    },
    // - singular
    Singular: {
      isA: 'Noun',
      notA: 'Plural'
    },
    //a specific thing that's capitalized
    ProperNoun: {
      isA: 'Noun'
    },
    // -- people
    Person: {
      isA: ['ProperNoun', 'Singular'],
      notA: ['Place', 'Organization', 'Date']
    },
    FirstName: {
      isA: 'Person'
    },
    MaleName: {
      isA: 'FirstName',
      notA: ['FemaleName', 'LastName']
    },
    FemaleName: {
      isA: 'FirstName',
      notA: ['MaleName', 'LastName']
    },
    LastName: {
      isA: 'Person',
      notA: ['FirstName']
    },
    NickName: {
      isA: 'Person',
      notA: ['FirstName', 'LastName']
    },
    Honorific: {
      isA: 'Noun',
      notA: ['FirstName', 'LastName', 'Value']
    },
    // -- places
    Place: {
      isA: 'Singular',
      notA: ['Person', 'Organization']
    },
    Country: {
      isA: ['Place', 'ProperNoun'],
      notA: ['City']
    },
    City: {
      isA: ['Place', 'ProperNoun'],
      notA: ['Country']
    },
    Region: {
      isA: ['Place', 'ProperNoun']
    },
    Address: {
      isA: 'Place'
    },
    //---Orgs---
    Organization: {
      isA: ['Singular', 'ProperNoun'],
      notA: ['Person', 'Place']
    },
    SportsTeam: {
      isA: 'Organization'
    },
    School: {
      isA: 'Organization'
    },
    Company: {
      isA: 'Organization'
    },
    // - plural
    Plural: {
      isA: 'Noun',
      notA: ['Singular']
    },
    //(not plural or singular)
    Uncountable: {
      isA: 'Noun'
    },
    Pronoun: {
      isA: 'Noun',
      notA: entity
    },
    //a word for someone doing something -'plumber'
    Actor: {
      isA: 'Noun',
      notA: entity
    },
    //a gerund-as-noun - 'swimming'
    Activity: {
      isA: 'Noun',
      notA: ['Person', 'Place']
    },
    //'kilograms'
    Unit: {
      isA: 'Noun',
      notA: entity
    },
    //'Canadians'
    Demonym: {
      isA: ['Noun', 'ProperNoun'],
      notA: entity
    },
    //`john's`
    Possessive: {
      isA: 'Noun' // notA: 'Pronoun',

    }
  };

  var verbs = {
    Verb: {
      notA: ['Noun', 'Adjective', 'Adverb', 'Value']
    },
    // walks
    PresentTense: {
      isA: 'Verb',
      notA: ['PastTense', 'Copula', 'FutureTense']
    },
    // neutral form - 'walk'
    Infinitive: {
      isA: 'PresentTense',
      notA: ['PastTense', 'Gerund']
    },
    // walking
    Gerund: {
      isA: 'PresentTense',
      notA: ['PastTense', 'Copula', 'FutureTense']
    },
    // walked
    PastTense: {
      isA: 'Verb',
      notA: ['FutureTense']
    },
    // will walk
    FutureTense: {
      isA: 'Verb'
    },
    // is
    Copula: {
      isA: 'Verb'
    },
    // would have
    Modal: {
      isA: 'Verb',
      notA: ['Infinitive']
    },
    // had walked
    PerfectTense: {
      isA: 'Verb',
      notA: 'Gerund'
    },
    Pluperfect: {
      isA: 'Verb'
    },
    // shown
    Participle: {
      isA: 'Verb'
    },
    // show up
    PhrasalVerb: {
      isA: 'Verb'
    },
    //'up' part
    Particle: {
      isA: 'PhrasalVerb'
    }
  };

  var values = {
    Value: {
      notA: ['Verb', 'Adjective', 'Adverb']
    },
    Ordinal: {
      isA: 'Value',
      notA: ['Cardinal']
    },
    Cardinal: {
      isA: 'Value',
      notA: ['Ordinal']
    },
    RomanNumeral: {
      isA: 'Cardinal',
      //can be a person, too
      notA: ['Ordinal', 'TextValue']
    },
    TextValue: {
      isA: 'Value',
      notA: ['NumericValue']
    },
    NumericValue: {
      isA: 'Value',
      notA: ['TextValue']
    },
    Money: {
      isA: 'Cardinal'
    },
    Percent: {
      isA: 'Value'
    }
  };

  var anything = ['Noun', 'Verb', 'Adjective', 'Adverb', 'Value', 'QuestionWord'];
  var misc = {
    //--Adjectives--
    Adjective: {
      notA: ['Noun', 'Verb', 'Adverb', 'Value']
    },
    // adjectives that can conjugate
    Comparable: {
      isA: ['Adjective']
    },
    // better
    Comparative: {
      isA: ['Adjective']
    },
    // best
    Superlative: {
      isA: ['Adjective'],
      notA: ['Comparative']
    },
    NumberRange: {
      isA: ['Contraction']
    },
    Adverb: {
      notA: ['Noun', 'Verb', 'Adjective', 'Value']
    },
    // Dates:
    //not a noun, but usually is
    Date: {
      notA: ['Verb', 'Conjunction', 'Adverb', 'Preposition', 'Adjective']
    },
    Month: {
      isA: ['Date', 'Singular'],
      notA: ['Year', 'WeekDay', 'Time']
    },
    WeekDay: {
      isA: ['Date', 'Noun']
    },
    // '9:20pm'
    Time: {
      isA: ['Date'],
      notA: ['Value']
    },
    //glue
    Determiner: {
      notA: anything
    },
    Conjunction: {
      notA: anything
    },
    Preposition: {
      notA: anything
    },
    // what, who, why
    QuestionWord: {
      notA: ['Determiner']
    },
    // peso, euro
    Currency: {},
    // ughh
    Expression: {
      notA: ['Noun', 'Adjective', 'Verb', 'Adverb']
    },
    // dr.
    Abbreviation: {},
    // internet tags
    Url: {
      notA: ['HashTag', 'PhoneNumber', 'Verb', 'Adjective', 'Value', 'AtMention', 'Email']
    },
    PhoneNumber: {
      notA: ['HashTag', 'Verb', 'Adjective', 'Value', 'AtMention', 'Email']
    },
    HashTag: {},
    AtMention: {
      isA: ['Noun'],
      notA: ['HashTag', 'Verb', 'Adjective', 'Value', 'Email']
    },
    Emoji: {
      notA: ['HashTag', 'Verb', 'Adjective', 'Value', 'AtMention']
    },
    Emoticon: {
      notA: ['HashTag', 'Verb', 'Adjective', 'Value', 'AtMention']
    },
    Email: {
      notA: ['HashTag', 'Verb', 'Adjective', 'Value', 'AtMention']
    },
    //non-exclusive
    Auxiliary: {
      notA: ['Noun', 'Adjective', 'Value']
    },
    Acronym: {
      notA: ['Plural', 'RomanNumeral']
    },
    Negative: {
      notA: ['Noun', 'Adjective', 'Value']
    },
    // if, unless, were
    Condition: {
      notA: ['Verb', 'Adjective', 'Noun', 'Value']
    }
  };

  // i just made these up
  var colorMap = {
    Noun: 'blue',
    Verb: 'green',
    Negative: 'green',
    Date: 'red',
    Value: 'red',
    Adjective: 'magenta',
    Preposition: 'cyan',
    Conjunction: 'cyan',
    Determiner: 'cyan',
    Adverb: 'cyan'
  };
  /** add a debug color to some tags */

  var addColors = function addColors(tags) {
    Object.keys(tags).forEach(function (k) {
      // assigned from plugin, for example
      if (tags[k].color) {
        tags[k].color = tags[k].color;
        return;
      } // defined above


      if (colorMap[k]) {
        tags[k].color = colorMap[k];
        return;
      }

      tags[k].isA.some(function (t) {
        if (colorMap[t]) {
          tags[k].color = colorMap[t];
          return true;
        }

        return false;
      });
    });
    return tags;
  };

  var _color = addColors;

  var unique$2 = function unique(arr) {
    return arr.filter(function (v, i, a) {
      return a.indexOf(v) === i;
    });
  }; //add 'downward' tags (that immediately depend on this one)


  var inferIsA = function inferIsA(tags) {
    Object.keys(tags).forEach(function (k) {
      var tag = tags[k];
      var len = tag.isA.length;

      for (var i = 0; i < len; i++) {
        var down = tag.isA[i];

        if (tags[down]) {
          tag.isA = tag.isA.concat(tags[down].isA);
        }
      } // clean it up


      tag.isA = unique$2(tag.isA);
    });
    return tags;
  };

  var _isA = inferIsA;

  var unique$3 = function unique(arr) {
    return arr.filter(function (v, i, a) {
      return a.indexOf(v) === i;
    });
  }; // crawl the tag-graph and infer any conflicts
  // faster than doing this at tag-time


  var inferNotA = function inferNotA(tags) {
    var keys = Object.keys(tags);
    keys.forEach(function (k) {
      var tag = tags[k];
      tag.notA = tag.notA || [];
      tag.isA.forEach(function (down) {
        if (tags[down] && tags[down].notA) {
          // borrow its conflicts
          var notA = typeof tags[down].notA === 'string' ? [tags[down].isA] : tags[down].notA || [];
          tag.notA = tag.notA.concat(notA);
        }
      }); // any tag that lists us as a conflict, we conflict it back.

      for (var i = 0; i < keys.length; i++) {
        var key = keys[i];

        if (tags[key].notA.indexOf(k) !== -1) {
          tag.notA.push(key);
        }
      } // clean it up


      tag.notA = unique$3(tag.notA);
    });
    return tags;
  };

  var _notA = inferNotA;

  // a lineage is all 'incoming' tags that have this as 'isA'
  var inferLineage = function inferLineage(tags) {
    var keys = Object.keys(tags);
    keys.forEach(function (k) {
      var tag = tags[k];
      tag.lineage = []; // find all tags with it in their 'isA' set

      for (var i = 0; i < keys.length; i++) {
        if (tags[keys[i]].isA.indexOf(k) !== -1) {
          tag.lineage.push(keys[i]);
        }
      }
    });
    return tags;
  };

  var _lineage = inferLineage;

  var validate = function validate(tags) {
    // cleanup format
    Object.keys(tags).forEach(function (k) {
      var tag = tags[k]; // ensure isA is an array

      tag.isA = tag.isA || [];

      if (typeof tag.isA === 'string') {
        tag.isA = [tag.isA];
      } // ensure notA is an array


      tag.notA = tag.notA || [];

      if (typeof tag.notA === 'string') {
        tag.notA = [tag.notA];
      }
    });
    return tags;
  }; // build-out the tag-graph structure


  var inferTags = function inferTags(tags) {
    // validate data
    tags = validate(tags); // build its 'down tags'

    tags = _isA(tags); // infer the conflicts

    tags = _notA(tags); // debug tag color

    tags = _color(tags); // find incoming links

    tags = _lineage(tags);
    return tags;
  };

  var inference = inferTags;

  var addIn = function addIn(obj, tags) {
    Object.keys(obj).forEach(function (k) {
      tags[k] = obj[k];
    });
  };

  var build = function build() {
    var tags = {};
    addIn(nouns, tags);
    addIn(verbs, tags);
    addIn(values, tags);
    addIn(misc, tags); // do the graph-stuff

    tags = inference(tags);
    return tags;
  };

  var tags = build();

  var seq = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ",
      cache = seq.split("").reduce(function (n, o, e) {
    return n[o] = e, n;
  }, {}),
      toAlphaCode = function toAlphaCode(n) {
    if (void 0 !== seq[n]) return seq[n];
    var o = 1,
        e = 36,
        t = "";

    for (; n >= e; n -= e, o++, e *= 36) {
    }

    for (; o--;) {
      var _o = n % 36;

      t = String.fromCharCode((_o < 10 ? 48 : 55) + _o) + t, n = (n - _o) / 36;
    }

    return t;
  },
      fromAlphaCode = function fromAlphaCode(n) {
    if (void 0 !== cache[n]) return cache[n];
    var o = 0,
        e = 1,
        t = 36,
        r = 1;

    for (; e < n.length; o += t, e++, t *= 36) {
    }

    for (var _e = n.length - 1; _e >= 0; _e--, r *= 36) {
      var _t = n.charCodeAt(_e) - 48;

      _t > 10 && (_t -= 7), o += _t * r;
    }

    return o;
  };

  var encoding = {
    toAlphaCode: toAlphaCode,
    fromAlphaCode: fromAlphaCode
  },
      symbols = function symbols(n) {
    var o = new RegExp("([0-9A-Z]+):([0-9A-Z]+)");

    for (var e = 0; e < n.nodes.length; e++) {
      var t = o.exec(n.nodes[e]);

      if (!t) {
        n.symCount = e;
        break;
      }

      n.syms[encoding.fromAlphaCode(t[1])] = encoding.fromAlphaCode(t[2]);
    }

    n.nodes = n.nodes.slice(n.symCount, n.nodes.length);
  };

  var indexFromRef = function indexFromRef(n, o, e) {
    var t = encoding.fromAlphaCode(o);
    return t < n.symCount ? n.syms[t] : e + t + 1 - n.symCount;
  },
      toArray = function toArray(n) {
    var o = [],
        e = function e(t, r) {
      var s = n.nodes[t];
      "!" === s[0] && (o.push(r), s = s.slice(1));
      var c = s.split(/([A-Z0-9,]+)/g);

      for (var _s = 0; _s < c.length; _s += 2) {
        var u = c[_s],
            i = c[_s + 1];
        if (!u) continue;
        var l = r + u;

        if ("," === i || void 0 === i) {
          o.push(l);
          continue;
        }

        var f = indexFromRef(n, i, t);
        e(f, l);
      }
    };

    return e(0, ""), o;
  },
      unpack = function unpack(n) {
    var o = {
      nodes: n.split(";"),
      syms: [],
      symCount: 0
    };
    return n.match(":") && symbols(o), toArray(o);
  };

  var unpack_1 = unpack,
      unpack_1$1 = function unpack_1$1(n) {
    var o = n.split("|").reduce(function (n, o) {
      var e = o.split("¦");
      return n[e[0]] = e[1], n;
    }, {}),
        e = {};
    return Object.keys(o).forEach(function (n) {
      var t = unpack_1(o[n]);
      "true" === n && (n = !0);

      for (var _o2 = 0; _o2 < t.length; _o2++) {
        var r = t[_o2];
        !0 === e.hasOwnProperty(r) ? !1 === Array.isArray(e[r]) ? e[r] = [e[r], n] : e[r].push(n) : e[r] = n;
      }
    }), e;
  };

  var efrtUnpack_min = unpack_1$1;

  //safely add it to the lexicon
  var addWord = function addWord(word, tag, lex) {
    if (lex[word] !== undefined && 
        typeof lex[word] != 'function') {     // @blab+ override prototype of object!!!
      if (typeof lex[word] === 'string') {
        lex[word] = [lex[word]];
      }

      if (typeof tag === 'string') {
        lex[word].push(tag);
      } else {
        lex[word] = lex[word].concat(tag);
      }
    } else {
      lex[word] = tag;
    }
  }; // blast-out more forms for some given words


  var addMore = function addMore(word, tag, world) {
    var lexicon = world.words;
    var transform = world.transforms; // cache multi-words

    var words = word.split(' ');

    if (words.length > 1) {
      //cache the beginning word
      world.hasCompound[words[0]] = true;
    } // inflect our nouns


    if (tag === 'Singular') {
      var plural = transform.toPlural(word, world);
      lexicon[plural] = lexicon[plural] || 'Plural'; // only if it's safe
    } //conjugate our verbs


    if (tag === 'Infinitive') {
      var conj = transform.conjugate(word, world);
      var tags = Object.keys(conj);

      for (var i = 0; i < tags.length; i++) {
        var w = conj[tags[i]];
        lexicon[w] = lexicon[w] || tags[i]; // only if it's safe
      }
    } //derive more adjective forms


    if (tag === 'Comparable') {
      var _conj = transform.adjectives(word);

      var _tags = Object.keys(_conj);

      for (var _i = 0; _i < _tags.length; _i++) {
        var _w = _conj[_tags[_i]];
        lexicon[_w] = lexicon[_w] || _tags[_i]; // only if it's safe
      }
    } //conjugate phrasal-verbs


    if (tag === 'PhrasalVerb') {
      //add original form
      addWord(word, 'Infinitive', lexicon); //conjugate first word

      var _conj2 = transform.conjugate(words[0], world);

      var _tags2 = Object.keys(_conj2);

      for (var _i2 = 0; _i2 < _tags2.length; _i2++) {
        //add it to our cache
        world.hasCompound[_conj2[_tags2[_i2]]] = true; //first + last words

        var _w2 = _conj2[_tags2[_i2]] + ' ' + words[1];

        addWord(_w2, _tags2[_i2], lexicon);
        addWord(_w2, 'PhrasalVerb', lexicon);
      }
    } // inflect our demonyms - 'germans'


    if (tag === 'Demonym') {
      var _plural = transform.toPlural(word, world);

      lexicon[_plural] = lexicon[_plural] || ['Demonym', 'Plural']; // only if it's safe
    }
  }; // throw a bunch of words in our lexicon
  // const doWord = function(words, tag, world) {
  //   let lexicon = world.words
  //   for (let i = 0; i < words.length; i++) {
  //     addWord(words[i], tag, lexicon)
  //     // do some fancier stuff
  //     addMore(words[i], tag, world)
  //   }
  // }


  var addWords = {
    addWord: addWord,
    addMore: addMore
  };

  // add words from plurals and conjugations data
  var addIrregulars = function addIrregulars(world) {
    //add irregular plural nouns
    var nouns = world.irregulars.nouns;
    var words = Object.keys(nouns);

    for (var i = 0; i < words.length; i++) {
      var w = words[i];
      world.words[w] = 'Singular';
      world.words[nouns[w]] = 'Plural';
    } // add irregular verb conjugations


    var verbs = world.irregulars.verbs;
    var keys = Object.keys(verbs);

    var _loop = function _loop(_i) {
      var inf = keys[_i]; //add only if it it's safe...

      world.words[inf] = world.words[inf] || 'Infinitive';
      var forms = world.transforms.conjugate(inf, world);
      forms = Object.assign(forms, verbs[inf]); //add the others

      Object.keys(forms).forEach(function (tag) {
        world.words[forms[tag]] = world.words[forms[tag]] || tag;
      });
    };

    for (var _i = 0; _i < keys.length; _i++) {
      _loop(_i);
    }
  };

  var addIrregulars_1 = addIrregulars;

  //words that can't be compressed, for whatever reason
  var misc$1 = {
    // numbers
    '20th century fox': 'Organization',
    // '3m': 'Organization',
    '7 eleven': 'Organization',
    '7-eleven': 'Organization',
    g8: 'Organization',
    'motel 6': 'Organization',
    vh1: 'Organization',
    q1: 'Date',
    q2: 'Date',
    q3: 'Date',
    q4: 'Date'
  };

  //nouns with irregular plural/singular forms
  //used in noun.inflect, and also in the lexicon.
  var plurals = {
    addendum: 'addenda',
    alga: 'algae',
    alumna: 'alumnae',
    alumnus: 'alumni',
    analysis: 'analyses',
    antenna: 'antennae',
    appendix: 'appendices',
    avocado: 'avocados',
    axis: 'axes',
    bacillus: 'bacilli',
    barracks: 'barracks',
    beau: 'beaux',
    bus: 'buses',
    cactus: 'cacti',
    chateau: 'chateaux',
    child: 'children',
    circus: 'circuses',
    clothes: 'clothes',
    corpus: 'corpora',
    criterion: 'criteria',
    curriculum: 'curricula',
    database: 'databases',
    deer: 'deer',
    diagnosis: 'diagnoses',
    echo: 'echoes',
    embargo: 'embargoes',
    epoch: 'epochs',
    foot: 'feet',
    formula: 'formulae',
    fungus: 'fungi',
    genus: 'genera',
    goose: 'geese',
    halo: 'halos',
    hippopotamus: 'hippopotami',
    index: 'indices',
    larva: 'larvae',
    leaf: 'leaves',
    libretto: 'libretti',
    loaf: 'loaves',
    man: 'men',
    matrix: 'matrices',
    memorandum: 'memoranda',
    modulus: 'moduli',
    mosquito: 'mosquitoes',
    mouse: 'mice',
    move: 'moves',
    nebula: 'nebulae',
    nucleus: 'nuclei',
    octopus: 'octopi',
    opus: 'opera',
    ovum: 'ova',
    ox: 'oxen',
    parenthesis: 'parentheses',
    person: 'people',
    phenomenon: 'phenomena',
    prognosis: 'prognoses',
    quiz: 'quizzes',
    radius: 'radii',
    referendum: 'referenda',
    rodeo: 'rodeos',
    sex: 'sexes',
    shoe: 'shoes',
    sombrero: 'sombreros',
    stimulus: 'stimuli',
    stomach: 'stomachs',
    syllabus: 'syllabi',
    synopsis: 'synopses',
    tableau: 'tableaux',
    thesis: 'theses',
    thief: 'thieves',
    tooth: 'teeth',
    tornado: 'tornados',
    tuxedo: 'tuxedos',
    vertebra: 'vertebrae' // virus: 'viri',
    // zero: 'zeros',

  };

  // a list of irregular verb conjugations
  // used in verbs().conjugate()
  // but also added to our lexicon
  //use shorter key-names
  var mapping = {
    g: 'Gerund',
    prt: 'Participle',
    perf: 'PerfectTense',
    pst: 'PastTense',
    fut: 'FuturePerfect',
    pres: 'PresentTense',
    pluperf: 'Pluperfect',
    a: 'Actor'
  }; // '_' in conjugations is the infinitive form

  var conjugations = {
    act: {
      a: '_or'
    },
    ache: {
      pst: 'ached',
      g: 'aching'
    },
    age: {
      g: 'ageing',
      pst: 'aged',
      pres: 'ages'
    },
    aim: {
      a: '_er',
      g: '_ing',
      pst: '_ed'
    },
    arise: {
      prt: '_n',
      pst: 'arose'
    },
    babysit: {
      a: '_ter',
      pst: 'babysat'
    },
    ban: {
      a: '',
      g: '_ning',
      pst: '_ned'
    },
    be: {
      a: '',
      g: 'am',
      prt: 'been',
      pst: 'was',
      pres: 'is'
    },
    beat: {
      a: '_er',
      g: '_ing',
      prt: '_en'
    },
    become: {
      prt: '_'
    },
    begin: {
      g: '_ning',
      prt: 'begun',
      pst: 'began'
    },
    being: {
      g: 'are',
      pst: 'were',
      pres: 'are'
    },
    bend: {
      prt: 'bent'
    },
    bet: {
      a: '_ter',
      prt: '_'
    },
    bind: {
      pst: 'bound'
    },
    bite: {
      g: 'biting',
      prt: 'bitten',
      pst: 'bit'
    },
    bleed: {
      prt: 'bled',
      pst: 'bled'
    },
    blow: {
      prt: '_n',
      pst: 'blew'
    },
    boil: {
      a: '_er'
    },
    brake: {
      prt: 'broken'
    },
    "break": {
      pst: 'broke'
    },
    breed: {
      pst: 'bred'
    },
    bring: {
      prt: 'brought',
      pst: 'brought'
    },
    broadcast: {
      pst: '_'
    },
    budget: {
      pst: '_ed'
    },
    build: {
      prt: 'built',
      pst: 'built'
    },
    burn: {
      prt: '_ed'
    },
    burst: {
      prt: '_'
    },
    buy: {
      prt: 'bought',
      pst: 'bought'
    },
    can: {
      a: '',
      fut: '_',
      g: '',
      pst: 'could',
      perf: 'could',
      pluperf: 'could',
      pres: '_'
    },
    "catch": {
      pst: 'caught'
    },
    choose: {
      g: 'choosing',
      prt: 'chosen',
      pst: 'chose'
    },
    cling: {
      prt: 'clung'
    },
    come: {
      prt: '_',
      pst: 'came',
      g: 'coming'
    },
    compete: {
      a: 'competitor',
      g: 'competing',
      pst: '_d'
    },
    cost: {
      pst: '_'
    },
    creep: {
      prt: 'crept'
    },
    cut: {
      prt: '_'
    },
    deal: {
      prt: '_t',
      pst: '_t'
    },
    develop: {
      a: '_er',
      g: '_ing',
      pst: '_ed'
    },
    die: {
      g: 'dying',
      pst: '_d'
    },
    dig: {
      g: '_ging',
      prt: 'dug',
      pst: 'dug'
    },
    dive: {
      prt: '_d'
    },
    "do": {
      pst: 'did',
      pres: '_es'
    },
    draw: {
      prt: '_n',
      pst: 'drew'
    },
    dream: {
      prt: '_t'
    },
    drink: {
      prt: 'drunk',
      pst: 'drank'
    },
    drive: {
      g: 'driving',
      prt: '_n',
      pst: 'drove'
    },
    drop: {
      g: '_ping',
      pst: '_ped'
    },
    eat: {
      a: '_er',
      g: '_ing',
      prt: '_en',
      pst: 'ate'
    },
    edit: {
      pst: '_ed',
      g: '_ing'
    },
    egg: {
      pst: '_ed'
    },
    fall: {
      prt: '_en',
      pst: 'fell'
    },
    feed: {
      prt: 'fed',
      pst: 'fed'
    },
    feel: {
      a: '_er',
      pst: 'felt'
    },
    fight: {
      prt: 'fought',
      pst: 'fought'
    },
    find: {
      pst: 'found'
    },
    flee: {
      g: '_ing',
      prt: 'fled'
    },
    fling: {
      prt: 'flung'
    },
    fly: {
      prt: 'flown',
      pst: 'flew'
    },
    forbid: {
      pst: 'forbade'
    },
    forget: {
      g: '_ing',
      prt: 'forgotten',
      pst: 'forgot'
    },
    forgive: {
      g: 'forgiving',
      prt: '_n',
      pst: 'forgave'
    },
    free: {
      a: '',
      g: '_ing'
    },
    freeze: {
      g: 'freezing',
      prt: 'frozen',
      pst: 'froze'
    },
    get: {
      pst: 'got',
      prt: 'gotten'
    },
    give: {
      g: 'giving',
      prt: '_n',
      pst: 'gave'
    },
    go: {
      prt: '_ne',
      pst: 'went',
      pres: 'goes'
    },
    grow: {
      prt: '_n'
    },
    hang: {
      prt: 'hung',
      pst: 'hung'
    },
    have: {
      g: 'having',
      prt: 'had',
      pst: 'had',
      pres: 'has'
    },
    hear: {
      prt: '_d',
      pst: '_d'
    },
    hide: {
      prt: 'hidden',
      pst: 'hid'
    },
    hit: {
      prt: '_'
    },
    hold: {
      prt: 'held',
      pst: 'held'
    },
    hurt: {
      prt: '_',
      pst: '_'
    },
    ice: {
      g: 'icing',
      pst: '_d'
    },
    imply: {
      pst: 'implied',
      pres: 'implies'
    },
    is: {
      a: '',
      g: 'being',
      pst: 'was',
      pres: '_'
    },
    keep: {
      prt: 'kept'
    },
    kneel: {
      prt: 'knelt'
    },
    know: {
      prt: '_n'
    },
    lay: {
      prt: 'laid',
      pst: 'laid'
    },
    lead: {
      prt: 'led',
      pst: 'led'
    },
    leap: {
      prt: '_t'
    },
    leave: {
      prt: 'left',
      pst: 'left'
    },
    lend: {
      prt: 'lent'
    },
    lie: {
      g: 'lying',
      pst: 'lay'
    },
    light: {
      prt: 'lit',
      pst: 'lit'
    },
    log: {
      g: '_ging',
      pst: '_ged'
    },
    loose: {
      prt: 'lost'
    },
    lose: {
      g: 'losing',
      pst: 'lost'
    },
    make: {
      prt: 'made',
      pst: 'made'
    },
    mean: {
      prt: '_t',
      pst: '_t'
    },
    meet: {
      a: '_er',
      g: '_ing',
      prt: 'met',
      pst: 'met'
    },
    miss: {
      pres: '_'
    },
    name: {
      g: 'naming'
    },
    pay: {
      prt: 'paid',
      pst: 'paid'
    },
    prove: {
      prt: '_n'
    },
    puke: {
      g: 'puking'
    },
    put: {
      prt: '_'
    },
    quit: {
      prt: '_'
    },
    read: {
      prt: '_',
      pst: '_'
    },
    ride: {
      prt: 'ridden'
    },
    ring: {
      prt: 'rung',
      pst: 'rang'
    },
    rise: {
      fut: 'will have _n',
      g: 'rising',
      prt: '_n',
      pst: 'rose',
      pluperf: 'had _n'
    },
    rub: {
      g: '_bing',
      pst: '_bed'
    },
    run: {
      g: '_ning',
      prt: '_',
      pst: 'ran'
    },
    say: {
      prt: 'said',
      pst: 'said',
      pres: '_s'
    },
    seat: {
      prt: 'sat'
    },
    see: {
      g: '_ing',
      prt: '_n',
      pst: 'saw'
    },
    seek: {
      prt: 'sought'
    },
    sell: {
      prt: 'sold',
      pst: 'sold'
    },
    send: {
      prt: 'sent'
    },
    set: {
      prt: '_'
    },
    sew: {
      prt: '_n'
    },
    shake: {
      prt: '_n'
    },
    shave: {
      prt: '_d'
    },
    shed: {
      g: '_ding',
      pst: '_',
      pres: '_s'
    },
    shine: {
      prt: 'shone',
      pst: 'shone'
    },
    shoot: {
      prt: 'shot',
      pst: 'shot'
    },
    show: {
      pst: '_ed'
    },
    shut: {
      prt: '_'
    },
    sing: {
      prt: 'sung',
      pst: 'sang'
    },
    sink: {
      pst: 'sank',
      pluperf: 'had sunk'
    },
    sit: {
      pst: 'sat'
    },
    ski: {
      pst: '_ied'
    },
    slay: {
      prt: 'slain'
    },
    sleep: {
      prt: 'slept'
    },
    slide: {
      prt: 'slid',
      pst: 'slid'
    },
    smash: {
      pres: '_es'
    },
    sneak: {
      prt: 'snuck'
    },
    speak: {
      fut: 'will have spoken',
      prt: 'spoken',
      pst: 'spoke',
      perf: 'have spoken',
      pluperf: 'had spoken'
    },
    speed: {
      prt: 'sped'
    },
    spend: {
      prt: 'spent'
    },
    spill: {
      prt: '_ed',
      pst: 'spilt'
    },
    spin: {
      g: '_ning',
      prt: 'spun',
      pst: 'spun'
    },
    spit: {
      prt: 'spat'
    },
    split: {
      prt: '_'
    },
    spread: {
      pst: '_'
    },
    spring: {
      prt: 'sprung'
    },
    stand: {
      pst: 'stood'
    },
    steal: {
      a: '_er',
      pst: 'stole'
    },
    stick: {
      pst: 'stuck'
    },
    sting: {
      pst: 'stung'
    },
    stink: {
      prt: 'stunk',
      pst: 'stunk'
    },
    stream: {
      a: '_er'
    },
    strew: {
      prt: '_n'
    },
    strike: {
      g: 'striking',
      pst: 'struck'
    },
    suit: {
      a: '_er',
      g: '_ing',
      pst: '_ed'
    },
    sware: {
      prt: 'sworn'
    },
    swear: {
      pst: 'swore'
    },
    sweep: {
      prt: 'swept'
    },
    swim: {
      g: '_ming',
      pst: 'swam'
    },
    swing: {
      pst: 'swung'
    },
    take: {
      fut: 'will have _n',
      pst: 'took',
      perf: 'have _n',
      pluperf: 'had _n'
    },
    teach: {
      pst: 'taught',
      pres: '_es'
    },
    tear: {
      pst: 'tore'
    },
    tell: {
      pst: 'told'
    },
    think: {
      pst: 'thought'
    },
    thrive: {
      prt: '_d'
    },
    tie: {
      g: 'tying',
      pst: '_d'
    },
    undergo: {
      prt: '_ne'
    },
    understand: {
      pst: 'understood'
    },
    upset: {
      prt: '_'
    },
    wait: {
      a: '_er',
      g: '_ing',
      pst: '_ed'
    },
    wake: {
      pst: 'woke'
    },
    wear: {
      pst: 'wore'
    },
    weave: {
      prt: 'woven'
    },
    wed: {
      pst: 'wed'
    },
    weep: {
      prt: 'wept'
    },
    win: {
      g: '_ning',
      pst: 'won'
    },
    wind: {
      prt: 'wound'
    },
    withdraw: {
      pst: 'withdrew'
    },
    wring: {
      prt: 'wrung'
    },
    write: {
      g: 'writing',
      prt: 'written',
      pst: 'wrote'
    }
  }; //uncompress our ad-hoc compression scheme

  var keys = Object.keys(conjugations);

  var _loop = function _loop(i) {
    var inf = keys[i];
    var _final = {};
    Object.keys(conjugations[inf]).forEach(function (key) {
      var str = conjugations[inf][key]; //swap-in infinitives for '_'

      str = str.replace('_', inf);
      var full = mapping[key];
      _final[full] = str;
    }); //over-write original

    conjugations[inf] = _final;
  };

  for (var i = 0; i < keys.length; i++) {
    _loop(i);
  }

  var conjugations_1 = conjugations;

  var endsWith = {
    b: [{
      reg: /([^aeiou][aeiou])b$/i,
      repl: {
        pr: '$1bs',
        pa: '$1bbed',
        gr: '$1bbing'
      }
    }],
    d: [{
      reg: /(end)$/i,
      repl: {
        pr: '$1s',
        pa: 'ent',
        gr: '$1ing',
        ar: '$1er'
      }
    }, {
      reg: /(eed)$/i,
      repl: {
        pr: '$1s',
        pa: '$1ed',
        gr: '$1ing',
        ar: '$1er'
      }
    }, {
      reg: /(ed)$/i,
      repl: {
        pr: '$1s',
        pa: '$1ded',
        ar: '$1der',
        gr: '$1ding'
      }
    }, {
      reg: /([^aeiou][ou])d$/i,
      repl: {
        pr: '$1ds',
        pa: '$1dded',
        gr: '$1dding'
      }
    }],
    e: [{
      reg: /(eave)$/i,
      repl: {
        pr: '$1s',
        pa: '$1d',
        gr: 'eaving',
        ar: '$1r'
      }
    }, {
      reg: /(ide)$/i,
      repl: {
        pr: '$1s',
        pa: 'ode',
        gr: 'iding',
        ar: 'ider'
      }
    }, {
      //shake
      reg: /(t|sh?)(ake)$/i,
      repl: {
        pr: '$1$2s',
        pa: '$1ook',
        gr: '$1aking',
        ar: '$1$2r'
      }
    }, {
      //awake
      reg: /w(ake)$/i,
      repl: {
        pr: 'w$1s',
        pa: 'woke',
        gr: 'waking',
        ar: 'w$1r'
      }
    }, {
      //make
      reg: /m(ake)$/i,
      repl: {
        pr: 'm$1s',
        pa: 'made',
        gr: 'making',
        ar: 'm$1r'
      }
    }, {
      reg: /(a[tg]|i[zn]|ur|nc|gl|is)e$/i,
      repl: {
        pr: '$1es',
        pa: '$1ed',
        gr: '$1ing' // prt: '$1en',

      }
    }, {
      reg: /([bd]l)e$/i,
      repl: {
        pr: '$1es',
        pa: '$1ed',
        gr: '$1ing'
      }
    }, {
      reg: /(om)e$/i,
      repl: {
        pr: '$1es',
        pa: 'ame',
        gr: '$1ing'
      }
    }],
    g: [{
      reg: /([^aeiou][ou])g$/i,
      repl: {
        pr: '$1gs',
        pa: '$1gged',
        gr: '$1gging'
      }
    }],
    h: [{
      reg: /(..)([cs]h)$/i,
      repl: {
        pr: '$1$2es',
        pa: '$1$2ed',
        gr: '$1$2ing'
      }
    }],
    k: [{
      reg: /(ink)$/i,
      repl: {
        pr: '$1s',
        pa: 'unk',
        gr: '$1ing',
        ar: '$1er'
      }
    }],
    m: [{
      reg: /([^aeiou][aeiou])m$/i,
      repl: {
        pr: '$1ms',
        pa: '$1mmed',
        gr: '$1mming'
      }
    }],
    n: [{
      reg: /(en)$/i,
      repl: {
        pr: '$1s',
        pa: '$1ed',
        gr: '$1ing'
      }
    }],
    p: [{
      reg: /(e)(ep)$/i,
      repl: {
        pr: '$1$2s',
        pa: '$1pt',
        gr: '$1$2ing',
        ar: '$1$2er'
      }
    }, {
      reg: /([^aeiou][aeiou])p$/i,
      repl: {
        pr: '$1ps',
        pa: '$1pped',
        gr: '$1pping'
      }
    }, {
      reg: /([aeiu])p$/i,
      repl: {
        pr: '$1ps',
        pa: '$1p',
        gr: '$1pping'
      }
    }],
    r: [{
      reg: /([td]er)$/i,
      repl: {
        pr: '$1s',
        pa: '$1ed',
        gr: '$1ing'
      }
    }, {
      reg: /(er)$/i,
      repl: {
        pr: '$1s',
        pa: '$1ed',
        gr: '$1ing'
      }
    }],
    s: [{
      reg: /(ish|tch|ess)$/i,
      repl: {
        pr: '$1es',
        pa: '$1ed',
        gr: '$1ing'
      }
    }],
    t: [{
      reg: /(ion|end|e[nc]t)$/i,
      repl: {
        pr: '$1s',
        pa: '$1ed',
        gr: '$1ing'
      }
    }, {
      reg: /(.eat)$/i,
      repl: {
        pr: '$1s',
        pa: '$1ed',
        gr: '$1ing'
      }
    }, {
      reg: /([aeiu])t$/i,
      repl: {
        pr: '$1ts',
        pa: '$1t',
        gr: '$1tting'
      }
    }, {
      reg: /([^aeiou][aeiou])t$/i,
      repl: {
        pr: '$1ts',
        pa: '$1tted',
        gr: '$1tting'
      }
    }],
    w: [{
      reg: /(..)(ow)$/i,
      repl: {
        pr: '$1$2s',
        pa: '$1ew',
        gr: '$1$2ing',
        prt: '$1$2n'
      }
    }],
    y: [{
      reg: /([i|f|rr])y$/i,
      repl: {
        pr: '$1ies',
        pa: '$1ied',
        gr: '$1ying'
      }
    }],
    z: [{
      reg: /([aeiou]zz)$/i,
      repl: {
        pr: '$1es',
        pa: '$1ed',
        gr: '$1ing'
      }
    }]
  };
  var suffixes = endsWith;

  var posMap = {
    pr: 'PresentTense',
    pa: 'PastTense',
    gr: 'Gerund',
    prt: 'Participle',
    ar: 'Actor'
  };

  var doTransform = function doTransform(str, obj) {
    var found = {};
    var keys = Object.keys(obj.repl);

    for (var i = 0; i < keys.length; i += 1) {
      var pos = keys[i];
      found[posMap[pos]] = str.replace(obj.reg, obj.repl[pos]);
    }

    return found;
  }; //look at the end of the word for clues


  var checkSuffix = function checkSuffix() {
    var str = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
    var c = str[str.length - 1];

    if (suffixes.hasOwnProperty(c) === true) {
      for (var r = 0; r < suffixes[c].length; r += 1) {
        var reg = suffixes[c][r].reg;

        if (reg.test(str) === true) {
          return doTransform(str, suffixes[c][r]);
        }
      }
    }

    return {};
  };

  var _01Suffixes = checkSuffix;

  //non-specifc, 'hail-mary' transforms from infinitive, into other forms
  var hasY = /[bcdfghjklmnpqrstvwxz]y$/;
  var generic = {
    Gerund: function Gerund(inf) {
      if (inf.charAt(inf.length - 1) === 'e') {
        return inf.replace(/e$/, 'ing');
      }

      return inf + 'ing';
    },
    PresentTense: function PresentTense(inf) {
      if (inf.charAt(inf.length - 1) === 's') {
        return inf + 'es';
      }

      if (hasY.test(inf) === true) {
        return inf.slice(0, -1) + 'ies';
      }

      return inf + 's';
    },
    PastTense: function PastTense(inf) {
      if (inf.charAt(inf.length - 1) === 'e') {
        return inf + 'd';
      }

      if (inf.substr(-2) === 'ed') {
        return inf;
      }

      if (hasY.test(inf) === true) {
        return inf.slice(0, -1) + 'ied';
      }

      return inf + 'ed';
    }
  };
  var _02Generic = generic;

  //we assume the input word is a proper infinitive

  var conjugate = function conjugate() {
    var inf = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
    var world = arguments.length > 1 ? arguments[1] : undefined;
    var found = {}; // 1. look at irregulars
    //the lexicon doesn't pass this in

    if (world && world.irregulars) {
      if (world.irregulars.verbs.hasOwnProperty(inf) === true) {
        found = Object.assign({}, world.irregulars.verbs[inf]);
      }
    } //2. rule-based regex


    found = Object.assign({}, _01Suffixes(inf), found); //3. generic transformations
    //'buzzing'

    if (found.Gerund === undefined) {
      found.Gerund = _02Generic.Gerund(inf);
    } //'buzzed'


    if (found.PastTense === undefined) {
      found.PastTense = _02Generic.PastTense(inf);
    } //'buzzes'


    if (found.PresentTense === undefined) {
      found.PresentTense = _02Generic.PresentTense(inf);
    }

    return found;
  };

  var conjugate_1 = conjugate; // console.log(conjugate('bake'))

  //turn 'quick' into 'quickest'
  var do_rules = [/ght$/, /nge$/, /ough$/, /ain$/, /uel$/, /[au]ll$/, /ow$/, /oud$/, /...p$/];
  var dont_rules = [/ary$/];
  var irregulars = {
    nice: 'nicest',
    late: 'latest',
    hard: 'hardest',
    inner: 'innermost',
    outer: 'outermost',
    far: 'furthest',
    worse: 'worst',
    bad: 'worst',
    good: 'best',
    big: 'biggest',
    large: 'largest'
  };
  var transforms = [{
    reg: /y$/i,
    repl: 'iest'
  }, {
    reg: /([aeiou])t$/i,
    repl: '$1ttest'
  }, {
    reg: /([aeou])de$/i,
    repl: '$1dest'
  }, {
    reg: /nge$/i,
    repl: 'ngest'
  }, {
    reg: /([aeiou])te$/i,
    repl: '$1test'
  }];

  var to_superlative = function to_superlative(str) {
    //irregulars
    if (irregulars.hasOwnProperty(str)) {
      return irregulars[str];
    } //known transforms


    for (var i = 0; i < transforms.length; i++) {
      if (transforms[i].reg.test(str)) {
        return str.replace(transforms[i].reg, transforms[i].repl);
      }
    } //dont-rules


    for (var _i = 0; _i < dont_rules.length; _i++) {
      if (dont_rules[_i].test(str) === true) {
        return null;
      }
    } //do-rules


    for (var _i2 = 0; _i2 < do_rules.length; _i2++) {
      if (do_rules[_i2].test(str) === true) {
        if (str.charAt(str.length - 1) === 'e') {
          return str + 'st';
        }

        return str + 'est';
      }
    }

    return str + 'est';
  };

  var toSuperlative = to_superlative;

  //turn 'quick' into 'quickly'
  var do_rules$1 = [/ght$/, /nge$/, /ough$/, /ain$/, /uel$/, /[au]ll$/, /ow$/, /old$/, /oud$/, /e[ae]p$/];
  var dont_rules$1 = [/ary$/, /ous$/];
  var irregulars$1 = {
    grey: 'greyer',
    gray: 'grayer',
    green: 'greener',
    yellow: 'yellower',
    red: 'redder',
    good: 'better',
    well: 'better',
    bad: 'worse',
    sad: 'sadder',
    big: 'bigger'
  };
  var transforms$1 = [{
    reg: /y$/i,
    repl: 'ier'
  }, {
    reg: /([aeiou])t$/i,
    repl: '$1tter'
  }, {
    reg: /([aeou])de$/i,
    repl: '$1der'
  }, {
    reg: /nge$/i,
    repl: 'nger'
  }];

  var to_comparative = function to_comparative(str) {
    //known-irregulars
    if (irregulars$1.hasOwnProperty(str)) {
      return irregulars$1[str];
    } //known-transforms


    for (var i = 0; i < transforms$1.length; i++) {
      if (transforms$1[i].reg.test(str) === true) {
        return str.replace(transforms$1[i].reg, transforms$1[i].repl);
      }
    } //dont-patterns


    for (var _i = 0; _i < dont_rules$1.length; _i++) {
      if (dont_rules$1[_i].test(str) === true) {
        return null;
      }
    } //do-patterns


    for (var _i2 = 0; _i2 < do_rules$1.length; _i2++) {
      if (do_rules$1[_i2].test(str) === true) {
        return str + 'er';
      }
    } //easy-one


    if (/e$/.test(str) === true) {
      return str + 'r';
    }

    return str + 'er';
  };

  var toComparative = to_comparative;

  var fns$1 = {
    toSuperlative: toSuperlative,
    toComparative: toComparative
  };
  /** conjugate an adjective into other forms */

  var conjugate$1 = function conjugate(w) {
    var res = {}; // 'greatest'

    var sup = fns$1.toSuperlative(w);

    if (sup) {
      res.Superlative = sup;
    } // 'greater'


    var comp = fns$1.toComparative(w);

    if (comp) {
      res.Comparative = comp;
    }

    return res;
  };

  var adjectives = conjugate$1;

  /** patterns for turning 'bus' to 'buses'*/
  var suffixes$1 = {
    a: [[/(antenn|formul|nebul|vertebr|vit)a$/i, '$1ae'], [/([ti])a$/i, '$1a']],
    e: [[/(kn|l|w)ife$/i, '$1ives'], [/(hive)$/i, '$1s'], [/([m|l])ouse$/i, '$1ice'], [/([m|l])ice$/i, '$1ice']],
    f: [[/^(dwar|handkerchie|hoo|scar|whar)f$/i, '$1ves'], [/^((?:ca|e|ha|(?:our|them|your)?se|she|wo)l|lea|loa|shea|thie)f$/i, '$1ves']],
    i: [[/(octop|vir)i$/i, '$1i']],
    m: [[/([ti])um$/i, '$1a']],
    n: [[/^(oxen)$/i, '$1']],
    o: [[/(al|ad|at|er|et|ed|ad)o$/i, '$1oes']],
    s: [[/(ax|test)is$/i, '$1es'], [/(alias|status)$/i, '$1es'], [/sis$/i, 'ses'], [/(bu)s$/i, '$1ses'], [/(sis)$/i, 'ses'], [/^(?!talis|.*hu)(.*)man$/i, '$1men'], [/(octop|vir|radi|nucle|fung|cact|stimul)us$/i, '$1i']],
    x: [[/(matr|vert|ind|cort)(ix|ex)$/i, '$1ices'], [/^(ox)$/i, '$1en']],
    y: [[/([^aeiouy]|qu)y$/i, '$1ies']],
    z: [[/(quiz)$/i, '$1zes']]
  };
  var _rules = suffixes$1;

  var addE = /(x|ch|sh|s|z)$/;

  var trySuffix = function trySuffix(str) {
    var c = str[str.length - 1];

    if (_rules.hasOwnProperty(c) === true) {
      for (var i = 0; i < _rules[c].length; i += 1) {
        var reg = _rules[c][i][0];

        if (reg.test(str) === true) {
          return str.replace(reg, _rules[c][i][1]);
        }
      }
    }

    return null;
  };
  /** Turn a singular noun into a plural
   * assume the given string is singular
   */


  var pluralize = function pluralize() {
    var str = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
    var world = arguments.length > 1 ? arguments[1] : undefined;
    var irregulars = world.irregulars.nouns; // check irregulars list

    if (irregulars.hasOwnProperty(str)) {
      return irregulars[str];
    } //we have some rules to try-out


    var plural = trySuffix(str);

    if (plural !== null) {
      return plural;
    } //like 'church'


    if (addE.test(str)) {
      return str + 'es';
    } // ¯\_(ツ)_/¯


    return str + 's';
  };

  var toPlural = pluralize;

  //patterns for turning 'dwarves' to 'dwarf'
  var _rules$1 = [[/([^v])ies$/i, '$1y'], [/ises$/i, 'isis'], [/(kn|[^o]l|w)ives$/i, '$1ife'], [/^((?:ca|e|ha|(?:our|them|your)?se|she|wo)l|lea|loa|shea|thie)ves$/i, '$1f'], [/^(dwar|handkerchie|hoo|scar|whar)ves$/i, '$1f'], [/(antenn|formul|nebul|vertebr|vit)ae$/i, '$1a'], [/(octop|vir|radi|nucle|fung|cact|stimul)(i)$/i, '$1us'], [/(buffal|tomat|tornad)(oes)$/i, '$1o'], // [/(analy|diagno|parenthe|progno|synop|the)ses$/i, '$1sis'],
  [/(eas)es$/i, '$1e'], //diseases
  [/(..[aeiou]s)es$/i, '$1'], //geniouses
  [/(vert|ind|cort)(ices)$/i, '$1ex'], [/(matr|append)(ices)$/i, '$1ix'], [/(x|ch|ss|sh|z|o)es$/i, '$1'], [/men$/i, 'man'], [/(n)ews$/i, '$1ews'], [/([ti])a$/i, '$1um'], [/([^aeiouy]|qu)ies$/i, '$1y'], [/(s)eries$/i, '$1eries'], [/(m)ovies$/i, '$1ovie'], [/([m|l])ice$/i, '$1ouse'], [/(cris|ax|test)es$/i, '$1is'], [/(alias|status)es$/i, '$1'], [/(ss)$/i, '$1'], [/(ics)$/i, '$1'], [/s$/i, '']];

  var invertObj = function invertObj(obj) {
    return Object.keys(obj).reduce(function (h, k) {
      h[obj[k]] = k;
      return h;
    }, {});
  };

  var toSingular = function toSingular(str, world) {
    var irregulars = world.irregulars.nouns;
    var invert = invertObj(irregulars); //(not very efficient)
    // check irregulars list

    if (invert.hasOwnProperty(str)) {
      return invert[str];
    } // go through our regexes


    for (var i = 0; i < _rules$1.length; i++) {
      if (_rules$1[i][0].test(str) === true) {
        str = str.replace(_rules$1[i][0], _rules$1[i][1]);
        return str;
      }
    }

    return str;
  };

  var toSingular_1 = toSingular;

  //rules for turning a verb into infinitive form
  var rules = {
    Participle: [{
      reg: /own$/i,
      to: 'ow'
    }, {
      reg: /(.)un([g|k])$/i,
      to: '$1in$2'
    }],
    Actor: [{
      reg: /(er)er$/i,
      to: '$1'
    }],
    PresentTense: [{
      reg: /(..)(ies)$/i,
      to: '$1y'
    }, {
      reg: /(tch|sh)es$/i,
      to: '$1'
    }, {
      reg: /(ss|zz)es$/i,
      to: '$1'
    }, {
      reg: /([tzlshicgrvdnkmu])es$/i,
      to: '$1e'
    }, {
      reg: /(n[dtk]|c[kt]|[eo]n|i[nl]|er|a[ytrl])s$/i,
      to: '$1'
    }, {
      reg: /(ow)s$/i,
      to: '$1'
    }, {
      reg: /(op)s$/i,
      to: '$1'
    }, {
      reg: /([eirs])ts$/i,
      to: '$1t'
    }, {
      reg: /(ll)s$/i,
      to: '$1'
    }, {
      reg: /(el)s$/i,
      to: '$1'
    }, {
      reg: /(ip)es$/i,
      to: '$1e'
    }, {
      reg: /ss$/i,
      to: 'ss'
    }, {
      reg: /s$/i,
      to: ''
    }],
    Gerund: [{
      //popping -> pop
      reg: /(..)(p|d|t|g){2}ing$/i,
      to: '$1$2'
    }, {
      //fuzzing -> fuzz
      reg: /(ll|ss|zz)ing$/i,
      to: '$1'
    }, {
      reg: /([^aeiou])ying$/i,
      to: '$1y'
    }, {
      reg: /([^ae]i.)ing$/i,
      to: '$1e'
    }, {
      //eating, reading
      reg: /(ea[dklnrtv])ing$/i,
      to: '$1'
    }, {
      //washing -> wash
      reg: /(ch|sh)ing$/i,
      to: '$1'
    }, //soft-e forms:
    {
      //z : hazing (not buzzing)
      reg: /(z)ing$/i,
      to: '$1e'
    }, {
      //a : baking, undulating
      reg: /(a[gdkvtc])ing$/i,
      to: '$1e'
    }, {
      //u : conjuring, tubing
      reg: /(u[rtcbn])ing$/i,
      to: '$1e'
    }, {
      //o : forboding, poking, hoping, boring (not hooping)
      reg: /([^o]o[bdknprv])ing$/i,
      to: '$1e'
    }, {
      //ling : tingling, wrinkling, circling, scrambling, bustling
      reg: /([tbckg]l)ing$/i,
      //dp
      to: '$1e'
    }, {
      //cing : bouncing, denouncing
      reg: /(c|s)ing$/i,
      //dp
      to: '$1e'
    }, // {
    //   //soft-e :
    //   reg: /([ua]s|[dr]g|z|o[rlsp]|cre)ing$/i,
    //   to: '$1e',
    // },
    {
      //fallback
      reg: /(..)ing$/i,
      to: '$1'
    }],
    PastTense: [{
      reg: /(ued)$/i,
      to: 'ue'
    }, {
      reg: /a([^aeiouy])ed$/i,
      to: 'a$1e'
    }, {
      reg: /([aeiou]zz)ed$/i,
      to: '$1'
    }, {
      reg: /(e|i)lled$/i,
      to: '$1ll'
    }, {
      reg: /(.)(sh|ch)ed$/i,
      to: '$1$2'
    }, {
      reg: /(tl|gl)ed$/i,
      to: '$1e'
    }, {
      reg: /(um?pt?)ed$/i,
      to: '$1'
    }, {
      reg: /(ss)ed$/i,
      to: '$1'
    }, {
      reg: /pped$/i,
      to: 'p'
    }, {
      reg: /tted$/i,
      to: 't'
    }, {
      reg: /(..)gged$/i,
      to: '$1g'
    }, {
      reg: /(..)lked$/i,
      to: '$1lk'
    }, {
      reg: /([^aeiouy][aeiou])ked$/i,
      to: '$1ke'
    }, {
      reg: /(.[aeiou])led$/i,
      to: '$1l'
    }, {
      reg: /(..)(h|ion|n[dt]|ai.|[cs]t|pp|all|ss|tt|int|ail|ld|en|oo.|er|k|pp|w|ou.|rt|ght|rm)ed$/i,
      to: '$1$2'
    }, {
      reg: /(.ut)ed$/i,
      to: '$1e'
    }, {
      reg: /(.pt)ed$/i,
      to: '$1'
    }, {
      reg: /(us)ed$/i,
      to: '$1e'
    }, {
      reg: /(dd)ed$/i,
      to: '$1'
    }, {
      reg: /(..[^aeiouy])ed$/i,
      to: '$1e'
    }, {
      reg: /(..)ied$/i,
      to: '$1y'
    }, {
      reg: /(.o)ed$/i,
      to: '$1o'
    }, {
      reg: /(..i)ed$/i,
      to: '$1'
    }, {
      reg: /(.a[^aeiou])ed$/i,
      to: '$1'
    }, {
      //owed, aced
      reg: /([aeiou][^aeiou])ed$/i,
      to: '$1e'
    }, {
      reg: /([rl])ew$/i,
      to: '$1ow'
    }, {
      reg: /([pl])t$/i,
      to: '$1t'
    }]
  };
  var _transform = rules;

  var guessVerb = {
    Gerund: ['ing'],
    Actor: ['erer'],
    Infinitive: ['ate', 'ize', 'tion', 'rify', 'then', 'ress', 'ify', 'age', 'nce', 'ect', 'ise', 'ine', 'ish', 'ace', 'ash', 'ure', 'tch', 'end', 'ack', 'and', 'ute', 'ade', 'ock', 'ite', 'ase', 'ose', 'use', 'ive', 'int', 'nge', 'lay', 'est', 'ain', 'ant', 'ent', 'eed', 'er', 'le', 'own', 'unk', 'ung', 'en'],
    PastTense: ['ed', 'lt', 'nt', 'pt', 'ew', 'ld'],
    PresentTense: ['rks', 'cks', 'nks', 'ngs', 'mps', 'tes', 'zes', 'ers', 'les', 'acks', 'ends', 'ands', 'ocks', 'lays', 'eads', 'lls', 'els', 'ils', 'ows', 'nds', 'ays', 'ams', 'ars', 'ops', 'ffs', 'als', 'urs', 'lds', 'ews', 'ips', 'es', 'ts', 'ns']
  }; //flip it into a lookup object

  guessVerb = Object.keys(guessVerb).reduce(function (h, k) {
    guessVerb[k].forEach(function (a) {
      return h[a] = k;
    });
    return h;
  }, {});
  var _guess = guessVerb;

  /** it helps to know what we're conjugating from */

  var guessTense = function guessTense(str) {
    var three = str.substr(str.length - 3);

    if (_guess.hasOwnProperty(three) === true) {
      return _guess[three];
    }

    var two = str.substr(str.length - 2);

    if (_guess.hasOwnProperty(two) === true) {
      return _guess[two];
    }

    var one = str.substr(str.length - 1);

    if (one === 's') {
      return 'PresentTense';
    }

    return null;
  };

  var toInfinitive = function toInfinitive(str, world, tense) {
    if (!str) {
      return '';
    } //1. look at known irregulars


    if (world.words.hasOwnProperty(str) === true) {
      var irregs = world.irregulars.verbs;
      var keys = Object.keys(irregs);

      for (var i = 0; i < keys.length; i++) {
        var forms = Object.keys(irregs[keys[i]]);

        for (var o = 0; o < forms.length; o++) {
          if (str === irregs[keys[i]][forms[o]]) {
            return keys[i];
          }
        }
      }
    } // give'r!


    tense = tense || guessTense(str);

    if (tense && _transform[tense]) {
      for (var _i = 0; _i < _transform[tense].length; _i++) {
        var rule = _transform[tense][_i];

        if (rule.reg.test(str) === true) {
          // console.log(rule.reg)
          return str.replace(rule.reg, rule.to);
        }
      }
    }

    return str;
  };

  var toInfinitive_1 = toInfinitive;

  var irregulars$2 = {
    nouns: plurals,
    verbs: conjugations_1
  }; //these behaviours are configurable & shared across some plugins

  var transforms$2 = {
    conjugate: conjugate_1,
    adjectives: adjectives,
    toPlural: toPlural,
    toSingular: toSingular_1,
    toInfinitive: toInfinitive_1
  };
  var _isVerbose = false;
  /** all configurable linguistic data */

  var World = /*#__PURE__*/function () {
    function World() {
      _classCallCheck(this, World);

      // quiet these properties from a console.log
      Object.defineProperty(this, 'words', {
        enumerable: false,
        value: misc$1,
        writable: true
      });
      Object.defineProperty(this, 'hasCompound', {
        enumerable: false,
        value: {},
        writable: true
      });
      Object.defineProperty(this, 'irregulars', {
        enumerable: false,
        value: irregulars$2,
        writable: true
      });
      Object.defineProperty(this, 'tags', {
        enumerable: false,
        value: Object.assign({}, tags),
        writable: true
      });
      Object.defineProperty(this, 'transforms', {
        enumerable: false,
        value: transforms$2,
        writable: true
      });
      Object.defineProperty(this, 'taggers', {
        enumerable: false,
        value: [],
        writable: true
      }); // add our compressed data to lexicon

      this.unpackWords(_data); // add our irregulars to lexicon

      this.addIrregulars(); // cache our abbreviations for our sentence-parser

      Object.defineProperty(this, 'cache', {
        enumerable: false,
        value: {
          abbreviations: this.getByTag('Abbreviation')
        }
      });
    }
    /** more logs for debugging */


    _createClass(World, [{
      key: "verbose",
      value: function verbose(bool) {
        _isVerbose = bool;
        return this;
      }
    }, {
      key: "isVerbose",
      value: function isVerbose() {
        return _isVerbose;
      }
      /** get all terms in our lexicon with this tag */

    }, {
      key: "getByTag",
      value: function getByTag(tag) {
        var lex = this.words;
        var res = {};
        var words = Object.keys(lex);

        for (var i = 0; i < words.length; i++) {
          if (typeof lex[words[i]] === 'string') {
            if (lex[words[i]] === tag) {
              res[words[i]] = true;
            }
          } else if (lex[words[i]].some(function (t) {
            return t === tag;
          })) {
            res[words[i]] = true;
          }
        }

        return res;
      }
      /** augment our lingustic data with new data */

    }, {
      key: "unpackWords",
      value: function unpackWords(lex) {
        var tags = Object.keys(lex);

        for (var i = 0; i < tags.length; i++) {
          var words = Object.keys(efrtUnpack_min(lex[tags[i]]));

          for (var w = 0; w < words.length; w++) {
            addWords.addWord(words[w], tags[i], this.words); // do some fancier stuff

            addWords.addMore(words[w], tags[i], this);
          }
        }
      }
      /** put new words into our lexicon, properly */

    }, {
      key: "addWords",
      value: function addWords$1(obj) {
        var keys = Object.keys(obj);

        for (var i = 0; i < keys.length; i++) {
          var word = keys[i].toLowerCase();
          addWords.addWord(word, obj[keys[i]], this.words); // do some fancier stuff

          addWords.addMore(word, obj[keys[i]], this);
        }
      }
    }, {
      key: "addIrregulars",
      value: function addIrregulars() {
        addIrregulars_1(this);

        return this;
      }
      /** extend the compromise tagset */

    }, {
      key: "addTags",
      value: function addTags(tags) {
        tags = Object.assign({}, tags);
        this.tags = Object.assign(this.tags, tags); // calculate graph implications for the new tags

        this.tags = inference(this.tags);
        return this;
      }
      /** call methods after tagger runs */

    }, {
      key: "postProcess",
      value: function postProcess(fn) {
        this.taggers.push(fn);
        return this;
      }
      /** helper method for logging + debugging */

    }, {
      key: "stats",
      value: function stats() {
        return {
          words: Object.keys(this.words).length,
          plurals: Object.keys(this.irregulars.nouns).length,
          conjugations: Object.keys(this.irregulars.verbs).length,
          compounds: Object.keys(this.hasCompound).length,
          postProcessors: this.taggers.length
        };
      }
    }]);

    return World;
  }(); //  ¯\_(:/)_/¯


  var clone$1 = function clone(obj) {
    return JSON.parse(JSON.stringify(obj));
  };
  /** produce a deep-copy of all lingustic data */


  World.prototype.clone = function () {
    var w2 = new World(); // these are simple to copy:

    w2.words = Object.assign({}, this.words);
    w2.hasCompound = Object.assign({}, this.hasCompound); //these ones are nested:

    w2.irregulars = clone$1(this.irregulars);
    w2.tags = clone$1(this.tags); // these are functions

    w2.transforms = this.transforms;
    w2.taggers = this.taggers;
    return w2;
  };

  var World_1 = World;

  var _01Utils$1 = createCommonjsModule(function (module, exports) {
    /** return the root, first document */
    exports.all = function () {
      return this.parents()[0] || this;
    };
    /** return the previous result */


    exports.parent = function () {
      if (this.from) {
        return this.from;
      }

      return this;
    };
    /**  return a list of all previous results */


    exports.parents = function (n) {
      var arr = [];

      var addParent = function addParent(doc) {
        if (doc.from) {
          arr.push(doc.from);
          addParent(doc.from);
        }
      };

      addParent(this);
      arr = arr.reverse();

      if (typeof n === 'number') {
        return arr[n];
      }

      return arr;
    };
    /** deep-copy the document, so that no references remain */


    exports.clone = function (doShallow) {
      var list = this.list.map(function (ts) {
        return ts.clone(doShallow);
      });
      var tmp = this.buildFrom(list);
      return tmp;
    };
    /** how many seperate terms does the document have? */


    exports.wordCount = function () {
      return this.list.reduce(function (count, p) {
        count += p.wordCount();
        return count;
      }, 0);
    };

    exports.wordcount = exports.wordCount;
    /** turn on logging for decision-debugging */
    // exports.verbose = function(bool) {
    //   if (bool === undefined) {
    //     bool = true
    //   }
    //   this.world.verbose = bool
    // }
  });

  var _02Accessors = createCommonjsModule(function (module, exports) {
    /** use only the first result(s) */
    exports.first = function (n) {
      if (n === undefined) {
        return this.get(0);
      }

      return this.slice(0, n);
    };
    /** use only the last result(s) */


    exports.last = function (n) {
      if (n === undefined) {
        return this.get(this.list.length - 1);
      }

      var end = this.list.length;
      return this.slice(end - n, end);
    };
    /** grab a given subset of the results*/


    exports.slice = function (start, end) {
      var list = this.list.slice(start, end);
      return this.buildFrom(list);
    };
    /* grab nth result */


    exports.eq = function (n) {
      var p = this.list[n];

      if (p === undefined) {
        return this.buildFrom([]);
      }

      return this.buildFrom([p]);
    };

    exports.get = exports.eq;
    /** grab term[0] for every match */

    exports.firstTerms = function () {
      return this.match('^.');
    };

    exports.firstTerm = exports.firstTerms;
    /** grab the last term for every match  */

    exports.lastTerms = function () {
      return this.match('.$');
    };

    exports.lastTerm = exports.lastTerms;
    /** return a flat array of term objects */

    exports.termList = function (num) {
      var arr = []; //'reduce' but faster

      for (var i = 0; i < this.list.length; i++) {
        var terms = this.list[i].terms();

        for (var o = 0; o < terms.length; o++) {
          arr.push(terms[o]); //support .termList(4)

          if (num !== undefined && arr[num] !== undefined) {
            return arr[num];
          }
        }
      }

      return arr;
    };
    /** return a flat array of term text strings */
    /* @blab+ */
    exports.textList = function (num) {
      var arr = []; //'reduce' but faster

      for (var i = 0; i < this.list.length; i++) {
        var terms = this.list[i].terms();

        for (var o = 0; o < terms.length; o++) {
          arr.push(terms[o]); //support .termList(4)

          if (num !== undefined && arr[num] !== undefined) {
            return arr[num];
          }
        }
      }

      return arr.map(function (term) { return term.text });
    };


    /* grab named capture group terms as object */
    var getGroups = function getGroups(doc) {
      var res = {};
      var allGroups = {};

      var _loop = function _loop(i) {
        var phrase = doc.list[i];
        var groups = Object.keys(phrase.groups).map(function (k) {
          return phrase.groups[k];
        });

        for (var j = 0; j < groups.length; j++) {
          var _groups$j = groups[j],
              group = _groups$j.group,
              start = _groups$j.start,
              length = _groups$j.length;

          if (!allGroups[group]) {
            allGroups[group] = [];
          }

          allGroups[group].push(phrase.buildFrom(start, length));
        }
      };

      for (var i = 0; i < doc.list.length; i++) {
        _loop(i);
      }

      var keys = Object.keys(allGroups);

      for (var _i = 0; _i < keys.length; _i++) {
        var key = keys[_i];
        res[key] = doc.buildFrom(allGroups[key]);
      }

      return res;
    };

    var getOneName = function getOneName(doc, name) {
      var arr = [];

      var _loop2 = function _loop2(i) {
        var phrase = doc.list[i];
        var keys = Object.keys(phrase.groups);
        keys = keys.filter(function (id) {
          return phrase.groups[id].group === name;
        });
        keys.forEach(function (id) {
          arr.push(phrase.buildFrom(phrase.groups[id].start, phrase.groups[id].length));
        });
      };

      for (var i = 0; i < doc.list.length; i++) {
        _loop2(i);
      }

      return doc.buildFrom(arr);
    };
    /** grab named capture group results */


    exports.groups = function (target) {
      if (target === undefined) {
        return getGroups(this);
      }

      if (typeof target === 'number') {
        target = String(target);
      }

      return getOneName(this, target) || this.buildFrom([]);
    };

    exports.group = exports.groups;
    /** get the full-sentence each phrase belongs to */

    exports.sentences = function (n) {
      var arr = [];
      this.list.forEach(function (p) {
        arr.push(p.fullSentence());
      });

      if (typeof n === 'number') {
        return this.buildFrom([arr[n]]);
      }

      return this.buildFrom(arr);
    };

    exports.sentence = exports.sentences;
  });

  // cache the easier conditions up-front
  var cacheRequired = function cacheRequired(reg) {
    var needTags = [];
    var needWords = [];
    reg.forEach(function (obj) {
      if (obj.optional === true || obj.negative === true) {
        return;
      }

      if (obj.tag !== undefined) {
        needTags.push(obj.tag);
      }

      if (obj.word !== undefined) {
        needWords.push(obj.word);
      }
    });
    return {
      tags: needTags,
      words: needWords
    };
  };

  var failFast$1 = function failFast(doc, regs) {
    if (doc._cache && doc._cache.set === true) {
      var _cacheRequired = cacheRequired(regs),
          words = _cacheRequired.words,
          tags = _cacheRequired.tags; //check required words


      for (var i = 0; i < words.length; i++) {
        if (doc._cache.words[words[i]] === undefined) {
          return false;
        }
      } //check required tags


      for (var _i = 0; _i < tags.length; _i++) {
        if (doc._cache.tags[tags[_i]] === undefined) {
          return false;
        }
      }
    }

    return true;
  };

  var checkCache = failFast$1;

  var _03Match = createCommonjsModule(function (module, exports) {
    /** return a new Doc, with this one as a parent */
    exports.match = function (reg, name) {
      //parse-up the input expression
      var regs = syntax_1(reg);

      if (regs.length === 0) {
        return this.buildFrom([]);
      } //check our cache, if it exists


      if (checkCache(this, regs) === false) {
        return this.buildFrom([]);
      } //try expression on each phrase


      var matches = this.list.reduce(function (arr, p) {
        return arr.concat(p.match(regs));
      }, []);

      if (name !== undefined && name !== null && name !== '') {
        return this.buildFrom(matches).groups(name);
      }

      return this.buildFrom(matches);
    };
    /** return all results except for this */


    exports.not = function (reg) {
      //parse-up the input expression
      var regs = syntax_1(reg); //if it's empty, return them all!

      if (regs.length === 0 || checkCache(this, regs) === false) {
        return this;
      } //try expression on each phrase


      var matches = this.list.reduce(function (arr, p) {
        return arr.concat(p.not(regs));
      }, []);
      return this.buildFrom(matches);
    };
    /** return only the first match */


    exports.matchOne = function (reg) {
      var regs = syntax_1(reg); //check our cache, if it exists

      if (checkCache(this, regs) === false) {
        return this.buildFrom([]);
      }

      for (var i = 0; i < this.list.length; i++) {
        var match = this.list[i].match(regs, true);
        return this.buildFrom(match);
      }

      return this.buildFrom([]);
    };
    /** return each current phrase, only if it contains this match */


    exports["if"] = function (reg) {
      var regs = syntax_1(reg); //consult our cache, if it exists

      if (checkCache(this, regs) === false) {
        return this.buildFrom([]);
      }

      var found = this.list.filter(function (p) {
        return p.has(regs) === true;
      });
      return this.buildFrom(found);
    };
    /** Filter-out any current phrases that have this match*/


    exports.ifNo = function (reg) {
      var regs = syntax_1(reg);
      var found = this.list.filter(function (p) {
        return p.has(regs) === false;
      });
      return this.buildFrom(found);
    };
    /**Return a boolean if this match exists */

    // function (reg:string|string [],all:boolean) -> boolean
    exports.has = function (reg,all) {
      // @blab+
      if (typeof reg == 'object' && reg.length) {
        for(var i in reg) { 
          var check=exports.has.call(this,reg[i]); 
          if (all && !check) return false;
          if (!all && check) return true;
        }
        return all?true:false;
      } 
      var regs = syntax_1(reg); //consult our cache, if it exists

      if (checkCache(this, regs) === false) {
        return false;
      }

      return this.list.some(function (p) {
        return p.has(regs) === true;
      });
    };
    
    // @blab; contains similar terms?
    exports.hasSimilar = function (reg,thres,all) {
      var terms = this.termList();
      var count=0;
      thres=thres||90;
      if (typeof reg=='string') reg=[reg];
      for(var i in terms) {
        for (var j in reg) {
          var check = similar_text(terms[i].text,reg[j],1)>thres;
          if (!all && check) return true;
          if (check) { count++; break }
        }
      }
      return all?count==res.length:false;
    }
    /** match any terms after our matches, within the sentence */


    exports.lookAhead = function (reg) {
      // find everything afterwards, by default
      if (!reg) {
        reg = '.*';
      }

      var regs = syntax_1(reg);
      var matches = [];
      this.list.forEach(function (p) {
        matches = matches.concat(p.lookAhead(regs));
      });
      matches = matches.filter(function (p) {
        return p;
      });
      return this.buildFrom(matches);
    };

    exports.lookAfter = exports.lookAhead;
    /** match any terms before our matches, within the sentence */

    exports.lookBehind = function (reg) {
      // find everything afterwards, by default
      if (!reg) {
        reg = '.*';
      }

      var regs = syntax_1(reg);
      var matches = [];
      this.list.forEach(function (p) {
        matches = matches.concat(p.lookBehind(regs));
      });
      matches = matches.filter(function (p) {
        return p;
      });
      return this.buildFrom(matches);
    };

    exports.lookBefore = exports.lookBehind;
    /** return all terms before a match, in each phrase */

    exports.before = function (reg) {
      var regs = syntax_1(reg); //only the phrases we care about

      var phrases = this["if"](regs).list;
      var befores = phrases.map(function (p) {
        var ids = p.terms().map(function (t) {
          return t.id;
        }); //run the search again

        var m = p.match(regs)[0];
        var index = ids.indexOf(m.start); //nothing is before a first-term match

        if (index === 0 || index === -1) {
          return null;
        }

        return p.buildFrom(p.start, index);
      });
      befores = befores.filter(function (p) {
        return p !== null;
      });
      return this.buildFrom(befores);
    };
    /** return all terms after a match, in each phrase */


    exports.after = function (reg) {
      var regs = syntax_1(reg); //only the phrases we care about

      var phrases = this["if"](regs).list;
      var befores = phrases.map(function (p) {
        var terms = p.terms();
        var ids = terms.map(function (t) {
          return t.id;
        }); //run the search again

        var m = p.match(regs)[0];
        var index = ids.indexOf(m.start); //skip if nothing is after it

        if (index === -1 || !terms[index + m.length]) {
          return null;
        } //create the new phrase, after our match.


        var id = terms[index + m.length].id;
        var len = p.length - index - m.length;
        return p.buildFrom(id, len);
      });
      befores = befores.filter(function (p) {
        return p !== null;
      });
      return this.buildFrom(befores);
    };
    /** return only results with this match afterwards */


    exports.hasAfter = function (reg) {
      return this.filter(function (doc) {
        return doc.lookAfter(reg).found;
      });
    };
    /** return only results with this match before it */


    exports.hasBefore = function (reg) {
      return this.filter(function (doc) {
        return doc.lookBefore(reg).found;
      });
    };
  });

  /** apply a tag, or tags to all terms */
  var tagTerms = function tagTerms(tag, doc, safe, reason) {
    var tagList = [];

    if (typeof tag === 'string') {
      tagList = tag.split(' ');
    } //do indepenent tags for each term:


    doc.list.forEach(function (p) {
      var terms = p.terms(); // tagSafe - apply only to fitting terms

      if (safe === true) {
        terms = terms.filter(function (t) {
          return t.canBe(tag, doc.world);
        });
      }

      terms.forEach(function (t, i) {
        //fancy version:
        if (tagList.length > 1) {
          if (tagList[i] && tagList[i] !== '.') {
            t.tag(tagList[i], reason, doc.world);
          }
        } else {
          //non-fancy version (same tag for all terms)
          t.tag(tag, reason, doc.world);
        }
      });
    });
    return;
  };

  var _setTag = tagTerms;

  /** Give all terms the given tag */

  var tag$1 = function tag(tags, why) {
    if (!tags) {
      return this;
    }

    _setTag(tags, this, false, why);
    return this;
  };
  /** Only apply tag to terms if it is consistent with current tags */


  var tagSafe$1 = function tagSafe(tags, why) {
    if (!tags) {
      return this;
    }

    _setTag(tags, this, true, why);
    return this;
  };
  /** Remove this term from the given terms */


  var unTag$1 = function unTag(tags, why) {
    var _this = this;

    this.list.forEach(function (p) {
      p.terms().forEach(function (t) {
        return t.unTag(tags, why, _this.world);
      });
    });
    return this;
  };
  /** return only the terms that can be this tag*/


  var canBe$2 = function canBe(tag) {
    if (!tag) {
      return this;
    }

    var world = this.world;
    var matches = this.list.reduce(function (arr, p) {
      return arr.concat(p.canBe(tag, world));
    }, []);
    return this.buildFrom(matches);
  };

  var _04Tag = {
    tag: tag$1,
    tagSafe: tagSafe$1,
    unTag: unTag$1,
    canBe: canBe$2
  };

  /* run each phrase through a function, and create a new document */
  var map = function map(fn) {
    var _this = this;

    if (!fn) {
      return this;
    }

    var list = this.list.map(function (p, i) {
      var doc = _this.buildFrom([p]);

      doc.from = null; //it's not a child/parent

      var res = fn(doc, i); // if its a doc, return one result

      if (res && res.list && res.list[0]) {
        return res.list[0];
      }

      return res;
    }); //remove nulls

    list = list.filter(function (x) {
      return x;
    }); // return an empty response

    if (list.length === 0) {
      return this.buildFrom(list);
    } // if it is not a list of Phrase objects, then don't try to make a Doc object


    if (_typeof(list[0]) !== 'object' || list[0].isA !== 'Phrase') {
      return list;
    }

    return this.buildFrom(list);
  };
  /** run a function on each phrase */


  var forEach = function forEach(fn, detachParent) {
    var _this2 = this;

    if (!fn) {
      return this;
    }

    this.list.forEach(function (p, i) {
      var sub = _this2.buildFrom([p]); // if we're doing fancy insertions, we may want to skip updating the parent each time.


      if (detachParent === true) {
        sub.from = null; //
      }

      fn(sub, i);
    });
    return this;
  };
  /** return only the phrases that return true */


  var filter = function filter(fn) {
    var _this3 = this;

    if (!fn) {
      return this;
    }

    var list = this.list.filter(function (p, i) {
      var doc = _this3.buildFrom([p]);

      doc.from = null; //it's not a child/parent

      return fn(doc, i);
    });
    return this.buildFrom(list);
  };
  /** return a document with only the first phrase that matches */


  var find = function find(fn) {
    var _this4 = this;

    if (!fn) {
      return this;
    }

    var phrase = this.list.find(function (p, i) {
      var doc = _this4.buildFrom([p]);

      doc.from = null; //it's not a child/parent

      return fn(doc, i);
    });

    if (phrase) {
      return this.buildFrom([phrase]);
    }

    return undefined;
  };
  /** return true or false if there is one matching phrase */


  var some = function some(fn) {
    var _this5 = this;

    if (!fn) {
      return this;
    }

    return this.list.some(function (p, i) {
      var doc = _this5.buildFrom([p]);

      doc.from = null; //it's not a child/parent

      return fn(doc, i);
    });
  };
  /** sample a subset of the results */


  var random = function random(n) {
    if (!this.found) {
      return this;
    }

    var r = Math.floor(Math.random() * this.list.length);

    if (n === undefined) {
      var list = [this.list[r]];
      return this.buildFrom(list);
    } //prevent it from going over the end


    if (r + n > this.length) {
      r = this.length - n;
      r = r < 0 ? 0 : r;
    }

    return this.slice(r, r + n);
  };
  /** combine each phrase into a new data-structure */
  // exports.reduce = function(fn, h) {
  //   let list = this.list.reduce((_h, ts) => {
  //     let doc = this.buildFrom([ts])
  //     doc.from = null //it's not a child/parent
  //     return fn(_h, doc)
  //   }, h)
  //   return this.buildFrom(list)
  // }


  var _05Loops = {
    map: map,
    forEach: forEach,
    filter: filter,
    find: find,
    some: some,
    random: random
  };

  // const tokenize = require('../../01-tokenizer/02-words')
  var tokenize = function tokenize(str) {
    return str.split(/[ -]/g);
  }; // take a list of strings
  // look them up in the document


  var buildTree = function buildTree(termList) {
    var values = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
    var root = {}; // parse our input

    termList.forEach(function (str, i) {
      var val = true;

      if (values[i] !== undefined) {
        val = values[i];
      } // some rough normalization


      str = (str || '').toLowerCase();
      str = str.replace(/[,;.!?]+$/, '');
      var arr = tokenize(str).map(function (s) {
        return s.trim();
      });
      root[arr[0]] = root[arr[0]] || {};

      if (arr.length === 1) {
        root[arr[0]].value = val;
      } else {
        root[arr[0]].more = root[arr[0]].more || [];
        root[arr[0]].more.push({
          rest: arr.slice(1),
          value: val
        });
      }
    }); // sort by longest-first?
    // console.log(JSON.stringify(root, null, 2))

    return root;
  };

  var fastLookup = function fastLookup(termList, values, doc) {
    var root = buildTree(termList, values);
    var found = []; // each phrase

    var _loop = function _loop(i) {
      var p = doc.list[i];
      var terms = p.terms();
      var words = terms.map(function (t) {
        return t.reduced;
      }); // each word

      var _loop2 = function _loop2(w) {
        if (root[words[w]] !== undefined) {
          // is it a multi-word match?
          if (root[words[w]].more !== undefined) {
            root[words[w]].more.forEach(function (more) {
              // is it too-long?
              if (words[w + more.rest.length] === undefined) {
                return;
              } // compare each subsequent term


              var everyTerm = more.rest.every(function (word, r) {
                return word === words[w + r + 1];
              });

              if (everyTerm === true) {
                found.push({
                  id: p.terms()[w].id,
                  value: more.value,
                  length: more.rest.length + 1
                });
              }
            });
          } // is it a single-word match?


          if (root[words[w]].value !== undefined) {
            found.push({
              id: p.terms()[w].id,
              value: root[words[w]].value,
              length: 1
            });
          }
        }
      };

      for (var w = 0; w < words.length; w++) {
        _loop2(w);
      }
    };

    for (var i = 0; i < doc.list.length; i++) {
      _loop(i);
    }

    return found;
  };

  var _lookup = fastLookup;

  var _06Lookup = createCommonjsModule(function (module, exports) {
    // compare one term and one match
    // const doesMatch = function(term, str) {
    //   if (str === '') {
    //     return false
    //   }
    //   return term.reduced === str || term.implicit === str || term.root === str || term.text.toLowerCase() === str
    // }
    var isObject = function isObject(obj) {
      return obj && Object.prototype.toString.call(obj) === '[object Object]';
    };
    /** lookup an array of words or phrases */


    exports.lookup = function (arr) {
      var _this = this;

      var values = []; //is it a {key:val} object?

      var isObj = isObject(arr);

      if (isObj === true) {
        arr = Object.keys(arr).map(function (k) {
          values.push(arr[k]);
          return k;
        });
      } // support .lookup('foo')


      if (typeof arr === 'string') {
        arr = [arr];
      } //make sure we go fast.


      if (this._cache.set !== true) {
        this.cache();
      }

      var found = _lookup(arr, values, this);
      var p = this.list[0]; // make object response

      if (isObj === true) {
        var byVal = {};
        found.forEach(function (o) {
          byVal[o.value] = byVal[o.value] || [];
          byVal[o.value].push(p.buildFrom(o.id, o.length));
        });
        Object.keys(byVal).forEach(function (k) {
          byVal[k] = _this.buildFrom(byVal[k]);
        });
        return byVal;
      } // otherwise, make array response:


      found = found.map(function (o) {
        return p.buildFrom(o.id, o.length);
      });
      return this.buildFrom(found);
    };

    exports.lookUp = exports.lookup;
  });

  /** freeze the current state of the document, for speed-purposes*/
  var cache$1 = function cache(options) {
    var _this = this;

    options = options || {};
    var words = {};
    var tags = {};
    this._cache.words = words;
    this._cache.tags = tags;
    this._cache.set = true;
    this.list.forEach(function (p, i) {
      p.cache = p.cache || {}; //p.terms get cached automatically

      var terms = p.terms(); // cache all the terms

      terms.forEach(function (t) {
        if (words[t.reduced] && !words.hasOwnProperty(t.reduced)) {
          return; //skip prototype words
        }

        words[t.reduced] = words[t.reduced] || [];
        words[t.reduced].push(i);
        Object.keys(t.tags).forEach(function (tag) {
          tags[tag] = tags[tag] || [];
          tags[tag].push(i);
        }); // cache root-form on Term, too

        if (options.root) {
          t.setRoot(_this.world);
          words[t.root] = true;
        }
      });
    });
    return this;
  };
  /** un-freezes the current state of the document, so it may be transformed */


  var uncache = function uncache() {
    this._cache = {};
    this.list.forEach(function (p) {
      p.cache = {};
    }); // do parents too?

    this.parents().forEach(function (doc) {
      doc._cache = {};
      doc.list.forEach(function (p) {
        p.cache = {};
      });
    });
    return this;
  };

  var _07Cache = {
    cache: cache$1,
    uncache: uncache
  };

  var titleCase$3 = function titleCase(str) {
    return str.charAt(0).toUpperCase() + str.substr(1);
  };
  /** substitute-in new content */


  var replaceWith = function replaceWith(replace) {
    var _this = this;

    var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};

    if (!replace) {
      return this["delete"]();
    } //support old-style params


    if (options === true) {
      options = {
        keepTags: true
      };
    }

    if (options === false) {
      options = {
        keepTags: false
      };
    }

    options = options || {}; // clear the cache

    this.uncache(); // return this

    this.list.forEach(function (p) {
      var input = replace; // accept a function for replace

      if (typeof replace === 'function') {
        input = replace(p);
      }

      var newPhrases; // accept a Doc object to replace

      if (input && _typeof(input) === 'object' && input.isA === 'Doc') {
        newPhrases = input.list;

        _this.pool().merge(input.pool());
      } else if (typeof input === 'string') {
        //input is a string
        if (options.keepCase !== false && p.terms(0).isTitleCase()) {
          input = titleCase$3(input);
        }

        newPhrases = _01Tokenizer(input, _this.world, _this.pool()); //tag the new phrases

        var tmpDoc = _this.buildFrom(newPhrases);

        tmpDoc.tagger();
        newPhrases = tmpDoc.list;
      } else {
        return; //don't even bother
      } // try to keep its old tags, if appropriate


      if (options.keepTags === true) {
        var oldTags = p.json({
          terms: {
            tags: true
          }
        }).terms;
        newPhrases[0].terms().forEach(function (t, i) {
          if (oldTags[i]) {
            t.tagSafe(oldTags[i].tags, 'keptTag', _this.world);
          }
        });
      }

      p.replace(newPhrases[0], _this); //Oneday: support multi-sentence replacements
    });
    return this;
  };
  /** search and replace match with new content */


  var replace$1 = function replace(match, _replace, options) {
    // if there's no 2nd param, use replaceWith
    if (_replace === undefined) {
      return this.replaceWith(match, options);
    }

    this.match(match).replaceWith(_replace, options);
    return this;
  };

  var _01Replace = {
    replaceWith: replaceWith,
    replace: replace$1
  };

  var _02Insert = createCommonjsModule(function (module, exports) {
    // if it's empty, just create the phrase
    var makeNew = function makeNew(str, doc) {
      var phrase = _01Tokenizer(str, doc.world)[0]; //assume it's one sentence, for now

      var tmpDoc = doc.buildFrom([phrase]);
      tmpDoc.tagger();
      doc.list = tmpDoc.list;
      return doc;
    };
    /** add these new terms to the end*/


    exports.append = function (str) {
      var _this = this;

      if (!str) {
        return this;
      } // if it's empty, just create the phrase


      if (!this.found) {
        return makeNew(str, this);
      } // clear the cache


      this.uncache(); //add it to end of every phrase

      this.list.forEach(function (p) {
        //build it
        var phrase = _01Tokenizer(str, _this.world, _this.pool())[0]; //assume it's one sentence, for now
        //tag it

        var tmpDoc = _this.buildFrom([phrase]);

        tmpDoc.tagger(); // push it onto the end

        p.append(phrase, _this);
      });
      return this;
    };

    exports.insertAfter = exports.append;
    exports.insertAt = exports.append;
    /** add these new terms to the front*/

    exports.prepend = function (str) {
      var _this2 = this;

      if (!str) {
        return this;
      } // if it's empty, just create the phrase


      if (!this.found) {
        return makeNew(str, this);
      } // clear the cache


      this.uncache(); //add it to start of every phrase

      this.list.forEach(function (p) {
        //build it
        var phrase = _01Tokenizer(str, _this2.world, _this2.pool())[0]; //assume it's one sentence, for now
        //tag it

        var tmpDoc = _this2.buildFrom([phrase]);

        tmpDoc.tagger(); // add it to the start

        p.prepend(phrase, _this2);
      });
      return this;
    };

    exports.insertBefore = exports.prepend;
    /** add these new things to the end*/

    exports.concat = function () {
      // clear the cache
      this.uncache();
      var list = this.list.slice(0); //repeat for any number of params

      for (var i = 0; i < arguments.length; i++) {
        var arg = arguments[i]; //support a fresh string

        if (typeof arg === 'string') {
          var arr = _01Tokenizer(arg, this.world); //TODO: phrase.tagger()?

          list = list.concat(arr);
        } else if (arg.isA === 'Doc') {
          list = list.concat(arg.list);
        } else if (arg.isA === 'Phrase') {
          list.push(arg);
        }
      }

      return this.buildFrom(list);
    };
    /** fully remove these terms from the document */


    exports["delete"] = function (match) {
      var _this3 = this;

      // clear the cache
      this.uncache();
      var toRemove = this;

      if (match) {
        toRemove = this.match(match);
      }

      toRemove.list.forEach(function (phrase) {
        return phrase["delete"](_this3);
      });
      return this;
    }; // aliases


    exports.remove = exports["delete"];
  });

  var shouldTrim = {
    clean: true,
    reduced: true,
    root: true
  };
  /** return the document as text */

  var text$1 = function text(options) {
    var _this = this;

    options = options || {}; //are we showing every phrase?

    var showFull = false;

    if (this.parents().length === 0) {
      showFull = true;
    } // cache roots, if necessary


    if (options === 'root' || _typeof(options) === 'object' && options.root) {
      this.list.forEach(function (p) {
        p.terms().forEach(function (t) {
          if (t.root === null) {
            t.setRoot(_this.world);
          }
        });
      });
    }

    var txt = this.list.reduce(function (str, p, i) {
      var trimPre = !showFull && i === 0;
      var trimPost = !showFull && i === _this.list.length - 1;
      return str + p.text(options, trimPre, trimPost);
    }, ''); // clumsy final trim of leading/trailing whitespace

    if (shouldTrim[options] === true || options.reduced === true || options.clean === true || options.root === true) {
      txt = txt.trim();
    }

    return txt;
  };

  var _01Text = {
    text: text$1
  };

  // get all character startings in doc
  var termOffsets = function termOffsets(doc) {
    var elapsed = 0;
    var index = 0;
    var offsets = {};
    doc.termList().forEach(function (term) {
      offsets[term.id] = {
        index: index,
        start: elapsed + term.pre.length,
        length: term.text.length
      };
      elapsed += term.pre.length + term.text.length + term.post.length;
      index += 1;
    });
    return offsets;
  };

  var calcOffset = function calcOffset(doc, result, options) {
    // calculate offsets for each term
    var offsets = termOffsets(doc.all()); // add index values

    if (options.terms.index || options.index) {
      result.forEach(function (o) {
        o.terms.forEach(function (t) {
          t.index = offsets[t.id].index;
        });
        o.index = o.terms[0].index;
      });
    } // add offset values


    if (options.terms.offset || options.offset) {
      result.forEach(function (o) {
        o.terms.forEach(function (t) {
          t.offset = offsets[t.id] || {};
        }); // let len = o.terms.reduce((n, t, i) => {
        //   n += t.offset.length || 0
        //   //add whitespace, too
        //   console.log(t.post)
        //   return n
        // }, 0)
        // The offset information for the entire doc starts at (or just before)
        // the first term, and is as long as the whole text.  The code originally
        // copied the entire offset value from terms[0], but since we're now
        // overriding 2 of the three fields, it's cleaner to just create an all-
        // new object and not pretend it's "just" the same as terms[0].

        o.offset = {
          index: o.terms[0].offset.index,
          start: o.terms[0].offset.start - o.text.indexOf(o.terms[0].text),
          length: o.text.length
        };
      });
    }
  };

  var _offset = calcOffset;

  var _02Json = createCommonjsModule(function (module, exports) {
    var jsonDefaults = {
      text: true,
      terms: true,
      trim: true
    }; //some options have dependents

    var setOptions = function setOptions(options) {
      options = Object.assign({}, jsonDefaults, options);

      if (options.unique) {
        options.reduced = true;
      } //offset calculation requires these options to be on


      if (options.offset) {
        options.text = true;

        if (!options.terms || options.terms === true) {
          options.terms = {};
        }

        options.terms.offset = true;
      }

      if (options.index || options.terms.index) {
        options.terms = options.terms === true ? {} : options.terms;
        options.terms.id = true;
      }

      return options;
    };
    /** pull out desired metadata from the document */


    exports.json = function () {
      var _this = this;

      var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};

      //support json(3) format
      if (typeof options === 'number' && this.list[options]) {
        return this.list[options].json(jsonDefaults);
      }

      options = setOptions(options); // cache root strings beforehand, if necessary

      if (options.root === true) {
        this.list.forEach(function (p) {
          p.terms().forEach(function (t) {
            if (t.root === null) {
              t.setRoot(_this.world);
            }
          });
        });
      }

      var result = this.list.map(function (p) {
        return p.json(options, _this.world);
      }); // add offset and index data for each term

      if (options.terms.offset || options.offset || options.terms.index || options.index) {
        _offset(this, result, options);
      } // add frequency #s


      if (options.frequency || options.freq || options.count) {
        var obj = {};
        this.list.forEach(function (p) {
          var str = p.text('reduced');
          obj[str] = obj[str] || 0;
          obj[str] += 1;
        });
        this.list.forEach(function (p, i) {
          result[i].count = obj[p.text('reduced')];
        });
      } // remove duplicates


      if (options.unique) {
        var already = {};
        result = result.filter(function (o) {
          if (already[o.reduced] === true) {
            return false;
          }

          already[o.reduced] = true;
          return true;
        });
      }

      return result;
    }; //aliases


    exports.data = exports.json;
  });

  var _debug = createCommonjsModule(function (module) {
    // https://stackoverflow.com/questions/9781218/how-to-change-node-jss-console-font-color
    var reset = '\x1b[0m';

    var padEnd = function padEnd(str, width) {
      str = str.toString();

      while (str.length < width) {
        str += ' ';
      }

      return str;
    };

    function isClientSide() {
      return typeof window !== 'undefined' && window.document;
    } // some nice colors for client-side debug


    var css = {
      green: '#7f9c6c',
      red: '#914045',
      blue: '#6699cc',
      magenta: '#6D5685',
      cyan: '#2D85A8',
      yellow: '#e6d7b3',
      black: '#303b50'
    };

    var logClientSide = function logClientSide(doc) {
      var tagset = doc.world.tags;
      doc.list.forEach(function (p) {
        console.log('\n%c"' + p.text() + '"', 'color: #e6d7b3;');
        var terms = p.terms();
        terms.forEach(function (t) {
          var tags = Object.keys(t.tags);
          var text = t.text || '-';

          if (t.implicit) {
            text = '[' + t.implicit + ']';
          }

          var word = "'" + text + "'";
          word = padEnd(word, 8);
          var found = tags.find(function (tag) {
            return tagset[tag] && tagset[tag].color;
          });
          var color = 'steelblue';

          if (tagset[found]) {
            color = tagset[found].color;
            color = css[color];
          }

          console.log("   ".concat(word, "  -  %c").concat(tags.join(', ')), "color: ".concat(color || 'steelblue', ";"));
        });
      });
    }; //cheaper than requiring chalk


    var cli = {
      green: function green(str) {
        return '\x1b[32m' + str + reset;
      },
      red: function red(str) {
        return '\x1b[31m' + str + reset;
      },
      blue: function blue(str) {
        return '\x1b[34m' + str + reset;
      },
      magenta: function magenta(str) {
        return '\x1b[35m' + str + reset;
      },
      cyan: function cyan(str) {
        return '\x1b[36m' + str + reset;
      },
      yellow: function yellow(str) {
        return '\x1b[33m' + str + reset;
      },
      black: function black(str) {
        return '\x1b[30m' + str + reset;
      }
    };

    var tagString = function tagString(tags, world) {
      tags = tags.map(function (tag) {
        if (!world.tags.hasOwnProperty(tag)) {
          return tag;
        }

        var c = world.tags[tag].color || 'blue';
        return cli[c](tag);
      });
      return tags.join(', ');
    }; //output some helpful stuff to the console


    var debug = function debug(doc) {
      if (isClientSide()) {
        logClientSide(doc);
        return doc;
      }

      console.log(cli.blue('====='));
      doc.list.forEach(function (p) {
        console.log(cli.blue('  -----'));
        var terms = p.terms();
        terms.forEach(function (t) {
          var tags = Object.keys(t.tags);
          var text = t.text || '-';

          if (t.implicit) {
            text = '[' + t.implicit + ']';
          }

          {
            text = cli.yellow(text);
          }

          var word = "'" + text + "'";
          word = padEnd(word, 18);
          var str = cli.blue('  | ') + word + '  - ' + tagString(tags, doc.world);
          console.log(str);
        });
      });
      console.log('');
      return doc;
    };

    module.exports = debug;
  });

  var topk = function topk(doc) {
    var list = doc.json({
      text: false,
      terms: false,
      reduced: true
    }); // combine them

    var obj = {};
    list.forEach(function (o) {
      if (!obj[o.reduced]) {
        o.count = 0;
        obj[o.reduced] = o;
      }

      obj[o.reduced].count += 1;
    });
    var arr = Object.keys(obj).map(function (k) {
      return obj[k];
    }); // sort them

    arr.sort(function (a, b) {
      if (a.count > b.count) {
        return -1;
      } else if (a.count < b.count) {
        return 1;
      }

      return 0;
    });
    return arr;
  };

  var _topk = topk;

  /** pretty-print the current document and its tags */

  var debug_1 = function debug_1() {
    _debug(this);
    return this;
  };
  /** some named output formats */


  var out = function out(method) {
    if (method === 'text') {
      return this.text();
    }

    if (method === 'normal') {
      return this.text('normal');
    }

    if (method === 'json') {
      return this.json();
    }

    if (method === 'offset' || method === 'offsets') {
      return this.json({
        offset: true
      });
    }

    if (method === 'array') {
      return this.json({
        terms: false
      }).map(function (obj) {
        return obj.text;
      });
    }

    if (method === 'freq' || method === 'frequency') {
      return _topk(this);
    }

    if (method === 'terms') {
      var list = [];
      this.json({
        text: false,
        terms: {
          text: true
        }
      }).forEach(function (obj) {
        var terms = obj.terms.map(function (t) {
          return t.text;
        });
        terms = terms.filter(function (t) {
          return t;
        });
        list = list.concat(terms);
      });
      return list;
    }

    if (method === 'tags') {
      return this.list.map(function (p) {
        return p.terms().reduce(function (h, t) {
          h[t.clean || t.implicit] = Object.keys(t.tags);
          return h;
        }, {});
      });
    }

    if (method === 'debug') {
      _debug(this);
      return this;
    }

    return this.text();
  };

  var _03Out = {
    debug: debug_1,
    out: out
  };

  var methods$2 = {
    /** alphabetical order */
    alpha: function alpha(a, b) {
      var left = a.text('clean');
      var right = b.text('clean');

      if (left < right) {
        return -1;
      }

      if (left > right) {
        return 1;
      }

      return 0;
    },

    /** count the # of characters of each match */
    length: function length(a, b) {
      var left = a.text().trim().length;
      var right = b.text().trim().length;

      if (left < right) {
        return 1;
      }

      if (left > right) {
        return -1;
      }

      return 0;
    },

    /** count the # of terms in each match */
    wordCount: function wordCount(a, b) {
      var left = a.wordCount();
      var right = b.wordCount();

      if (left < right) {
        return 1;
      }

      if (left > right) {
        return -1;
      }

      return 0;
    }
  };
  /** sort by # of duplicates in the document*/

  var byFreq = function byFreq(doc) {
    var counts = {};
    var options = {
      "case": true,
      punctuation: false,
      whitespace: true,
      unicode: true
    };
    doc.list.forEach(function (p) {
      var str = p.text(options);
      counts[str] = counts[str] || 0;
      counts[str] += 1;
    }); // sort by freq

    doc.list.sort(function (a, b) {
      var left = counts[a.text(options)];
      var right = counts[b.text(options)];

      if (left < right) {
        return 1;
      }

      if (left > right) {
        return -1;
      }

      return 0;
    });
    return doc;
  }; // order results 'chronologically', or document-order


  var sortSequential = function sortSequential(doc) {
    var order = {};
    doc.json({
      terms: {
        offset: true
      }
    }).forEach(function (o) {
      order[o.terms[0].id] = o.terms[0].offset.start;
    });
    doc.list = doc.list.sort(function (a, b) {
      if (order[a.start] > order[b.start]) {
        return 1;
      } else if (order[a.start] < order[b.start]) {
        return -1;
      }

      return 0;
    });
    return doc;
  }; //aliases


  methods$2.alphabetical = methods$2.alpha;
  methods$2.wordcount = methods$2.wordCount; // aliases for sequential ordering

  var seqNames = {
    index: true,
    sequence: true,
    seq: true,
    sequential: true,
    chron: true,
    chronological: true
  };
  /** re-arrange the order of the matches (in place) */

  var sort = function sort(input) {
    input = input || 'alpha'; //do this one up-front

    if (input === 'freq' || input === 'frequency' || input === 'topk') {
      return byFreq(this);
    }

    if (seqNames.hasOwnProperty(input)) {
      return sortSequential(this);
    }

    input = methods$2[input] || input; // apply sort method on each phrase

    if (typeof input === 'function') {
      this.list = this.list.sort(input);
      return this;
    }

    return this;
  };
  /** reverse the order of the matches, but not the words */


  var reverse = function reverse() {
    var list = [].concat(this.list);
    list = list.reverse();
    return this.buildFrom(list);
  };
  /** remove any duplicate matches */


  var unique$4 = function unique() {
    var list = [].concat(this.list);
    var obj = {};
    list = list.filter(function (p) {
      var str = p.text('reduced').trim();

      if (obj.hasOwnProperty(str) === true) {
        return false;
      }

      obj[str] = true;
      return true;
    });
    return this.buildFrom(list);
  };

  var _01Sort = {
    sort: sort,
    reverse: reverse,
    unique: unique$4
  };

  var isPunct = /[\[\]{}⟨⟩:,،、‒–—―…‹›«»‐\-;\/⁄·*\•^†‡°¡¿※№÷׺ª%‰=‱¶§~|‖¦©℗®℠™¤₳฿]/g;
  var quotes = /['‘’“”"′″‴]+/g;
  var methods$3 = {
    // cleanup newlines and extra spaces
    whitespace: function whitespace(doc) {
      var termArr = doc.list.map(function (ts) {
        return ts.terms();
      });
      termArr.forEach(function (terms, o) {
        terms.forEach(function (t, i) {
          // keep dashes between words
          if (t.hasDash() === true) {
            t.post = ' - ';
            return;
          } // remove existing spaces


          t.pre = t.pre.replace(/\s/g, '');
          t.post = t.post.replace(/\s/g, ''); //last word? ensure there's a next sentence.

          if (terms.length - 1 === i && !termArr[o + 1]) {
            return;
          } // no extra spaces for contractions


          if (t.implicit && Boolean(t.text) === true) {
            return;
          } // no extra spaces for hyphenated words


          if (t.hasHyphen() === true) {
            return;
          }

          t.post += ' ';
        });
      });
    },
    punctuation: function punctuation(termList) {
      termList.forEach(function (t) {
        // space between hyphenated words
        if (t.hasHyphen() === true) {
          t.post = ' ';
        }

        t.pre = t.pre.replace(isPunct, '');
        t.post = t.post.replace(isPunct, ''); // elipses

        t.post = t.post.replace(/\.\.\./, ''); // only allow one exclamation

        if (/!/.test(t.post) === true) {
          t.post = t.post.replace(/!/g, '');
          t.post = '!' + t.post;
        } // only allow one question mark


        if (/\?/.test(t.post) === true) {
          t.post = t.post.replace(/[\?!]*/, '');
          t.post = '?' + t.post;
        }
      });
    },
    unicode: function unicode(termList) {
      termList.forEach(function (t) {
        if (t.isImplicit() === true) {
          return;
        }

        t.text = unicode_1(t.text);
      });
    },
    quotations: function quotations(termList) {
      termList.forEach(function (t) {
        t.post = t.post.replace(quotes, '');
        t.pre = t.pre.replace(quotes, '');
      });
    },
    adverbs: function adverbs(doc) {
      doc.match('#Adverb').not('(not|nary|seldom|never|barely|almost|basically|so)').remove();
    },
    // remove the '.' from 'Mrs.' (safely)
    abbreviations: function abbreviations(doc) {
      doc.list.forEach(function (ts) {
        var terms = ts.terms();
        terms.forEach(function (t, i) {
          if (t.tags.Abbreviation === true && terms[i + 1]) {
            t.post = t.post.replace(/^\./, '');
          }
        });
      });
    }
  };
  var _methods = methods$3;

  var defaults = {
    // light
    whitespace: true,
    unicode: true,
    punctuation: true,
    emoji: true,
    acronyms: true,
    abbreviations: true,
    // medium
    "case": false,
    contractions: false,
    parentheses: false,
    quotations: false,
    adverbs: false,
    // heavy (loose legibility)
    possessives: false,
    verbs: false,
    nouns: false,
    honorifics: false // pronouns: true,

  };
  var mapping$1 = {
    light: {},
    medium: {
      "case": true,
      contractions: true,
      parentheses: true,
      quotations: true,
      adverbs: true
    }
  };
  mapping$1.heavy = Object.assign({}, mapping$1.medium, {
    possessives: true,
    verbs: true,
    nouns: true,
    honorifics: true
  });
  /** common ways to clean-up the document, and reduce noise */

  var normalize = function normalize(options) {
    options = options || {}; // support named forms

    if (typeof options === 'string') {
      options = mapping$1[options] || {};
    } // set defaults


    options = Object.assign({}, defaults, options); // clear the cache

    this.uncache();
    var termList = this.termList(); // lowercase things

    if (options["case"]) {
      this.toLowerCase();
    } //whitespace


    if (options.whitespace) {
      _methods.whitespace(this);
    } // unicode: é -> e


    if (options.unicode) {
      _methods.unicode(termList);
    } //punctuation - keep sentence punctation, quotes, parenths


    if (options.punctuation) {
      _methods.punctuation(termList);
    } // remove ':)'


    if (options.emoji) {
      this.remove('(#Emoji|#Emoticon)');
    } // 'f.b.i.' -> 'FBI'


    if (options.acronyms) {
      this.acronyms().strip(); // .toUpperCase()
    } // remove period from abbreviations


    if (options.abbreviations) {
      _methods.abbreviations(this);
    } // --Medium methods--
    // `isn't` -> 'is not'


    if (options.contraction || options.contractions) {
      this.contractions().expand();
    } // '(word)' -> 'word'


    if (options.parentheses) {
      this.parentheses().unwrap();
    } // remove "" punctuation


    if (options.quotations || options.quotes) {
      _methods.quotations(termList);
    } // remove any un-necessary adverbs


    if (options.adverbs) {
      _methods.adverbs(this);
    } // --Heavy methods--
    // `cory hart's -> cory hart'


    if (options.possessive || options.possessives) {
      this.possessives().strip();
    } // 'he walked' -> 'he walk'


    if (options.verbs) {
      this.verbs().toInfinitive();
    } // 'three dogs' -> 'three dog'


    if (options.nouns || options.plurals) {
      this.nouns().toSingular();
    } // remove 'Mr.' from 'Mr John Smith'


    if (options.honorifics) {
      this.remove('#Honorific');
    }

    return this;
  };

  var _02Normalize = {
    normalize: normalize
  };

  var _03Split = createCommonjsModule(function (module, exports) {
    /** return a Document with three parts for every match
     * seperate everything before the word, as a new phrase
     */
    exports.splitOn = function (reg) {
      // if there's no match, split parent, instead
      if (!reg) {
        var parent = this.parent();
        return parent.splitOn(this);
      } //start looking for a match..


      var regs = syntax_1(reg);
      var matches = [];
      this.list.forEach(function (p) {
        var foundEm = p.match(regs); //no match here, add full sentence

        if (foundEm.length === 0) {
          matches.push(p);
          return;
        } // we found something here.


        var carry = p;
        foundEm.forEach(function (found) {
          var parts = carry.splitOn(found); // add em in

          if (parts.before) {
            matches.push(parts.before);
          }

          if (parts.match) {
            matches.push(parts.match);
          } // start matching now on the end


          carry = parts.after;
        }); // add that last part

        if (carry) {
          matches.push(carry);
        }
      });
      return this.buildFrom(matches);
    };
    /** return a Document with two parts for every match
     * seperate everything after the word, as a new phrase
     */


    exports.splitAfter = function (reg) {
      // if there's no match, split parent, instead
      if (!reg) {
        var parent = this.parent();
        return parent.splitAfter(this);
      } // start looking for our matches


      var regs = syntax_1(reg);
      var matches = [];
      this.list.forEach(function (p) {
        var foundEm = p.match(regs); //no match here, add full sentence

        if (foundEm.length === 0) {
          matches.push(p);
          return;
        } // we found something here.


        var carry = p;
        foundEm.forEach(function (found) {
          var parts = carry.splitOn(found); // add em in

          if (parts.before && parts.match) {
            // merge these two together
            parts.before.length += parts.match.length;
            matches.push(parts.before);
          } else if (parts.match) {
            matches.push(parts.match);
          } // start matching now on the end


          carry = parts.after;
        }); // add that last part

        if (carry) {
          matches.push(carry);
        }
      });
      return this.buildFrom(matches);
    };

    exports.split = exports.splitAfter; //i guess?

    /** return a Document with two parts for every match */

    exports.splitBefore = function (reg) {
      // if there's no match, split parent, instead
      if (!reg) {
        var parent = this.parent();
        return parent.splitBefore(this);
      } //start looking for a match..


      var regs = syntax_1(reg);
      var matches = [];
      this.list.forEach(function (p) {
        var foundEm = p.match(regs); //no match here, add full sentence

        if (foundEm.length === 0) {
          matches.push(p);
          return;
        } // we found something here.


        var carry = p;
        foundEm.forEach(function (found) {
          var parts = carry.splitOn(found); // add before part in

          if (parts.before) {
            matches.push(parts.before);
          } // merge match+after


          if (parts.match && parts.after) {
            parts.match.length += parts.after.length;
          } // start matching now on the end


          carry = parts.match;
        }); // add that last part

        if (carry) {
          matches.push(carry);
        }
      });
      return this.buildFrom(matches);
    };
    /** split a document into labeled sections */


    exports.segment = function (regs, options) {
      regs = regs || {};
      options = options || {
        text: true
      };
      var doc = this;
      var keys = Object.keys(regs); // split em

      keys.forEach(function (k) {
        doc = doc.splitOn(k);
      }); //add labels for each section

      doc.list.forEach(function (p) {
        for (var i = 0; i < keys.length; i += 1) {
          if (p.has(keys[i])) {
            p.segment = regs[keys[i]];
            return;
          }
        }
      });
      return doc.list.map(function (p) {
        var res = p.json(options);
        res.segment = p.segment || null;
        return res;
      });
    };
  });

  var eachTerm = function eachTerm(doc, fn) {
    var world = doc.world;
    doc.list.forEach(function (p) {
      p.terms().forEach(function (t) {
        return t[fn](world);
      });
    });
    return doc;
  };
  /** turn every letter of every term to lower-cse */


  var toLowerCase = function toLowerCase() {
    return eachTerm(this, 'toLowerCase');
  };
  /** turn every letter of every term to upper case */


  var toUpperCase = function toUpperCase() {
    return eachTerm(this, 'toUpperCase');
  };
  /** upper-case the first letter of each term */


  var toTitleCase = function toTitleCase() {
    return eachTerm(this, 'toTitleCase');
  };
  /** remove whitespace and title-case each term */


  var toCamelCase = function toCamelCase() {
    this.list.forEach(function (p) {
      //remove whitespace
      var terms = p.terms();
      terms.forEach(function (t, i) {
        if (i !== 0) {
          t.toTitleCase();
        }

        if (i !== terms.length - 1) {
          t.post = '';
        }
      });
    }); // this.tag('#CamelCase', 'toCamelCase')

    return this;
  };

  var _04Case = {
    toLowerCase: toLowerCase,
    toUpperCase: toUpperCase,
    toTitleCase: toTitleCase,
    toCamelCase: toCamelCase
  };

  var _05Whitespace = createCommonjsModule(function (module, exports) {
    /** add this punctuation or whitespace before each match: */
    exports.pre = function (str, concat) {
      if (str === undefined) {
        return this.list[0].terms(0).pre;
      }

      this.list.forEach(function (p) {
        var term = p.terms(0);

        if (concat === true) {
          term.pre += str;
        } else {
          term.pre = str;
        }
      });
      return this;
    };
    /** add this punctuation or whitespace after each match: */


    exports.post = function (str, concat) {
      // return array of post strings
      if (str === undefined) {
        return this.list.map(function (p) {
          var terms = p.terms();
          var term = terms[terms.length - 1];
          return term.post;
        });
      } // set post string on all ends


      this.list.forEach(function (p) {
        var terms = p.terms();
        var term = terms[terms.length - 1];

        if (concat === true) {
          term.post += str;
        } else {
          term.post = str;
        }
      });
      return this;
    };
    /** remove start and end whitespace */


    exports.trim = function () {
      this.list = this.list.map(function (p) {
        return p.trim();
      });
      return this;
    };
    /** connect words with hyphen, and remove whitespace */


    exports.hyphenate = function () {
      this.list.forEach(function (p) {
        var terms = p.terms(); //remove whitespace

        terms.forEach(function (t, i) {
          if (i !== 0) {
            t.pre = '';
          }

          if (terms[i + 1]) {
            t.post = '-';
          }
        });
      });
      return this;
    };
    /** remove hyphens between words, and set whitespace */


    exports.dehyphenate = function () {
      var hasHyphen = /(-|–|—)/;
      this.list.forEach(function (p) {
        var terms = p.terms(); //remove whitespace

        terms.forEach(function (t) {
          if (hasHyphen.test(t.post)) {
            t.post = ' ';
          }
        });
      });
      return this;
    };

    exports.deHyphenate = exports.dehyphenate;
    /** add quotations around these matches */

    exports.toQuotations = function (start, end) {
      start = start || "\"";
      end = end || "\"";
      this.list.forEach(function (p) {
        var terms = p.terms();
        terms[0].pre = start + terms[0].pre;
        var last = terms[terms.length - 1];
        last.post = end + last.post;
      });
      return this;
    };

    exports.toQuotation = exports.toQuotations;
    /** add brackets around these matches */

    exports.toParentheses = function (start, end) {
      start = start || "(";
      end = end || ")";
      this.list.forEach(function (p) {
        var terms = p.terms();
        terms[0].pre = start + terms[0].pre;
        var last = terms[terms.length - 1];
        last.post = end + last.post;
      });
      return this;
    };
  });

  /** make all phrases into one phrase */
  var join = function join(str) {
    // clear the cache
    this.uncache(); // make one large phrase - 'main'

    var main = this.list[0];
    var before = main.length;
    var removed = {};

    for (var i = 1; i < this.list.length; i++) {
      var p = this.list[i];
      removed[p.start] = true;
      var term = main.lastTerm(); // add whitespace between them

      if (str) {
        term.post += str;
      } //  main -> p


      term.next = p.start; // main <- p

      p.terms(0).prev = term.id;
      main.length += p.length;
      main.cache = {};
    } // parents are bigger than than their children.
    // when we increase a child, we increase their parent too.


    var increase = main.length - before;
    this.parents().forEach(function (doc) {
      // increase length on each effected phrase
      doc.list.forEach(function (p) {
        var terms = p.terms();

        for (var _i = 0; _i < terms.length; _i++) {
          if (terms[_i].id === main.start) {
            p.length += increase;
            break;
          }
        }

        p.cache = {};
      }); // remove redundant phrases now

      doc.list = doc.list.filter(function (p) {
        return removed[p.start] !== true;
      });
    }); // return one major phrase

    return this.buildFrom([main]);
  };

  var _06Join = {
    join: join
  };

  var postPunct = /[,\)"';:\-–—\.…]/; // const irregulars = {
  //   'will not': `won't`,
  //   'i am': `i'm`,
  // }

  var setContraction = function setContraction(m, suffix) {
    if (!m.found) {
      return;
    }

    var terms = m.termList(); //avoid any problematic punctuation

    for (var i = 0; i < terms.length - 1; i++) {
      var t = terms[i];

      if (postPunct.test(t.post)) {
        return;
      }
    } // set them as implict


    terms.forEach(function (t) {
      t.implicit = t.clean;
    }); // perform the contraction

    terms[0].text += suffix; // clean-up the others

    terms.slice(1).forEach(function (t) {
      t.text = '';
    });

    for (var _i = 0; _i < terms.length - 1; _i++) {
      var _t = terms[_i];
      _t.post = _t.post.replace(/ /, '');
    }
  };
  /** turn 'i am' into i'm */


  var contract = function contract() {
    var doc = this.not('@hasContraction'); // we are -> we're

    var m = doc.match('(we|they|you) are');
    setContraction(m, "'re"); // they will -> they'll

    m = doc.match('(he|she|they|it|we|you) will');
    setContraction(m, "'ll"); // she is -> she's

    m = doc.match('(he|she|they|it|we) is');
    setContraction(m, "'s"); // spencer is -> spencer's

    m = doc.match('#Person is');
    setContraction(m, "'s"); // spencer would -> spencer'd

    m = doc.match('#Person would');
    setContraction(m, "'d"); // would not -> wouldn't

    m = doc.match('(is|was|had|would|should|could|do|does|have|has|can) not');
    setContraction(m, "n't"); // i have -> i've

    m = doc.match('(i|we|they) have');
    setContraction(m, "'ve"); // would have -> would've

    m = doc.match('(would|should|could) have');
    setContraction(m, "'ve"); // i am -> i'm

    m = doc.match('i am');
    setContraction(m, "'m"); // going to -> gonna

    m = doc.match('going to');
    return this;
  };

  var _07Contract = {
    contract: contract
  };

  var methods$4 = Object.assign({}, _01Utils$1, _02Accessors, _03Match, _04Tag, _05Loops, _06Lookup, _07Cache, _01Replace, _02Insert, _01Text, _02Json, _03Out, _01Sort, _02Normalize, _03Split, _04Case, _05Whitespace, _06Join, _07Contract);

  var methods$5 = {}; // allow helper methods like .adjectives() and .adverbs()

  var arr = [['terms', '.'], ['hyphenated', '@hasHyphen .'], ['adjectives', '#Adjective'], ['hashTags', '#HashTag'], ['emails', '#Email'], ['emoji', '#Emoji'], ['emoticons', '#Emoticon'], ['atMentions', '#AtMention'], ['urls', '#Url'], ['adverbs', '#Adverb'], ['pronouns', '#Pronoun'], ['conjunctions', '#Conjunction'], ['prepositions', '#Preposition']];
  arr.forEach(function (a) {
    methods$5[a[0]] = function (n) {
      var m = this.match(a[1]);

      if (typeof n === 'number') {
        m = m.get(n);
      }

      return m;
    };
  }); // aliases

  methods$5.emojis = methods$5.emoji;
  methods$5.atmentions = methods$5.atMentions;
  methods$5.words = methods$5.terms;
  /** return anything tagged as a phone number */

  methods$5.phoneNumbers = function (n) {
    var m = this.splitAfter('@hasComma');
    m = m.match('#PhoneNumber+');

    if (typeof n === 'number') {
      m = m.get(n);
    }

    return m;
  };
  /** Deprecated: please use compromise-numbers plugin */


  methods$5.money = function (n) {
    var m = this.match('#Money #Currency?');

    if (typeof n === 'number') {
      m = m.get(n);
    }

    return m;
  };
  /** return all cities, countries, addresses, and regions */


  methods$5.places = function (n) {
    // don't split 'paris, france'
    var keep = this.match('(#City && @hasComma) (#Region|#Country)'); // but split the other commas

    var m = this.not(keep).splitAfter('@hasComma'); // combine them back together

    m = m.concat(keep);
    m.sort('index');
    m = m.match('#Place+');

    if (typeof n === 'number') {
      m = m.get(n);
    }

    return m;
  };
  /** return all schools, businesses and institutions */


  methods$5.organizations = function (n) {
    var m = this.clauses();
    m = m.match('#Organization+');

    if (typeof n === 'number') {
      m = m.get(n);
    }

    return m;
  }; //combine them with .topics() method


  methods$5.entities = function (n) {
    var r = this.clauses(); // Find people, places, and organizations

    var yup = r.people();
    yup = yup.concat(r.places());
    yup = yup.concat(r.organizations());
    var ignore = ['someone', 'man', 'woman', 'mother', 'brother', 'sister', 'father'];
    yup = yup.not(ignore); //return them to normal ordering

    yup.sort('sequence'); // yup.unique() //? not sure

    if (typeof n === 'number') {
      yup = yup.get(n);
    }

    return yup;
  }; //aliases


  methods$5.things = methods$5.entities;
  methods$5.topics = methods$5.entities;
  var _simple = methods$5;

  var underOver = /^(under|over)-?/;
  /** match a word-sequence, like 'super bowl' in the lexicon */

  var tryMultiple = function tryMultiple(terms, t, world) {
    var lex = world.words; //try a two-word version

    var txt = terms[t].reduced + ' ' + terms[t + 1].reduced;

    if (lex[txt] !== undefined && lex.hasOwnProperty(txt) === true) {
      terms[t].tag(lex[txt], 'lexicon-two', world);
      terms[t + 1].tag(lex[txt], 'lexicon-two', world);
      return 1;
    } //try a three-word version?


    if (t + 2 < terms.length) {
      txt += ' ' + terms[t + 2].reduced;

      if (lex[txt] !== undefined && lex.hasOwnProperty(txt) === true) {
        terms[t].tag(lex[txt], 'lexicon-three', world);
        terms[t + 1].tag(lex[txt], 'lexicon-three', world);
        terms[t + 2].tag(lex[txt], 'lexicon-three', world);
        return 2;
      }
    } //try a four-word version?


    if (t + 3 < terms.length) {
      txt += ' ' + terms[t + 3].reduced;

      if (lex[txt] !== undefined && lex.hasOwnProperty(txt) === true) {
        terms[t].tag(lex[txt], 'lexicon-four', world);
        terms[t + 1].tag(lex[txt], 'lexicon-four', world);
        terms[t + 2].tag(lex[txt], 'lexicon-four', world);
        terms[t + 3].tag(lex[txt], 'lexicon-four', world);
        return 3;
      }
    }

    return 0;
  };
  /** look at each word in our list of known-words */


  var checkLexicon = function checkLexicon(terms, world) {
    var lex = world.words;
    var hasCompound = world.hasCompound; // use reduced?
    //go through each term, and check the lexicon

    for (var t = 0; t < terms.length; t += 1) {
      var str = terms[t].clean; //is it the start of a compound word, like 'super bowl'?

      if (hasCompound[str] === true && t + 1 < terms.length) {
        var foundWords = tryMultiple(terms, t, world);

        if (foundWords > 0) {
          t += foundWords; //skip any already-found words

          continue;
        }
      } //try one-word lexicon


      if (lex[str] !== undefined && lex.hasOwnProperty(str) === true) {
        terms[t].tag(lex[str], 'lexicon', world);
        continue;
      } // look at reduced version of term, too


      if (str !== terms[t].reduced && lex.hasOwnProperty(terms[t].reduced) === true) {
        terms[t].tag(lex[terms[t].reduced], 'lexicon', world);
        continue;
      } // prefix strip: try to match 'take' for 'undertake'


      if (underOver.test(str) === true) {
        var noPrefix = str.replace(underOver, '');

        if (lex.hasOwnProperty(noPrefix) === true) {
          terms[t].tag(lex[noPrefix], 'noprefix-lexicon', world);
        }
      }
    }

    return terms;
  };

  var _01Lexicon = checkLexicon;

  var apostrophes = /[\'‘’‛‵′`´]$/;
  var perSec = /^(m|k|cm|km|m)\/(s|h|hr)$/; // '5 k/m'
  //

  var checkPunctuation = function checkPunctuation(terms, i, world) {
    var term = terms[i]; //check hyphenation
    // if (term.post.indexOf('-') !== -1 && terms[i + 1] && terms[i + 1].pre === '') {
    //   term.tag('Hyphenated', 'has-hyphen', world)
    // }
    // support 'head-over'
    // if (term.hasHyphen() === true) {
    //   console.log(term.tags)
    // }
    // console.log(term.hasHyphen(), term.text)
    //an end-tick (trailing apostrophe) - flanders', or Carlos'

    if (apostrophes.test(term.text)) {
      if (!apostrophes.test(term.pre) && !apostrophes.test(term.post) && term.clean.length > 2) {
        var endChar = term.clean[term.clean.length - 2]; //flanders'

        if (endChar === 's') {
          term.tag(['Possessive', 'Noun'], 'end-tick', world);
          return;
        } //chillin'


        if (endChar === 'n') {
          term.tag(['Gerund'], 'chillin', world);
        }
      }
    } // '5 km/s'


    if (perSec.test(term.text)) {
      term.tag('Unit', 'per-sec', world);
    } // 'NASA' is, but not 'i REALLY love it.'
    // if (term.tags.Noun === true && isAcronym(term, world)) {
    //   term.tag('Acronym', 'acronym-step', world)
    //   term.tag('Noun', 'acronym-infer', world)
    // } else if (!oneLetterWord.hasOwnProperty(term.text) && oneLetterAcronym.test(term.text)) {
    //   term.tag('Acronym', 'one-letter-acronym', world)
    //   term.tag('Noun', 'one-letter-infer', world)
    // }

  };

  var _02Punctuation$1 = checkPunctuation;

  //these are regexes applied to t.text, instead of t.clean
  // order matters.
  var startsWith = [//web tags
  [/^[\w\.]+@[\w\.]+\.[a-z]{2,3}$/, 'Email'], //not fancy
  [/^#[a-z0-9_\u00C0-\u00FF]{2,}$/, 'HashTag'], [/^@\w{2,}$/, 'AtMention'], [/^(https?:\/\/|www\.)\w+\.[a-z]{2,3}/, 'Url'], //with http/www
  [/^[\w./]+\.(com|net|gov|org|ly|edu|info|biz|ru|jp|de|in|uk|br)/, 'Url'], //http://mostpopularwebsites.net/top-level-domain
  //dates/times
  [/^[012]?[0-9](:[0-5][0-9])(:[0-5][0-9])$/, 'Time'], //4:32:32
  [/^[012]?[0-9](:[0-5][0-9])?(:[0-5][0-9])? ?(am|pm)$/, 'Time'], //4pm
  [/^[012]?[0-9](:[0-5][0-9])(:[0-5][0-9])? ?(am|pm)?$/, 'Time'], //4:00pm
  [/^[PMCE]ST$/, 'Time'], //PST, time zone abbrevs
  [/^utc ?[+-]?[0-9]+?$/, 'Time'], //UTC 8+
  [/^[a-z0-9]*? o\'?clock$/, 'Time'], //3 oclock
  [/^[0-9]{1,4}-[0-9]{1,2}-[0-9]{1,4}$/, 'Date'], // 03-02-89
  [/^[0-9]{1,4}\/[0-9]{1,2}\/[0-9]{1,4}$/, 'Date'], // 03/02/89
  [/^[0-9]{1,4}-[a-z]{2,9}-[0-9]{1,4}$/i, 'Date'], // 03-March-89
  //names
  [/^ma?c\'.*/, 'LastName'], //mc'adams
  [/^o\'[drlkn].*/, 'LastName'], //o'douggan
  [/^ma?cd[aeiou]/, 'LastName'], //macdonell - Last patterns https://en.wikipedia.org/wiki/List_of_family_name_affixes
  //slang things
  [/^(lol)+[sz]$/, 'Expression'], //lol
  [/^woo+a*?h?$/, 'Expression'], //whoaa, wooo
  [/^(un|de|re)\\-[a-z\u00C0-\u00FF]{2}/, 'Verb'], // [/^(over|under)[a-z]{2,}/, 'Adjective'],
  [/^[0-9]{1,4}\.[0-9]{1,2}\.[0-9]{1,4}$/, 'Date'], // 03-02-89
  //phone numbers
  [/^[0-9]{3}-[0-9]{4}$/, 'PhoneNumber'], //589-3809
  [/^(\+?[0-9][ -])?[0-9]{3}[ -]?[0-9]{3}-[0-9]{4}$/, 'PhoneNumber'], //632-589-3809
  //money
  // currency regex
  // /[\$\xA2-\xA5\u058F\u060B\u09F2\u09F3\u09FB\u0AF1\u0BF9\u0E3F\u17DB\u20A0-\u20BD\uA838\uFDFC\uFE69\uFF04\uFFE0\uFFE1\uFFE5\uFFE6]
  //like $5.30
  [/^[-+]?[\$\xA2-\xA5\u058F\u060B\u09F2\u09F3\u09FB\u0AF1\u0BF9\u0E3F\u17DB\u20A0-\u20BD\uA838\uFDFC\uFE69\uFF04\uFFE0\uFFE1\uFFE5\uFFE6][-+]?[0-9]+(,[0-9]{3})*(\.[0-9]+)?(k|m|b|bn)?\+?$/, ['Money', 'Value']], //like 5.30$
  [/^[-+]?[0-9]+(,[0-9]{3})*(\.[0-9]+)?[\$\xA2-\xA5\u058F\u060B\u09F2\u09F3\u09FB\u0AF1\u0BF9\u0E3F\u17DB\u20A0-\u20BD\uA838\uFDFC\uFE69\uFF04\uFFE0\uFFE1\uFFE5\uFFE6]\+?$/, ['Money', 'Value']], //like 400usd
  [/^[-+]?[0-9]([0-9,.])+?(usd|eur|jpy|gbp|cad|aud|chf|cny|hkd|nzd|kr|rub)$/i, ['Money', 'Value']], //numbers
  // 50 | -50 | 3.23  | 5,999.0  | 10+
  [/^[-+]?[0-9]+(,[0-9]{3})*(\.[0-9]+)?\+?$/, ['Cardinal', 'NumericValue']], [/^[-+]?[0-9]+(,[0-9]{3})*(\.[0-9]+)?(st|nd|rd|th)$/, ['Ordinal', 'NumericValue']], // .73th
  [/^\.[0-9]+\+?$/, ['Cardinal', 'NumericValue']], //percent
  [/^[-+]?[0-9]+(,[0-9]{3})*(\.[0-9]+)?%\+?$/, ['Percent', 'Cardinal', 'NumericValue']], //7%  ..
  [/^\.[0-9]+%$/, ['Percent', 'Cardinal', 'NumericValue']], //.7%  ..
  //fraction
  [/^[0-9]{1,4}\/[0-9]{1,4}$/, 'Fraction'], //3/2ths
  //range
  [/^[0-9.]{1,2}[-–][0-9]{1,2}$/, ['Value', 'NumberRange']], //7-8
  [/^[0-9.]{1,4}(st|nd|rd|th)?[-–][0-9\.]{1,4}(st|nd|rd|th)?$/, 'NumberRange'], //5-7
  //with unit
  [/^[0-9.]+([a-z]{1,4})$/, 'Value'] //like 5tbsp
  //ordinal
  // [/^[0-9][0-9,.]*(st|nd|rd|r?th)$/, ['NumericValue', 'Ordinal']], //like 5th
  // [/^[0-9]+(st|nd|rd|th)$/, 'Ordinal'], //like 5th
  ];

  var romanNumeral = /^[IVXLCDM]{2,}$/;
  var romanNumValid = /^M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$/; //  https://stackoverflow.com/a/267405/168877
  //try each of the ^regexes in our list

  var checkRegex = function checkRegex(term, world) {
    var str = term.text; // do them all!

    for (var r = 0; r < startsWith.length; r += 1) {
      if (startsWith[r][0].test(str) === true) {
        term.tagSafe(startsWith[r][1], 'prefix #' + r, world);
        break;
      }
    } // do some more!
    //roman numberals - XVII


    if (term.text.length >= 2 && romanNumeral.test(str) && romanNumValid.test(str)) {
      term.tag('RomanNumeral', 'xvii', world);
    }
  };

  var _03Prefixes = checkRegex;

  //regex suffix patterns and their most common parts of speech,
  //built using wordnet, by spencer kelly.
  //this mapping shrinks-down the uglified build
  var Adj = 'Adjective';
  var Inf = 'Infinitive';
  var Pres = 'PresentTense';
  var Sing = 'Singular';
  var Past = 'PastTense';
  var Adverb = 'Adverb';
  var Exp = 'Expression';
  var Actor = 'Actor';
  var Verb = 'Verb';
  var Noun = 'Noun';
  var Last = 'LastName'; //the order here matters.
  //regexes indexed by mandated last-character

  var endsWith$1 = {
    a: [[/.[aeiou]na$/, Noun], [/.[oau][wvl]ska$/, Last], //polish (female)
    [/.[^aeiou]ica$/, Sing], [/^([hyj]a)+$/, Exp] //hahah
    ],
    c: [[/.[^aeiou]ic$/, Adj]],
    d: [//==-ed==
    //double-consonant
    [/[aeiou](pp|ll|ss|ff|gg|tt|rr|bb|nn|mm)ed$/, Past], //popped, planned
    //double-vowel
    [/.[aeo]{2}[bdgmnprvz]ed$/, Past], //beeped, mooned, veered
    //-hed
    [/.[aeiou][sg]hed$/, Past], //stashed, sighed
    //-rd
    [/.[aeiou]red$/, Past], //stored
    [/.[aeiou]r?ried$/, Past], //buried
    //-led
    [/.[bcdgtr]led$/, Past], //startled, rumbled
    [/.[aoui]f?led$/, Past], //impaled, stifled
    //-sed
    [/.[iao]sed$/, Past], //franchised
    [/[aeiou]n?[cs]ed$/, Past], //laced, lanced
    //-med
    [/[aeiou][rl]?[mnf]ed$/, Past], //warmed, attained, engulfed
    //-ked
    [/[aeiou][ns]?c?ked$/, Past], //hooked, masked
    //-ged
    [/[aeiou][nl]?ged$/, Past], //engaged
    //-ted
    [/.[tdbwxz]ed$/, Past], //bribed, boxed
    [/[^aeiou][aeiou][tvx]ed$/, Past], //boxed
    //-ied
    [/.[cdlmnprstv]ied$/, Past], //rallied
    [/[^aeiou]ard$/, Sing], //card
    [/[aeiou][^aeiou]id$/, Adj], [/.[vrl]id$/, Adj]],
    e: [[/.[lnr]ize$/, Inf], [/.[^aeiou]ise$/, Inf], [/.[aeiou]te$/, Inf], [/.[^aeiou][ai]ble$/, Adj], [/.[^aeiou]eable$/, Adj], [/.[ts]ive$/, Adj]],
    h: [[/.[^aeiouf]ish$/, Adj], [/.v[iy]ch$/, Last], //east-europe
    [/^ug?h+$/, Exp], //uhh
    [/^uh[ -]?oh$/, Exp] //uhoh
    ],
    i: [[/.[oau][wvl]ski$/, Last] //polish (male)
    ],
    k: [[/^(k){2}$/, Exp] //kkkk
    ],
    l: [[/.[gl]ial$/, Adj], [/.[^aeiou]ful$/, Adj], [/.[nrtumcd]al$/, Adj], [/.[^aeiou][ei]al$/, Adj]],
    m: [[/.[^aeiou]ium$/, Sing], [/[^aeiou]ism$/, Sing], [/^h*u*m+$/, Exp], //mmmmmmm / ummmm / huuuuuummmmmm
    [/^\d+ ?[ap]m$/, 'Date']],
    n: [[/.[lsrnpb]ian$/, Adj], [/[^aeiou]ician$/, Actor], [/[aeiou][ktrp]in$/, 'Gerund'] // 'cookin', 'hootin'
    ],
    o: [[/^no+$/, Exp], //noooo
    [/^(yo)+$/, Exp], //yoyo
    [/^woo+[pt]?$/, Exp] //woo
    ],
    r: [[/.[bdfklmst]ler$/, 'Noun'], [/.[ilk]er$/, 'Comparative'], [/[aeiou][pns]er$/, Sing], [/[^i]fer$/, Inf], [/.[^aeiou][ao]pher$/, Actor]],
    t: [[/.[di]est$/, 'Superlative'], [/.[icldtgrv]ent$/, Adj], [/[aeiou].*ist$/, Adj], [/^[a-z]et$/, Verb]],
    s: [[/.[rln]ates$/, Pres], [/.[^z]ens$/, Verb], [/.[lstrn]us$/, Sing], [/.[aeiou]sks$/, Pres], //masks
    [/.[aeiou]kes$/, Pres], //bakes
    [/[aeiou][^aeiou]is$/, Sing], [/[a-z]\'s$/, Noun], [/^yes+$/, Exp] //yessss
    ],
    v: [[/.[^aeiou][ai][kln]ov$/, Last] //east-europe
    ],
    y: [[/.[cts]hy$/, Adj], [/.[st]ty$/, Adj], [/.[gk]y$/, Adj], [/.[tnl]ary$/, Adj], [/.[oe]ry$/, Sing], [/[rdntkbhs]ly$/, Adverb], [/...lly$/, Adverb], [/[bszmp]{2}y$/, Adj], [/.(gg|bb|zz)ly$/, Adj], [/.[aeiou]my$/, Adj], [/[ea]{2}zy$/, Adj], [/.[^aeiou]ity$/, Sing]]
  };

  //just a foolish lookup of known suffixes
  var Adj$1 = 'Adjective';
  var Inf$1 = 'Infinitive';
  var Pres$1 = 'PresentTense';
  var Sing$1 = 'Singular';
  var Past$1 = 'PastTense';
  var Avb = 'Adverb';
  var Plrl = 'Plural';
  var Actor$1 = 'Actor';
  var Vb = 'Verb';
  var Noun$1 = 'Noun';
  var Last$1 = 'LastName';
  var Modal = 'Modal';
  var Place = 'Place'; // find any issues - https://observablehq.com/@spencermountain/suffix-word-lookup

  var suffixMap = [null, //0
  null, //1
  {
    //2-letter
    ea: Sing$1,
    ia: Noun$1,
    ic: Adj$1,
    ly: Avb,
    "'n": Vb,
    "'t": Vb
  }, {
    //3-letter
    oed: Past$1,
    ued: Past$1,
    xed: Past$1,
    ' so': Avb,
    "'ll": Modal,
    "'re": 'Copula',
    azy: Adj$1,
    end: Vb,
    ped: Past$1,
    ffy: Adj$1,
    ify: Inf$1,
    ing: 'Gerund',
    //likely to be converted to Adj after lexicon pass
    ize: Inf$1,
    lar: Adj$1,
    mum: Adj$1,
    nes: Pres$1,
    nny: Adj$1,
    oid: Adj$1,
    ous: Adj$1,
    que: Adj$1,
    rmy: Adj$1,
    rol: Sing$1,
    sis: Sing$1,
    zes: Pres$1
  }, {
    //4-letter
    amed: Past$1,
    aped: Past$1,
    ched: Past$1,
    lked: Past$1,
    nded: Past$1,
    cted: Past$1,
    dged: Past$1,
    akis: Last$1,
    //greek
    cede: Inf$1,
    chuk: Last$1,
    //east-europe
    czyk: Last$1,
    //polish (male)
    ects: Pres$1,
    ends: Vb,
    enko: Last$1,
    //east-europe
    ette: Sing$1,
    fies: Pres$1,
    fore: Avb,
    gate: Inf$1,
    gone: Adj$1,
    ices: Plrl,
    ints: Plrl,
    ines: Plrl,
    ions: Plrl,
    less: Avb,
    llen: Adj$1,
    made: Adj$1,
    nsen: Last$1,
    //norway
    oses: Pres$1,
    ould: Modal,
    some: Adj$1,
    sson: Last$1,
    //swedish male
    tage: Inf$1,
    teen: 'Value',
    tion: Sing$1,
    tive: Adj$1,
    tors: Noun$1,
    vice: Sing$1
  }, {
    //5-letter
    tized: Past$1,
    urned: Past$1,
    eased: Past$1,
    ances: Plrl,
    bound: Adj$1,
    ettes: Plrl,
    fully: Avb,
    ishes: Pres$1,
    ities: Plrl,
    marek: Last$1,
    //polish (male)
    nssen: Last$1,
    //norway
    ology: Noun$1,
    ports: Plrl,
    rough: Adj$1,
    tches: Pres$1,
    tieth: 'Ordinal',
    tures: Plrl,
    wards: Avb,
    where: Avb
  }, {
    //6-letter
    auskas: Last$1,
    //lithuania
    keeper: Actor$1,
    logist: Actor$1,
    teenth: 'Value'
  }, {
    //7-letter
    opoulos: Last$1,
    //greek
    borough: Place,
    //Hillsborough
    sdottir: Last$1 //swedish female

  }];

  var endRegexs = function endRegexs(term, world) {
    var str = term.clean;
    var _char = str[str.length - 1];

    if (endsWith$1.hasOwnProperty(_char) === true) {
      var regs = endsWith$1[_char];

      for (var r = 0; r < regs.length; r += 1) {
        if (regs[r][0].test(str) === true) {
          term.tagSafe(regs[r][1], "endReg ".concat(_char, " #").concat(r), world);
          break;
        }
      }
    }
  }; //sweep-through all suffixes


  var knownSuffixes = function knownSuffixes(term, world) {
    var len = term.clean.length;
    var max = 7;

    if (len <= max) {
      max = len - 1;
    }

    for (var i = max; i > 1; i -= 1) {
      var str = term.clean.substr(len - i, len);

      if (suffixMap[str.length].hasOwnProperty(str) === true) {
        var tag = suffixMap[str.length][str];
        term.tagSafe(tag, 'suffix -' + str, world);
        break;
      }
    }
  }; //all-the-way-down!


  var checkRegex$1 = function checkRegex(term, world) {
    knownSuffixes(term, world);
    endRegexs(term, world);
  };

  var _04Suffixes = checkRegex$1;

  //just some of the most common emoticons
  //faster than
  //http://stackoverflow.com/questions/28077049/regex-matching-emoticons
  var emoticons = {
    ':(': true,
    ':)': true,
    ':P': true,
    ':p': true,
    ':O': true,
    ':3': true,
    ':|': true,
    ':/': true,
    ':\\': true,
    ':$': true,
    ':*': true,
    ':@': true,
    ':-(': true,
    ':-)': true,
    ':-P': true,
    ':-p': true,
    ':-O': true,
    ':-3': true,
    ':-|': true,
    ':-/': true,
    ':-\\': true,
    ':-$': true,
    ':-*': true,
    ':-@': true,
    ':^(': true,
    ':^)': true,
    ':^P': true,
    ':^p': true,
    ':^O': true,
    ':^3': true,
    ':^|': true,
    ':^/': true,
    ':^\\': true,
    ':^$': true,
    ':^*': true,
    ':^@': true,
    '):': true,
    '(:': true,
    '$:': true,
    '*:': true,
    ')-:': true,
    '(-:': true,
    '$-:': true,
    '*-:': true,
    ')^:': true,
    '(^:': true,
    '$^:': true,
    '*^:': true,
    '<3': true,
    '</3': true,
    '<\\3': true
  };

  var emojiReg = /^(\u00a9|\u00ae|[\u2319-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])/; //for us, there's three types -
  // * ;) - emoticons
  // * 🌵 - unicode emoji
  // * :smiling_face: - asci-represented emoji
  //test for forms like ':woman_tone2:‍:ear_of_rice:'
  //https://github.com/Kikobeats/emojis-keywords/blob/master/index.js

  var isCommaEmoji = function isCommaEmoji(raw) {
    if (raw.charAt(0) === ':') {
      //end comma can be last or second-last ':haircut_tone3:‍♀️'
      if (raw.match(/:.?$/) === null) {
        return false;
      } //ensure no spaces


      if (raw.match(' ')) {
        return false;
      } //reasonably sized


      if (raw.length > 35) {
        return false;
      }

      return true;
    }

    return false;
  }; //check against emoticon whitelist


  var isEmoticon = function isEmoticon(str) {
    str = str.replace(/^[:;]/, ':'); //normalize the 'eyes'

    return emoticons.hasOwnProperty(str);
  };

  var tagEmoji = function tagEmoji(term, world) {
    var raw = term.pre + term.text + term.post;
    raw = raw.trim(); //dont double-up on ending periods

    raw = raw.replace(/[.!?,]$/, ''); //test for :keyword: emojis

    if (isCommaEmoji(raw) === true) {
      term.tag('Emoji', 'comma-emoji', world);
      term.text = raw;
      term.pre = term.pre.replace(':', '');
      term.post = term.post.replace(':', '');
    } //test for unicode emojis


    if (term.text.match(emojiReg)) {
      term.tag('Emoji', 'unicode-emoji', world);
      term.text = raw;
    } //test for emoticon ':)' emojis


    if (isEmoticon(raw) === true) {
      term.tag('Emoticon', 'emoticon-emoji', world);
      term.text = raw;
    }
  };

  var _05Emoji = tagEmoji;

  var steps = {
    lexicon: _01Lexicon,
    punctuation: _02Punctuation$1,
    regex: _03Prefixes,
    suffix: _04Suffixes,
    emoji: _05Emoji
  }; //'lookups' look at a term by itself

  var lookups = function lookups(doc, terms) {
    var world = doc.world; //our list of known-words

    steps.lexicon(terms, world); //try these other methods

    for (var i = 0; i < terms.length; i += 1) {
      var term = terms[i]; //or maybe some helpful punctuation

      steps.punctuation(terms, i, world); //mostly prefix checks

      steps.regex(term, world); //maybe we can guess

      steps.suffix(term, world); //emoji and emoticons

      steps.emoji(term, world);
    }

    return doc;
  };

  var _01Init = lookups;

  //markov-like stats about co-occurance, for hints about unknown terms
  //basically, a little-bit better than the noun-fallback
  //just top n-grams from nlp tags, generated from nlp-corpus
  //after this word, here's what happens usually
  var afterThisWord = {
    i: 'Verb',
    //44% //i walk..
    first: 'Noun',
    //50% //first principles..
    it: 'Verb',
    //33%
    there: 'Verb',
    //35%
    not: 'Verb',
    //33%
    because: 'Noun',
    //31%
    "if": 'Noun',
    //32%
    but: 'Noun',
    //26%
    who: 'Verb',
    //40%
    "this": 'Noun',
    //37%
    his: 'Noun',
    //48%
    when: 'Noun',
    //33%
    you: 'Verb',
    //35%
    very: 'Adjective',
    // 39%
    old: 'Noun',
    //51%
    never: 'Verb',
    //42%
    before: 'Noun' //28%

  }; //in advance of this word, this is what happens usually

  var beforeThisWord = {
    there: 'Verb',
    //23% // be there
    me: 'Verb',
    //31% //see me
    man: 'Adjective',
    // 80% //quiet man
    only: 'Verb',
    //27% //sees only
    him: 'Verb',
    //32% //show him
    were: 'Noun',
    //48% //we were
    took: 'Noun',
    //38% //he took
    himself: 'Verb',
    //31% //see himself
    went: 'Noun',
    //43% //he went
    who: 'Noun',
    //47% //person who
    jr: 'Person'
  }; //following this POS, this is likely

  var afterThisPOS = {
    Adjective: 'Noun',
    //36% //blue dress
    Possessive: 'Noun',
    //41% //his song
    Determiner: 'Noun',
    //47%
    Adverb: 'Verb',
    //20%
    Pronoun: 'Verb',
    //40%
    Value: 'Noun',
    //47%
    Ordinal: 'Noun',
    //53%
    Modal: 'Verb',
    //35%
    Superlative: 'Noun',
    //43%
    Demonym: 'Noun',
    //38%
    Honorific: 'Person' //

  }; //in advance of this POS, this is likely

  var beforeThisPOS = {
    Copula: 'Noun',
    //44% //spencer is
    PastTense: 'Noun',
    //33% //spencer walked
    Conjunction: 'Noun',
    //36%
    Modal: 'Noun',
    //38%
    Pluperfect: 'Noun',
    //40%
    PerfectTense: 'Verb' //32%

  };
  var markov = {
    beforeThisWord: beforeThisWord,
    afterThisWord: afterThisWord,
    beforeThisPos: beforeThisPOS,
    afterThisPos: afterThisPOS
  };

  var afterKeys = Object.keys(markov.afterThisPos);
  var beforeKeys = Object.keys(markov.beforeThisPos);

  var checkNeighbours = function checkNeighbours(terms, world) {
    var _loop = function _loop(i) {
      var term = terms[i]; //do we still need a tag?

      if (term.isKnown() === true) {
        return "continue";
      } //ok, this term needs a tag.
      //look at previous word for clues..


      var lastTerm = terms[i - 1];

      if (lastTerm) {
        // 'foobar term'
        if (markov.afterThisWord.hasOwnProperty(lastTerm.clean) === true) {
          var tag = markov.afterThisWord[lastTerm.clean];
          term.tag(tag, 'after-' + lastTerm.clean, world);
          return "continue";
        } // 'Tag term'
        // (look at previous POS tags for clues..)


        var foundTag = afterKeys.find(function (tag) {
          return lastTerm.tags[tag];
        });

        if (foundTag !== undefined) {
          var _tag = markov.afterThisPos[foundTag];
          term.tag(_tag, 'after-' + foundTag, world);
          return "continue";
        }
      } //look at next word for clues..


      var nextTerm = terms[i + 1];

      if (nextTerm) {
        // 'term foobar'
        if (markov.beforeThisWord.hasOwnProperty(nextTerm.clean) === true) {
          var _tag2 = markov.beforeThisWord[nextTerm.clean];
          term.tag(_tag2, 'before-' + nextTerm.clean, world);
          return "continue";
        } // 'term Tag'
        // (look at next POS tags for clues..)


        var _foundTag = beforeKeys.find(function (tag) {
          return nextTerm.tags[tag];
        });

        if (_foundTag !== undefined) {
          var _tag3 = markov.beforeThisPos[_foundTag];
          term.tag(_tag3, 'before-' + _foundTag, world);
          return "continue";
        }
      }
    };

    for (var i = 0; i < terms.length; i += 1) {
      var _ret = _loop(i);

      if (_ret === "continue") continue;
    }
  };

  var _01Neighbours = checkNeighbours;

  var titleCase$4 = /^[A-Z][a-z'\u00C0-\u00FF]/;
  var hasNumber = /[0-9]/;
  /** look for any grammar signals based on capital/lowercase */

  var checkCase = function checkCase(doc) {
    var world = doc.world;
    doc.list.forEach(function (p) {
      var terms = p.terms();

      for (var i = 1; i < terms.length; i++) {
        var term = terms[i];

        if (titleCase$4.test(term.text) === true && hasNumber.test(term.text) === false) {
          term.tag('ProperNoun', 'titlecase-noun', world);
        }
      }
    });
  };

  var _02Case = checkCase;

  var hasPrefix = /^(re|un)-?[a-z\u00C0-\u00FF]/;
  var prefix = /^(re|un)-?/;
  /** check 'rewatch' in lexicon as 'watch' */

  var checkPrefix = function checkPrefix(terms, world) {
    var lex = world.words;
    terms.forEach(function (term) {
      // skip if we have a good tag already
      if (term.isKnown() === true) {
        return;
      } //does it start with 'un|re'


      if (hasPrefix.test(term.clean) === true) {
        // look for the root word in the lexicon:
        var stem = term.clean.replace(prefix, '');

        if (stem && stem.length > 3 && lex[stem] !== undefined && lex.hasOwnProperty(stem) === true) {
          term.tag(lex[stem], 'stem-' + stem, world);
        }
      }
    });
  };

  var _03Stem = checkPrefix;

  //similar to plural/singularize rules, but not the same
  var isPlural = [/(^v)ies$/i, /ises$/i, /ives$/i, /(antenn|formul|nebul|vertebr|vit)ae$/i, /(octop|vir|radi|nucle|fung|cact|stimul)i$/i, /(buffal|tomat|tornad)oes$/i, /(analy|ba|diagno|parenthe|progno|synop|the)ses$/i, /(vert|ind|cort)ices$/i, /(matr|append)ices$/i, /(x|ch|ss|sh|s|z|o)es$/i, /is$/i, /men$/i, /news$/i, /.tia$/i, /(^f)ves$/i, /(lr)ves$/i, /(^aeiouy|qu)ies$/i, /(m|l)ice$/i, /(cris|ax|test)es$/i, /(alias|status)es$/i, /ics$/i]; //similar to plural/singularize rules, but not the same

  var isSingular = [/(ax|test)is$/i, /(octop|vir|radi|nucle|fung|cact|stimul)us$/i, /(octop|vir)i$/i, /(rl)f$/i, /(alias|status)$/i, /(bu)s$/i, /(al|ad|at|er|et|ed|ad)o$/i, /(ti)um$/i, /(ti)a$/i, /sis$/i, /(?:(^f)fe|(lr)f)$/i, /hive$/i, /s[aeiou]+ns$/i, // sans, siens
  /(^aeiouy|qu)y$/i, /(x|ch|ss|sh|z)$/i, /(matr|vert|ind|cort)(ix|ex)$/i, /(m|l)ouse$/i, /(m|l)ice$/i, /(antenn|formul|nebul|vertebr|vit)a$/i, /.sis$/i, /^(?!talis|.*hu)(.*)man$/i];
  var isPlural_1 = {
    isSingular: isSingular,
    isPlural: isPlural
  };

  var noPlurals = ['Uncountable', 'Pronoun', 'Place', 'Value', 'Person', 'Month', 'WeekDay', 'Holiday'];
  var notPlural = [/ss$/, /sis$/, /[^aeiou][uo]s$/, /'s$/];
  var notSingular = [/i$/, /ae$/];
  /** turn nouns into singular/plural */

  var checkPlural = function checkPlural(t, world) {
    if (t.tags.Noun && !t.tags.Acronym) {
      var str = t.clean; //skip existing tags, fast

      if (t.tags.Singular || t.tags.Plural) {
        return;
      } //too short


      if (str.length <= 3) {
        t.tag('Singular', 'short-singular', world);
        return;
      } //is it impossible to be plural?


      if (noPlurals.find(function (tag) {
        return t.tags[tag];
      })) {
        return;
      } // isPlural suffix rules


      if (isPlural_1.isPlural.find(function (reg) {
        return reg.test(str);
      })) {
        t.tag('Plural', 'plural-rules', world);
        return;
      } // isSingular suffix rules


      if (isPlural_1.isSingular.find(function (reg) {
        return reg.test(str);
      })) {
        t.tag('Singular', 'singular-rules', world);
        return;
      } // finally, fallback 'looks plural' rules..


      if (/s$/.test(str) === true) {
        //avoid anything too sketchy to be plural
        if (notPlural.find(function (reg) {
          return reg.test(str);
        })) {
          return;
        }

        t.tag('Plural', 'plural-fallback', world);
        return;
      } //avoid anything too sketchy to be singular


      if (notSingular.find(function (reg) {
        return reg.test(str);
      })) {
        return;
      }

      t.tag('Singular', 'singular-fallback', world);
    }
  };

  var _04Plurals = checkPlural;

  //nouns that also signal the title of an unknown organization
  //todo remove/normalize plural forms
  var orgWords = ['academy', 'administration', 'agence', 'agences', 'agencies', 'agency', 'airlines', 'airways', 'army', 'assoc', 'associates', 'association', 'assurance', 'authority', 'autorite', 'aviation', 'bank', 'banque', 'board', 'boys', 'brands', 'brewery', 'brotherhood', 'brothers', 'building society', 'bureau', 'cafe', 'caisse', 'capital', 'care', 'cathedral', 'center', 'central bank', 'centre', 'chemicals', 'choir', 'chronicle', 'church', 'circus', 'clinic', 'clinique', 'club', 'co', 'coalition', 'coffee', 'collective', 'college', 'commission', 'committee', 'communications', 'community', 'company', 'comprehensive', 'computers', 'confederation', 'conference', 'conseil', 'consulting', 'containers', 'corporation', 'corps', 'corp', 'council', 'crew', 'daily news', 'data', 'departement', 'department', 'department store', 'departments', 'design', 'development', 'directorate', 'division', 'drilling', 'education', 'eglise', 'electric', 'electricity', 'energy', 'ensemble', 'enterprise', 'enterprises', 'entertainment', 'estate', 'etat', 'evening news', 'faculty', 'federation', 'financial', 'fm', 'foundation', 'fund', 'gas', 'gazette', 'girls', 'government', 'group', 'guild', 'health authority', 'herald', 'holdings', 'hospital', 'hotel', 'hotels', 'inc', 'industries', 'institut', 'institute', 'institute of technology', 'institutes', 'insurance', 'international', 'interstate', 'investment', 'investments', 'investors', 'journal', 'laboratory', 'labs', // 'law',
  'liberation army', 'limited', 'local authority', 'local health authority', 'machines', 'magazine', 'management', 'marine', 'marketing', 'markets', 'media', 'memorial', 'mercantile exchange', 'ministere', 'ministry', 'military', 'mobile', 'motor', 'motors', 'musee', 'museum', // 'network',
  'news', 'news service', 'observatory', 'office', 'oil', 'optical', 'orchestra', 'organization', 'partners', 'partnership', // 'party',
  "people's party", 'petrol', 'petroleum', 'pharmacare', 'pharmaceutical', 'pharmaceuticals', 'pizza', 'plc', 'police', 'polytechnic', 'post', 'power', 'press', 'productions', 'quartet', 'radio', 'regional authority', 'regional health authority', 'reserve', 'resources', 'restaurant', 'restaurants', 'savings', 'school', 'securities', 'service', 'services', 'social club', 'societe', 'society', 'sons', 'standard', 'state police', 'state university', 'stock exchange', 'subcommittee', 'syndicat', 'systems', 'telecommunications', 'telegraph', 'television', 'times', 'tribunal', 'tv', 'union', 'university', 'utilities', 'workers'];
  var organizations = orgWords.reduce(function (h, str) {
    h[str] = 'Noun';
    return h;
  }, {});

  var maybeOrg = function maybeOrg(t) {
    //must be a noun
    if (!t.tags.Noun) {
      return false;
    } //can't be these things


    if (t.tags.Pronoun || t.tags.Comma || t.tags.Possessive) {
      return false;
    } //must be one of these


    if (t.tags.Organization || t.tags.Acronym || t.tags.Place || t.titleCase()) {
      return true;
    }

    return false;
  };

  var tagOrgs = function tagOrgs(terms, world) {
    for (var i = 0; i < terms.length; i += 1) {
      var t = terms[i];

      if (organizations[t.clean] !== undefined && organizations.hasOwnProperty(t.clean) === true) {
        // look-backward - eg. 'Toronto University'
        var lastTerm = terms[i - 1];

        if (lastTerm !== undefined && maybeOrg(lastTerm) === true) {
          lastTerm.tagSafe('Organization', 'org-word-1', world);
          t.tagSafe('Organization', 'org-word-2', world);
          continue;
        } //look-forward - eg. University of Toronto


        var nextTerm = terms[i + 1];

        if (nextTerm !== undefined && nextTerm.clean === 'of') {
          if (terms[i + 2] && maybeOrg(terms[i + 2])) {
            t.tagSafe('Organization', 'org-of-word-1', world);
            nextTerm.tagSafe('Organization', 'org-of-word-2', world);
            terms[i + 2].tagSafe('Organization', 'org-of-word-3', world);
            continue;
          }
        }
      }
    }
  };

  var _05Organizations = tagOrgs;

  var oneLetterAcronym$1 = /^[A-Z]('s|,)?$/;
  var periodSeperated = /([A-Z]\.){2}[A-Z]?/i;
  var oneLetterWord = {
    I: true,
    A: true
  };

  var isAcronym$2 = function isAcronym(term, world) {
    var str = term.reduced; // a known acronym like fbi

    if (term.tags.Acronym) {
      return true;
    } // if (term.tags.Adverb || term.tags.Verb || term.tags.Value || term.tags.Plural) {
    //   return false
    // }
    // known-words, like 'PIZZA' is not an acronym.


    if (world.words[str]) {
      return false;
    }

    return term.isAcronym();
  }; // F.B.I., NBC, - but not 'NO COLLUSION'


  var checkAcronym = function checkAcronym(terms, world) {
    terms.forEach(function (term) {
      //these are not acronyms
      if (term.tags.RomanNumeral === true) {
        return;
      } //period-ones F.D.B.


      if (periodSeperated.test(term.text) === true) {
        term.tag('Acronym', 'period-acronym', world);
      } //non-period ones are harder


      if (term.isUpperCase() && isAcronym$2(term, world)) {
        term.tag('Acronym', 'acronym-step', world);
        term.tag('Noun', 'acronym-infer', world);
      } else if (!oneLetterWord.hasOwnProperty(term.text) && oneLetterAcronym$1.test(term.text)) {
        term.tag('Acronym', 'one-letter-acronym', world);
        term.tag('Noun', 'one-letter-infer', world);
      } //if it's a organization,


      if (term.tags.Organization && term.text.length <= 3) {
        term.tag('Acronym', 'acronym-org', world);
      }

      if (term.tags.Organization && term.isUpperCase() && term.text.length <= 6) {
        term.tag('Acronym', 'acronym-org-case', world);
      }
    });
  };

  var _06Acronyms = checkAcronym;

  var step = {
    neighbours: _01Neighbours,
    "case": _02Case,
    stem: _03Stem,
    plural: _04Plurals,
    organizations: _05Organizations,
    acronyms: _06Acronyms
  }; //

  var fallbacks = function fallbacks(doc, terms) {
    var world = doc.world; // if it's empty, consult it's neighbours, first

    step.neighbours(terms, world); // is there a case-sensitive clue?

    step["case"](doc); // check 'rewatch' as 'watch'

    step.stem(terms, world); // ... fallback to a noun!

    terms.forEach(function (t) {
      if (t.isKnown() === false) {
        t.tag('Noun', 'noun-fallback', doc.world);
      }
    }); // turn 'Foo University' into an Org

    step.organizations(terms, world); //turn 'FBD' into an acronym

    step.acronyms(terms, world); //are the nouns singular or plural?

    terms.forEach(function (t) {
      step.plural(t, doc.world);
    });
    return doc;
  };

  var _02Fallbacks = fallbacks;

  var hasNegative = /n't$/;
  var irregulars$3 = {
    "won't": ['will', 'not'],
    wont: ['will', 'not'],
    "can't": ['can', 'not'],
    cant: ['can', 'not'],
    cannot: ['can', 'not'],
    "shan't": ['should', 'not'],
    dont: ['do', 'not'],
    dun: ['do', 'not'] // "ain't" is ambiguous for is/was

  }; // either 'is not' or 'are not'

  var doAint = function doAint(term, phrase) {
    var terms = phrase.terms();
    var index = terms.indexOf(term);
    var before = terms.slice(0, index); //look for the preceding noun

    var noun = before.find(function (t) {
      return t.tags.Noun;
    });

    if (noun && noun.tags.Plural) {
      return ['are', 'not'];
    }

    return ['is', 'not'];
  };

  var checkNegative = function checkNegative(term, phrase) {
    //check named-ones
    if (irregulars$3.hasOwnProperty(term.clean) === true) {
      return irregulars$3[term.clean];
    } //this word needs it's own logic:


    if (term.clean === "ain't" || term.clean === 'aint') {
      return doAint(term, phrase);
    } //try it normally


    if (hasNegative.test(term.clean) === true) {
      var main = term.clean.replace(hasNegative, '');
      return [main, 'not'];
    }

    return null;
  };

  var _01Negative = checkNegative;

  var contraction = /([a-z\u00C0-\u00FF]+)[\u0027\u0060\u00B4\u2018\u2019\u201A\u201B\u2032\u2035\u2039\u203A]([a-z]{1,2})$/i; //these ones don't seem to be ambiguous

  var easy = {
    ll: 'will',
    ve: 'have',
    re: 'are',
    m: 'am',
    "n't": 'not'
  }; //

  var checkApostrophe = function checkApostrophe(term) {
    var parts = term.text.match(contraction);

    if (parts === null) {
      return null;
    }

    if (easy.hasOwnProperty(parts[2])) {
      return [parts[1], easy[parts[2]]];
    }

    return null;
  };

  var _02Simple = checkApostrophe;

  var irregulars$4 = {
    wanna: ['want', 'to'],
    gonna: ['going', 'to'],
    im: ['i', 'am'],
    alot: ['a', 'lot'],
    ive: ['i', 'have'],
    imma: ['I', 'will'],
    "where'd": ['where', 'did'],
    whered: ['where', 'did'],
    "when'd": ['when', 'did'],
    whend: ['when', 'did'],
    // "how'd": ['how', 'did'], //'how would?'
    // "what'd": ['what', 'did'], //'what would?'
    howd: ['how', 'did'],
    whatd: ['what', 'did'],
    // "let's": ['let', 'us'], //too weird
    //multiple word contractions
    dunno: ['do', 'not', 'know'],
    brb: ['be', 'right', 'back'],
    gtg: ['got', 'to', 'go'],
    irl: ['in', 'real', 'life'],
    tbh: ['to', 'be', 'honest'],
    imo: ['in', 'my', 'opinion'],
    til: ['today', 'i', 'learned'],
    rn: ['right', 'now'],
    twas: ['it', 'was'],
    '@': ['at']
  }; //

  var checkIrregulars = function checkIrregulars(term) {
    //check white-list
    if (irregulars$4.hasOwnProperty(term.clean)) {
      return irregulars$4[term.clean];
    }

    return null;
  };

  var _03Irregulars = checkIrregulars;

  var hasApostropheS = /([a-z\u00C0-\u00FF]+)[\u0027\u0060\u00B4\u2018\u2019\u201A\u201B\u2032\u2035\u2039\u203A]s$/i;
  var banList = {
    that: true,
    there: true
  };

  var isPossessive = function isPossessive(term, pool) {
    // if we already know it
    if (term.tags.Possessive) {
      return true;
    } //a pronoun can't be possessive - "he's house"


    if (term.tags.Pronoun || term.tags.QuestionWord) {
      return false;
    }

    if (banList.hasOwnProperty(term.reduced)) {
      return false;
    } //if end of sentence, it is possessive - "was spencer's"


    var nextTerm = pool.get(term.next);

    if (!nextTerm) {
      return true;
    } //a gerund suggests 'is walking'


    if (nextTerm.tags.Verb) {
      //fix 'jamie's bite'
      if (nextTerm.tags.Infinitive) {
        return true;
      } //fix 'spencer's runs'


      if (nextTerm.tags.PresentTense) {
        return true;
      }

      return false;
    } //spencer's house


    if (nextTerm.tags.Noun) {
      return true;
    } //rocket's red glare


    var twoTerm = pool.get(nextTerm.next);

    if (twoTerm && twoTerm.tags.Noun && !twoTerm.tags.Pronoun) {
      return true;
    } //othwerwise, an adjective suggests 'is good'


    if (nextTerm.tags.Adjective || nextTerm.tags.Adverb || nextTerm.tags.Verb) {
      return false;
    }

    return false;
  };

  var isHas = function isHas(term, phrase) {
    var terms = phrase.terms();
    var index = terms.indexOf(term);
    var after = terms.slice(index + 1, index + 3); //look for a past-tense verb

    return after.find(function (t) {
      return t.tags.PastTense;
    });
  };

  var checkPossessive = function checkPossessive(term, phrase, world) {
    //the rest of 's
    var found = term.text.match(hasApostropheS);

    if (found !== null) {
      //spencer's thing vs spencer-is
      if (isPossessive(term, phrase.pool) === true) {
        term.tag('#Possessive', 'isPossessive', world);
        return null;
      } //'spencer is'


      if (found !== null) {
        if (isHas(term, phrase)) {
          return [found[1], 'has'];
        }

        return [found[1], 'is'];
      }
    }

    return null;
  };

  var _04Possessive = checkPossessive;

  var hasPerfect = /[a-z\u00C0-\u00FF]'d$/;
  var useDid = {
    how: true,
    what: true
  };
  /** split `i'd` into 'i had',  or 'i would'  */

  var checkPerfect = function checkPerfect(term, phrase) {
    if (hasPerfect.test(term.clean)) {
      var root = term.clean.replace(/'d$/, ''); //look at the next few words

      var terms = phrase.terms();
      var index = terms.indexOf(term);
      var after = terms.slice(index + 1, index + 4); //is it before a past-tense verb? - 'i'd walked'

      for (var i = 0; i < after.length; i++) {
        var t = after[i];

        if (t.tags.Verb) {
          if (t.tags.PastTense) {
            return [root, 'had'];
          } //what'd you see


          if (useDid[root] === true) {
            return [root, 'did'];
          }

          return [root, 'would'];
        }
      } //otherwise, 'i'd walk'


      return [root, 'would'];
    }

    return null;
  };

  var _05PerfectTense = checkPerfect;

  var isRange = /^([0-9]+)[-–—]([0-9]+)$/i; //split '2-4' into '2 to 4'

  var checkRange = function checkRange(term) {
    if (term.tags.PhoneNumber === true) {
      return null;
    }

    var parts = term.text.match(isRange);

    if (parts !== null) {
      return [parts[1], 'to', parts[2]];
    }

    return null;
  };

  var _06Ranges = checkRange;

  var contraction$1 = /^(l|c|d|j|m|n|qu|s|t)[\u0027\u0060\u00B4\u2018\u2019\u201A\u201B\u2032\u2035\u2039\u203A]([a-z\u00C0-\u00FF]+)$/i; // basic support for ungendered french contractions
  // not perfect, but better than nothing, to support matching on french text.

  var french = {
    l: 'le',
    // l'amour
    c: 'ce',
    // c'est
    d: 'de',
    // d'amerique
    j: 'je',
    // j'aime
    m: 'me',
    // m'appelle
    n: 'ne',
    // n'est
    qu: 'que',
    // qu'il
    s: 'se',
    // s'appelle
    t: 'tu' // t'aime

  };

  var checkFrench = function checkFrench(term) {
    var parts = term.text.match(contraction$1);

    if (parts === null || french.hasOwnProperty(parts[1]) === false) {
      return null;
    }

    var arr = [french[parts[1]], parts[2]];

    if (arr[0] && arr[1]) {
      return arr;
    }

    return null;
  };

  var _07French = checkFrench;

  var isNumber = /^[0-9]+$/;

  var createPhrase = function createPhrase(found, doc) {
    //create phrase from ['would', 'not']
    var phrase = _01Tokenizer(found.join(' '), doc.world, doc.pool())[0]; //tag it

    var terms = phrase.terms();
    _01Lexicon(terms, doc.world); //make these terms implicit

    terms.forEach(function (t) {
      t.implicit = t.text;
      t.text = '';
      t.clean = ''; // remove whitespace for implicit terms

      t.pre = '';
      t.post = ''; // tag number-ranges

      if (isNumber.test(t.implicit)) {
        t.tags.Number = true;
        t.tags.Cardinal = true;
      }
    });
    return phrase;
  };

  var contractions = function contractions(doc) {
    var world = doc.world;
    doc.list.forEach(function (p) {
      var terms = p.terms();

      for (var i = 0; i < terms.length; i += 1) {
        var term = terms[i];
        var found = _01Negative(term, p);
        found = found || _02Simple(term);
        found = found || _03Irregulars(term);
        found = found || _04Possessive(term, p, world);
        found = found || _05PerfectTense(term, p);
        found = found || _06Ranges(term);
        found = found || _07French(term); //add them in

        if (found !== null) {
          var newPhrase = createPhrase(found, doc); // keep tag NumberRange, if we had it

          if (p.has('#NumberRange') === true) {
            doc.buildFrom([newPhrase]).tag('NumberRange');
          } //set text as contraction


          var firstTerm = newPhrase.terms(0);
          firstTerm.text = term.text; //grab sub-phrase to remove

          var match = p.buildFrom(term.id, 1, doc.pool());
          match.replace(newPhrase, doc, true);
        }
      }
    });
    return doc;
  };

  var _03Contractions = contractions;

  var hasWord = function hasWord(doc, word) {
    var arr = doc._cache.words[word] || [];
    arr = arr.map(function (i) {
      return doc.list[i];
    });
    return doc.buildFrom(arr);
  };

  var hasTag = function hasTag(doc, tag) {
    var arr = doc._cache.tags[tag] || [];
    arr = arr.map(function (i) {
      return doc.list[i];
    });
    return doc.buildFrom(arr);
  }; //mostly pos-corections here


  var miscCorrection = function miscCorrection(doc) {
    //exactly like
    var m = hasWord(doc, 'like');
    m.match('#Adverb like').notIf('(really|generally|typically|usually|sometimes|often) [like]').tag('Adverb', 'adverb-like'); //the orange.

    m = hasTag(doc, 'Adjective');
    m.match('#Determiner #Adjective$').notIf('(#Comparative|#Superlative)').terms(1).tag('Noun', 'the-adj-1'); // Firstname x (dangerous)

    m = hasTag(doc, 'FirstName');
    m.match('#FirstName (#Noun|@titleCase)').ifNo('^#Possessive').ifNo('#Pronoun').ifNo('@hasComma .').lastTerm().tag('#LastName', 'firstname-noun'); //three trains / one train

    m = hasTag(doc, 'Value');
    m = m.match('#Value #PresentTense');

    if (m.found) {
      if (m.has('(one|1)') === true) {
        m.terms(1).tag('Singular', 'one-presentTense');
      } else {
        m.terms(1).tag('Plural', 'value-presentTense');
      }
    } // well i've been...


    doc.match('^(well|so|okay)').tag('Expression', 'well-'); //been walking

    m = hasTag(doc, 'Gerund');
    m.match("(be|been) (#Adverb|not)+? #Gerund").not('#Verb$').tag('Auxiliary', 'be-walking'); // directive verb - 'use reverse'

    doc.match('(try|use|attempt|build|make) #Verb').ifNo('(@hasComma|#Negative|#Copula|will|be)').lastTerm().tag('#Noun', 'do-verb'); //possessives
    //'her match' vs 'let her match'

    m = hasTag(doc, 'Possessive');
    m = m.match('#Possessive [#Infinitive]', 0);

    if (!m.lookBehind('(let|made|make|force|ask)').found) {
      m.tag('Noun', 'her-match');
    }

    return doc;
  };

  var fixMisc = miscCorrection;

  var unique$5 = function unique(arr) {
    var obj = {};

    for (var i = 0; i < arr.length; i++) {
      obj[arr[i]] = true;
    }

    return Object.keys(obj);
  };

  var _unique = unique$5;

  // order matters
  var list = [// ==== Mutliple tags ====
  {
    match: 'too much',
    tag: 'Adverb Adjective',
    reason: 'bit-4'
  }, // u r cool
  {
    match: 'u r',
    tag: 'Pronoun Copula',
    reason: 'u r'
  }, //sometimes adverbs - 'pretty good','well above'
  {
    match: '#Copula (pretty|dead|full|well) (#Adjective|#Noun)',
    tag: '#Copula #Adverb #Adjective',
    reason: 'sometimes-adverb'
  }, //walking is cool
  {
    match: '[#Gerund] #Adverb? not? #Copula',
    group: 0,
    tag: 'Activity',
    reason: 'gerund-copula'
  }, //walking should be fun
  {
    match: '[#Gerund] #Modal',
    group: 0,
    tag: 'Activity',
    reason: 'gerund-modal'
  }, //swear-words as non-expression POS
  {
    match: 'holy (shit|fuck|hell)',
    tag: 'Expression',
    reason: 'swears-expression'
  }, //Aircraft designer
  {
    match: '#Noun #Actor',
    tag: 'Actor',
    reason: 'thing-doer'
  }, {
    match: '#Conjunction [u]',
    group: 0,
    tag: 'Pronoun',
    reason: 'u-pronoun-2'
  }, //'u' as pronoun
  {
    match: '[u] #Verb',
    group: 0,
    tag: 'Pronoun',
    reason: 'u-pronoun-1'
  }, // ==== Determiners ====
  {
    match: '#Noun [(who|whom)]',
    group: 0,
    tag: 'Determiner',
    reason: 'captain-who'
  }, //that car goes
  {
    match: 'that #Noun [#Verb]',
    group: 0,
    tag: 'Determiner',
    reason: 'that-determiner'
  }, {
    match: 'a bit much',
    tag: 'Determiner Adverb Adjective',
    reason: 'bit-3'
  }, // ==== Propositions ====
  //all students
  {
    match: '#Verb #Adverb? #Noun [(that|which)]',
    group: 0,
    tag: 'Preposition',
    reason: 'that-prep'
  }, //work, which has been done.
  {
    match: '@hasComma [which] (#Pronoun|#Verb)',
    group: 0,
    tag: 'Preposition',
    reason: 'which-copula'
  }, 
  {
    match: 'just [like]',
    group: 0,
    tag: 'Preposition',
    reason: 'like-preposition'
  }, //folks like her
  {
    match: '#Noun [like] #Noun',
    group: 0,
    tag: 'Preposition',
    reason: 'noun-like'
  }, //fix for busted-up phrasalVerbs
  {
    match: '#Noun [#Particle]',
    group: 0,
    tag: 'Preposition',
    reason: 'repair-noPhrasal'
  }, // ==== Conditions ====
  // had he survived,
  {
    match: '[had] #Noun+ #PastTense',
    group: 0,
    tag: 'Condition',
    reason: 'had-he'
  }, // were he to survive
  {
    match: '[were] #Noun+ to #Infinitive',
    group: 0,
    tag: 'Condition',
    reason: 'were-he'
  }, // ==== Questions ====
  //the word 'how'
  {
    match: '^how',
    tag: 'QuestionWord',
    reason: 'how-question'
  }, {
    match: '[how] (#Determiner|#Copula|#Modal|#PastTense)',
    group: 0,
    tag: 'QuestionWord',
    reason: 'how-is'
  }, // //the word 'which'
  {
    match: '^which',
    tag: 'QuestionWord',
    reason: 'which-question'
  }, {
    match: '[which] . (#Noun)+ #Pronoun',
    group: 0,
    tag: 'QuestionWord',
    reason: 'which-question2'
  }, // { match: 'which', tag: 'QuestionWord', reason: 'which-question3' },
  // ==== Conjunctions ====
  {
    match: '[so] #Noun',
    group: 0,
    tag: 'Conjunction',
    reason: 'so-conj'
  }, //how he is driving
  {
    match: '[(who|what|where|why|how|when)] #Noun #Copula #Adverb? (#Verb|#Adjective)',
    group: 0,
    tag: 'Conjunction',
    reason: 'how-he-is-x'
  }, 
  {
    match: '[(who|what|where|why|how|when)] #Noun #Adverb? #Infinitive not? #Gerund',
    group: 0,
    tag: 'Conjunction',
    reason: 'when i go fishing'
  },
  { /*@blab+*/
    match: '^[(who|what|where|why|how|when)] #Adjective? #Verb #Pronoun',
    group: 0,
    tag: 'Adverb',
    reason: 'where-question'
  },
  { /*@blab+*/
    match: 'the way',
    tag: 'Noun',
    reason: 'fix-1'
  },  
  ];
  var _01Misc = list;

  //Dates: 'june' or 'may'
  var dates = '(april|june|may|jan|august|eve)';
  var list$1 = [// ==== Holiday ====
  {
    match: '#Holiday (day|eve)',
    tag: 'Holiday',
    reason: 'holiday-day'
  }, // the captain who
  // ==== WeekDay ====
  // sun the 5th
  {
    match: '[sun] the #Ordinal',
    tag: 'WeekDay',
    reason: 'sun-the-5th'
  }, //sun feb 2
  {
    match: '[sun] #Date',
    group: 0,
    tag: 'WeekDay',
    reason: 'sun-feb'
  }, //1pm next sun
  {
    match: '#Date (on|this|next|last|during)? [sun]',
    group: 0,
    tag: 'WeekDay',
    reason: '1pm-sun'
  }, //this sat
  {
    match: "(in|by|before|during|on|until|after|of|within|all) [sat]",
    group: 0,
    tag: 'WeekDay',
    reason: 'sat'
  }, //sat november
  {
    match: '[sat] #Date',
    group: 0,
    tag: 'WeekDay',
    reason: 'sat-feb'
  }, // ==== Month ====
  //all march
  {
    match: "#Preposition [(march|may)]",
    group: 0,
    tag: 'Month',
    reason: 'in-month'
  }, //this march
  {
    match: "this [(march|may)]",
    group: 0,
    tag: 'Month',
    reason: 'this-month'
  }, {
    match: "next [(march|may)]",
    group: 0,
    tag: 'Month',
    reason: 'this-month'
  }, {
    match: "last [(march|may)]",
    group: 0,
    tag: 'Month',
    reason: 'this-month'
  }, // march 5th
  {
    match: "[(march|may)] the? #Value",
    group: 0,
    tag: 'Month',
    reason: 'march-5th'
  }, // 5th of march
  {
    match: "#Value of? [(march|may)]",
    group: 0,
    tag: 'Month',
    reason: '5th-of-march'
  }, // march and feb
  {
    match: "[(march|may)] .? #Date",
    group: 0,
    tag: 'Month',
    reason: 'march-and-feb'
  }, // feb to march
  {
    match: "#Date .? [(march|may)]",
    group: 0,
    tag: 'Month',
    reason: 'feb-and-march'
  }, //quickly march
  {
    match: "#Adverb [(march|may)]",
    group: 0,
    tag: 'Verb',
    reason: 'quickly-march'
  }, //march quickly
  {
    match: "[(march|may)] #Adverb",
    group: 0,
    tag: 'Verb',
    reason: 'march-quickly'
  }, //5th of March
  {
    match: '#Value of #Month',
    tag: 'Date',
    reason: 'value-of-month'
  }, //5 March
  {
    match: '#Cardinal #Month',
    tag: 'Date',
    reason: 'cardinal-month'
  }, //march 5 to 7
  {
    match: '#Month #Value to #Value',
    tag: 'Date',
    reason: 'value-to-value'
  }, //march the 12th
  {
    match: '#Month the #Value',
    tag: 'Date',
    reason: 'month-the-value'
  }, //june 7
  {
    match: '(#WeekDay|#Month) #Value',
    tag: 'Date',
    reason: 'date-value'
  }, //7 june
  {
    match: '#Value (#WeekDay|#Month)',
    tag: 'Date',
    reason: 'value-date'
  }, //may twenty five
  {
    match: '(#TextValue && #Date) #TextValue',
    tag: 'Date',
    reason: 'textvalue-date'
  }, // in june
  {
    match: "in [".concat(dates, "]"),
    group: 0,
    tag: 'Date',
    reason: 'in-june'
  }, {
    match: "during [".concat(dates, "]"),
    group: 0,
    tag: 'Date',
    reason: 'in-june'
  }, {
    match: "on [".concat(dates, "]"),
    group: 0,
    tag: 'Date',
    reason: 'in-june'
  }, {
    match: "by [".concat(dates, "]"),
    group: 0,
    tag: 'Date',
    reason: 'in-june'
  }, {
    match: "before [".concat(dates, "]"),
    group: 0,
    tag: 'Date',
    reason: 'in-june'
  }, {
    match: "#Date [".concat(dates, "]"),
    group: 0,
    tag: 'Date',
    reason: 'in-june'
  }, // june 1992
  {
    match: "".concat(dates, " #Value"),
    tag: 'Date',
    reason: 'june-5th'
  }, {
    match: "".concat(dates, " #Date"),
    tag: 'Date',
    reason: 'june-5th'
  }, // June Smith
  {
    match: "".concat(dates, " #ProperNoun"),
    tag: 'Person',
    reason: 'june-smith',
    safe: true
  }, // june m. Cooper
  {
    match: "".concat(dates, " #Acronym? (#ProperNoun && !#Month)"),
    tag: 'Person',
    reason: 'june-smith-jr'
  }, // 'second'
  {
    match: "#Cardinal [second]",
    tag: 'Unit',
    reason: 'one-second'
  }];
  var _02Dates = list$1;

  var _03Noun = [// ==== Plural ====
  //there are reasons
  {
    match: 'there (are|were) #Adjective? [#PresentTense]',
    group: 0,
    tag: 'Plural',
    reason: 'there-are'
  }, // ==== Singular ====
  //the sun
  {
    match: '#Determiner [sun]',
    group: 0,
    tag: 'Singular',
    reason: 'the-sun'
  }, //did a 900, paid a 20
  {
    match: '#Verb (a|an) [#Value]',
    group: 0,
    tag: 'Singular',
    reason: 'did-a-value'
  }, //'the can'
  {
    match: '#Determiner [(can|will|may)]',
    group: 0,
    tag: 'Singular',
    reason: 'the can'
  }, // ==== Possessive ====
  //spencer kelly's
  {
    match: '#FirstName #Acronym? (#Possessive && #LastName)',
    tag: 'Possessive',
    reason: 'name-poss'
  }, //Super Corp's fundraiser
  {
    match: '#Organization+ #Possessive',
    tag: 'Possessive',
    reason: 'org-possessive'
  }, //Los Angeles's fundraiser
  {
    match: '#Place+ #Possessive',
    tag: 'Possessive',
    reason: 'place-possessive'
  }, // assign all tasks
  {
    match: '#Verb (all|every|each|most|some|no) [#PresentTense]',
    group: 0,
    tag: 'Noun',
    reason: 'all-presentTense'
  }, //big dreams, critical thinking
  {
    match: '(#Adjective && !all) [#PresentTense]',
    group: 0,
    tag: 'Noun',
    reason: 'adj-presentTense'
  }, //his fine
  {
    match: '(his|her|its) [#Adjective]',
    group: 0,
    tag: 'Noun',
    reason: 'his-fine'
  }, //some pressing issues
  {
    match: 'some [#Verb] #Plural',
    group: 0,
    tag: 'Noun',
    reason: 'determiner6'
  }, //'more' is not always an adverb
  {
    match: 'more #Noun',
    tag: 'Noun',
    reason: 'more-noun'
  }, {
    match: '(#Noun && @hasComma) #Noun (and|or) [#PresentTense]',
    group: 0,
    tag: 'Noun',
    reason: 'noun-list'
  }, //3 feet
  {
    match: '(right|rights) of .',
    tag: 'Noun',
    reason: 'right-of'
  }, // a bit
  {
    match: 'a [bit]',
    group: 0,
    tag: 'Noun',
    reason: 'bit-2'
  }, //running-a-show
  {
    match: '#Gerund #Determiner [#Infinitive]',
    group: 0,
    tag: 'Noun',
    reason: 'running-a-show'
  }, //the-only-reason
  {
    match: '#Determiner #Adverb [#Infinitive]',
    group: 0,
    tag: 'Noun',
    reason: 'the-reason'
  }, //the nice swim
  {
    match: '(the|this|those|these) #Adjective [#Verb]',
    group: 0,
    tag: 'Noun',
    reason: 'the-adj-verb'
  }, // the truly nice swim
  {
    match: '(the|this|those|these) #Adverb #Adjective [#Verb]',
    group: 0,
    tag: 'Noun',
    reason: 'determiner4'
  }, //the orange is
  {
    match: '#Determiner [#Adjective] (#Copula|#PastTense|#Auxiliary)',
    group: 0,
    tag: 'Noun',
    reason: 'the-adj-2'
  }, // a stream runs
  {
    match: '(the|this|a|an) [#Infinitive] #Adverb? #Verb',
    group: 0,
    tag: 'Noun',
    reason: 'determiner5'
  }, //the test string
  {
    match: '#Determiner [#Infinitive] #Noun',
    group: 0,
    tag: 'Noun',
    reason: 'determiner7'
  }, //by a bear.
  {
    match: '#Determiner #Adjective [#Infinitive]$',
    group: 0,
    tag: 'Noun',
    reason: 'a-inf'
  }, //the wait to vote
  {
    match: '(the|this) [#Verb] #Preposition .',
    group: 0,
    tag: 'Noun',
    reason: 'determiner1'
  }, //a sense of
  {
    match: '#Determiner [#Verb] of',
    group: 0,
    tag: 'Noun',
    reason: 'the-verb-of'
  }, //the threat of force
  {
    match: '#Determiner #Noun of [#Verb]',
    group: 0,
    tag: 'Noun',
    reason: 'noun-of-noun'
  }, //the western line
  {
    match: '#Determiner [(western|eastern|northern|southern|central)] #Noun',
    group: 0,
    tag: 'Noun',
    reason: 'western-line'
  }, //her polling
  {
    match: '#Possessive [#Gerund]',
    group: 0,
    tag: 'Noun',
    reason: 'her-polling'
  }, //her fines
  {
    match: '(his|her|its) [#PresentTense]',
    group: 0,
    tag: 'Noun',
    reason: 'its-polling'
  }, //linear algebra
  {
    match: '(#Determiner|#Value) [(linear|binary|mobile|lexical|technical|computer|scientific|formal)] #Noun',
    group: 0,
    tag: 'Noun',
    reason: 'technical-noun'
  }, // walk the walk
  {
    match: '(the|those|these) #Adjective? [#Infinitive]',
    group: 0,
    tag: 'Noun',
    reason: 'det-inf'
  }, {
    match: '(the|those|these) #Adjective? [#PresentTense]',
    group: 0,
    tag: 'Noun',
    reason: 'det-pres'
  }, {
    match: '(the|those|these) #Adjective? [#PastTense]',
    group: 0,
    tag: 'Noun',
    reason: 'det-past'
  }, //air-flow
  {
    match: '(#Noun && @hasHyphen) #Verb',
    tag: 'Noun',
    reason: 'hyphen-verb'
  }, //is no walk
  {
    match: 'is no [#Verb]',
    group: 0,
    tag: 'Noun',
    reason: 'is-no-verb'
  }, //different views than
  {
    match: '[#Verb] than',
    group: 0,
    tag: 'Noun',
    reason: 'correction'
  }, // goes to sleep
  {
    match: '(go|goes|went) to [#Infinitive]',
    group: 0,
    tag: 'Noun',
    reason: 'goes-to-verb'
  }, //a great run
  {
    match: '(a|an) #Adjective [(#Infinitive|#PresentTense)]',
    tag: 'Noun',
    reason: 'a|an2'
  }, //a tv show
  {
    match: '(a|an) #Noun [#Infinitive]',
    group: 0,
    tag: 'Noun',
    reason: 'a-noun-inf'
  }, //do so
  {
    match: 'do [so]',
    group: 0,
    tag: 'Noun',
    reason: 'so-noun'
  }, //is mark hughes
  {
    match: '#Copula [#Infinitive] #Noun',
    group: 0,
    tag: 'Noun',
    reason: 'is-pres-noun'
  }, //
  // { match: '[#Infinitive] #Copula', group: 0, tag: 'Noun', reason: 'inf-copula' },
  //a close
  {
    match: '#Determiner #Adverb? [close]',
    group: 0,
    tag: 'Adjective',
    reason: 'a-close'
  }, // what the hell
  {
    match: '#Determiner [(shit|damn|hell)]',
    group: 0,
    tag: 'Noun',
    reason: 'swears-noun'
  }];

  var adjectives$1 = '(misty|rusty|dusty|rich|randy)';
  var list$2 = [// all fell apart
  {
    match: '[all] #Determiner? #Noun',
    group: 0,
    tag: 'Adjective',
    reason: 'all-noun'
  }, // very rusty
  {
    match: "#Adverb [".concat(adjectives$1, "]"),
    group: 0,
    tag: 'Adjective',
    reason: 'really-rich'
  }, // rusty smith
  {
    match: "".concat(adjectives$1, " #Person"),
    tag: 'Person',
    reason: 'randy-smith'
  }, // rusty a. smith
  {
    match: "".concat(adjectives$1, " #Acronym? #ProperNoun"),
    tag: 'Person',
    reason: 'rusty-smith'
  }, //sometimes not-adverbs
  {
    match: '#Copula [(just|alone)]$',
    group: 0,
    tag: 'Adjective',
    reason: 'not-adverb'
  }, //jack is guarded
  {
    match: '#Singular is #Adverb? [#PastTense$]',
    group: 0,
    tag: 'Adjective',
    reason: 'is-filled'
  }, // smoked poutine is
  {
    match: '[#PastTense] #Singular is',
    group: 0,
    tag: 'Adjective',
    reason: 'smoked-poutine'
  }, // baked onions are
  {
    match: '[#PastTense] #Plural are',
    group: 0,
    tag: 'Adjective',
    reason: 'baked-onions'
  }, //a staggering cost
  {
    match: '(a|an) [#Gerund]',
    group: 0,
    tag: 'Adjective',
    reason: 'a|an'
  }, // is f*ed up
  {
    match: '#Copula [fucked up?]',
    tag: 'Adjective',
    reason: 'swears-adjective'
  }, //jack seems guarded
  {
    match: '#Singular (seems|appears) #Adverb? [#PastTense$]',
    group: 0,
    tag: 'Adjective',
    reason: 'seems-filled'
  }];
  var _04Adjective = list$2;

  var _05Adverb = [//still good
  {
    match: '[still] #Adjective',
    group: 0,
    tag: 'Adverb',
    reason: 'still-advb'
  }, //still make
  {
    match: '[still] #Verb',
    group: 0,
    tag: 'Adverb',
    reason: 'still-verb'
  }, // so hot
  {
    match: '[so] #Adjective',
    group: 0,
    tag: 'Adverb',
    reason: 'so-adv'
  }, // all singing
  {
    match: '[all] #Verb',
    group: 0,
    tag: 'Adverb',
    reason: 'all-verb'
  }, // sing like an angel
  {
    match: '#Verb [like]',
    group: 0,
    tag: 'Adverb',
    reason: 'verb-like'
  }, //barely even walk
  {
    match: '(barely|hardly) even',
    tag: 'Adverb',
    reason: 'barely-even'
  }, //cheering hard - dropped -ly's
  {
    match: '#PresentTense [(hard|quick|long|bright|slow)]',
    group: 0,
    tag: 'Adverb',
    reason: 'lazy-ly'
  }, // much appreciated
  {
    match: '[much] #Adjective',
    group: 0,
    tag: 'Adverb',
    reason: 'bit-1'
  }];

  var _06Value = [// ==== PhoneNumber ====
  //1 800 ...
  {
    match: '1 #Value #PhoneNumber',
    tag: 'PhoneNumber',
    reason: '1-800-Value'
  }, //(454) 232-9873
  {
    match: '#NumericValue #PhoneNumber',
    tag: 'PhoneNumber',
    reason: '(800) PhoneNumber'
  }, // ==== Currency ====
  // chinese yuan
  {
    match: '#Demonym #Currency',
    tag: 'Currency',
    reason: 'demonym-currency'
  }, // ==== Ordinal ====
  {
    match: '[second] #Noun',
    group: 0,
    tag: 'Ordinal',
    reason: 'second-noun'
  }, // ==== Unit ====
  //5 yan
  {
    match: '#Value+ [#Currency]',
    group: 0,
    tag: 'Unit',
    reason: '5-yan'
  }, {
    match: '#Value [(foot|feet)]',
    group: 0,
    tag: 'Unit',
    reason: 'foot-unit'
  }, //minus 7
  {
    match: '(minus|negative) #Value',
    tag: 'Value',
    reason: 'minus-value'
  }, //5 kg.
  {
    match: '#Value [#Abbreviation]',
    group: 0,
    tag: 'Unit',
    reason: 'value-abbr'
  }, {
    match: '#Value [k]',
    group: 0,
    tag: 'Unit',
    reason: 'value-k'
  }, {
    match: '#Unit an hour',
    tag: 'Unit',
    reason: 'unit-an-hour'
  }, //seven point five
  {
    match: '#Value (point|decimal) #Value',
    tag: 'Value',
    reason: 'value-point-value'
  }, // ten bucks
  {
    match: '(#Value|a) [(buck|bucks|grand)]',
    group: 0,
    tag: 'Currency',
    reason: 'value-bucks'
  }, //quarter million
  {
    match: '#Determiner [(half|quarter)] #Ordinal',
    group: 0,
    tag: 'Value',
    reason: 'half-ordinal'
  }, {
    match: 'a #Value',
    tag: 'Value',
    reason: 'a-value'
  }, // ==== Money ====
  {
    match: '[#Value+] #Currency',
    group: 0,
    tag: 'Money',
    reason: '15 usd'
  }, // thousand and two
  {
    match: "(hundred|thousand|million|billion|trillion|quadrillion)+ and #Value",
    tag: 'Value',
    reason: 'magnitude-and-value'
  }, //'a/an' can mean 1 - "a hour"
  {
    match: '!once [(a|an)] (#Duration|hundred|thousand|million|billion|trillion)',
    group: 0,
    tag: 'Value',
    reason: 'a-is-one'
  }];

  var verbs$1 = '(pat|wade|ollie|will|rob|buck|bob|mark|jack)';
  var list$3 = [// ==== Tense ====
  //he left
  {
    match: '#Noun #Adverb? [left]',
    group: 0,
    tag: 'PastTense',
    reason: 'left-verb'
  }, //this rocks
  {
    match: '(this|that) [#Plural]',
    group: 0,
    tag: 'PresentTense',
    reason: 'this-verbs'
  }, // ==== Auxiliary ====
  //was walking
  {
    match: "[#Copula (#Adverb|not)+?] (#Gerund|#PastTense)",
    group: 0,
    tag: 'Auxiliary',
    reason: 'copula-walking'
  }, //support a splattering of auxillaries before a verb
  {
    match: "[(has|had) (#Adverb|not)+?] #PastTense",
    group: 0,
    tag: 'Auxiliary',
    reason: 'had-walked'
  }, //would walk
  {
    match: "[#Adverb+? (#Modal|did)+ (#Adverb|not)+?] #Verb",
    group: 0,
    tag: 'Auxiliary',
    reason: 'modal-verb'
  }, //would have had
  {
    match: "[#Modal (#Adverb|not)+? have (#Adverb|not)+? had (#Adverb|not)+?] #Verb",
    group: 0,
    tag: 'Auxiliary',
    reason: 'would-have'
  }, //would be walking
  {
    match: "#Modal (#Adverb|not)+? be (#Adverb|not)+? #Verb",
    group: 0,
    tag: 'Auxiliary',
    reason: 'would-be'
  }, //had been walking
  {
    match: "(#Modal|had|has) (#Adverb|not)+? been (#Adverb|not)+? #Verb",
    group: 0,
    tag: 'Auxiliary',
    reason: 'had-been'
  }, //was walking
  {
    match: "[#Copula (#Adverb|not)+?] (#Gerund|#PastTense)",
    group: 0,
    tag: 'Auxiliary',
    reason: 'copula-walking'
  }, //support a splattering of auxillaries before a verb
  {
    match: "[(has|had) (#Adverb|not)+?] #PastTense",
    group: 0,
    tag: 'Auxiliary',
    reason: 'had-walked'
  }, // will walk
  {
    match: '[(do|does|will|have|had)] (not|#Adverb)? #Verb',
    group: 0,
    tag: 'Auxiliary',
    reason: 'have-had'
  }, // about to go
  {
    match: '[about to] #Adverb? #Verb',
    group: 0,
    tag: ['Auxiliary', 'Verb'],
    reason: 'about-to'
  }, //would be walking
  {
    match: "#Modal (#Adverb|not)+? be (#Adverb|not)+? #Verb",
    group: 0,
    tag: 'Auxiliary',
    reason: 'would-be'
  }, //would have had
  {
    match: "[#Modal (#Adverb|not)+? have (#Adverb|not)+? had (#Adverb|not)+?] #Verb",
    group: 0,
    tag: 'Auxiliary',
    reason: 'would-have'
  }, //had been walking
  {
    match: "(#Modal|had|has) (#Adverb|not)+? been (#Adverb|not)+? #Verb",
    group: 0,
    tag: 'Auxiliary',
    reason: 'had-been'
  }, // was being driven
  {
    match: '[(be|being|been)] #Participle',
    group: 0,
    tag: 'Auxiliary',
    reason: 'being-foo'
  }, // ==== Phrasal ====
  //'foo-up'
  {
    match: '(#Verb && @hasHyphen) up',
    group: 0,
    tag: 'PhrasalVerb',
    reason: 'foo-up'
  }, {
    match: '(#Verb && @hasHyphen) off',
    group: 0,
    tag: 'PhrasalVerb',
    reason: 'foo-off'
  }, {
    match: '(#Verb && @hasHyphen) over',
    group: 0,
    tag: 'PhrasalVerb',
    reason: 'foo-over'
  }, {
    match: '(#Verb && @hasHyphen) out',
    group: 0,
    tag: 'PhrasalVerb',
    reason: 'foo-out'
  }, //fall over
  {
    match: '#PhrasalVerb [#PhrasalVerb]',
    group: 0,
    tag: 'Particle',
    reason: 'phrasal-particle'
  }, // ==== Copula ====
  //will be running (not copula)
  {
    match: '[will #Adverb? not? #Adverb? be] #Gerund',
    group: 0,
    tag: 'Copula',
    reason: 'will-be-copula'
  }, //for more complex forms, just tag 'be'
  {
    match: 'will #Adverb? not? #Adverb? [be] #Adjective',
    group: 0,
    tag: 'Copula',
    reason: 'be-copula'
  }, // ==== Infinitive ====
  //march to
  {
    match: '[march] (up|down|back|to|toward)',
    group: 0,
    tag: 'Infinitive',
    reason: 'march-to'
  }, //must march
  {
    match: '#Modal [march]',
    group: 0,
    tag: 'Infinitive',
    reason: 'must-march'
  }, //let him glue
  {
    match: '(let|make|made) (him|her|it|#Person|#Place|#Organization)+ [#Singular] (a|an|the|it)',
    group: 0,
    tag: 'Infinitive',
    reason: 'let-him-glue'
  }, //he quickly foo
  {
    match: '#Noun #Adverb [#Noun]',
    group: 0,
    tag: 'Verb',
    reason: 'quickly-foo'
  }, //will secure our
  {
    match: 'will [#Adjective]',
    group: 0,
    tag: 'Verb',
    reason: 'will-adj'
  }, //he disguised the thing
  {
    match: '#Pronoun [#Adjective] #Determiner #Adjective? #Noun',
    group: 0,
    tag: 'Verb',
    reason: 'he-adj-the'
  }, //is eager to go
  {
    match: '#Copula [#Adjective to] #Verb',
    group: 0,
    tag: 'Verb',
    reason: 'adj-to'
  }, // open the door
  {
    match: '[open] #Determiner',
    group: 0,
    tag: 'Infinitive',
    reason: 'open-the'
  }, // would wade
  {
    match: "#Modal [".concat(verbs$1, "]"),
    group: 0,
    tag: 'Verb',
    reason: 'would-mark'
  }, {
    match: "#Adverb [".concat(verbs$1, "]"),
    group: 0,
    tag: 'Verb',
    reason: 'really-mark'
  }, // wade smith
  {
    match: "".concat(verbs$1, " #Person"),
    tag: 'Person',
    reason: 'rob-smith'
  }, // wade m. Cooper
  {
    match: "".concat(verbs$1, " #Acronym? #ProperNoun"),
    tag: 'Person',
    reason: 'rob-a-smith'
  }, // damn them
  {
    match: '[shit] (#Determiner|#Possessive|them)',
    group: 0,
    tag: 'Verb',
    reason: 'swear1-verb'
  }, {
    match: '[damn] (#Determiner|#Possessive|them)',
    group: 0,
    tag: 'Verb',
    reason: 'swear2-verb'
  }, {
    match: '[fuck] (#Determiner|#Possessive|them)',
    group: 0,
    tag: 'Verb',
    reason: 'swear3-verb'
  }];
  var _07Verbs = list$3;

  var places = '(paris|alexandria|houston|kobe|salvador|sydney)';
  var list$4 = [// ==== Region ====
  //West Norforlk
  {
    match: '(west|north|south|east|western|northern|southern|eastern)+ #Place',
    tag: 'Region',
    reason: 'west-norfolk'
  }, //some us-state acronyms (exlude: al, in, la, mo, hi, me, md, ok..)
  {
    match: '#City [(al|ak|az|ar|ca|ct|dc|fl|ga|id|il|nv|nh|nj|ny|oh|or|pa|sc|tn|tx|ut|vt|pr)]',
    group: 0,
    tag: 'Region',
    reason: 'us-state'
  }, //Foo District
  {
    match: '#ProperNoun+ (district|region|province|county|prefecture|municipality|territory|burough|reservation)',
    tag: 'Region',
    reason: 'foo-district'
  }, //District of Foo
  {
    match: '(district|region|province|municipality|territory|burough|state) of #ProperNoun',
    tag: 'Region',
    reason: 'district-of-Foo'
  }, // in Foo California
  {
    match: 'in [#ProperNoun] #Place',
    group: 0,
    tag: 'Place',
    reason: 'propernoun-place'
  }, // ==== Address ====
  {
    match: '#Value #Noun (st|street|rd|road|crescent|cr|way|tr|terrace|avenue|ave)',
    tag: 'Address',
    reason: 'address-st'
  }, // in houston
  {
    match: "in [".concat(places, "]"),
    group: 0,
    tag: 'Place',
    reason: 'in-paris'
  }, {
    match: "near [".concat(places, "]"),
    group: 0,
    tag: 'Place',
    reason: 'near-paris'
  }, {
    match: "at [".concat(places, "]"),
    group: 0,
    tag: 'Place',
    reason: 'at-paris'
  }, {
    match: "from [".concat(places, "]"),
    group: 0,
    tag: 'Place',
    reason: 'from-paris'
  }, {
    match: "to [".concat(places, "]"),
    group: 0,
    tag: 'Place',
    reason: 'to-paris'
  }, {
    match: "#Place [".concat(places, "]"),
    group: 0,
    tag: 'Place',
    reason: 'tokyo-paris'
  }, // houston texas
  {
    match: "[".concat(places, "] #Place"),
    group: 0,
    tag: 'Place',
    reason: 'paris-france'
  }];
  var _08Place = list$4;

  var _09Org = [//John & Joe's
  {
    match: '#Noun (&|n) #Noun',
    tag: 'Organization',
    reason: 'Noun-&-Noun'
  }, // teachers union of Ontario
  {
    match: '#Organization of the? #ProperNoun',
    tag: 'Organization',
    reason: 'org-of-place',
    safe: true
  }, //walmart USA
  {
    match: '#Organization #Country',
    tag: 'Organization',
    reason: 'org-country'
  }, //organization
  {
    match: '#ProperNoun #Organization',
    tag: 'Organization',
    reason: 'titlecase-org'
  }, //FitBit Inc
  {
    match: '#ProperNoun (ltd|co|inc|dept|assn|bros)',
    tag: 'Organization',
    reason: 'org-abbrv'
  }, // the OCED
  {
    match: 'the [#Acronym]',
    group: 0,
    tag: 'Organization',
    reason: 'the-acronym',
    safe: true
  }, // global trade union
  {
    match: '(world|global|international|national|#Demonym) #Organization',
    tag: 'Organization',
    reason: 'global-org'
  }, // schools
  {
    match: '#Noun+ (public|private) school',
    tag: 'School',
    reason: 'noun-public-school'
  }];

  var nouns$1 = '(rose|robin|dawn|ray|holly|bill|joy|viola|penny|sky|violet|daisy|melody|kelvin|hope|mercedes|olive|jewel|faith|van|charity|miles|lily|summer|dolly|rod|dick|cliff|lane|reed|kitty|art|jean|trinity)';
  var months = '(january|april|may|june|jan|sep)'; //summer|autumn

  var list$5 = [// ==== Honorific ====
  {
    match: '[(1st|2nd|first|second)] #Honorific',
    group: 0,
    tag: 'Honorific',
    reason: 'ordinal-honorific'
  }, {
    match: '[(private|general|major|corporal|lord|lady|secretary|premier)] #Honorific? #Person',
    group: 0,
    tag: 'Honorific',
    reason: 'ambg-honorifics'
  }, // ==== FirstNames ====
  //is foo Smith
  {
    match: '#Copula [(#Noun|#PresentTense)] #LastName',
    group: 0,
    tag: 'FirstName',
    reason: 'copula-noun-lastname'
  }, //pope francis
  {
    match: '(lady|queen|sister) #ProperNoun',
    tag: 'FemaleName',
    reason: 'lady-titlecase',
    safe: true
  }, {
    match: '(king|pope|father) #ProperNoun',
    tag: 'MaleName',
    reason: 'pope-titlecase',
    safe: true
  }, //ambiguous-but-common firstnames
  {
    match: '[(will|may|april|june|said|rob|wade|ray|rusty|drew|miles|jack|chuck|randy|jan|pat|cliff|bill)] #LastName',
    group: 0,
    tag: 'FirstName',
    reason: 'maybe-lastname'
  }, // ==== Nickname ====
  // Dwayne 'the rock' Johnson
  {
    match: '#FirstName [#Determiner #Noun] #LastName',
    group: 0,
    tag: 'NickName',
    reason: 'first-noun-last'
  }, //my buddy
  {
    match: '#Possessive [#FirstName]',
    group: 0,
    tag: 'Person',
    reason: 'possessive-name'
  }, {
    match: '#Acronym #ProperNoun',
    tag: 'Person',
    reason: 'acronym-titlecase',
    safe: true
  }, //ludwig van beethovan
  {
    match: '#Person (jr|sr|md)',
    tag: 'Person',
    reason: 'person-honorific'
  }, //peter II
  {
    match: '#Person #Person the? #RomanNumeral',
    tag: 'Person',
    reason: 'roman-numeral'
  }, //'Professor Fink', 'General McCarthy'
  {
    match: '#FirstName [/^[^aiurck]$/]',
    group: 0,
    tag: ['Acronym', 'Person'],
    reason: 'john-e'
  }, //Doctor john smith jr
  //general pearson
  {
    match: '#Honorific #Person',
    tag: 'Person',
    reason: 'honorific-person'
  }, //remove single 'mr'
  {
    match: '#Honorific #Acronym',
    tag: 'Person',
    reason: 'Honorific-TitleCase'
  }, //j.k Rowling
  {
    match: '#Noun van der? #Noun',
    tag: 'Person',
    reason: 'von der noun',
    safe: true
  }, //king of spain
  {
    match: '(king|queen|prince|saint|lady) of? #Noun',
    tag: 'Person',
    reason: 'king-of-noun',
    safe: true
  }, //Foo U Ford
  {
    match: '[#ProperNoun] #Person',
    group: 0,
    tag: 'Person',
    reason: 'proper-person',
    safe: true
  }, // al sharpton
  {
    match: 'al (#Person|#ProperNoun)',
    tag: 'Person',
    reason: 'al-borlen',
    safe: true
  }, //ferdinand de almar
  {
    match: '#FirstName de #Noun',
    tag: 'Person',
    reason: 'bill-de-noun'
  }, //Osama bin Laden
  {
    match: '#FirstName (bin|al) #Noun',
    tag: 'Person',
    reason: 'bill-al-noun'
  }, //John L. Foo
  {
    match: '#FirstName #Acronym #ProperNoun',
    tag: 'Person',
    reason: 'bill-acronym-title'
  }, //Andrew Lloyd Webber
  {
    match: '#FirstName #FirstName #ProperNoun',
    tag: 'Person',
    reason: 'bill-firstname-title'
  }, //Mr Foo
  {
    match: '#Honorific #FirstName? #ProperNoun',
    tag: 'Person',
    reason: 'dr-john-Title'
  }, //peter the great
  {
    match: '#FirstName the #Adjective',
    tag: 'Person',
    reason: 'name-the-great'
  }, //very common-but-ambiguous lastnames
  {
    match: '#FirstName (green|white|brown|hall|young|king|hill|cook|gray|price)',
    tag: 'Person',
    reason: 'bill-green'
  }, // faith smith
  {
    match: "".concat(nouns$1, " #Person"),
    tag: 'Person',
    reason: 'ray-smith',
    safe: true
  }, // faith m. Smith
  {
    match: "".concat(nouns$1, " #Acronym? #ProperNoun"),
    tag: 'Person',
    reason: 'ray-a-smith',
    safe: true
  }, //give to april
  {
    match: "#Infinitive #Determiner? #Adjective? #Noun? (to|for) [".concat(months, "]"),
    group: 0,
    tag: 'Person',
    reason: 'ambig-person'
  }, // remind june
  {
    match: "#Infinitive [".concat(months, "]"),
    group: 0,
    tag: 'Person',
    reason: 'infinitive-person'
  }, // may waits for
  {
    match: "[".concat(months, "] #PresentTense for"),
    group: 0,
    tag: 'Person',
    reason: 'ambig-active-for'
  }, // may waits to
  {
    match: "[".concat(months, "] #PresentTense to"),
    group: 0,
    tag: 'Person',
    reason: 'ambig-active-to'
  }, // april will
  {
    match: "[".concat(months, "] #Modal"),
    group: 0,
    tag: 'Person',
    reason: 'ambig-modal'
  }, // would april
  {
    match: "#Modal [".concat(months, "]"),
    group: 0,
    tag: 'Person',
    reason: 'modal-ambig'
  }, // it is may
  {
    match: "#Copula [".concat(months, "]"),
    group: 0,
    tag: 'Person',
    reason: 'is-may'
  }, // may is
  {
    match: "[".concat(months, "] #Copula"),
    group: 0,
    tag: 'Person',
    reason: 'may-is'
  }, // with april
  {
    match: "that [".concat(months, "]"),
    group: 0,
    tag: 'Person',
    reason: 'that-month'
  }, // with april
  {
    match: "with [".concat(months, "]"),
    group: 0,
    tag: 'Person',
    reason: 'with-month'
  }, // for april
  {
    match: "for [".concat(months, "]"),
    group: 0,
    tag: 'Person',
    reason: 'for-month'
  }, // this april
  {
    match: "this [".concat(months, "]"),
    group: 0,
    tag: 'Month',
    reason: 'this-may'
  }, //maybe not 'this'
  // next april
  {
    match: "next [".concat(months, "]"),
    group: 0,
    tag: 'Month',
    reason: 'next-may'
  }, // last april
  {
    match: "last [".concat(months, "]"),
    group: 0,
    tag: 'Month',
    reason: 'last-may'
  }, // wednesday april
  {
    match: "#Date [".concat(months, "]"),
    group: 0,
    tag: 'Month',
    reason: 'date-may'
  }, // may 5th
  {
    match: "[".concat(months, "] the? #Value"),
    group: 0,
    tag: 'Month',
    reason: 'may-5th'
  }, // 5th of may
  {
    match: "#Value of [".concat(months, "]"),
    group: 0,
    tag: 'Month',
    reason: '5th-of-may'
  }, // dick van dyke
  {
    match: '#ProperNoun (van|al|bin) #ProperNoun',
    tag: 'Person',
    reason: 'title-van-title',
    safe: true
  }, //jose de Sucre
  {
    match: '#ProperNoun (de|du) la? #ProperNoun',
    tag: 'Person',
    reason: 'title-de-title',
    safe: true
  }, //Jani K. Smith
  {
    match: '#Singular #Acronym #LastName',
    tag: '#Person',
    reason: 'title-acro-noun',
    safe: true
  }, //John Foo
  {
    match: '#FirstName (#Noun && #ProperNoun) #ProperNoun?',
    tag: 'Person',
    reason: 'firstname-titlecase'
  }, //Joe K. Sombrero
  {
    match: '#FirstName #Acronym #Noun',
    tag: 'Person',
    reason: 'n-acro-noun',
    safe: true
  }];
  var _10People = list$5;

  var matches = [];
  matches = matches.concat(_01Misc);
  matches = matches.concat(_02Dates);
  matches = matches.concat(_03Noun);
  matches = matches.concat(_04Adjective);
  matches = matches.concat(_05Adverb);
  matches = matches.concat(_06Value);
  matches = matches.concat(_07Verbs);
  matches = matches.concat(_08Place);
  matches = matches.concat(_09Org);
  matches = matches.concat(_10People); // cache the easier conditions up-front

  var cacheRequired$1 = function cacheRequired(reg) {
    var needTags = [];
    var needWords = [];
    reg.forEach(function (obj) {
      if (obj.optional === true || obj.negative === true) {
        return;
      }

      if (obj.tag !== undefined) {
        needTags.push(obj.tag);
      }

      if (obj.word !== undefined) {
        needWords.push(obj.word);
      }
    });
    return {
      tags: _unique(needTags),
      words: _unique(needWords)
    };
  };

  var allLists = function allLists(m) {
    var more = [];
    var lists = m.reg.filter(function (r) {
      return r.oneOf !== undefined;
    });

    if (lists.length === 1) {
      var i = m.reg.findIndex(function (r) {
        return r.oneOf !== undefined;
      });
      Object.keys(m.reg[i].oneOf).forEach(function (w) {
        var newM = Object.assign({}, m);
        newM.reg = newM.reg.slice(0);
        newM.reg[i] = Object.assign({}, newM.reg[i]);
        newM.reg[i].word = w;
        delete newM.reg[i].operator;
        delete newM.reg[i].oneOf;
        newM.reason += '-' + w;
        more.push(newM);
      });
    }

    return more;
  }; // parse them


  var all = [];
  matches.forEach(function (m) {
    m.reg = syntax_1(m.match);
    var enumerated = allLists(m);

    if (enumerated.length > 0) {
      all = all.concat(enumerated);
    } else {
      all.push(m);
    }
  });
  all.forEach(function (m) {
    m.required = cacheRequired$1(m.reg);
    return m;
  });
  var matches_1 = all;

  var hasEvery = function hasEvery(chances) {
    if (chances.length === 0) {
      return [];
    }

    var obj = {};
    chances.forEach(function (arr) {
      arr = _unique(arr);

      for (var i = 0; i < arr.length; i++) {
        obj[arr[i]] = obj[arr[i]] || 0;
        obj[arr[i]] += 1;
      }
    });
    var res = Object.keys(obj);
    res = res.filter(function (k) {
      return obj[k] === chances.length;
    });
    res = res.map(function (num) {
      return Number(num);
    });
    return res;
  };

  var runner = function runner(doc) {
    //find phrases to try for each match
    matches_1.forEach(function (m) {
      var allChances = [];
      m.required.words.forEach(function (w) {
        allChances.push(doc._cache.words[w] || []);
      });
      m.required.tags.forEach(function (tag) {
        allChances.push(doc._cache.tags[tag] || []);
      });
      var worthIt = hasEvery(allChances);

      if (worthIt.length === 0) {
        return;
      }

      var phrases = worthIt.map(function (index) {
        return doc.list[index];
      });
      var tryDoc = doc.buildFrom(phrases); // phrases getting tagged

      var match = tryDoc.match(m.reg, m.group);

      if (match.found) {
        if (m.safe === true) {
          match.tagSafe(m.tag, m.reason);
        } else {
          match.tag(m.tag, m.reason);
        }
      }
    });
  };

  var runner_1 = runner; // console.log(hasEvery([[1, 2, 2, 3], [2, 3], []]))

  // misc: 40ms
  //sequence of match-tag statements to correct mis-tags

  var corrections = function corrections(doc) {
    runner_1(doc);
    fixMisc(doc);
    return doc;
  };

  var _04Correction = corrections;

  /** POS-tag all terms in this document */

  var tagger = function tagger(doc) {
    var terms = doc.termList(); // check against any known-words

    doc = _01Init(doc, terms); // everything has gotta be something. ¯\_(:/)_/¯

    doc = _02Fallbacks(doc, terms); // support "didn't" & "spencer's"

    doc = _03Contractions(doc); //set our cache, to speed things up

    doc.cache(); // wiggle-around the results, so they make more sense

    doc = _04Correction(doc); // remove our cache, as it's invalidated now

    doc.uncache(); // run any user-given tagger functions

    doc.world.taggers.forEach(function (fn) {
      fn(doc);
    });
    return doc;
  };

  var _02Tagger = tagger;

  var addMethod = function addMethod(Doc) {
    /**  */
    var Abbreviations = /*#__PURE__*/function (_Doc) {
      _inherits(Abbreviations, _Doc);

      var _super = _createSuper(Abbreviations);

      function Abbreviations() {
        _classCallCheck(this, Abbreviations);

        return _super.apply(this, arguments);
      }

      _createClass(Abbreviations, [{
        key: "stripPeriods",
        value: function stripPeriods() {
          this.termList().forEach(function (t) {
            if (t.tags.Abbreviation === true && t.next) {
              t.post = t.post.replace(/^\./, '');
            }

            var str = t.text.replace(/\./, '');
            t.set(str);
          });
          return this;
        }
      }, {
        key: "addPeriods",
        value: function addPeriods() {
          this.termList().forEach(function (t) {
            t.post = t.post.replace(/^\./, '');
            t.post = '.' + t.post;
          });
          return this;
        }
      }]);

      return Abbreviations;
    }(Doc);

    Abbreviations.prototype.unwrap = Abbreviations.prototype.stripPeriods;

    Doc.prototype.abbreviations = function (n) {
      var match = this.match('#Abbreviation');

      if (typeof n === 'number') {
        match = match.get(n);
      }

      return new Abbreviations(match.list, this, this.world);
    };

    return Doc;
  };

  var Abbreviations = addMethod;

  var hasPeriod = /\./;

  var addMethod$1 = function addMethod(Doc) {
    /**  */
    var Acronyms = /*#__PURE__*/function (_Doc) {
      _inherits(Acronyms, _Doc);

      var _super = _createSuper(Acronyms);

      function Acronyms() {
        _classCallCheck(this, Acronyms);

        return _super.apply(this, arguments);
      }

      _createClass(Acronyms, [{
        key: "stripPeriods",
        value: function stripPeriods() {
          this.termList().forEach(function (t) {
            var str = t.text.replace(/\./g, '');
            t.set(str);
          });
          return this;
        }
      }, {
        key: "addPeriods",
        value: function addPeriods() {
          this.termList().forEach(function (t) {
            var str = t.text.replace(/\./g, '');
            str = str.split('').join('.'); // don't add a end-period if there's a sentence-end one

            if (hasPeriod.test(t.post) === false) {
              str += '.';
            }

            t.set(str);
          });
          return this;
        }
      }]);

      return Acronyms;
    }(Doc);

    Acronyms.prototype.unwrap = Acronyms.prototype.stripPeriods;
    Acronyms.prototype.strip = Acronyms.prototype.stripPeriods;

    Doc.prototype.acronyms = function (n) {
      var match = this.match('#Acronym');

      if (typeof n === 'number') {
        match = match.get(n);
      }

      return new Acronyms(match.list, this, this.world);
    };

    return Doc;
  };

  var Acronyms = addMethod$1;

  var addMethod$2 = function addMethod(Doc) {
    /** split into approximate sub-sentence phrases */
    Doc.prototype.clauses = function (n) {
      // an awkward way to disambiguate a comma use
      var commas = this["if"]('@hasComma').notIf('@hasComma @hasComma') //fun, cool...
      .notIf('@hasComma . .? (and|or) .') //cool, and fun
      .notIf('(#City && @hasComma) #Country') //'toronto, canada'
      .notIf('(#Date && @hasComma) #Year') //'july 6, 1992'
      .notIf('@hasComma (too|also)$') //at end of sentence
      .match('@hasComma');
      var found = this.splitAfter(commas);
      var quotes = found.quotations();
      found = found.splitOn(quotes);
      var parentheses = found.parentheses();
      found = found.splitOn(parentheses); // it is cool and it is ..

      var conjunctions = found["if"]('#Copula #Adjective #Conjunction (#Pronoun|#Determiner) #Verb').match('#Conjunction');
      found = found.splitBefore(conjunctions); // if it is this then that

      var condition = found["if"]('if .{2,9} then .').match('then');
      found = found.splitBefore(condition); // misc clause partitions

      found = found.splitBefore('as well as .');
      found = found.splitBefore('such as .');
      found = found.splitBefore('in addition to .'); // semicolons, dashes

      found = found.splitAfter('@hasSemicolon');
      found = found.splitAfter('@hasDash'); // passive voice verb - '.. which was robbed is empty'
      // let passive = found.match('#Noun (which|that) (was|is) #Adverb? #PastTense #Adverb?')
      // if (passive.found) {
      //   found = found.splitAfter(passive)
      // }
      // //which the boy robbed
      // passive = found.match('#Noun (which|that) the? #Noun+ #Adverb? #PastTense #Adverb?')
      // if (passive.found) {
      //   found = found.splitAfter(passive)
      // }
      // does there appear to have relative/subordinate clause still?

      var tooLong = found.filter(function (d) {
        return d.wordCount() > 5 && d.match('#Verb+').length >= 2;
      });

      if (tooLong.found) {
        var m = tooLong.splitAfter('#Noun .* #Verb .* #Noun+');
        found = found.splitOn(m.eq(0));
      }

      if (typeof n === 'number') {
        found = found.get(n);
      }

      return new Doc(found.list, this, this.world);
    };

    return Doc;
  };

  var Clauses = addMethod$2;

  var addMethod$3 = function addMethod(Doc) {
    /**  */
    var Contractions = /*#__PURE__*/function (_Doc) {
      _inherits(Contractions, _Doc);

      var _super = _createSuper(Contractions);

      function Contractions(list, from, world) {
        var _this;

        _classCallCheck(this, Contractions);

        _this = _super.call(this, list, from, world);
        _this.contracted = null;
        return _this;
      }
      /** turn didn't into 'did not' */


      _createClass(Contractions, [{
        key: "expand",
        value: function expand() {
          this.list.forEach(function (p) {
            var terms = p.terms(); //change the case?

            var isTitlecase = terms[0].isTitleCase();
            terms.forEach(function (t, i) {
              //use the implicit text
              t.set(t.implicit || t.text);
              t.implicit = undefined; //add whitespace

              if (i < terms.length - 1 && t.post === '') {
                t.post += ' ';
              }
            }); //set titlecase

            if (isTitlecase) {
              terms[0].toTitleCase();
            }
          });
          return this;
        }
      }]);

      return Contractions;
    }(Doc); //find contractable, expanded-contractions
    // const findExpanded = r => {
    //   let remain = r.not('#Contraction')
    //   let m = remain.match('(#Noun|#QuestionWord) (#Copula|did|do|have|had|could|would|will)')
    //   m.concat(remain.match('(they|we|you|i) have'))
    //   m.concat(remain.match('i am'))
    //   m.concat(remain.match('(#Copula|#Modal|do|does|have|has|can|will) not'))
    //   return m
    // }


    Doc.prototype.contractions = function (n) {
      //find currently-contracted
      var found = this.match('@hasContraction+'); //(may want to split these up)
      //todo: split consecutive contractions

      if (typeof n === 'number') {
        found = found.get(n);
      }

      return new Contractions(found.list, this, this.world);
    }; //aliases


    Doc.prototype.expanded = Doc.prototype.isExpanded;
    Doc.prototype.contracted = Doc.prototype.isContracted;
    return Doc;
  };

  var Contractions = addMethod$3;

  var addMethod$4 = function addMethod(Doc) {
    //pull it apart..
    var parse = function parse(doc) {
      var things = doc.splitAfter('@hasComma').splitOn('(and|or) not?').not('(and|or) not?');
      var beforeLast = doc.match('[.] (and|or)', 0);
      return {
        things: things,
        conjunction: doc.match('(and|or) not?'),
        beforeLast: beforeLast,
        hasOxford: beforeLast.has('@hasComma')
      };
    };
    /** cool, fun, and nice */


    var Lists = /*#__PURE__*/function (_Doc) {
      _inherits(Lists, _Doc);

      var _super = _createSuper(Lists);

      function Lists() {
        _classCallCheck(this, Lists);

        return _super.apply(this, arguments);
      }

      _createClass(Lists, [{
        key: "conjunctions",

        /** coordinating conjunction */
        value: function conjunctions() {
          return this.match('(and|or)');
        }
        /** split-up by list object */

      }, {
        key: "parts",
        value: function parts() {
          return this.splitAfter('@hasComma').splitOn('(and|or) not?');
        }
        /** remove the conjunction */

      }, {
        key: "items",
        value: function items() {
          return parse(this).things;
        }
        /** add a new unit to the list */

      }, {
        key: "add",
        value: function add(str) {
          this.forEach(function (p) {
            var beforeLast = parse(p).beforeLast;
            beforeLast.append(str); //add a comma to it

            beforeLast.termList(0).addPunctuation(',');
          });
          return this;
        }
        /** remove any matching unit from the list */

      }, {
        key: "remove",
        value: function remove(match) {
          return this.items()["if"](match).remove();
        }
        /** return only lists that use a serial comma */

      }, {
        key: "hasOxfordComma",
        value: function hasOxfordComma() {
          return this.filter(function (doc) {
            return parse(doc).hasOxford;
          });
        }
      }, {
        key: "addOxfordComma",
        value: function addOxfordComma() {
          var items = this.items();
          var needsComma = items.eq(items.length - 2);

          if (needsComma.found && needsComma.has('@hasComma') === false) {
            needsComma.post(', ');
          }

          return this;
        }
      }, {
        key: "removeOxfordComma",
        value: function removeOxfordComma() {
          var items = this.items();
          var needsComma = items.eq(items.length - 2);

          if (needsComma.found && needsComma.has('@hasComma') === true) {
            needsComma.post(' ');
          }

          return this;
        }
      }]);

      return Lists;
    }(Doc); // aliases


    Lists.prototype.things = Lists.prototype.items;

    Doc.prototype.lists = function (n) {
      var m = this["if"]('@hasComma+ .? (and|or) not? .'); // person-list

      var nounList = m.match('(#Noun|#Adjective|#Determiner|#Article)+ #Conjunction not? (#Article|#Determiner)? #Adjective? #Noun+')["if"]('#Noun');
      var adjList = m.match('(#Adjective|#Adverb)+ #Conjunction not? #Adverb? #Adjective+');
      var verbList = m.match('(#Verb|#Adverb)+ #Conjunction not? #Adverb? #Verb+');
      var result = nounList.concat(adjList);
      result = result.concat(verbList);
      result = result["if"]('@hasComma');

      if (typeof n === 'number') {
        result = m.get(n);
      }

      return new Lists(result.list, this, this.world);
    };

    return Doc;
  };

  var Lists = addMethod$4;

  var noPlural = '(#Pronoun|#Place|#Value|#Person|#Uncountable|#Month|#WeekDay|#Holiday|#Possessive)'; //certain words can't be plural, like 'peace'

  var hasPlural = function hasPlural(doc) {
    if (doc.has('#Plural') === true) {
      return true;
    } // these can't be plural


    if (doc.has(noPlural) === true) {
      return false;
    }

    return true;
  };

  var hasPlural_1 = hasPlural;

  var irregulars$5 = {
    hour: 'an',
    heir: 'an',
    heirloom: 'an',
    honest: 'an',
    honour: 'an',
    honor: 'an',
    uber: 'an' //german u

  }; //pronounced letters of acronyms that get a 'an'

  var an_acronyms = {
    a: true,
    e: true,
    f: true,
    h: true,
    i: true,
    l: true,
    m: true,
    n: true,
    o: true,
    r: true,
    s: true,
    x: true
  }; //'a' regexes

  var a_regexs = [/^onc?e/i, //'wu' sound of 'o'
  /^u[bcfhjkqrstn][aeiou]/i, // 'yu' sound for hard 'u'
  /^eul/i];

  var makeArticle = function makeArticle(doc) {
    //no 'the john smith', but 'a london hotel'
    if (doc.has('#Person') || doc.has('#Place')) {
      return '';
    } //no a/an if it's plural


    if (doc.has('#Plural')) {
      return 'the';
    }

    var str = doc.text('normal').trim(); //explicit irregular forms

    if (irregulars$5.hasOwnProperty(str)) {
      return irregulars$5[str];
    } //spelled-out acronyms


    var firstLetter = str.substr(0, 1);

    if (doc.has('^@isAcronym') && an_acronyms.hasOwnProperty(firstLetter)) {
      return 'an';
    } //'a' regexes


    for (var i = 0; i < a_regexs.length; i++) {
      if (a_regexs[i].test(str)) {
        return 'a';
      }
    } //basic vowel-startings


    if (/^[aeiou]/i.test(str)) {
      return 'an';
    }

    return 'a';
  };

  var getArticle = makeArticle;

  //similar to plural/singularize rules, but not the same
  var isPlural$1 = [/(antenn|formul|nebul|vertebr|vit)ae$/i, /(octop|vir|radi|nucle|fung|cact|stimul)i$/i, /men$/i, /.tia$/i, /(m|l)ice$/i]; //similar to plural/singularize rules, but not the same

  var isSingular$1 = [/(ax|test)is$/i, /(octop|vir|radi|nucle|fung|cact|stimul)us$/i, /(octop|vir)i$/i, /(rl)f$/i, /(alias|status)$/i, /(bu)s$/i, /(al|ad|at|er|et|ed|ad)o$/i, /(ti)um$/i, /(ti)a$/i, /sis$/i, /(?:(^f)fe|(lr)f)$/i, /hive$/i, /(^aeiouy|qu)y$/i, /(x|ch|ss|sh|z)$/i, /(matr|vert|ind|cort)(ix|ex)$/i, /(m|l)ouse$/i, /(m|l)ice$/i, /(antenn|formul|nebul|vertebr|vit)a$/i, /.sis$/i, /^(?!talis|.*hu)(.*)man$/i];
  var _rules$2 = {
    isSingular: isSingular$1,
    isPlural: isPlural$1
  };

  var endS = /s$/; // double-check this term, if it is not plural, or singular.
  // (this is a partial copy of ./tagger/fallbacks/plural)
  // fallback plural if it ends in an 's'.

  var isPlural$2 = function isPlural(str) {
    // isSingular suffix rules
    if (_rules$2.isSingular.find(function (reg) {
      return reg.test(str);
    })) {
      return false;
    } // does it end in an s?


    if (endS.test(str) === true) {
      return true;
    } // is it a plural like 'fungi'?


    if (_rules$2.isPlural.find(function (reg) {
      return reg.test(str);
    })) {
      return true;
    }

    return null;
  };

  var isPlural_1$1 = isPlural$2;

  var exceptions = {
    he: 'his',
    she: 'hers',
    they: 'theirs',
    we: 'ours',
    i: 'mine',
    you: 'yours',
    her: 'hers',
    their: 'theirs',
    our: 'ours',
    my: 'mine',
    your: 'yours'
  }; // turn "David" to "David's"

  var toPossessive = function toPossessive(doc) {
    var str = doc.text('text').trim(); // exceptions

    if (exceptions.hasOwnProperty(str)) {
      doc.replaceWith(exceptions[str], true);
      doc.tag('Possessive', 'toPossessive');
      return;
    } // flanders'


    if (/s$/.test(str)) {
      str += "'";
      doc.replaceWith(str, true);
      doc.tag('Possessive', 'toPossessive');
      return;
    } //normal form:


    str += "'s";
    doc.replaceWith(str, true);
    doc.tag('Possessive', 'toPossessive');
    return;
  };

  var toPossessive_1 = toPossessive;

  // .nouns() supports some noun-phrase-ish groupings
  // pull these apart, if necessary
  var parse$1 = function parse(doc) {
    var res = {
      main: doc
    }; //support 'mayor of chicago' as one noun-phrase

    if (doc.has('#Noun (of|by|for) .')) {
      var m = doc.splitAfter('[#Noun+]', 0);
      res.main = m.eq(0);
      res.post = m.eq(1);
    }

    return res;
  };

  var parse_1 = parse$1;

  var methods$6 = {
    /** overload the original json with noun information */
    json: function json(options) {
      var n = null;

      if (typeof options === 'number') {
        n = options;
        options = null;
      }

      options = options || {
        text: true,
        normal: true,
        trim: true,
        terms: true
      };
      var res = [];
      this.forEach(function (doc) {
        var json = doc.json(options)[0];
        json.article = getArticle(doc);
        res.push(json);
      });

      if (n !== null) {
        return res[n];
      }

      return res;
    },

    /** get all adjectives describing this noun*/
    adjectives: function adjectives() {
      var list = this.lookAhead('^(that|who|which)? (was|is|will)? be? #Adverb? #Adjective+');
      list = list.concat(this.lookBehind('#Adjective+ #Adverb?$'));
      list = list.match('#Adjective');
      return list.sort('index');
    },
    isPlural: function isPlural() {
      return this["if"]('#Plural'); //assume tagger has run?
    },
    hasPlural: function hasPlural() {
      return this.filter(function (d) {
        return hasPlural_1(d);
      });
    },
    toPlural: function toPlural(agree) {
      var _this = this;

      var toPlural = this.world.transforms.toPlural;
      this.forEach(function (doc) {
        if (doc.has('#Plural') || hasPlural_1(doc) === false) {
          return;
        } // double-check it isn't an un-tagged plural


        var main = parse_1(doc).main;
        var str = main.text('reduced');

        if (!main.has('#Singular') && isPlural_1$1(str) === true) {
          return;
        }

        str = toPlural(str, _this.world);
        main.replace(str).tag('#Plural'); // 'an apple' -> 'apples'

        if (agree) {
          var an = main.lookBefore('(an|a) #Adjective?$').not('#Adjective');

          if (an.found === true) {
            an.remove();
          }
        }
      });
      return this;
    },
    toSingular: function toSingular(agree) {
      var _this2 = this;

      var toSingular = this.world.transforms.toSingular;
      this.forEach(function (doc) {
        if (doc.has('^#Singular+$') || hasPlural_1(doc) === false) {
          return;
        } // double-check it isn't an un-tagged plural


        var main = parse_1(doc).main;
        var str = main.text('reduced');

        if (!main.has('#Plural') && isPlural_1$1(str) !== true) {
          return;
        }

        str = toSingular(str, _this2.world);
        main.replace(str).tag('#Singular'); // add an article

        if (agree) {
          // 'apples' -> 'an apple'
          var start = doc;
          var adj = doc.lookBefore('#Adjective');

          if (adj.found) {
            start = adj;
          }

          var article = getArticle(start);
          start.insertBefore(article);
        }
      });
      return this;
    },
    toPossessive: function toPossessive() {
      this.forEach(function (d) {
        toPossessive_1(d);
      });
      return this;
    }
  };
  var methods_1 = methods$6;

  var addMethod$5 = function addMethod(Doc) {
    /**  */
    var Nouns = /*#__PURE__*/function (_Doc) {
      _inherits(Nouns, _Doc);

      var _super = _createSuper(Nouns);

      function Nouns() {
        _classCallCheck(this, Nouns);

        return _super.apply(this, arguments);
      }

      return Nouns;
    }(Doc); // add-in our methods


    Object.assign(Nouns.prototype, methods_1);

    Doc.prototype.nouns = function (n) {
      // don't split 'paris, france'
      var keep = this.match('(#City && @hasComma) (#Region|#Country)'); // but split the other commas

      var m = this.not(keep).splitAfter('@hasComma'); // combine them back together

      m = m.concat(keep);
      m = m.match('#Noun+ (of|by)? the? #Noun+?'); //nouns that we don't want in these results, for weird reasons

      m = m.not('#Pronoun');
      m = m.not('(there|these)');
      m = m.not('(#Month|#WeekDay)'); //allow Durations, Holidays
      // //allow possessives like "spencer's", but not generic ones like,

      m = m.not('(my|our|your|their|her|his)');
      m = m.not('(of|for|by|the)$');

      if (typeof n === 'number') {
        m = m.get(n);
      }

      return new Nouns(m.list, this, this.world);
    };

    return Doc;
  };

  var Nouns = addMethod$5;

  var open = /\(/;
  var close = /\)/;

  var addMethod$6 = function addMethod(Doc) {
    /** anything between (these things) */
    var Parentheses = /*#__PURE__*/function (_Doc) {
      _inherits(Parentheses, _Doc);

      var _super = _createSuper(Parentheses);

      function Parentheses() {
        _classCallCheck(this, Parentheses);

        return _super.apply(this, arguments);
      }

      _createClass(Parentheses, [{
        key: "unwrap",

        /** remove the parentheses characters */
        value: function unwrap() {
          this.list.forEach(function (p) {
            var first = p.terms(0);
            first.pre = first.pre.replace(open, '');
            var last = p.lastTerm();
            last.post = last.post.replace(close, '');
          });
          return this;
        }
      }]);

      return Parentheses;
    }(Doc);

    Doc.prototype.parentheses = function (n) {
      var list = [];
      this.list.forEach(function (p) {
        var terms = p.terms(); //look for opening brackets

        for (var i = 0; i < terms.length; i += 1) {
          var t = terms[i];

          if (open.test(t.pre)) {
            //look for the closing bracket..
            for (var o = i; o < terms.length; o += 1) {
              if (close.test(terms[o].post)) {
                var len = o - i + 1;
                list.push(p.buildFrom(t.id, len));
                i = o;
                break;
              }
            }
          }
        }
      }); //support nth result

      if (typeof n === 'number') {
        if (list[n]) {
          list = [list[n]];
        } else {
          list = [];
        }

        return new Parentheses(list, this, this.world);
      }

      return new Parentheses(list, this, this.world);
    };

    return Doc;
  };

  var Parentheses = addMethod$6;

  var addMethod$7 = function addMethod(Doc) {
    /**  */
    var Possessives = /*#__PURE__*/function (_Doc) {
      _inherits(Possessives, _Doc);

      var _super = _createSuper(Possessives);

      function Possessives(list, from, world) {
        var _this;

        _classCallCheck(this, Possessives);

        _this = _super.call(this, list, from, world);
        _this.contracted = null;
        return _this;
      }
      /** turn didn't into 'did not' */


      _createClass(Possessives, [{
        key: "strip",
        value: function strip() {
          this.list.forEach(function (p) {
            var terms = p.terms();
            terms.forEach(function (t) {
              var str = t.text.replace(/'s$/, '');
              t.set(str || t.text);
            });
          });
          return this;
        }
      }]);

      return Possessives;
    }(Doc); //find contractable, expanded-contractions
    // const findExpanded = r => {
    //   let remain = r.not('#Contraction')
    //   let m = remain.match('(#Noun|#QuestionWord) (#Copula|did|do|have|had|could|would|will)')
    //   m.concat(remain.match('(they|we|you|i) have'))
    //   m.concat(remain.match('i am'))
    //   m.concat(remain.match('(#Copula|#Modal|do|does|have|has|can|will) not'))
    //   return m
    // }


    Doc.prototype.possessives = function (n) {
      //find currently-contracted
      var found = this.match('#Noun+? #Possessive'); //todo: split consecutive contractions

      if (typeof n === 'number') {
        found = found.get(n);
      }

      return new Possessives(found.list, this, this.world);
    };

    return Doc;
  };

  var Possessives = addMethod$7;

  var pairs = {
    "\"": "\"",
    // 'StraightDoubleQuotes'
    "\uFF02": "\uFF02",
    // 'StraightDoubleQuotesWide'
    "'": "'",
    // 'StraightSingleQuotes'
    "\u201C": "\u201D",
    // 'CommaDoubleQuotes'
    "\u2018": "\u2019",
    // 'CommaSingleQuotes'
    "\u201F": "\u201D",
    // 'CurlyDoubleQuotesReversed'
    "\u201B": "\u2019",
    // 'CurlySingleQuotesReversed'
    "\u201E": "\u201D",
    // 'LowCurlyDoubleQuotes'
    "\u2E42": "\u201D",
    // 'LowCurlyDoubleQuotesReversed'
    "\u201A": "\u2019",
    // 'LowCurlySingleQuotes'
    "\xAB": "\xBB",
    // 'AngleDoubleQuotes'
    "\u2039": "\u203A",
    // 'AngleSingleQuotes'
    // Prime 'non quotation'
    "\u2035": "\u2032",
    // 'PrimeSingleQuotes'
    "\u2036": "\u2033",
    // 'PrimeDoubleQuotes'
    "\u2037": "\u2034",
    // 'PrimeTripleQuotes'
    // Prime 'quotation' variation
    "\u301D": "\u301E",
    // 'PrimeDoubleQuotes'
    "`": "\xB4",
    // 'PrimeSingleQuotes'
    "\u301F": "\u301E" // 'LowPrimeDoubleQuotesReversed'

  };
  var hasOpen = RegExp('(' + Object.keys(pairs).join('|') + ')');

  var addMethod$8 = function addMethod(Doc) {
    /** "these things" */
    var Quotations = /*#__PURE__*/function (_Doc) {
      _inherits(Quotations, _Doc);

      var _super = _createSuper(Quotations);

      function Quotations() {
        _classCallCheck(this, Quotations);

        return _super.apply(this, arguments);
      }

      _createClass(Quotations, [{
        key: "unwrap",

        /** remove the quote characters */
        value: function unwrap() {
          return this;
        }
      }]);

      return Quotations;
    }(Doc);

    Doc.prototype.quotations = function (n) {
      var list = [];
      this.list.forEach(function (p) {
        var terms = p.terms(); //look for opening quotes

        for (var i = 0; i < terms.length; i += 1) {
          var t = terms[i];

          if (hasOpen.test(t.pre)) {
            var _char = (t.pre.match(hasOpen) || [])[0];
            var want = pairs[_char]; // if (!want) {
            //   console.warn('missing quote char ' + char)
            // }
            //look for the closing bracket..

            for (var o = i; o < terms.length; o += 1) {
              if (terms[o].post.indexOf(want) !== -1) {
                var len = o - i + 1;
                list.push(p.buildFrom(t.id, len));
                i = o;
                break;
              }
            }
          }
        }
      }); //support nth result

      if (typeof n === 'number') {
        if (list[n]) {
          list = [list[n]];
        } else {
          list = [];
        }

        return new Quotations(list, this, this.world);
      }

      return new Quotations(list, this, this.world);
    }; // alias


    Doc.prototype.quotes = Doc.prototype.quotations;
    return Doc;
  };

  var Quotations = addMethod$8;

  // walked => walk  - turn a verb into it's root form
  var toInfinitive$1 = function toInfinitive(parsed, world) {
    var verb = parsed.verb; // console.log(parsed)
    // verb.debug()
    //1. if it's already infinitive

    var str = verb.text('normal');

    if (verb.has('#Infinitive')) {
      return str;
    } // 2. world transform does the heavy-lifting


    var tense = null;

    if (verb.has('#PastTense')) {
      tense = 'PastTense';
    } else if (verb.has('#Gerund')) {
      tense = 'Gerund';
    } else if (verb.has('#PresentTense')) {
      tense = 'PresentTense';
    } else if (verb.has('#Participle')) {
      tense = 'Participle';
    } else if (verb.has('#Actor')) {
      tense = 'Actor';
    }

    return world.transforms.toInfinitive(str, world, tense);
  };

  var toInfinitive_1$1 = toInfinitive$1;

  // spencer walks -> singular
  // we walk -> plural
  // the most-recent noun-phrase, before this verb.
  var findNoun = function findNoun(vb) {
    var noun = vb.lookBehind('#Noun+').last();
    return noun;
  }; //sometimes you can tell if a verb is plural/singular, just by the verb
  // i am / we were
  // othertimes you need its subject 'we walk' vs 'i walk'


  var isPlural$3 = function isPlural(parsed) {
    var vb = parsed.verb;

    if (vb.has('(are|were|does)') || parsed.auxiliary.has('(are|were|does)')) {
      return true;
    }

    if (vb.has('(is|am|do|was)') || parsed.auxiliary.has('(is|am|do|was)')) {
      return false;
    } //consider its prior noun


    var noun = findNoun(vb);

    if (noun.has('(we|they|you)')) {
      return true;
    }

    if (noun.has('#Plural')) {
      return true;
    }

    if (noun.has('#Singular')) {
      return false;
    }

    return null;
  };

  var isPlural_1$2 = isPlural$3;

  // #Copula : is           -> 'is not'
  // #PastTense : walked    -> did not walk
  // #PresentTense : walks  -> does not walk
  // #Gerund : walking:     -> not walking
  // #Infinitive : walk     -> do not walk

  var toNegative = function toNegative(parsed, world) {
    var vb = parsed.verb; // if it's already negative...

    if (parsed.negative.found) {
      return;
    } // would walk -> would not walk


    if (parsed.auxiliary.found) {
      parsed.auxiliary.eq(0).append('not'); // 'would not have' ➔ 'would not have'

      if (parsed.auxiliary.has('#Modal have not')) {
        parsed.auxiliary.replace('have not', 'not have');
      }

      return;
    } // is walking -> is not walking


    if (vb.has('(#Copula|will|has|had|do)')) {
      vb.append('not');
      return;
    } // walked -> did not walk


    if (vb.has('#PastTense')) {
      var inf = toInfinitive_1$1(parsed, world);
      vb.replaceWith(inf, true);
      vb.prepend('did not');
      return;
    } // walks -> does not walk


    if (vb.has('#PresentTense')) {
      var _inf = toInfinitive_1$1(parsed, world);

      vb.replaceWith(_inf, true);

      if (isPlural_1$2(parsed)) {
        vb.prepend('do not');
      } else {
        vb.prepend('does not');
      }

      return;
    } //walking -> not walking


    if (vb.has('#Gerund')) {
      var _inf2 = toInfinitive_1$1(parsed, world);

      vb.replaceWith(_inf2, true);
      vb.prepend('not');
      return;
    } //fallback 1:  walk -> does not walk


    if (isPlural_1$2(parsed)) {
      vb.prepend('does not');
      return;
    } //fallback 2:  walk -> do not walk


    vb.prepend('do not');
    return;
  };

  var toNegative_1 = toNegative;

  // turn 'would not really walk up' into parts
  var parseVerb = function parseVerb(vb) {
    var parsed = {
      adverb: vb.match('#Adverb+'),
      // 'really'
      negative: vb.match('#Negative'),
      // 'not'
      auxiliary: vb.match('#Auxiliary+').not('(#Negative|#Adverb)'),
      // 'will' of 'will go'
      particle: vb.match('#Particle'),
      // 'up' of 'pull up'
      verb: vb.match('#Verb+').not('(#Adverb|#Negative|#Auxiliary|#Particle)')
    }; // fallback, if no verb found

    if (!parsed.verb.found) {
      // blank-everything
      Object.keys(parsed).forEach(function (k) {
        parsed[k] = parsed[k].not('.');
      }); // it's all the verb

      parsed.verb = vb;
      return parsed;
    } //


    if (parsed.adverb && parsed.adverb.found) {
      var match = parsed.adverb.text('reduced') + '$';

      if (vb.has(match)) {
        parsed.adverbAfter = true;
      }
    }

    return parsed;
  };

  var parse$2 = parseVerb;

  /** too many special cases for is/was/will be*/

  var toBe = function toBe(parsed) {
    var isI = false;
    var plural = isPlural_1$2(parsed);
    var isNegative = parsed.negative.found; //account for 'i is' -> 'i am' irregular
    // if (vb.parent && vb.parent.has('i #Adverb? #Copula')) {
    //   isI = true;
    // }
    // 'i look', not 'i looks'

    if (parsed.verb.lookBehind('(i|we) (#Adverb|#Verb)?$').found) {
      isI = true;
    }

    var obj = {
      PastTense: 'was',
      PresentTense: 'is',
      FutureTense: 'will be',
      Infinitive: 'is',
      Gerund: 'being',
      Actor: '',
      PerfectTense: 'been',
      Pluperfect: 'been'
    }; //"i is" -> "i am"

    if (isI === true) {
      obj.PresentTense = 'am';
      obj.Infinitive = 'am';
    }

    if (plural) {
      obj.PastTense = 'were';
      obj.PresentTense = 'are';
      obj.Infinitive = 'are';
    }

    if (isNegative) {
      obj.PastTense += ' not';
      obj.PresentTense += ' not';
      obj.FutureTense = 'will not be';
      obj.Infinitive += ' not';
      obj.PerfectTense = 'not ' + obj.PerfectTense;
      obj.Pluperfect = 'not ' + obj.Pluperfect;
      obj.Gerund = 'not ' + obj.Gerund;
    }

    return obj;
  };

  var toBe_1 = toBe;

  // 'may/could/should' -> 'may/could/should have'
  var doModal = function doModal(parsed) {
    var str = parsed.verb.text();
    var res = {
      PastTense: str + ' have',
      PresentTense: str,
      FutureTense: str,
      Infinitive: str // Gerund: ,
      // Actor: '',
      // PerfectTense: '',
      // Pluperfect: '',

    };
    return res;
  };

  var doModal_1 = doModal;

  var conjugate$2 = function conjugate(parsed, world) {
    var verb = parsed.verb; //special handling of 'is', 'will be', etc.

    if (verb.has('#Copula') || verb.out('normal') === 'be' && parsed.auxiliary.has('will')) {
      return toBe_1(parsed);
    } // special handling of 'he could.'


    if (verb.has('#Modal')) {
      return doModal_1(parsed);
    }

    var hasHyphen = parsed.verb.termList(0).hasHyphen();
    var infinitive = toInfinitive_1$1(parsed, world);

    if (!infinitive) {
      return {};
    }

    var forms = world.transforms.conjugate(infinitive, world);
    forms.Infinitive = infinitive; // add particle to phrasal verbs ('fall over')

    if (parsed.particle.found) {
      var particle = parsed.particle.text();
      var space = hasHyphen === true ? '-' : ' ';
      Object.keys(forms).forEach(function (k) {
        return forms[k] += space + particle;
      });
    } //put the adverb at the end?
    // if (parsed.adverb.found) {
    // let adverb = parsed.adverb.text()
    // let space = hasHyphen === true ? '-' : ' '
    // if (parsed.adverbAfter === true) {
    //   Object.keys(forms).forEach(k => (forms[k] += space + adverb))
    // } else {
    //   Object.keys(forms).forEach(k => (forms[k] = adverb + space + forms[k]))
    // }
    // }
    //apply negative


    var isNegative = parsed.negative.found;

    if (isNegative) {
      forms.PastTense = 'did not ' + forms.Infinitive;
      forms.PresentTense = 'does not ' + forms.Infinitive;
      forms.Gerund = 'not ' + forms.Gerund;
    } //future Tense is pretty straightforward


    if (!forms.FutureTense) {
      if (isNegative) {
        forms.FutureTense = 'will not ' + forms.Infinitive;
      } else {
        forms.FutureTense = 'will ' + forms.Infinitive;
      }
    }

    if (isNegative) {
      forms.Infinitive = 'not ' + forms.Infinitive;
    }

    return forms;
  };

  var conjugate_1$1 = conjugate$2;

  // if something is 'modal-ish' we are forced to use past-participle
  // ('i could drove' is wrong)

  var useParticiple = function useParticiple(parsed) {
    if (parsed.auxiliary.has('(could|should|would|may|can|must)')) {
      return true;
    }

    if (parsed.auxiliary.has('am .+? being')) {
      return true;
    }

    if (parsed.auxiliary.has('had .+? been')) {
      return true;
    }

    return false;
  }; // conjugate 'drive' ➔ 'have driven'


  var toParticiple = function toParticiple(parsed, world) {
    //is it already a participle?
    if (parsed.auxiliary.has('(have|had)') && parsed.verb.has('#Participle')) {
      return;
    } // try to swap the main verb to its participle form


    var obj = conjugate_1$1(parsed, world);
    var str = obj.Participle || obj.PastTense;

    if (str) {
      parsed.verb.replaceWith(str, false);
    } // 'am being driven' ➔ 'have been driven'


    if (parsed.auxiliary.has('am .+? being')) {
      parsed.auxiliary.remove('am');
      parsed.auxiliary.replace('being', 'have been');
    } // add a 'have'


    if (!parsed.auxiliary.has('have')) {
      parsed.auxiliary.append('have');
    } // tag it as a participle


    parsed.verb.tag('Participle', 'toParticiple'); // turn 'i can swim' to -> 'i could swim'

    parsed.auxiliary.replace('can', 'could'); //'must be' ➔ 'must have been'

    parsed.auxiliary.replace('be have', 'have been'); //'not have' ➔ 'have not'

    parsed.auxiliary.replace('not have', 'have not'); // ensure all new words are tagged right

    parsed.auxiliary.tag('Auxiliary');
  };

  var participle = {
    useParticiple: useParticiple,
    toParticiple: toParticiple
  };

  var _toParticiple = participle.toParticiple,
      useParticiple$1 = participle.useParticiple; // remove any tense-information in auxiliary verbs

  var makeNeutral = function makeNeutral(parsed) {
    //remove tense-info from auxiliaries
    parsed.auxiliary.remove('(will|are|am|being)');
    parsed.auxiliary.remove('(did|does)');
    parsed.auxiliary.remove('(had|has|have)'); //our conjugation includes the 'not' and the phrasal-verb particle

    parsed.particle.remove();
    parsed.negative.remove();
    return parsed;
  };

  var methods$7 = {
    /** overload the original json with verb information */
    json: function json(options) {
      var _this = this;

      var n = null;

      if (typeof options === 'number') {
        n = options;
        options = null;
      }

      options = options || {
        text: true,
        normal: true,
        trim: true,
        terms: true
      };
      var res = [];
      this.forEach(function (p) {
        var json = p.json(options)[0];
        var parsed = parse$2(p);
        json.parts = {};
        Object.keys(parsed).forEach(function (k) {
          if (parsed[k] && parsed[k].isA === 'Doc') {
            json.parts[k] = parsed[k].text('normal');
          } else {
            json.parts[k] = parsed[k];
          }
        });
        json.isNegative = p.has('#Negative');
        json.conjugations = conjugate_1$1(parsed, _this.world);
        res.push(json);
      });

      if (n !== null) {
        return res[n];
      }

      return res;
    },

    /** grab the adverbs describing these verbs */
    adverbs: function adverbs() {
      var list = []; // look at internal adverbs

      this.forEach(function (vb) {
        var advb = parse$2(vb).adverb;

        if (advb.found) {
          list = list.concat(advb.list);
        }
      }); // look for leading adverbs

      var m = this.lookBehind('#Adverb+$');

      if (m.found) {
        list = m.list.concat(list);
      } // look for trailing adverbs


      m = this.lookAhead('^#Adverb+');

      if (m.found) {
        list = list.concat(m.list);
      }

      return this.buildFrom(list);
    },
    /// Verb Inflection

    /**return verbs like 'we walk' and not 'spencer walks' */
    isPlural: function isPlural() {
      var _this2 = this;

      var list = [];
      this.forEach(function (vb) {
        var parsed = parse$2(vb);

        if (isPlural_1$2(parsed, _this2.world) === true) {
          list.push(vb.list[0]);
        }
      });
      return this.buildFrom(list);
    },

    /** return verbs like 'spencer walks' and not 'we walk' */
    isSingular: function isSingular() {
      var _this3 = this;

      var list = [];
      this.forEach(function (vb) {
        var parsed = parse$2(vb);

        if (isPlural_1$2(parsed, _this3.world) === false) {
          list.push(vb.list[0]);
        }
      });
      return this.buildFrom(list);
    },
    /// Conjugation

    /** return all forms of this verb  */
    conjugate: function conjugate() {
      var _this4 = this;

      var result = [];
      this.forEach(function (vb) {
        var parsed = parse$2(vb);

        var forms = conjugate_1$1(parsed, _this4.world);

        result.push(forms);
      });
      return result;
    },

    /** walk ➔ walked*/
    toPastTense: function toPastTense() {
      var _this5 = this;

      this.forEach(function (vb) {
        var parsed = parse$2(vb); // should we support 'would swim' ➔ 'would have swam'

        if (useParticiple$1(parsed)) {
          _toParticiple(parsed, _this5.world);

          return;
        }

        var str = conjugate_1$1(parsed, _this5.world).PastTense;

        if (str) {
          parsed = makeNeutral(parsed);
          parsed.verb.replaceWith(str, false); // vb.tag('PastTense')
        }
      });
      return this;
    },

    /** walk ➔ walks */
    toPresentTense: function toPresentTense() {
      var _this6 = this;

      this.forEach(function (vb) {
        var parsed = parse$2(vb);

        var obj = conjugate_1$1(parsed, _this6.world);

        var str = obj.PresentTense; // 'i look', not 'i looks'

        if (vb.lookBehind('(i|we) (#Adverb|#Verb)?$').found) {
          str = obj.Infinitive;
        }

        if (str) {
          //awkward support for present-participle form
          // -- should we support 'have been swimming' ➔ 'am swimming'
          if (parsed.auxiliary.has('(have|had) been')) {
            parsed.auxiliary.replace('(have|had) been', 'am being');

            if (obj.Particle) {
              str = obj.Particle || obj.PastTense;
            }

            return;
          }

          parsed.verb.replaceWith(str, false);
          parsed.verb.tag('PresentTense');
          parsed = makeNeutral(parsed); // avoid 'he would walks'

          parsed.auxiliary.remove('#Modal');
        }
      });
      return this;
    },

    /** walk ➔ will walk*/
    toFutureTense: function toFutureTense() {
      var _this7 = this;

      this.forEach(function (vb) {
        var parsed = parse$2(vb); // 'i should drive' is already future-enough

        if (useParticiple$1(parsed)) {
          return;
        }

        var str = conjugate_1$1(parsed, _this7.world).FutureTense;

        if (str) {
          parsed = makeNeutral(parsed); // avoid 'he would will go'

          parsed.auxiliary.remove('#Modal');
          parsed.verb.replaceWith(str, false);
          parsed.verb.tag('FutureTense');
        }
      });
      return this;
    },

    /** walks ➔ walk */
    toInfinitive: function toInfinitive() {
      var _this8 = this;

      this.forEach(function (vb) {
        var parsed = parse$2(vb);

        var str = conjugate_1$1(parsed, _this8.world).Infinitive;

        if (str) {
          vb.replaceWith(str, false);
          vb.tag('Infinitive');
        }
      });
      return this;
    },

    /** walk ➔ walking */
    toGerund: function toGerund() {
      var _this9 = this;

      this.forEach(function (vb) {
        var parsed = parse$2(vb);

        var str = conjugate_1$1(parsed, _this9.world).Gerund;

        if (str) {
          vb.replaceWith(str, false);
          vb.tag('Gerund');
        }
      });
      return this;
    },

    /** drive ➔ driven - naked past-participle if it exists, otherwise past-tense */
    toParticiple: function toParticiple() {
      var _this10 = this;

      this.forEach(function (vb) {
        var parsed = parse$2(vb);
        var noAux = !parsed.auxiliary.found;

        _toParticiple(parsed, _this10.world); // dirty trick to  ensure our new auxiliary is found


        if (noAux) {
          parsed.verb.prepend(parsed.auxiliary.text());
          parsed.auxiliary.remove();
        }
      });
      return this;
    },
    /// Negation

    /** return only verbs with 'not'*/
    isNegative: function isNegative() {
      return this["if"]('#Negative');
    },

    /**  return only verbs without 'not'*/
    isPositive: function isPositive() {
      return this.ifNo('#Negative');
    },

    /** add a 'not' to these verbs */
    toNegative: function toNegative() {
      var _this11 = this;

      this.list.forEach(function (p) {
        var doc = _this11.buildFrom([p]);

        var parsed = parse$2(doc);

        toNegative_1(parsed, doc.world);
      });
      return this;
    },

    /** remove 'not' from these verbs */
    toPositive: function toPositive() {
      var m = this.match('do not #Verb');

      if (m.found) {
        m.remove('do not');
      }

      return this.remove('#Negative');
    }
  };

  var addMethod$9 = function addMethod(Doc) {
    /**  */
    var Verbs = /*#__PURE__*/function (_Doc) {
      _inherits(Verbs, _Doc);

      var _super = _createSuper(Verbs);

      function Verbs() {
        _classCallCheck(this, Verbs);

        return _super.apply(this, arguments);
      }

      return Verbs;
    }(Doc); // add-in our methods


    Object.assign(Verbs.prototype, methods$7); // aliases

    Verbs.prototype.negate = Verbs.prototype.toNegative;

    Doc.prototype.verbs = function (n) {
      var match = this.match('(#Adverb|#Auxiliary|#Verb|#Negative|#Particle)+'); // try to ignore leading and trailing adverbs

      match = match.not('^#Adverb+');
      match = match.not('#Adverb+$'); // handle commas:
      // don't split 'really, really'

      var keep = match.match('(#Adverb && @hasComma) #Adverb'); // // but split the other commas

      var m = match.not(keep).splitAfter('@hasComma'); // // combine them back together

      m = m.concat(keep);
      m.sort('index'); //handle slashes?
      //ensure there's actually a verb

      m = m["if"]('#Verb'); // the reason he will is ...

      if (m.has('(is|was)$')) {
        m = m.splitBefore('(is|was)$');
      } //grab (n)th result


      if (typeof n === 'number') {
        m = m.get(n);
      }

      var vb = new Verbs(m.list, this, this.world);
      return vb;
    };

    return Doc;
  };

  var Verbs = addMethod$9;

  var addMethod$a = function addMethod(Doc) {
    /**  */
    var People = /*#__PURE__*/function (_Doc) {
      _inherits(People, _Doc);

      var _super = _createSuper(People);

      function People() {
        _classCallCheck(this, People);

        return _super.apply(this, arguments);
      }

      return People;
    }(Doc);

    Doc.prototype.people = function (n) {
      var match = this.splitAfter('@hasComma');
      match = match.match('#Person+'); //grab (n)th result

      if (typeof n === 'number') {
        match = match.get(n);
      }

      return new People(match.list, this, this.world);
    };

    return Doc;
  };

  var People = addMethod$a;

  var subclass = [Abbreviations, Acronyms, Clauses, Contractions, Lists, Nouns, Parentheses, Possessives, Quotations, Verbs, People];

  var extend = function extend(Doc) {
    // add basic methods
    Object.keys(_simple).forEach(function (k) {
      return Doc.prototype[k] = _simple[k];
    }); // add subclassed methods

    subclass.forEach(function (addFn) {
      return addFn(Doc);
    });
    return Doc;
  };

  var Subset = extend;

  var methods$8 = {
    misc: methods$4,
    selections: _simple
  };
  /** a parsed text object */

  var Doc = /*#__PURE__*/function () {
    function Doc(list, from, world) {
      var _this = this;

      _classCallCheck(this, Doc);

      this.list = list; //quiet these properties in console.logs

      Object.defineProperty(this, 'from', {
        enumerable: false,
        value: from,
        writable: true
      }); //borrow some missing data from parent

      if (world === undefined && from !== undefined) {
        world = from.world;
      } //'world' getter


      Object.defineProperty(this, 'world', {
        enumerable: false,
        value: world,
        writable: true
      }); //fast-scans for our data

      Object.defineProperty(this, '_cache', {
        enumerable: false,
        writable: true,
        value: {}
      }); //'found' getter

      Object.defineProperty(this, 'found', {
        get: function get() {
          return _this.list.length > 0;
        }
      }); //'length' getter

      Object.defineProperty(this, 'length', {
        get: function get() {
          return _this.list.length;
        }
      }); // this is way easier than .constructor.name...

      Object.defineProperty(this, 'isA', {
        get: function get() {
          return 'Doc';
        }
      });
    }
    /** run part-of-speech tagger on all results*/


    _createClass(Doc, [{
      key: "tagger",
      value: function tagger() {
        return _02Tagger(this);
      }
      /** pool is stored on phrase objects */

    }, {
      key: "pool",
      value: function pool() {
        if (this.list.length > 0) {
          return this.list[0].pool;
        }

        return this.all().list[0].pool;
      }
    }]);

    return Doc;
  }();
  /** create a new Document object */


  Doc.prototype.buildFrom = function (list) {
    list = list.map(function (p) {
      return p.clone(true);
    }); // new this.constructor()

    var doc = new Doc(list, this, this.world);
    return doc;
  };
  /** create a new Document from plaintext. */


  Doc.prototype.fromText = function (str) {
    var list = _01Tokenizer(str, this.world, this.pool());
    return this.buildFrom(list);
  };

  Object.assign(Doc.prototype, methods$8.misc);
  Object.assign(Doc.prototype, methods$8.selections); //add sub-classes

  Subset(Doc); //aliases

  var aliases$1 = {
    untag: 'unTag',
    and: 'match',
    notIf: 'ifNo',
    only: 'if',
    onlyIf: 'if'
  };
  Object.keys(aliases$1).forEach(function (k) {
    return Doc.prototype[k] = Doc.prototype[aliases$1[k]];
  });
  var Doc_1 = Doc;

  var smallTagger = function smallTagger(doc) {
    var terms = doc.termList();
    _01Lexicon(terms, doc.world);
    return doc;
  };

  var tiny = smallTagger;

  function instance(worldInstance) {
    //blast-out our word-lists, just once
    var world = worldInstance;
    /** parse and tag text into a compromise object  */

    var nlp = function nlp() {
      var text = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
      var lexicon = arguments.length > 1 ? arguments[1] : undefined;

      if (lexicon) {
        world.addWords(lexicon);
      }

      var list = _01Tokenizer(text, world);
      var doc = new Doc_1(list, null, world);
      doc.tagger();
      return doc;
    };
    
    nlp.similar = similar_text;
    /** parse text into a compromise object, without running POS-tagging */


    nlp.tokenize = function () {
      var text = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
      var lexicon = arguments.length > 1 ? arguments[1] : undefined;
      var w = world;

      if (lexicon) {
        w = w.clone();
        w.words = {};
        w.addWords(lexicon);
      }

      var list = _01Tokenizer(text, w);
      var doc = new Doc_1(list, null, w);

      if (lexicon) {
        tiny(doc);
      }

      return doc;
    };
    /** mix in a compromise-plugin */


    nlp.extend = function (fn) {
      fn(Doc_1, world, this, Phrase_1, Term_1, Pool_1);
      return this;
    };
    /** create a compromise Doc object from .json() results */


    nlp.fromJSON = function (json) {
      var list = fromJSON_1(json, world);
      return new Doc_1(list, null, world);
    };
    /** make a deep-copy of the library state */


    nlp.clone = function () {
      return instance(world.clone());
    };
    /** log our decision-making for debugging */


    nlp.verbose = function () {
      var bool = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
      world.verbose(bool);
      return this;
    };
    /** grab currently-used World object */


    nlp.world = function () {
      return world;
    };
    /** pre-parse any match statements */


    nlp.parseMatch = function (str) {
      return syntax_1(str);
    };
    /** current version of the library */


    nlp.version = _version; // alias

    nlp["import"] = nlp.load;
    return nlp;
  }

  var src = instance(new World_1());

  return src;

})));
};
BundleModuleCode['nlp/compromise-adjectives']=function (module,exports){
/* compromise-adjectives 0.0.6 MIT */
(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  typeof define === 'function' && define.amd ? define(factory) :
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.compromiseAdjectives = factory());
}(this, (function () { 'use strict';

  function _classCallCheck(instance, Constructor) {
    if (!(instance instanceof Constructor)) {
      throw new TypeError("Cannot call a class as a function");
    }
  }

  function _defineProperties(target, props) {
    for (var i = 0; i < props.length; i++) {
      var descriptor = props[i];
      descriptor.enumerable = descriptor.enumerable || false;
      descriptor.configurable = true;
      if ("value" in descriptor) descriptor.writable = true;
      Object.defineProperty(target, descriptor.key, descriptor);
    }
  }

  function _createClass(Constructor, protoProps, staticProps) {
    if (protoProps) _defineProperties(Constructor.prototype, protoProps);
    if (staticProps) _defineProperties(Constructor, staticProps);
    return Constructor;
  }

  function _inherits(subClass, superClass) {
    if (typeof superClass !== "function" && superClass !== null) {
      throw new TypeError("Super expression must either be null or a function");
    }

    subClass.prototype = Object.create(superClass && superClass.prototype, {
      constructor: {
        value: subClass,
        writable: true,
        configurable: true
      }
    });
    if (superClass) _setPrototypeOf(subClass, superClass);
  }

  function _getPrototypeOf(o) {
    _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
      return o.__proto__ || Object.getPrototypeOf(o);
    };
    return _getPrototypeOf(o);
  }

  function _setPrototypeOf(o, p) {
    _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
      o.__proto__ = p;
      return o;
    };

    return _setPrototypeOf(o, p);
  }

  function _isNativeReflectConstruct() {
    if (typeof Reflect === "undefined" || !Reflect.construct) return false;
    if (Reflect.construct.sham) return false;
    if (typeof Proxy === "function") return true;

    try {
      Date.prototype.toString.call(Reflect.construct(Date, [], function () {}));
      return true;
    } catch (e) {
      return false;
    }
  }

  function _assertThisInitialized(self) {
    if (self === void 0) {
      throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
    }

    return self;
  }

  function _possibleConstructorReturn(self, call) {
    if (call && (typeof call === "object" || typeof call === "function")) {
      return call;
    }

    return _assertThisInitialized(self);
  }

  function _createSuper(Derived) {
    var hasNativeReflectConstruct = _isNativeReflectConstruct();

    return function _createSuperInternal() {
      var Super = _getPrototypeOf(Derived),
          result;

      if (hasNativeReflectConstruct) {
        var NewTarget = _getPrototypeOf(this).constructor;

        result = Reflect.construct(Super, arguments, NewTarget);
      } else {
        result = Super.apply(this, arguments);
      }

      return _possibleConstructorReturn(this, result);
    };
  }

  //turn 'quick' into 'quickly'
  var not_matches = [/airs$/, /ll$/, /ee.$/, /ile$/, /y$/];
  var irregulars = {
    bad: 'badly',
    good: 'well',
    icy: 'icily',
    idle: 'idly',
    male: 'manly',
    "public": 'publicly',
    simple: 'simply',
    single: 'singly',
    special: 'especially',
    straight: 'straight',
    vague: 'vaguely',
    whole: 'wholly'
  };
  var dontChange = ['best', 'early', 'hard', 'fast', 'wrong', 'well', 'late', 'latter', 'little', 'long', 'low'].reduce(function (h, c) {
    h[c] = true;
    return h;
  }, {});
  var transforms = [{
    reg: /al$/i,
    repl: 'ally'
  }, {
    reg: /ly$/i,
    repl: 'ly'
  }, {
    reg: /(.{3})y$/i,
    repl: '$1ily'
  }, {
    reg: /que$/i,
    repl: 'quely'
  }, {
    reg: /ue$/i,
    repl: 'uly'
  }, {
    reg: /ic$/i,
    repl: 'ically'
  }, {
    reg: /ble$/i,
    repl: 'bly'
  }, {
    reg: /l$/i,
    repl: 'ly'
  }];

  var adj_to_adv = function adj_to_adv(str) {
    if (irregulars.hasOwnProperty(str) === true) {
      return irregulars[str];
    }

    if (dontChange.hasOwnProperty(str) === true) {
      return str;
    }

    for (var i = 0; i < not_matches.length; i++) {
      if (not_matches[i].test(str) === true) {
        return null;
      }
    }

    for (var _i = 0; _i < transforms.length; _i++) {
      if (transforms[_i].reg.test(str) === true) {
        return str.replace(transforms[_i].reg, transforms[_i].repl);
      }
    }

    return str + 'ly';
  };

  var toAdverb = adj_to_adv;

  //convert 'cute' to 'cuteness'
  var irregulars$1 = {
    clean: 'cleanliness',
    naivety: 'naivety',
    hurt: 'hurt'
  };
  var transforms$1 = [{
    reg: /y$/,
    repl: 'iness'
  }, {
    reg: /le$/,
    repl: 'ility'
  }, {
    reg: /ial$/,
    repl: 'y'
  }, {
    reg: /al$/,
    repl: 'ality'
  }, {
    reg: /ting$/,
    repl: 'ting'
  }, {
    reg: /ring$/,
    repl: 'ring'
  }, {
    reg: /bing$/,
    repl: 'bingness'
  }, {
    reg: /sing$/,
    repl: 'se'
  }, {
    reg: /ing$/,
    repl: 'ment'
  }, {
    reg: /ess$/,
    repl: 'essness'
  }, {
    reg: /ous$/,
    repl: 'ousness'
  }];

  var to_noun = function to_noun(w) {
    if (irregulars$1.hasOwnProperty(w)) {
      return irregulars$1[w];
    }

    var lastChar = w.charAt(w.length - 1);

    if (lastChar === 'w' || lastChar === 's') {
      return null;
    }

    for (var i = 0; i < transforms$1.length; i++) {
      if (transforms$1[i].reg.test(w) === true) {
        return w.replace(transforms$1[i].reg, transforms$1[i].repl);
      }
    }

    return w + 'ness';
  };

  var toNoun = to_noun;

  //turn an adjective like 'soft' into a verb like 'soften'
  //(don't do words like 'green' -> 'greenen')
  //these are suffices that are usually too weird
  var dontDo = ['c', 'e', 'g', 'l', 'n', 'r', 'w', 'y'].reduce(function (h, c) {
    h[c] = true;
    return h;
  }, {});
  var dontDoTwo = {
    ed: true,
    nt: true
  };
  var banList = {
    random: true,
    wild: true
  };
  var irregulars$2 = {
    bored: 'bore',
    red: 'redden',
    sad: 'sadden',
    fat: 'fatten',
    small: 'shrink',
    full: 'fill',
    tired: 'tire'
  };

  var toVerb = function toVerb(str) {
    if (irregulars$2.hasOwnProperty(str) === true) {
      return irregulars$2[str];
    } //don't bother with these:


    if (str.length <= 3) {
      return null;
    }

    if (banList.hasOwnProperty(str) === true) {
      return null;
    } //suffixes to avoid


    if (dontDo.hasOwnProperty(str[str.length - 1])) {
      return null;
    }

    var suffix = str.substr(str.length - 2);

    if (dontDoTwo.hasOwnProperty(suffix) === true) {
      return null;
    }

    if (/e$/.test(str) === true) {
      return str + 'n';
    }

    return str + 'en';
  };

  var toVerb_1 = toVerb;

  var addMethods = function addMethods(Doc) {
    /**  */
    var Adjective = /*#__PURE__*/function (_Doc) {
      _inherits(Adjective, _Doc);

      var _super = _createSuper(Adjective);

      function Adjective() {
        _classCallCheck(this, Adjective);

        return _super.apply(this, arguments);
      }

      _createClass(Adjective, [{
        key: "json",

        /** overload the original json with noun information */
        value: function json(options) {
          var n = null;

          if (typeof options === 'number') {
            n = options;
            options = null;
          }

          var res = [];
          this.forEach(function (doc) {
            var json = doc.json(options)[0];
            var str = doc.text('reduced');
            json.toAdverb = toAdverb(str);
            json.toNoun = toNoun(str);
            json.toVerb = toVerb_1(str);
            res.push(json);
          });

          if (n !== null) {
            return res[n];
          }

          return res;
        }
      }, {
        key: "conjugate",
        value: function conjugate(n) {
          var transform = this.world.transforms.adjectives;
          var arr = [];
          this.forEach(function (doc) {
            var str = doc.text('reduced');
            var obj = transform(str);
            obj.Adverb = toAdverb(str);
            obj.Noun = toNoun(str);
            obj.Verb = toVerb_1(str);
            arr.push(obj);
          }); //support nth result

          if (typeof n === 'number') {
            return arr[n];
          }

          return arr;
        }
      }, {
        key: "toSuperlative",
        value: function toSuperlative() {
          var transform = this.world.transforms.adjectives;
          this.forEach(function (doc) {
            var obj = transform(doc.text('reduced'));
            doc.replaceWith(obj.Superlative, true);
          });
          return this;
        }
      }, {
        key: "toComparative",
        value: function toComparative() {
          var transform = this.world.transforms.adjectives;
          this.forEach(function (doc) {
            var obj = transform(doc.text('reduced'));
            doc.replaceWith(obj.Comparative, true);
          });
          return this;
        }
      }, {
        key: "toAdverb",
        value: function toAdverb$1() {
          this.forEach(function (doc) {
            var adverb = toAdverb(doc.text('reduced'));

            doc.replaceWith(adverb, true);
          });
          return this;
        }
      }, {
        key: "toVerb",
        value: function toVerb() {
          this.forEach(function (doc) {
            var verb = toVerb_1(doc.text('reduced'));

            doc.replaceWith(verb, true);
          });
          return this;
        }
      }, {
        key: "toNoun",
        value: function toNoun$1() {
          this.forEach(function (doc) {
            var noun = toNoun(doc.text('reduced'));

            doc.replaceWith(noun, true);
          });
          return this;
        }
      }]);

      return Adjective;
    }(Doc);
    /** grab all the adjectives */


    Doc.prototype.adjectives = function (n) {
      var m = this.match('#Adjective'); //grab (n)th result

      if (typeof n === 'number') {
        m = m.get(n);
      }

      return new Adjective(m.list, this, this.world);
    };
  };

  var src = addMethods;

  return src;

})));
//# sourceMappingURL=compromise-adjectives.js.map
};
BundleModuleCode['nlp/compromise-dates']=function (module,exports){
/* compromise-dates 1.3.0 MIT */
(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  typeof define === 'function' && define.amd ? define(factory) :
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.compromiseDates = factory());
}(this, (function () { 'use strict';

  function _typeof(obj) {
    "@babel/helpers - typeof";

    if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
      _typeof = function (obj) {
        return typeof obj;
      };
    } else {
      _typeof = function (obj) {
        return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
      };
    }

    return _typeof(obj);
  }

  function _classCallCheck(instance, Constructor) {
    if (!(instance instanceof Constructor)) {
      throw new TypeError("Cannot call a class as a function");
    }
  }

  function _defineProperties(target, props) {
    for (var i = 0; i < props.length; i++) {
      var descriptor = props[i];
      descriptor.enumerable = descriptor.enumerable || false;
      descriptor.configurable = true;
      if ("value" in descriptor) descriptor.writable = true;
      Object.defineProperty(target, descriptor.key, descriptor);
    }
  }

  function _createClass(Constructor, protoProps, staticProps) {
    if (protoProps) _defineProperties(Constructor.prototype, protoProps);
    if (staticProps) _defineProperties(Constructor, staticProps);
    return Constructor;
  }

  function _inherits(subClass, superClass) {
    if (typeof superClass !== "function" && superClass !== null) {
      throw new TypeError("Super expression must either be null or a function");
    }

    subClass.prototype = Object.create(superClass && superClass.prototype, {
      constructor: {
        value: subClass,
        writable: true,
        configurable: true
      }
    });
    if (superClass) _setPrototypeOf(subClass, superClass);
  }

  function _getPrototypeOf(o) {
    _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
      return o.__proto__ || Object.getPrototypeOf(o);
    };
    return _getPrototypeOf(o);
  }

  function _setPrototypeOf(o, p) {
    _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
      o.__proto__ = p;
      return o;
    };

    return _setPrototypeOf(o, p);
  }

  function _isNativeReflectConstruct() {
    if (typeof Reflect === "undefined" || !Reflect.construct) return false;
    if (Reflect.construct.sham) return false;
    if (typeof Proxy === "function") return true;

    try {
      Date.prototype.toString.call(Reflect.construct(Date, [], function () {}));
      return true;
    } catch (e) {
      return false;
    }
  }

  function _assertThisInitialized(self) {
    if (self === void 0) {
      throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
    }

    return self;
  }

  function _possibleConstructorReturn(self, call) {
    if (call && (typeof call === "object" || typeof call === "function")) {
      return call;
    }

    return _assertThisInitialized(self);
  }

  function _createSuper(Derived) {
    var hasNativeReflectConstruct = _isNativeReflectConstruct();

    return function _createSuperInternal() {
      var Super = _getPrototypeOf(Derived),
          result;

      if (hasNativeReflectConstruct) {
        var NewTarget = _getPrototypeOf(this).constructor;

        result = Reflect.construct(Super, arguments, NewTarget);
      } else {
        result = Super.apply(this, arguments);
      }

      return _possibleConstructorReturn(this, result);
    };
  }

  //ambiguous 'may' and 'march'
  var preps = '(in|by|before|during|on|until|after|of|within|all)'; //6

  var thisNext = '(last|next|this|previous|current|upcoming|coming)'; //2

  var sections = '(start|end|middle|starting|ending|midpoint|beginning)'; //2

  var seasons = '(spring|summer|winter|fall|autumn)'; //ensure a year is approximately typical for common years
  //please change in one thousand years

  var tagYear = function tagYear(m, reason) {
    if (m.found !== true) {
      return;
    }

    m.forEach(function (p) {
      var str = p.text('reduced');
      var num = parseInt(str, 10);

      if (num && num > 1000 && num < 3000) {
        p.tag('Year', reason);
      }
    });
  }; //same, but for less-confident values


  var tagYearSafe = function tagYearSafe(m, reason) {
    if (m.found !== true) {
      return;
    }

    m.forEach(function (p) {
      var str = p.text('reduced');
      var num = parseInt(str, 10);

      if (num && num > 1900 && num < 2030) {
        p.tag('Year', reason);
      }
    });
  };

  var tagDates = function tagDates(doc) {
    // in the evening
    doc.match('in the (night|evening|morning|afternoon|day|daytime)').tag('Time', 'in-the-night'); // 8 pm

    doc.match('(#Value|#Time) (am|pm)').tag('Time', 'value-ampm'); // 22-aug
    // doc.match('/^[0-9]{2}-(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov)/').tag('Date', '20-jan')
    // 2012-06

    doc.match('/^[0-9]{4}-[0-9]{2}$/').tag('Date', '2012-06'); // misc weekday words

    doc.match('(tue|thu)').tag('WeekDay', 'misc-weekday'); //months:

    var month = doc["if"]('#Month');

    if (month.found === true) {
      //June 5-7th
      month.match("#Month #Date+").tag('Date', 'correction-numberRange'); //5th of March

      month.match('#Value of #Month').tag('Date', 'value-of-month'); //5 March

      month.match('#Cardinal #Month').tag('Date', 'cardinal-month'); //march 5 to 7

      month.match('#Month #Value to #Value').tag('Date', 'value-to-value'); //march the 12th

      month.match('#Month the #Value').tag('Date', 'month-the-value');
    } //months:


    var val = doc["if"]('#Value');

    if (val.found === true) {
      //june 7
      val.match('(#WeekDay|#Month) #Value').ifNo('#Money').tag('Date', 'date-value'); //7 june

      val.match('#Value (#WeekDay|#Month)').ifNo('#Money').tag('Date', 'value-date'); //may twenty five

      val.match('#TextValue #TextValue')["if"]('#Date').tag('#Date', 'textvalue-date'); //two thursdays back

      val.match('#Value (#WeekDay|#Duration) back').tag('#Date', '3-back'); //eg 'year'

      var duration = val["if"]('#Duration');

      if (duration.found === true) {
        //for 4 months
        duration.match('for #Value #Duration').tag('Date', 'for-x-duration'); //two days before

        duration.match('#Value #Duration #Conjunction').tag('Date', 'val-duration-conjunction'); //for four days

        duration.match("".concat(preps, "? #Value #Duration")).tag('Date', 'value-duration'); //two years old

        duration.match('#Value #Duration old').unTag('Date', 'val-years-old');
      }
    } //seasons


    var season = doc["if"](seasons);

    if (season.found === true) {
      season.match("".concat(preps, "? ").concat(thisNext, " ").concat(seasons)).tag('Date', 'thisNext-season');
      season.match("the? ".concat(sections, " of ").concat(seasons)).tag('Date', 'section-season');
      season.match("".concat(seasons, " ").concat(preps, "? #Cardinal")).tag('Date', 'season-year');
    } //rest-dates


    var date = doc["if"]('#Date');

    if (date.found === true) {
      //june the 5th
      date.match('#Date the? #Ordinal').tag('Date', 'correction'); //last month

      date.match("".concat(thisNext, " #Date")).tag('Date', 'thisNext'); //by 5 March

      date.match('due? (by|before|after|until) #Date').tag('Date', 'by'); //next feb

      date.match('(last|next|this|previous|current|upcoming|coming|the) #Date').tag('Date', 'next-feb'); //start of june

      date.match("the? ".concat(sections, " of #Date")).tag('Date', 'section-of'); //fifth week in 1998

      date.match('#Ordinal #Duration in #Date').tag('Date', 'duration-in'); //early in june

      date.match('(early|late) (at|in)? the? #Date').tag('Time', 'early-evening'); //tomorrow before 3

      date.match('#Date (by|before|after|at|@|about) #Cardinal').not('^#Date').tag('Time', 'date-before-Cardinal'); //saturday am

      date.match('#Date [(am|pm)]', 0).unTag('Verb').unTag('Copula').tag('Time', 'date-am'); //feb to june

      date.match('#Date (#Preposition|to) #Date').ifNo('#Duration').tag('Date', 'date-prep-date'); //2nd quarter of 2019
      // date.match('#Date of #Date').tag('Date', 'date-of-date')
    } //year/cardinal tagging


    var cardinal = doc["if"]('#Cardinal');

    if (cardinal.found === true) {
      var v = cardinal.match("#Date #Value [#Cardinal]", 0);
      tagYear(v, 'date-value-year'); //scoops up a bunch

      v = cardinal.match("#Date [#Cardinal]", 0);
      tagYearSafe(v, 'date-year'); //middle of 1999

      v = cardinal.match("".concat(sections, " of [#Cardinal]"));
      tagYearSafe(v, 'section-year'); //feb 8 2018

      v = cardinal.match("#Month #Value [#Cardinal]", 0);
      tagYear(v, 'month-value-year'); //feb 8 to 10th 2018

      v = cardinal.match("#Month #Value to #Value [#Cardinal]", 0);
      tagYear(v, 'month-range-year'); //in 1998

      v = cardinal.match("(in|of|by|during|before|starting|ending|for|year|since) [#Cardinal]", 0);
      tagYear(v, 'in-year-1'); //q2 2009

      v = cardinal.match('(q1|q2|q3|q4) [#Cardinal]', 0);
      tagYear(v, 'in-year-2'); //2nd quarter 2009

      v = cardinal.match('#Ordinal quarter [#Cardinal]', 0);
      tagYear(v, 'in-year-3'); //in the year 1998

      v = cardinal.match('the year [#Cardinal]', 0);
      tagYear(v, 'in-year-4'); //it was 1998

      v = cardinal.match('it (is|was) [#Cardinal]', 0);
      tagYearSafe(v, 'in-year-5'); // re-tag this part

      cardinal.match("".concat(sections, " of #Year")).tag('Date');
    }

    var time = doc["if"]('#Time');

    if (time.found === true) {
      //by 6pm
      time.match('(by|before|after|at|@|about) #Time').tag('Time', 'preposition-time'); //7 7pm
      // time.match('#Cardinal #Time').not('#Year').tag('Time', 'value-time')
      //2pm est

      time.match('#Time [(eastern|pacific|central|mountain)]', 0).tag('Date', 'timezone'); //6pm est

      time.match('#Time [(est|pst|gmt)]', 0).tag('Date', 'timezone abbr');
    } //'2020' bare input


    var m = doc.match('^/^20[012][0-9]$/$');
    tagYearSafe(m, '2020-ish'); // in 20mins

    doc.match('(in|after) /^[0-9]+(min|sec|wk)s?/').tag('Date', 'shift-units');
    return doc;
  };

  var _00Basic = tagDates;

  var here = 'date-values'; //

  var values = function values(doc) {
    // a year ago
    if (!doc.has('once [a] #Duration')) {
      doc.match('[a] #Duration', 0).replaceWith('1').tag('Cardinal', here);
    }

    if (doc.has('#Value')) {
      //june 5 to 7th
      doc.match('#Month #Value to #Value of? #Year?').tag('Date', here); //5 to 7th june

      doc.match('#Value to #Value of? #Month #Year?').tag('Date', here); //third week of may

      doc.match('#Value #Duration of #Date').tag('Date', here); //two days after

      doc.match('#Value+ #Duration (after|before|into|later|afterwards|ago)?').tag('Date', here); //two days

      doc.match('#Value #Date').tag('Date', here); //june 5th

      doc.match('#Date #Value').tag('Date', here); //tuesday at 5

      doc.match('#Date #Preposition #Value').tag('Date', here); //tomorrow before 3

      doc.match('#Date (after|before|during|on|in) #Value').tag('Date', here); //a year and a half

      doc.match('#Value (year|month|week|day) and a half').tag('Date', here); //5 and a half years

      doc.match('#Value and a half (years|months|weeks|days)').tag('Date', here); //on the fifth

      doc.match('on the #Ordinal').tag('Date', here);
    }

    return doc;
  };

  var _01Values = values;

  var here$1 = 'date-tagger'; //

  var dateTagger = function dateTagger(doc) {
    doc.match('(spring|summer|winter|fall|autumn|springtime|wintertime|summertime)').match('#Noun').tag('Season', here$1);
    doc.match('(q1|q2|q3|q4)').tag('FinancialQuarter', here$1);
    doc.match('(this|next|last|current) quarter').tag('FinancialQuarter', here$1);
    doc.match('(this|next|last|current) season').tag('Season', here$1);

    if (doc.has('#Date')) {
      //friday to sunday
      doc.match('#Date #Preposition #Date').tag('Date', here$1); //once a day..

      doc.match('(once|twice) (a|an|each) #Date').tag('Date', here$1); //TODO:fixme

      doc.match('(by|until|on|in|at|during|over|every|each|due) the? #Date').tag('Date', here$1); //tuesday

      doc.match('#Date+').tag('Date', here$1); //by June

      doc.match('(by|until|on|in|at|during|over|every|each|due) the? #Date').tag('Date', here$1); //a year after..

      doc.match('a #Duration').tag('Date', here$1); //between x and y

      doc.match('(between|from) #Date').tag('Date', here$1);
      doc.match('(to|until|upto) #Date').tag('Date', here$1);
      doc.match('#Date and #Date').tag('Date', here$1); //during this june

      doc.match('(by|until|after|before|during|on|in|following|since) (next|this|last)? (#Date|#Date)').tag('Date', here$1); //day after next

      doc.match('the? #Date after next one?').tag('Date', here$1); //approximately...

      doc.match('(about|approx|approximately|around) #Date').tag('Date', here$1);
    }

    return doc;
  };

  var _02Dates = dateTagger;

  var here$2 = 'section-tagger'; //

  var sectionTagger = function sectionTagger(doc) {
    if (doc.has('#Date')) {
      // //next september
      doc.match('this? (last|next|past|this|previous|current|upcoming|coming|the) #Date').tag('Date', here$2); //starting this june

      doc.match('(starting|beginning|ending) #Date').tag('Date', here$2); //start of june

      doc.match('the? (start|end|middle|beginning) of (last|next|this|the) (#Date|#Date)').tag('Date', here$2); //this coming june

      doc.match('(the|this) #Date').tag('Date', here$2); //january up to june

      doc.match('#Date up to #Date').tag('Date', here$2);
    }

    return doc;
  };

  var _03Sections = sectionTagger;

  var here$3 = 'time-tagger'; //

  var timeTagger = function timeTagger(doc) {
    // 2 oclock
    doc.match('#Cardinal oclock').tag('Time', here$3); // 13h30

    doc.match('/^[0-9]{2}h[0-9]{2}$/').tag('Time', here$3); // 03/02

    doc.match('/^[0-9]{2}/[0-9]{2}/').tag('Date', here$3).unTag('Value'); // 3 in the morning

    doc.match('[#Value] (in|at) the? (morning|evening|night|nighttime)').tag('Time', here$3); // quarter to seven (not march 5 to 7)

    if (doc.has('#Cardinal') && !doc.has('#Month')) {
      doc.match('1? (half|quarter|25|15|10|5) (past|after|to) #Cardinal').tag('Time', here$3);
    } //timezone


    if (doc.has('#Date')) {
      // iso  (2020-03-02T00:00:00.000Z)
      doc.match('/^[0-9]{4}[:-][0-9]{2}[:-][0-9]{2}T[0-9]/').tag('Time', here$3); // tuesday at 4

      doc.match('#Date [at #Cardinal]', 0).notIf('#Year').tag('Time', here$3); // half an hour

      doc.match('half an (hour|minute|second)').tag('Date', here$3); //eastern daylight time

      doc.match('#Noun (standard|daylight|central|mountain)? time').tag('Timezone', here$3); //utc+5

      doc.match('/^utc[+-][0-9]/').tag('Timezone', here$3);
      doc.match('/^gmt[+-][0-9]/').tag('Timezone', here$3);
      doc.match('(in|for|by|near|at) #Timezone').tag('Timezone', here$3); // 2pm eastern

      doc.match('#Time [(eastern|mountain|pacific|central)]', 0).tag('Timezone', here$3);
    }

    return doc;
  };

  var _04Time = timeTagger;

  var here$4 = 'shift-tagger'; //

  var shiftTagger = function shiftTagger(doc) {
    if (doc.has('#Date')) {
      //'two days before'/ 'nine weeks frow now'
      doc.match('#Cardinal #Duration (before|after|ago|from|hence|back)').tag('DateShift', here$4); // in two weeks

      doc.match('in #Cardinal #Duration').tag('DateShift', here$4); // in a few weeks

      doc.match('in a (few|couple) of? #Duration').tag('DateShift', here$4); //two weeks and three days before

      doc.match('#Cardinal #Duration and? #DateShift').tag('DateShift', here$4);
      doc.match('#DateShift and #Cardinal #Duration').tag('DateShift', here$4); // 'day after tomorrow'

      doc.match('[#Duration (after|before)] #Date', 0).tag('DateShift', here$4); // in half an hour

      doc.match('in half (a|an) #Duration').tag('DateShift', here$4);
    }

    return doc;
  };

  var _05Shifts = shiftTagger;

  var here$5 = 'fix-tagger'; //

  var fixUp = function fixUp(doc) {
    //fixups
    if (doc.has('#Date')) {
      //first day by monday
      var oops = doc.match('#Date+ by #Date+');

      if (oops.found && !oops.has('^due')) {
        oops.match('^#Date+').unTag('Date', 'by-monday');
      }

      var d = doc.match('#Date+'); //'spa day'

      d.match('^day$').unTag('Date', 'spa-day'); // tomorrow's meeting

      d.match('(in|of|by|for)? (#Possessive && #Date)').unTag('Date', 'tomorrows meeting');
      var knownDate = '(yesterday|today|tomorrow)';

      if (d.has(knownDate)) {
        //yesterday 7
        d.match("".concat(knownDate, " [#Value]$")).unTag('Date', 'yesterday-7'); //7 yesterday

        d.match("^[#Value] ".concat(knownDate, "$"), 0).unTag('Date', '7 yesterday'); //friday yesterday

        d.match("#WeekDay+ ".concat(knownDate, "$")).unTag('Date').lastTerm().tag('Date', 'fri-yesterday'); // yesterday yesterday
        // d.match(`${knownDate}+ ${knownDate}$`)
        //   .unTag('Date')
        //   .lastTerm()
        //   .tag('Date', here)

        d.match("(this|last|next) #Date ".concat(knownDate, "$")).unTag('Date').lastTerm().tag('Date', 'this month yesterday');
      } //tomorrow on 5


      d.match("on #Cardinal$").unTag('Date', here$5); //this tomorrow

      d.match("this tomorrow").terms(0).unTag('Date', 'this-tomorrow'); //q2 2019

      d.match("(q1|q2|q3|q4) #Year").tag('Date', here$5); //5 tuesday
      // d.match(`^#Value #WeekDay`).terms(0).unTag('Date');
      //5 next week

      d.match("^#Value (this|next|last)").terms(0).unTag('Date', here$5);

      if (d.has('(last|this|next)')) {
        //this month 7
        d.match("(last|this|next) #Duration #Value").terms(2).unTag('Date', here$5); //7 this month

        d.match("!#Month #Value (last|this|next) #Date").terms(0).unTag('Date', here$5);
      } //january 5 5


      if (d.has('(#Year|#Time|#TextValue|#NumberRange)') === false) {
        d.match('(#Month|#WeekDay) #Value #Value').terms(2).unTag('Date', here$5);
      } //between june


      if (d.has('^between') && !d.has('and .')) {
        d.unTag('Date', here$5);
      } //june june


      if (d.has('#Month #Month') && !d.has('@hasHyphen') && !d.has('@hasComma')) {
        d.match('#Month').lastTerm().unTag('Date', 'month-month');
      } // log the hours


      if (d.has('(minutes|seconds|weeks|hours|days|months)') && !d.has('#Value #Duration')) {
        d.match('(minutes|seconds|weeks|hours|days|months)').unTag('Date', 'log-hours');
      } // about thanksgiving


      if (d.has('about #Holiday')) {
        d.match('about').unTag('#Date', 'about-thanksgiving');
      } // a month from now


      d.match('(from|by|before) now').unTag('Time'); // dangling date-chunks
      // if (d.has('!#Date (in|of|by|for) !#Date')) {
      //   d.unTag('Date', 'dangling-date')
      // }
      // the day after next

      d.match('#Date+').match('^the').unTag('Date');
    }

    return doc;
  };

  var _06Fixup = fixUp;

  var methods = [_00Basic, _01Values, _02Dates, _03Sections, _04Time, _05Shifts, _06Fixup]; // normalizations to run before tagger

  var normalize = function normalize(doc) {
    // turn '20mins' into '20 mins'
    doc.numbers().normalize(); // this is sorta problematic

    return doc;
  }; // run each of the taggers


  var tagDate = function tagDate(doc) {
    doc = normalize(doc); // run taggers

    methods.forEach(function (fn) {
      return fn(doc);
    });
    return doc;
  };

  var _01Tagger = tagDate;

  var _tags = {
    FinancialQuarter: {
      isA: 'Date'
    },
    // 'summer'
    Season: {
      isA: 'Date'
    },
    // '1982'
    Year: {
      isA: ['Date'],
      notA: 'RomanNumeral'
    },
    // 'months'
    Duration: {
      isA: ['Date', 'Noun']
    },
    // 'easter'
    Holiday: {
      isA: ['Date', 'Noun']
    },
    // 'PST'
    Timezone: {
      isA: ['Date', 'Noun'],
      notA: ['Adjective', 'DateShift']
    },
    // 'two weeks before'
    DateShift: {
      isA: ['Date'],
      notA: ['TimeZone', 'Holiday']
    }
  };

  var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};

  function createCommonjsModule(fn) {
    var module = { exports: {} };
  	return fn(module, module.exports), module.exports;
  }

  /* spencermountain/spacetime 6.12.2 Apache 2.0 */
  var spacetime = createCommonjsModule(function (module, exports) {
    (function (global, factory) {
       module.exports = factory() ;
    })(commonjsGlobal, function () {

      function _slicedToArray(arr, i) {
        return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();
      }

      function _arrayWithHoles(arr) {
        if (Array.isArray(arr)) return arr;
      }

      function _iterableToArrayLimit(arr, i) {
        if (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr))) return;
        var _arr = [];
        var _n = true;
        var _d = false;
        var _e = undefined;

        try {
          for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
            _arr.push(_s.value);

            if (i && _arr.length === i) break;
          }
        } catch (err) {
          _d = true;
          _e = err;
        } finally {
          try {
            if (!_n && _i["return"] != null) _i["return"]();
          } finally {
            if (_d) throw _e;
          }
        }

        return _arr;
      }

      function _unsupportedIterableToArray(o, minLen) {
        if (!o) return;
        if (typeof o === "string") return _arrayLikeToArray(o, minLen);
        var n = Object.prototype.toString.call(o).slice(8, -1);
        if (n === "Object" && o.constructor) n = o.constructor.name;
        if (n === "Map" || n === "Set") return Array.from(o);
        if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
      }

      function _arrayLikeToArray(arr, len) {
        if (len == null || len > arr.length) len = arr.length;

        for (var i = 0, arr2 = new Array(len); i < len; i++) {
          arr2[i] = arr[i];
        }

        return arr2;
      }

      function _nonIterableRest() {
        throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
      }

      var MSEC_IN_HOUR = 60 * 60 * 1000; //convert our local date syntax a javascript UTC date

      var toUtc = function toUtc(dstChange, offset, year) {
        var _dstChange$split = dstChange.split('/'),
            _dstChange$split2 = _slicedToArray(_dstChange$split, 2),
            month = _dstChange$split2[0],
            rest = _dstChange$split2[1];

        var _rest$split = rest.split(':'),
            _rest$split2 = _slicedToArray(_rest$split, 2),
            day = _rest$split2[0],
            hour = _rest$split2[1];

        return Date.UTC(year, month - 1, day, hour) - offset * MSEC_IN_HOUR;
      }; // compare epoch with dst change events (in utc)


      var inSummerTime = function inSummerTime(epoch, start, end, summerOffset, winterOffset) {
        var year = new Date(epoch).getUTCFullYear();
        var startUtc = toUtc(start, winterOffset, year);
        var endUtc = toUtc(end, summerOffset, year); // console.log(epoch, endUtc)
        // simple number comparison now

        return epoch >= startUtc && epoch < endUtc;
      };

      var summerTime = inSummerTime; // it reproduces some things in ./index.js, but speeds up spacetime considerably

      var quickOffset = function quickOffset(s) {
        var zones = s.timezones;
        var obj = zones[s.tz];

        if (obj === undefined) {
          console.warn("Warning: couldn't find timezone " + s.tz);
          return 0;
        }

        if (obj.dst === undefined) {
          return obj.offset;
        } //get our two possible offsets


        var jul = obj.offset;
        var dec = obj.offset + 1; // assume it's the same for now

        if (obj.hem === 'n') {
          dec = jul - 1;
        }

        var split = obj.dst.split('->');
        var inSummer = summerTime(s.epoch, split[0], split[1], jul, dec);

        if (inSummer === true) {
          return jul;
        }

        return dec;
      };

      var quick = quickOffset;
      var _build = {
        "9|s": "2/dili,2/jayapura",
        "9|n": "2/chita,2/khandyga,2/pyongyang,2/seoul,2/tokyo,11/palau",
        "9.5|s|04/05:03->10/04:02": "4/adelaide,4/broken_hill,4/south,4/yancowinna",
        "9.5|s": "4/darwin,4/north",
        "8|s|03/08:01->10/04:00": "12/casey",
        "8|s": "2/kuala_lumpur,2/makassar,2/singapore,4/perth,4/west",
        "8|n|03/25:03->09/29:23": "2/ulan_bator",
        "8|n": "2/brunei,2/choibalsan,2/chongqing,2/chungking,2/harbin,2/hong_kong,2/irkutsk,2/kuching,2/macao,2/macau,2/manila,2/shanghai,2/taipei,2/ujung_pandang,2/ulaanbaatar",
        "8.75|s": "4/eucla",
        "7|s": "12/davis,2/jakarta,9/christmas",
        "7|n": "2/bangkok,2/barnaul,2/ho_chi_minh,2/hovd,2/krasnoyarsk,2/novokuznetsk,2/novosibirsk,2/phnom_penh,2/pontianak,2/saigon,2/tomsk,2/vientiane",
        "6|s": "12/vostok",
        "6|n": "2/almaty,2/bishkek,2/dacca,2/dhaka,2/kashgar,2/omsk,2/qyzylorda,2/qostanay,2/thimbu,2/thimphu,2/urumqi,9/chagos",
        "6.5|n": "2/rangoon,2/yangon,9/cocos",
        "5|s": "12/mawson,9/kerguelen",
        "5|n": "2/aqtau,2/aqtobe,2/ashgabat,2/ashkhabad,2/atyrau,2/baku,2/dushanbe,2/karachi,2/oral,2/samarkand,2/tashkent,2/yekaterinburg,9/maldives",
        "5.75|n": "2/kathmandu,2/katmandu",
        "5.5|n": "2/calcutta,2/colombo,2/kolkata",
        "4|s": "9/reunion",
        "4|n": "2/dubai,2/muscat,2/tbilisi,2/yerevan,8/astrakhan,8/samara,8/saratov,8/ulyanovsk,8/volgograd,2/volgograd,9/mahe,9/mauritius",
        "4.5|n|03/21:00->09/20:24": "2/tehran",
        "4.5|n": "2/kabul",
        "3|s": "12/syowa,9/antananarivo",
        "3|n|03/29:03->10/25:04": "2/famagusta,2/nicosia,8/athens,8/bucharest,8/helsinki,8/kiev,8/mariehamn,8/nicosia,8/riga,8/sofia,8/tallinn,8/uzhgorod,8/vilnius,8/zaporozhye",
        "3|n|03/29:02->10/25:03": "8/chisinau,8/tiraspol",
        "3|n|03/29:00->10/24:24": "2/beirut",
        "3|n|03/28:00->10/24:01": "2/gaza,2/hebron",
        "3|n|03/27:02->10/25:02": "2/jerusalem,2/tel_aviv",
        "3|n|03/27:00->10/30:01": "2/amman",
        "3|n|03/27:00->10/29:24": "2/damascus",
        "3|n": "0/addis_ababa,0/asmara,0/asmera,0/dar_es_salaam,0/djibouti,0/juba,0/kampala,0/mogadishu,0/nairobi,2/aden,2/baghdad,2/bahrain,2/istanbul,2/kuwait,2/qatar,2/riyadh,8/istanbul,8/kirov,8/minsk,8/moscow,8/simferopol,9/comoro,9/mayotte",
        "2|s|03/29:02->10/25:02": "12/troll",
        "2|s": "0/gaborone,0/harare,0/johannesburg,0/lubumbashi,0/lusaka,0/maputo,0/maseru,0/mbabane",
        "2|n|03/29:02->10/25:03": "0/ceuta,arctic/longyearbyen,3/jan_mayen,8/amsterdam,8/andorra,8/belgrade,8/berlin,8/bratislava,8/brussels,8/budapest,8/busingen,8/copenhagen,8/gibraltar,8/ljubljana,8/luxembourg,8/madrid,8/malta,8/monaco,8/oslo,8/paris,8/podgorica,8/prague,8/rome,8/san_marino,8/sarajevo,8/skopje,8/stockholm,8/tirane,8/vaduz,8/vatican,8/vienna,8/warsaw,8/zagreb,8/zurich",
        "2|n": "0/blantyre,0/bujumbura,0/cairo,0/khartoum,0/kigali,0/tripoli,8/kaliningrad",
        "1|s|04/02:01->09/03:03": "0/windhoek",
        "1|s": "0/kinshasa,0/luanda",
        "1|n|04/19:03->05/31:02": "0/casablanca,0/el_aaiun",
        "1|n|03/29:01->10/25:02": "3/canary,3/faeroe,3/faroe,3/madeira,8/belfast,8/dublin,8/guernsey,8/isle_of_man,8/jersey,8/lisbon,8/london",
        "1|n": "0/algiers,0/bangui,0/brazzaville,0/douala,0/lagos,0/libreville,0/malabo,0/ndjamena,0/niamey,0/porto-novo,0/tunis",
        "14|n": "11/kiritimati",
        "13|s|04/05:04->09/27:03": "11/apia",
        "13|s|01/15:02->11/05:03": "11/tongatapu",
        "13|n": "11/enderbury,11/fakaofo",
        "12|s|04/05:03->09/27:02": "12/mcmurdo,12/south_pole,11/auckland",
        "12|s|01/12:03->12/20:02": "11/fiji",
        "12|n": "2/anadyr,2/kamchatka,2/srednekolymsk,11/funafuti,11/kwajalein,11/majuro,11/nauru,11/tarawa,11/wake,11/wallis",
        "12.75|s|04/05:03->04/05:02": "11/chatham",
        "11|s|04/05:03->10/04:02": "12/macquarie",
        "11|s": "11/bougainville",
        "11|n": "2/magadan,2/sakhalin,11/efate,11/guadalcanal,11/kosrae,11/noumea,11/pohnpei,11/ponape",
        "11.5|n|04/05:03->10/04:02": "11/norfolk",
        "10|s|04/05:03->10/04:02": "4/act,4/canberra,4/currie,4/hobart,4/melbourne,4/nsw,4/sydney,4/tasmania,4/victoria",
        "10|s": "12/dumontdurville,4/brisbane,4/lindeman,4/queensland",
        "10|n": "2/ust-nera,2/vladivostok,2/yakutsk,11/chuuk,11/guam,11/port_moresby,11/saipan,11/truk,11/yap",
        "10.5|s|04/05:01->10/04:02": "4/lhi,4/lord_howe",
        "0|n|03/29:00->10/25:01": "1/scoresbysund,3/azores",
        "0|n": "0/abidjan,0/accra,0/bamako,0/banjul,0/bissau,0/conakry,0/dakar,0/freetown,0/lome,0/monrovia,0/nouakchott,0/ouagadougou,0/sao_tome,0/timbuktu,1/danmarkshavn,3/reykjavik,3/st_helena,13/gmt,13/gmt+0,13/gmt-0,13/gmt0,13/greenwich,13/utc,13/universal,13/zulu",
        "-9|n|03/08:02->11/01:02": "1/adak,1/atka",
        "-9|n": "11/gambier",
        "-9.5|n": "11/marquesas",
        "-8|n|03/08:02->11/01:02": "1/anchorage,1/juneau,1/metlakatla,1/nome,1/sitka,1/yakutat",
        "-8|n": "11/pitcairn",
        "-7|n|03/08:02->11/01:02": "1/ensenada,1/los_angeles,1/santa_isabel,1/tijuana,1/vancouver,6/pacific,10/bajanorte",
        "-7|n|03/08:02->11/01:01": "1/dawson,1/whitehorse,6/yukon",
        "-7|n": "1/creston,1/dawson_creek,1/fort_nelson,1/hermosillo,1/phoenix",
        "-6|s|04/04:22->09/05:22": "7/easterisland,11/easter",
        "-6|n|04/05:02->10/25:02": "1/chihuahua,1/mazatlan,10/bajasur",
        "-6|n|03/08:02->11/01:02": "1/boise,1/cambridge_bay,1/denver,1/edmonton,1/inuvik,1/ojinaga,1/shiprock,1/yellowknife,6/mountain",
        "-6|n": "1/belize,1/costa_rica,1/el_salvador,1/guatemala,1/managua,1/regina,1/swift_current,1/tegucigalpa,6/east-saskatchewan,6/saskatchewan,11/galapagos",
        "-5|s": "1/lima,1/rio_branco,5/acre",
        "-5|n|04/05:02->10/25:02": "1/bahia_banderas,1/merida,1/mexico_city,1/monterrey,10/general",
        "-5|n|03/12:03->11/05:01": "1/north_dakota",
        "-5|n|03/08:02->11/01:02": "1/chicago,1/knox_in,1/matamoros,1/menominee,1/rainy_river,1/rankin_inlet,1/resolute,1/winnipeg,6/central",
        "-5|n": "1/atikokan,1/bogota,1/cancun,1/cayman,1/coral_harbour,1/eirunepe,1/guayaquil,1/jamaica,1/panama,1/porto_acre",
        "-4|s|05/13:23->08/13:01": "12/palmer",
        "-4|s|04/04:24->09/06:00": "1/santiago,7/continental",
        "-4|s|03/21:24->10/04:00": "1/asuncion",
        "-4|s|02/16:24->11/03:00": "1/campo_grande,1/cuiaba",
        "-4|s": "1/la_paz,1/manaus,5/west",
        "-4|n|03/12:03->11/05:01": "1/indiana,1/kentucky",
        "-4|n|03/08:02->11/01:02": "1/detroit,1/fort_wayne,1/grand_turk,1/indianapolis,1/iqaluit,1/louisville,1/montreal,1/nassau,1/new_york,1/nipigon,1/pangnirtung,1/port-au-prince,1/thunder_bay,1/toronto,6/eastern",
        "-4|n|03/08:00->11/01:01": "1/havana",
        "-4|n": "1/anguilla,1/antigua,1/aruba,1/barbados,1/blanc-sablon,1/boa_vista,1/caracas,1/curacao,1/dominica,1/grenada,1/guadeloupe,1/guyana,1/kralendijk,1/lower_princes,1/marigot,1/martinique,1/montserrat,1/port_of_spain,1/porto_velho,1/puerto_rico,1/santo_domingo,1/st_barthelemy,1/st_kitts,1/st_lucia,1/st_thomas,1/st_vincent,1/tortola,1/virgin",
        "-3|s": "1/argentina,1/buenos_aires,1/cordoba,1/fortaleza,1/montevideo,1/punta_arenas,1/sao_paulo,12/rothera,3/stanley,5/east",
        "-3|n|03/28:22->10/24:23": "1/nuuk",
        "-3|n|03/08:02->11/01:02": "1/glace_bay,1/goose_bay,1/halifax,1/moncton,1/thule,3/bermuda,6/atlantic",
        "-3|n": "1/araguaina,1/bahia,1/belem,1/catamarca,1/cayenne,1/jujuy,1/maceio,1/mendoza,1/paramaribo,1/recife,1/rosario,1/santarem",
        "-2|s": "5/denoronha",
        "-2|n|03/28:22->10/24:23": "1/godthab",
        "-2|n|03/08:02->11/01:02": "1/miquelon",
        "-2|n": "1/noronha,3/south_georgia",
        "-2.5|n|03/08:02->11/01:02": "1/st_johns,6/newfoundland",
        "-1|n": "3/cape_verde",
        "-11|n": "11/midway,11/niue,11/pago_pago,11/samoa",
        "-10|n": "11/honolulu,11/johnston,11/rarotonga,11/tahiti"
      };

      var _build$1 = /*#__PURE__*/Object.freeze({
        __proto__: null,
        'default': _build
      }); //prefixes for iana names..


      var _prefixes = ['africa', 'america', 'asia', 'atlantic', 'australia', 'brazil', 'canada', 'chile', 'europe', 'indian', 'mexico', 'pacific', 'antarctica', 'etc'];

      function createCommonjsModule(fn, module) {
        return module = {
          exports: {}
        }, fn(module, module.exports), module.exports;
      }

      function getCjsExportFromNamespace(n) {
        return n && n['default'] || n;
      }

      var data = getCjsExportFromNamespace(_build$1);
      var all = {};
      Object.keys(data).forEach(function (k) {
        var split = k.split('|');
        var obj = {
          offset: Number(split[0]),
          hem: split[1]
        };

        if (split[2]) {
          obj.dst = split[2];
        }

        var names = data[k].split(',');
        names.forEach(function (str) {
          str = str.replace(/(^[0-9]+)\//, function (before, num) {
            num = Number(num);
            return _prefixes[num] + '/';
          });
          all[str] = obj;
        });
      });
      all['utc'] = {
        offset: 0,
        hem: 'n' //default to northern hemisphere - (sorry!)

      }; //add etc/gmt+n

      for (var i = -14; i <= 14; i += 0.5) {
        var num = i;

        if (num > 0) {
          num = '+' + num;
        }

        var name = 'etc/gmt' + num;
        all[name] = {
          offset: i * -1,
          //they're negative!
          hem: 'n' //(sorry)

        };
        name = 'utc/gmt' + num; //this one too, why not.

        all[name] = {
          offset: i * -1,
          hem: 'n'
        };
      }

      var unpack = all; //find the implicit iana code for this machine.
      //safely query the Intl object
      //based on - https://bitbucket.org/pellepim/jstimezonedetect/src

      var fallbackTZ = 'utc'; //
      //this Intl object is not supported often, yet

      var safeIntl = function safeIntl() {
        if (typeof Intl === 'undefined' || typeof Intl.DateTimeFormat === 'undefined') {
          return null;
        }

        var format = Intl.DateTimeFormat();

        if (typeof format === 'undefined' || typeof format.resolvedOptions === 'undefined') {
          return null;
        }

        var timezone = format.resolvedOptions().timeZone;

        if (!timezone) {
          return null;
        }

        return timezone.toLowerCase();
      };

      var guessTz = function guessTz() {
        var timezone = safeIntl();

        if (timezone === null) {
          return fallbackTZ;
        }

        return timezone;
      }; //do it once per computer


      var guessTz_1 = guessTz;
      var isOffset = /(\-?[0-9]+)h(rs)?/i;
      var isNumber = /(\-?[0-9]+)/;
      var utcOffset = /utc([\-+]?[0-9]+)/i;
      var gmtOffset = /gmt([\-+]?[0-9]+)/i;

      var toIana = function toIana(num) {
        num = Number(num);

        if (num >= -13 && num <= 13) {
          num = num * -1; //it's opposite!

          num = (num > 0 ? '+' : '') + num; //add plus sign

          return 'etc/gmt' + num;
        }

        return null;
      };

      var parseOffset = function parseOffset(tz) {
        // '+5hrs'
        var m = tz.match(isOffset);

        if (m !== null) {
          return toIana(m[1]);
        } // 'utc+5'


        m = tz.match(utcOffset);

        if (m !== null) {
          return toIana(m[1]);
        } // 'GMT-5' (not opposite)


        m = tz.match(gmtOffset);

        if (m !== null) {
          var num = Number(m[1]) * -1;
          return toIana(num);
        } // '+5'


        m = tz.match(isNumber);

        if (m !== null) {
          return toIana(m[1]);
        }

        return null;
      };

      var parseOffset_1 = parseOffset;
      var local = guessTz_1(); //add all the city names by themselves

      var cities = Object.keys(unpack).reduce(function (h, k) {
        var city = k.split('/')[1] || '';
        city = city.replace(/_/g, ' ');
        h[city] = k;
        return h;
      }, {}); //try to match these against iana form

      var normalize = function normalize(tz) {
        tz = tz.replace(/ time/g, '');
        tz = tz.replace(/ (standard|daylight|summer)/g, '');
        tz = tz.replace(/\b(east|west|north|south)ern/g, '$1');
        tz = tz.replace(/\b(africa|america|australia)n/g, '$1');
        tz = tz.replace(/\beuropean/g, 'europe');
        tz = tz.replace(/\islands/g, 'island');
        return tz;
      }; // try our best to reconcile the timzone to this given string


      var lookupTz = function lookupTz(str, zones) {
        if (!str) {
          return local;
        }

        if (typeof str !== 'string') {
          console.error("Timezone must be a string - recieved: '", str, "'\n");
        }

        var tz = str.trim();
        var split = str.split('/'); //support long timezones like 'America/Argentina/Rio_Gallegos'

        if (split.length > 2 && zones.hasOwnProperty(tz) === false) {
          tz = split[0] + '/' + split[1];
        }

        tz = tz.toLowerCase();

        if (zones.hasOwnProperty(tz) === true) {
          return tz;
        } //lookup more loosely..


        tz = normalize(tz);

        if (zones.hasOwnProperty(tz) === true) {
          return tz;
        } //try city-names


        if (cities.hasOwnProperty(tz) === true) {
          return cities[tz];
        } // //try to parse '-5h'


        if (/[0-9]/.test(tz) === true) {
          var id = parseOffset_1(tz);

          if (id) {
            return id;
          }
        }

        throw new Error("Spacetime: Cannot find timezone named: '" + str + "'. Please enter an IANA timezone id.");
      };

      var find = lookupTz;
      var o = {
        millisecond: 1
      };
      o.second = 1000;
      o.minute = 60000;
      o.hour = 3.6e6; // dst is supported post-hoc

      o.day = 8.64e7; //

      o.date = o.day;
      o.month = 8.64e7 * 29.5; //(average)

      o.week = 6.048e8;
      o.year = 3.154e10; // leap-years are supported post-hoc
      //add plurals

      Object.keys(o).forEach(function (k) {
        o[k + 's'] = o[k];
      });
      var milliseconds = o;

      var walk = function walk(s, n, fn, unit, previous) {
        var current = s.d[fn]();

        if (current === n) {
          return; //already there
        }

        var startUnit = previous === null ? null : s.d[previous]();
        var original = s.epoch; //try to get it as close as we can

        var diff = n - current;
        s.epoch += milliseconds[unit] * diff; //DST edge-case: if we are going many days, be a little conservative
        // console.log(unit, diff)

        if (unit === 'day') {
          // s.epoch -= ms.minute
          //but don't push it over a month
          if (Math.abs(diff) > 28 && n < 28) {
            s.epoch += milliseconds.hour;
          }
        } // 1st time: oops, did we change previous unit? revert it.


        if (previous !== null && startUnit !== s.d[previous]()) {
          // console.warn('spacetime warning: missed setting ' + unit)
          s.epoch = original; // s.epoch += ms[unit] * diff * 0.89 // maybe try and make it close...?
        } //repair it if we've gone too far or something
        //(go by half-steps, just in case)


        var halfStep = milliseconds[unit] / 2;

        while (s.d[fn]() < n) {
          s.epoch += halfStep;
        }

        while (s.d[fn]() > n) {
          s.epoch -= halfStep;
        } // 2nd time: did we change previous unit? revert it.


        if (previous !== null && startUnit !== s.d[previous]()) {
          // console.warn('spacetime warning: missed setting ' + unit)
          s.epoch = original;
        }
      }; //find the desired date by a increment/check while loop


      var units = {
        year: {
          valid: function valid(n) {
            return n > -4000 && n < 4000;
          },
          walkTo: function walkTo(s, n) {
            return walk(s, n, 'getFullYear', 'year', null);
          }
        },
        month: {
          valid: function valid(n) {
            return n >= 0 && n <= 11;
          },
          walkTo: function walkTo(s, n) {
            var d = s.d;
            var current = d.getMonth();
            var original = s.epoch;
            var startUnit = d.getFullYear();

            if (current === n) {
              return;
            } //try to get it as close as we can..


            var diff = n - current;
            s.epoch += milliseconds.day * (diff * 28); //special case
            //oops, did we change the year? revert it.

            if (startUnit !== s.d.getFullYear()) {
              s.epoch = original;
            } //incriment by day


            while (s.d.getMonth() < n) {
              s.epoch += milliseconds.day;
            }

            while (s.d.getMonth() > n) {
              s.epoch -= milliseconds.day;
            }
          }
        },
        date: {
          valid: function valid(n) {
            return n > 0 && n <= 31;
          },
          walkTo: function walkTo(s, n) {
            return walk(s, n, 'getDate', 'day', 'getMonth');
          }
        },
        hour: {
          valid: function valid(n) {
            return n >= 0 && n < 24;
          },
          walkTo: function walkTo(s, n) {
            return walk(s, n, 'getHours', 'hour', 'getDate');
          }
        },
        minute: {
          valid: function valid(n) {
            return n >= 0 && n < 60;
          },
          walkTo: function walkTo(s, n) {
            return walk(s, n, 'getMinutes', 'minute', 'getHours');
          }
        },
        second: {
          valid: function valid(n) {
            return n >= 0 && n < 60;
          },
          walkTo: function walkTo(s, n) {
            //do this one directly
            s.epoch = s.seconds(n).epoch;
          }
        },
        millisecond: {
          valid: function valid(n) {
            return n >= 0 && n < 1000;
          },
          walkTo: function walkTo(s, n) {
            //do this one directly
            s.epoch = s.milliseconds(n).epoch;
          }
        }
      };

      var walkTo = function walkTo(s, wants) {
        var keys = Object.keys(units);
        var old = s.clone();

        for (var i = 0; i < keys.length; i++) {
          var k = keys[i];
          var n = wants[k];

          if (n === undefined) {
            n = old[k]();
          }

          if (typeof n === 'string') {
            n = parseInt(n, 10);
          } //make-sure it's valid


          if (!units[k].valid(n)) {
            s.epoch = null;

            if (s.silent === false) {
              console.warn('invalid ' + k + ': ' + n);
            }

            return;
          }

          units[k].walkTo(s, n);
        }

        return;
      };

      var walk_1 = walkTo;
      var shortMonths = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sept', 'oct', 'nov', 'dec'];
      var longMonths = ['january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december'];

      function buildMapping() {
        var obj = {
          sep: 8 //support this format

        };

        for (var i = 0; i < shortMonths.length; i++) {
          obj[shortMonths[i]] = i;
        }

        for (var _i = 0; _i < longMonths.length; _i++) {
          obj[longMonths[_i]] = _i;
        }

        return obj;
      }

      var months = {
        "short": function _short() {
          return shortMonths;
        },
        "long": function _long() {
          return longMonths;
        },
        mapping: function mapping() {
          return buildMapping();
        },
        set: function set(i18n) {
          shortMonths = i18n["short"] || shortMonths;
          longMonths = i18n["long"] || longMonths;
        }
      }; //pull-apart ISO offsets, like "+0100"

      var parseOffset$1 = function parseOffset(s, offset) {
        if (!offset) {
          return s;
        } //this is a fancy-move


        if (offset === 'Z' || offset === 'z') {
          offset = '+0000';
        } // according to ISO8601, tz could be hh:mm, hhmm or hh
        // so need few more steps before the calculation.


        var num = 0; // for (+-)hh:mm

        if (/^[\+-]?[0-9]{2}:[0-9]{2}$/.test(offset)) {
          //support "+01:00"
          if (/:00/.test(offset) === true) {
            offset = offset.replace(/:00/, '');
          } //support "+01:30"


          if (/:30/.test(offset) === true) {
            offset = offset.replace(/:30/, '.5');
          }
        } // for (+-)hhmm


        if (/^[\+-]?[0-9]{4}$/.test(offset)) {
          offset = offset.replace(/30$/, '.5');
        }

        num = parseFloat(offset); //divide by 100 or 10 - , "+0100", "+01"

        if (Math.abs(num) > 100) {
          num = num / 100;
        } //okay, try to match it to a utc timezone
        //remember - this is opposite! a -5 offset maps to Etc/GMT+5  ¯\_(:/)_/¯
        //https://askubuntu.com/questions/519550/why-is-the-8-timezone-called-gmt-8-in-the-filesystem


        num *= -1;

        if (num >= 0) {
          num = '+' + num;
        }

        var tz = 'etc/gmt' + num;
        var zones = s.timezones;

        if (zones[tz]) {
          // log a warning if we're over-writing a given timezone?
          // console.log('changing timezone to: ' + tz)
          s.tz = tz;
        }

        return s;
      };

      var parseOffset_1$1 = parseOffset$1;

      var parseTime = function parseTime(s) {
        var str = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
        str = str.replace(/^\s+/, '').toLowerCase(); //trim
        //formal time formats - 04:30.23

        var arr = str.match(/([0-9]{1,2}):([0-9]{1,2}):?([0-9]{1,2})?[:\.]?([0-9]{1,4})?/);

        if (arr !== null) {
          //validate it a little
          var h = Number(arr[1]);

          if (h < 0 || h > 24) {
            return s.startOf('day');
          }

          var m = Number(arr[2]); //don't accept '5:3pm'

          if (arr[2].length < 2 || m < 0 || m > 59) {
            return s.startOf('day');
          }

          if (arr[4] > 999) {
            // fix overflow issue with milliseconds, if input is longer than standard (e.g. 2017-08-06T09:00:00.123456Z)
            arr[4] = parseInt("".concat(arr[4]).substring(0, 3), 10);
          }

          s = s.hour(h);
          s = s.minute(m);
          s = s.seconds(arr[3] || 0);
          s = s.millisecond(arr[4] || 0); //parse-out am/pm

          var ampm = str.match(/[\b0-9](am|pm)\b/);

          if (ampm !== null && ampm[1]) {
            s = s.ampm(ampm[1]);
          }

          return s;
        } //try an informal form - 5pm (no minutes)


        arr = str.match(/([0-9]+) ?(am|pm)/);

        if (arr !== null && arr[1]) {
          var _h = Number(arr[1]); //validate it a little..


          if (_h > 12 || _h < 1) {
            return s.startOf('day');
          }

          s = s.hour(arr[1] || 0);
          s = s.ampm(arr[2]);
          s = s.startOf('hour');
          return s;
        } //no time info found, use start-of-day


        s = s.startOf('day');
        return s;
      };

      var parseTime_1 = parseTime;
      var monthLengths = [31, // January - 31 days
      28, // February - 28 days in a common year and 29 days in leap years
      31, // March - 31 days
      30, // April - 30 days
      31, // May - 31 days
      30, // June - 30 days
      31, // July - 31 days
      31, // August - 31 days
      30, // September - 30 days
      31, // October - 31 days
      30, // November - 30 days
      31 // December - 31 days
      ];
      var monthLengths_1 = monthLengths; // 28 - feb

      var fns = createCommonjsModule(function (module, exports) {
        //git:blame @JuliasCaesar https://www.timeanddate.com/date/leapyear.html
        exports.isLeapYear = function (year) {
          return year % 4 === 0 && year % 100 !== 0 || year % 400 === 0;
        }; // unsurprisingly-nasty `typeof date` call


        exports.isDate = function (d) {
          return Object.prototype.toString.call(d) === '[object Date]' && !isNaN(d.valueOf());
        };

        exports.isArray = function (input) {
          return Object.prototype.toString.call(input) === '[object Array]';
        };

        exports.isObject = function (input) {
          return Object.prototype.toString.call(input) === '[object Object]';
        };

        exports.isBoolean = function (input) {
          return Object.prototype.toString.call(input) === '[object Boolean]';
        };

        exports.zeroPad = function (str) {
          var len = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 2;
          var pad = '0';
          str = str + '';
          return str.length >= len ? str : new Array(len - str.length + 1).join(pad) + str;
        };

        exports.titleCase = function (str) {
          if (!str) {
            return '';
          }

          return str[0].toUpperCase() + str.substr(1);
        };

        exports.ordinal = function (i) {
          var j = i % 10;
          var k = i % 100;

          if (j === 1 && k !== 11) {
            return i + 'st';
          }

          if (j === 2 && k !== 12) {
            return i + 'nd';
          }

          if (j === 3 && k !== 13) {
            return i + 'rd';
          }

          return i + 'th';
        }; //strip 'st' off '1st'..


        exports.toCardinal = function (str) {
          str = String(str);
          str = str.replace(/([0-9])(st|nd|rd|th)$/i, '$1');
          return parseInt(str, 10);
        }; //used mostly for cleanup of unit names, like 'months'


        exports.normalize = function () {
          var str = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
          str = str.toLowerCase().trim();
          str = str.replace(/ies$/, 'y'); //'centuries'

          str = str.replace(/s$/, '');
          str = str.replace(/-/g, '');

          if (str === 'day' || str === 'days') {
            return 'date';
          }

          if (str === 'min' || str === 'mins') {
            return 'minute';
          }

          return str;
        };

        exports.getEpoch = function (tmp) {
          //support epoch
          if (typeof tmp === 'number') {
            return tmp;
          } //suport date objects


          if (exports.isDate(tmp)) {
            return tmp.getTime();
          }

          if (tmp.epoch) {
            return tmp.epoch;
          }

          return null;
        }; //make sure this input is a spacetime obj


        exports.beADate = function (d, s) {
          if (exports.isObject(d) === false) {
            return s.clone().set(d);
          }

          return d;
        };

        exports.formatTimezone = function (offset) {
          var delimiter = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
          var sign = offset > 0 ? '+' : '-';
          var absOffset = Math.abs(offset);
          var hours = exports.zeroPad(parseInt('' + absOffset, 10));
          var minutes = exports.zeroPad(absOffset % 1 * 60);
          return "".concat(sign).concat(hours).concat(delimiter).concat(minutes);
        };
      });
      var fns_1 = fns.isLeapYear;
      var fns_2 = fns.isDate;
      var fns_3 = fns.isArray;
      var fns_4 = fns.isObject;
      var fns_5 = fns.isBoolean;
      var fns_6 = fns.zeroPad;
      var fns_7 = fns.titleCase;
      var fns_8 = fns.ordinal;
      var fns_9 = fns.toCardinal;
      var fns_10 = fns.normalize;
      var fns_11 = fns.getEpoch;
      var fns_12 = fns.beADate;
      var fns_13 = fns.formatTimezone;
      var isLeapYear = fns.isLeapYear; //given a month, return whether day number exists in it

      var hasDate = function hasDate(obj) {
        //invalid values
        if (monthLengths_1.hasOwnProperty(obj.month) !== true) {
          return false;
        } //support leap-year in february


        if (obj.month === 1) {
          if (isLeapYear(obj.year) && obj.date <= 29) {
            return true;
          } else {
            return obj.date <= 28;
          }
        } //is this date too-big for this month?


        var max = monthLengths_1[obj.month] || 0;

        if (obj.date <= max) {
          return true;
        }

        return false;
      };

      var hasDate_1 = hasDate;
      var months$1 = months.mapping();

      var parseYear = function parseYear() {
        var str = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
        var today = arguments.length > 1 ? arguments[1] : undefined;
        var year = parseInt(str.trim(), 10); // use a given year from options.today

        if (!year && today) {
          year = today.year;
        } // fallback to this year


        year = year || new Date().getFullYear();
        return year;
      };

      var strFmt = [//iso-this 1998-05-30T22:00:00:000Z, iso-that 2017-04-03T08:00:00-0700
      {
        reg: /^(\-?0?0?[0-9]{3,4})-([0-9]{1,2})-([0-9]{1,2})[T| ]([0-9.:]+)(Z|[0-9\-\+:]+)?$/i,
        parse: function parse(s, arr, givenTz, options) {
          var month = parseInt(arr[2], 10) - 1;
          var obj = {
            year: arr[1],
            month: month,
            date: arr[3]
          };

          if (hasDate_1(obj) === false) {
            s.epoch = null;
            return s;
          }

          parseOffset_1$1(s, arr[5]);
          walk_1(s, obj);
          s = parseTime_1(s, arr[4]);
          return s;
        }
      }, //iso "2015-03-25" or "2015/03/25" or "2015/03/25 12:26:14 PM"
      {
        reg: /^([0-9]{4})[\-\/.]([0-9]{1,2})[\-\/.]([0-9]{1,2}),?( [0-9]{1,2}:[0-9]{2}:?[0-9]{0,2}? ?(am|pm|gmt))?$/i,
        parse: function parse(s, arr) {
          var obj = {
            year: arr[1],
            month: parseInt(arr[2], 10) - 1,
            date: parseInt(arr[3], 10)
          };

          if (obj.month >= 12) {
            //support yyyy/dd/mm (weird, but ok)
            obj.date = parseInt(arr[2], 10);
            obj.month = parseInt(arr[3], 10) - 1;
          }

          if (hasDate_1(obj) === false) {
            s.epoch = null;
            return s;
          }

          walk_1(s, obj);
          s = parseTime_1(s, arr[4]);
          return s;
        }
      }, //mm/dd/yyyy - uk/canada "6/28/2019, 12:26:14 PM"
      {
        reg: /^([0-9]{1,2})[\-\/.]([0-9]{1,2})[\-\/.]?([0-9]{4})?,?( [0-9]{1,2}:[0-9]{2}:?[0-9]{0,2}? ?(am|pm|gmt))?$/i,
        parse: function parse(s, arr) {
          var month = parseInt(arr[1], 10) - 1;
          var date = parseInt(arr[2], 10); //support dd/mm/yyy

          if (s.british || month >= 12) {
            date = parseInt(arr[1], 10);
            month = parseInt(arr[2], 10) - 1;
          }

          var year = arr[3] || new Date().getFullYear();
          var obj = {
            year: year,
            month: month,
            date: date
          };

          if (hasDate_1(obj) === false) {
            s.epoch = null;
            return s;
          }

          walk_1(s, obj);
          s = parseTime_1(s, arr[4]);
          return s;
        }
      }, // '2012-06' last attempt at iso-like format
      {
        reg: /^([0-9]{4})[\-\/]([0-9]{2})$/i,
        parse: function parse(s, arr, givenTz, options) {
          var month = parseInt(arr[2], 10) - 1;
          var obj = {
            year: arr[1],
            month: month,
            date: 1
          };

          if (hasDate_1(obj) === false) {
            s.epoch = null;
            return s;
          }

          parseOffset_1$1(s, arr[5]);
          walk_1(s, obj);
          s = parseTime_1(s, arr[4]);
          return s;
        }
      }, //common british format - "25-feb-2015"
      {
        reg: /^([0-9]{1,2})[\-\/]([a-z]+)[\-\/]?([0-9]{4})?$/i,
        parse: function parse(s, arr) {
          var month = months$1[arr[2].toLowerCase()];
          var year = parseYear(arr[3], s._today);
          var obj = {
            year: year,
            month: month,
            date: fns.toCardinal(arr[1] || '')
          };

          if (hasDate_1(obj) === false) {
            s.epoch = null;
            return s;
          }

          walk_1(s, obj);
          s = parseTime_1(s, arr[4]);
          return s;
        }
      }, //alt short format - "feb-25-2015"
      {
        reg: /^([a-z]+)[\-\/]([0-9]{1,2})[\-\/]?([0-9]{4})?$/i,
        parse: function parse(s, arr) {
          var month = months$1[arr[1].toLowerCase()];
          var year = parseYear(arr[3], s._today);
          var obj = {
            year: year,
            month: month,
            date: fns.toCardinal(arr[2] || '')
          };

          if (hasDate_1(obj) === false) {
            s.epoch = null;
            return s;
          }

          walk_1(s, obj);
          s = parseTime_1(s, arr[4]);
          return s;
        }
      }, //Long "Mar 25 2015"
      //February 22, 2017 15:30:00
      {
        reg: /^([a-z]+) ([0-9]{1,2}(?:st|nd|rd|th)?),?( [0-9]{4})?( ([0-9:]+( ?am| ?pm| ?gmt)?))?$/i,
        parse: function parse(s, arr) {
          var month = months$1[arr[1].toLowerCase()];
          var year = parseYear(arr[3], s._today);
          var obj = {
            year: year,
            month: month,
            date: fns.toCardinal(arr[2] || '')
          };

          if (hasDate_1(obj) === false) {
            s.epoch = null;
            return s;
          }

          walk_1(s, obj);
          s = parseTime_1(s, arr[4]);
          return s;
        }
      }, //February 2017 (implied date)
      {
        reg: /^([a-z]+) ([0-9]{4})$/i,
        parse: function parse(s, arr) {
          var month = months$1[arr[1].toLowerCase()];
          var year = parseYear(arr[2], s._today);
          var obj = {
            year: year,
            month: month,
            date: s._today.date || 1
          };

          if (hasDate_1(obj) === false) {
            s.epoch = null;
            return s;
          }

          walk_1(s, obj);
          s = parseTime_1(s, arr[4]);
          return s;
        }
      }, //Long "25 Mar 2015"
      {
        reg: /^([0-9]{1,2}(?:st|nd|rd|th)?) ([a-z]+),?( [0-9]{4})?,? ?([0-9]{1,2}:[0-9]{2}:?[0-9]{0,2}? ?(am|pm|gmt))?$/i,
        parse: function parse(s, arr) {
          var month = months$1[arr[2].toLowerCase()];

          if (!month) {
            return null;
          }

          var year = parseYear(arr[3], s._today);
          var obj = {
            year: year,
            month: month,
            date: fns.toCardinal(arr[1])
          };

          if (hasDate_1(obj) === false) {
            s.epoch = null;
            return s;
          }

          walk_1(s, obj);
          s = parseTime_1(s, arr[4]);
          return s;
        }
      }, {
        // 'q2 2002'
        reg: /^(q[0-9])( of)?( [0-9]{4})?/i,
        parse: function parse(s, arr) {
          var quarter = arr[1] || '';
          s = s.quarter(quarter);
          var year = arr[3] || '';

          if (year) {
            year = year.trim();
            s = s.year(year);
          }

          return s;
        }
      }, {
        // 'summer 2002'
        reg: /^(spring|summer|winter|fall|autumn)( of)?( [0-9]{4})?/i,
        parse: function parse(s, arr) {
          var season = arr[1] || '';
          s = s.season(season);
          var year = arr[3] || '';

          if (year) {
            year = year.trim();
            s = s.year(year);
          }

          return s;
        }
      }, {
        // '200bc'
        reg: /^[0-9,]+ ?b\.?c\.?$/i,
        parse: function parse(s, arr) {
          var str = arr[0] || ''; //make negative-year

          str = str.replace(/^([0-9,]+) ?b\.?c\.?$/i, '-$1'); //remove commas

          str = str.replace(/,/g, '');
          var year = parseInt(str.trim(), 10);
          var d = new Date();
          var obj = {
            year: year,
            month: d.getMonth(),
            date: d.getDate()
          };

          if (hasDate_1(obj) === false) {
            s.epoch = null;
            return s;
          }

          walk_1(s, obj);
          s = parseTime_1(s);
          return s;
        }
      }, {
        // '200ad'
        reg: /^[0-9,]+ ?(a\.?d\.?|c\.?e\.?)$/i,
        parse: function parse(s, arr) {
          var str = arr[0] || ''; //remove commas

          str = str.replace(/,/g, '');
          var year = parseInt(str.trim(), 10);
          var d = new Date();
          var obj = {
            year: year,
            month: d.getMonth(),
            date: d.getDate()
          };

          if (hasDate_1(obj) === false) {
            s.epoch = null;
            return s;
          }

          walk_1(s, obj);
          s = parseTime_1(s);
          return s;
        }
      }, {
        // '1992'
        reg: /^[0-9]{4}( ?a\.?d\.?)?$/i,
        parse: function parse(s, arr) {
          var today = s._today;
          var year = parseYear(arr[0], today);
          var d = new Date(); // using today's date, but a new month is awkward.

          if (today.month && !today.date) {
            today.date = 1;
          }

          var obj = {
            year: year,
            month: today.month || d.getMonth(),
            date: today.date || d.getDate()
          };

          if (hasDate_1(obj) === false) {
            s.epoch = null;
            return s;
          }

          walk_1(s, obj);
          s = parseTime_1(s);
          return s;
        }
      }];
      var strParse = strFmt; // pull in 'today' data for the baseline moment

      var getNow = function getNow(s) {
        s.epoch = Date.now();
        Object.keys(s._today || {}).forEach(function (k) {
          if (typeof s[k] === 'function') {
            s = s[k](s._today[k]);
          }
        });
        return s;
      };

      var dates = {
        now: function now(s) {
          return getNow(s);
        },
        today: function today(s) {
          return getNow(s);
        },
        tonight: function tonight(s) {
          s = getNow(s);
          s = s.hour(18); //6pm

          return s;
        },
        tomorrow: function tomorrow(s) {
          s = getNow(s);
          s = s.add(1, 'day');
          s = s.startOf('day');
          return s;
        },
        yesterday: function yesterday(s) {
          s = getNow(s);
          s = s.subtract(1, 'day');
          s = s.startOf('day');
          return s;
        },
        christmas: function christmas(s) {
          var year = getNow(s).year();
          s = s.set([year, 11, 25, 18, 0, 0]); // Dec 25

          return s;
        },
        'new years': function newYears(s) {
          var year = getNow(s).year();
          s = s.set([year, 11, 31, 18, 0, 0]); // Dec 31

          return s;
        }
      };
      dates['new years eve'] = dates['new years'];
      var namedDates = dates; //  -  can't use built-in js parser ;(
      //=========================================
      // ISO Date	  "2015-03-25"
      // Short Date	"03/25/2015" or "2015/03/25"
      // Long Date	"Mar 25 2015" or "25 Mar 2015"
      // Full Date	"Wednesday March 25 2015"
      //=========================================
      //-- also -
      // if the given epoch is really small, they've probably given seconds and not milliseconds
      // anything below this number is likely (but not necessarily) a mistaken input.
      // this may seem like an arbitrary number, but it's 'within jan 1970'
      // this is only really ambiguous until 2054 or so

      var minimumEpoch = 2500000000;
      var defaults = {
        year: new Date().getFullYear(),
        month: 0,
        date: 1
      }; //support [2016, 03, 01] format

      var handleArray = function handleArray(s, arr, today) {
        if (arr.length === 0) {
          return s;
        }

        var order = ['year', 'month', 'date', 'hour', 'minute', 'second', 'millisecond'];

        for (var i = 0; i < order.length; i++) {
          var num = arr[i] || today[order[i]] || defaults[order[i]] || 0;
          s = s[order[i]](num);
        }

        return s;
      }; //support {year:2016, month:3} format


      var handleObject = function handleObject(s, obj, today) {
        // if obj is empty, do nothing
        if (Object.keys(obj).length === 0) {
          return s;
        }

        obj = Object.assign({}, defaults, today, obj);
        var keys = Object.keys(obj);

        for (var i = 0; i < keys.length; i++) {
          var unit = keys[i]; //make sure we have this method

          if (s[unit] === undefined || typeof s[unit] !== 'function') {
            continue;
          } //make sure the value is a number


          if (obj[unit] === null || obj[unit] === undefined || obj[unit] === '') {
            continue;
          }

          var num = obj[unit] || today[unit] || defaults[unit] || 0;
          s = s[unit](num);
        }

        return s;
      }; //find the epoch from different input styles


      var parseInput = function parseInput(s, input, givenTz) {
        var today = s._today || defaults; //if we've been given a epoch number, it's easy

        if (typeof input === 'number') {
          if (input > 0 && input < minimumEpoch && s.silent === false) {
            console.warn('  - Warning: You are setting the date to January 1970.');
            console.warn('       -   did input seconds instead of milliseconds?');
          }

          s.epoch = input;
          return s;
        } //set tmp time


        s.epoch = Date.now(); // overwrite tmp time with 'today' value, if exists

        if (s._today && fns.isObject(s._today) && Object.keys(s._today).length > 0) {
          var res = handleObject(s, today, defaults);

          if (res.isValid()) {
            s.epoch = res.epoch;
          }
        } // null input means 'now'


        if (input === null || input === undefined || input === '') {
          return s; //k, we're good.
        } //support input of Date() object


        if (fns.isDate(input) === true) {
          s.epoch = input.getTime();
          return s;
        } //support [2016, 03, 01] format


        if (fns.isArray(input) === true) {
          s = handleArray(s, input, today);
          return s;
        } //support {year:2016, month:3} format


        if (fns.isObject(input) === true) {
          //support spacetime object as input
          if (input.epoch) {
            s.epoch = input.epoch;
            s.tz = input.tz;
            return s;
          }

          s = handleObject(s, input, today);
          return s;
        } //input as a string..


        if (typeof input !== 'string') {
          return s;
        } //little cleanup..


        input = input.replace(/\b(mon|tues|wed|wednes|thu|thurs|fri|sat|satur|sun)(day)?\b/i, '');
        input = input.replace(/,/g, '');
        input = input.replace(/ +/g, ' ').trim(); //try some known-words, like 'now'

        if (namedDates.hasOwnProperty(input) === true) {
          s = namedDates[input](s);
          return s;
        } //try each text-parse template, use the first good result


        for (var i = 0; i < strParse.length; i++) {
          var m = input.match(strParse[i].reg);

          if (m) {
            // console.log(strFmt[i].reg)
            var _res = strParse[i].parse(s, m, givenTz);

            if (_res !== null && _res.isValid()) {
              return _res;
            }
          }
        }

        if (s.silent === false) {
          console.warn("Warning: couldn't parse date-string: '" + input + "'");
        }

        s.epoch = null;
        return s;
      };

      var input = parseInput;
      var shortDays = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
      var longDays = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'];
      var days = {
        "short": function _short2() {
          return shortDays;
        },
        "long": function _long2() {
          return longDays;
        },
        set: function set(i18n) {
          shortDays = i18n["short"] || shortDays;
          longDays = i18n["long"] || longDays;
        },
        aliases: {
          tues: 2,
          thur: 4,
          thurs: 4
        }
      };
      var titleCaseEnabled = true;
      var caseFormat = {
        useTitleCase: function useTitleCase() {
          return titleCaseEnabled;
        },
        set: function set(useTitleCase) {
          titleCaseEnabled = useTitleCase;
        }
      }; // it's kind of nuts how involved this is
      // "+01:00", "+0100", or simply "+01"

      var isoOffset = function isoOffset(s) {
        var offset = s.timezone().current.offset;
        return !offset ? 'Z' : fns.formatTimezone(offset, ':');
      };

      var _offset = isoOffset;

      var applyCaseFormat = function applyCaseFormat(str) {
        if (caseFormat.useTitleCase()) {
          return fns.titleCase(str);
        }

        return str;
      };

      var format = {
        day: function day(s) {
          return applyCaseFormat(s.dayName());
        },
        'day-short': function dayShort(s) {
          return applyCaseFormat(days["short"]()[s.day()]);
        },
        'day-number': function dayNumber(s) {
          return s.day();
        },
        'day-ordinal': function dayOrdinal(s) {
          return fns.ordinal(s.day());
        },
        'day-pad': function dayPad(s) {
          return fns.zeroPad(s.day());
        },
        date: function date(s) {
          return s.date();
        },
        'date-ordinal': function dateOrdinal(s) {
          return fns.ordinal(s.date());
        },
        'date-pad': function datePad(s) {
          return fns.zeroPad(s.date());
        },
        month: function month(s) {
          return applyCaseFormat(s.monthName());
        },
        'month-short': function monthShort(s) {
          return applyCaseFormat(months["short"]()[s.month()]);
        },
        'month-number': function monthNumber(s) {
          return s.month();
        },
        'month-ordinal': function monthOrdinal(s) {
          return fns.ordinal(s.month());
        },
        'month-pad': function monthPad(s) {
          return fns.zeroPad(s.month());
        },
        'iso-month': function isoMonth(s) {
          return fns.zeroPad(s.month() + 1);
        },
        //1-based months
        year: function year(s) {
          var year = s.year();

          if (year > 0) {
            return year;
          }

          year = Math.abs(year);
          return year + ' BC';
        },
        'year-short': function yearShort(s) {
          var year = s.year();

          if (year > 0) {
            return "'".concat(String(s.year()).substr(2, 4));
          }

          year = Math.abs(year);
          return year + ' BC';
        },
        'iso-year': function isoYear(s) {
          var year = s.year();
          var isNegative = year < 0;
          var str = fns.zeroPad(Math.abs(year), 4); //0-padded

          if (isNegative) {
            //negative years are for some reason 6-digits ('-00008')
            str = fns.zeroPad(str, 6);
            str = '-' + str;
          }

          return str;
        },
        time: function time(s) {
          return s.time();
        },
        'time-24': function time24(s) {
          return "".concat(s.hour24(), ":").concat(fns.zeroPad(s.minute()));
        },
        hour: function hour(s) {
          return s.hour12();
        },
        'hour-pad': function hourPad(s) {
          return fns.zeroPad(s.hour12());
        },
        'hour-24': function hour24(s) {
          return s.hour24();
        },
        'hour-24-pad': function hour24Pad(s) {
          return fns.zeroPad(s.hour24());
        },
        minute: function minute(s) {
          return s.minute();
        },
        'minute-pad': function minutePad(s) {
          return fns.zeroPad(s.minute());
        },
        second: function second(s) {
          return s.second();
        },
        'second-pad': function secondPad(s) {
          return fns.zeroPad(s.second());
        },
        ampm: function ampm(s) {
          return s.ampm();
        },
        quarter: function quarter(s) {
          return 'Q' + s.quarter();
        },
        season: function season(s) {
          return s.season();
        },
        era: function era(s) {
          return s.era();
        },
        json: function json(s) {
          return s.json();
        },
        timezone: function timezone(s) {
          return s.timezone().name;
        },
        offset: function offset(s) {
          return _offset(s);
        },
        numeric: function numeric(s) {
          return "".concat(s.year(), "/").concat(fns.zeroPad(s.month() + 1), "/").concat(fns.zeroPad(s.date()));
        },
        // yyyy/mm/dd
        'numeric-us': function numericUs(s) {
          return "".concat(fns.zeroPad(s.month() + 1), "/").concat(fns.zeroPad(s.date()), "/").concat(s.year());
        },
        // mm/dd/yyyy
        'numeric-uk': function numericUk(s) {
          return "".concat(fns.zeroPad(s.date()), "/").concat(fns.zeroPad(s.month() + 1), "/").concat(s.year());
        },
        //dd/mm/yyyy
        'mm/dd': function mmDd(s) {
          return "".concat(fns.zeroPad(s.month() + 1), "/").concat(fns.zeroPad(s.date()));
        },
        //mm/dd
        // ... https://en.wikipedia.org/wiki/ISO_8601 ;(((
        iso: function iso(s) {
          var year = s.format('iso-year');
          var month = fns.zeroPad(s.month() + 1); //1-based months

          var date = fns.zeroPad(s.date());
          var hour = fns.zeroPad(s.h24());
          var minute = fns.zeroPad(s.minute());
          var second = fns.zeroPad(s.second());
          var ms = fns.zeroPad(s.millisecond(), 3);

          var offset = _offset(s);

          return "".concat(year, "-").concat(month, "-").concat(date, "T").concat(hour, ":").concat(minute, ":").concat(second, ".").concat(ms).concat(offset); //2018-03-09T08:50:00.000-05:00
        },
        'iso-short': function isoShort(s) {
          var month = fns.zeroPad(s.month() + 1); //1-based months

          var date = fns.zeroPad(s.date());
          return "".concat(s.year(), "-").concat(month, "-").concat(date); //2017-02-15
        },
        'iso-utc': function isoUtc(s) {
          return new Date(s.epoch).toISOString(); //2017-03-08T19:45:28.367Z
        },
        //i made these up
        nice: function nice(s) {
          return "".concat(months["short"]()[s.month()], " ").concat(fns.ordinal(s.date()), ", ").concat(s.time());
        },
        'nice-year': function niceYear(s) {
          return "".concat(months["short"]()[s.month()], " ").concat(fns.ordinal(s.date()), ", ").concat(s.year());
        },
        'nice-day': function niceDay(s) {
          return "".concat(days["short"]()[s.day()], " ").concat(applyCaseFormat(months["short"]()[s.month()]), " ").concat(fns.ordinal(s.date()));
        },
        'nice-full': function niceFull(s) {
          return "".concat(s.dayName(), " ").concat(applyCaseFormat(s.monthName()), " ").concat(fns.ordinal(s.date()), ", ").concat(s.time());
        }
      }; //aliases

      var aliases = {
        'day-name': 'day',
        'month-name': 'month',
        'iso 8601': 'iso',
        'time-h24': 'time-24',
        'time-12': 'time',
        'time-h12': 'time',
        tz: 'timezone',
        'day-num': 'day-number',
        'month-num': 'month-number',
        'month-iso': 'iso-month',
        'year-iso': 'iso-year',
        'nice-short': 'nice',
        mdy: 'numeric-us',
        dmy: 'numeric-uk',
        ymd: 'numeric',
        'yyyy/mm/dd': 'numeric',
        'mm/dd/yyyy': 'numeric-us',
        'dd/mm/yyyy': 'numeric-us',
        'little-endian': 'numeric-uk',
        'big-endian': 'numeric',
        'day-nice': 'nice-day'
      };
      Object.keys(aliases).forEach(function (k) {
        return format[k] = format[aliases[k]];
      });

      var printFormat = function printFormat(s) {
        var str = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ''; //don't print anything if it's an invalid date

        if (s.isValid() !== true) {
          return '';
        } //support .format('month')


        if (format.hasOwnProperty(str)) {
          var out = format[str](s) || '';

          if (str !== 'json') {
            out = String(out);

            if (str !== 'ampm') {
              out = applyCaseFormat(out);
            }
          }

          return out;
        } //support '{hour}:{minute}' notation


        if (str.indexOf('{') !== -1) {
          var sections = /\{(.+?)\}/g;
          str = str.replace(sections, function (_, fmt) {
            fmt = fmt.toLowerCase().trim();

            if (format.hasOwnProperty(fmt)) {
              return String(format[fmt](s));
            }

            return '';
          });
          return str;
        }

        return s.format('iso-short');
      };

      var format_1 = printFormat;
      var pad = fns.zeroPad;
      var formatTimezone = fns.formatTimezone; //parse this insane unix-time-templating thing, from the 19th century
      //http://unicode.org/reports/tr35/tr35-25.html#Date_Format_Patterns
      //time-symbols we support

      var mapping = {
        G: function G(s) {
          return s.era();
        },
        GG: function GG(s) {
          return s.era();
        },
        GGG: function GGG(s) {
          return s.era();
        },
        GGGG: function GGGG(s) {
          return s.era() === 'AD' ? 'Anno Domini' : 'Before Christ';
        },
        //year
        y: function y(s) {
          return s.year();
        },
        yy: function yy(s) {
          //last two chars
          return parseInt(String(s.year()).substr(2, 4), 10);
        },
        yyy: function yyy(s) {
          return s.year();
        },
        yyyy: function yyyy(s) {
          return s.year();
        },
        yyyyy: function yyyyy(s) {
          return '0' + s.year();
        },
        // u: (s) => {},//extended non-gregorian years
        //quarter
        Q: function Q(s) {
          return s.quarter();
        },
        QQ: function QQ(s) {
          return s.quarter();
        },
        QQQ: function QQQ(s) {
          return s.quarter();
        },
        QQQQ: function QQQQ(s) {
          return s.quarter();
        },
        //month
        M: function M(s) {
          return s.month() + 1;
        },
        MM: function MM(s) {
          return pad(s.month() + 1);
        },
        MMM: function MMM(s) {
          return s.format('month-short');
        },
        MMMM: function MMMM(s) {
          return s.format('month');
        },
        //week
        w: function w(s) {
          return s.week();
        },
        ww: function ww(s) {
          return pad(s.week());
        },
        //week of month
        // W: (s) => s.week(),
        //date of month
        d: function d(s) {
          return s.date();
        },
        dd: function dd(s) {
          return pad(s.date());
        },
        //date of year
        D: function D(s) {
          return s.dayOfYear();
        },
        DD: function DD(s) {
          return pad(s.dayOfYear());
        },
        DDD: function DDD(s) {
          return pad(s.dayOfYear(), 3);
        },
        // F: (s) => {},//date of week in month
        // g: (s) => {},//modified julian day
        //day
        E: function E(s) {
          return s.format('day-short');
        },
        EE: function EE(s) {
          return s.format('day-short');
        },
        EEE: function EEE(s) {
          return s.format('day-short');
        },
        EEEE: function EEEE(s) {
          return s.format('day');
        },
        EEEEE: function EEEEE(s) {
          return s.format('day')[0];
        },
        e: function e(s) {
          return s.day();
        },
        ee: function ee(s) {
          return s.day();
        },
        eee: function eee(s) {
          return s.format('day-short');
        },
        eeee: function eeee(s) {
          return s.format('day');
        },
        eeeee: function eeeee(s) {
          return s.format('day')[0];
        },
        //am/pm
        a: function a(s) {
          return s.ampm().toUpperCase();
        },
        aa: function aa(s) {
          return s.ampm().toUpperCase();
        },
        aaa: function aaa(s) {
          return s.ampm().toUpperCase();
        },
        aaaa: function aaaa(s) {
          return s.ampm().toUpperCase();
        },
        //hour
        h: function h(s) {
          return s.h12();
        },
        hh: function hh(s) {
          return pad(s.h12());
        },
        H: function H(s) {
          return s.hour();
        },
        HH: function HH(s) {
          return pad(s.hour());
        },
        // j: (s) => {},//weird hour format
        m: function m(s) {
          return s.minute();
        },
        mm: function mm(s) {
          return pad(s.minute());
        },
        s: function s(_s) {
          return _s.second();
        },
        ss: function ss(s) {
          return pad(s.second());
        },
        //milliseconds in the day
        A: function A(s) {
          return s.epoch - s.startOf('day').epoch;
        },
        //timezone
        z: function z(s) {
          return s.timezone().name;
        },
        zz: function zz(s) {
          return s.timezone().name;
        },
        zzz: function zzz(s) {
          return s.timezone().name;
        },
        zzzz: function zzzz(s) {
          return s.timezone().name;
        },
        Z: function Z(s) {
          return formatTimezone(s.timezone().current.offset);
        },
        ZZ: function ZZ(s) {
          return formatTimezone(s.timezone().current.offset);
        },
        ZZZ: function ZZZ(s) {
          return formatTimezone(s.timezone().current.offset);
        },
        ZZZZ: function ZZZZ(s) {
          return formatTimezone(s.timezone().current.offset, ':');
        }
      };

      var addAlias = function addAlias(_char, to, n) {
        var name = _char;
        var toName = to;

        for (var i = 0; i < n; i += 1) {
          mapping[name] = mapping[toName];
          name += _char;
          toName += to;
        }
      };

      addAlias('q', 'Q', 4);
      addAlias('L', 'M', 4);
      addAlias('Y', 'y', 4);
      addAlias('c', 'e', 4);
      addAlias('k', 'H', 2);
      addAlias('K', 'h', 2);
      addAlias('S', 's', 2);
      addAlias('v', 'z', 4);
      addAlias('V', 'Z', 4); // support unix-style escaping with ' character

      var escapeChars = function escapeChars(arr) {
        for (var i = 0; i < arr.length; i += 1) {
          if (arr[i] === "'") {
            // greedy-search for next apostrophe
            for (var o = i + 1; o < arr.length; o += 1) {
              if (arr[o]) {
                arr[i] += arr[o];
              }

              if (arr[o] === "'") {
                arr[o] = null;
                break;
              }

              arr[o] = null;
            }
          }
        }

        return arr.filter(function (ch) {
          return ch;
        });
      }; //combine consecutive chars, like 'yyyy' as one.


      var combineRepeated = function combineRepeated(arr) {
        for (var i = 0; i < arr.length; i += 1) {
          var c = arr[i]; // greedy-forward

          for (var o = i + 1; o < arr.length; o += 1) {
            if (arr[o] === c) {
              arr[i] += arr[o];
              arr[o] = null;
            } else {
              break;
            }
          }
        } // '' means one apostrophe


        arr = arr.filter(function (ch) {
          return ch;
        });
        arr = arr.map(function (str) {
          if (str === "''") {
            str = "'";
          }

          return str;
        });
        return arr;
      };

      var unixFmt = function unixFmt(s, str) {
        var arr = str.split(''); // support character escaping

        arr = escapeChars(arr); //combine 'yyyy' as string.

        arr = combineRepeated(arr);
        return arr.reduce(function (txt, c) {
          if (mapping[c] !== undefined) {
            txt += mapping[c](s) || '';
          } else {
            // 'unescape'
            if (/^'.{1,}'$/.test(c)) {
              c = c.replace(/'/g, '');
            }

            txt += c;
          }

          return txt;
        }, '');
      };

      var unixFmt_1 = unixFmt;
      var units$1 = ['year', 'season', 'quarter', 'month', 'week', 'day', 'quarterHour', 'hour', 'minute'];

      var doUnit = function doUnit(s, k) {
        var start = s.clone().startOf(k);
        var end = s.clone().endOf(k);
        var duration = end.epoch - start.epoch;
        var percent = (s.epoch - start.epoch) / duration;
        return parseFloat(percent.toFixed(2));
      }; //how far it is along, from 0-1


      var progress = function progress(s, unit) {
        if (unit) {
          unit = fns.normalize(unit);
          return doUnit(s, unit);
        }

        var obj = {};
        units$1.forEach(function (k) {
          obj[k] = doUnit(s, k);
        });
        return obj;
      };

      var progress_1 = progress;

      var nearest = function nearest(s, unit) {
        //how far have we gone?
        var prog = s.progress();
        unit = fns.normalize(unit); //fix camel-case for this one

        if (unit === 'quarterhour') {
          unit = 'quarterHour';
        }

        if (prog[unit] !== undefined) {
          // go forward one?
          if (prog[unit] > 0.5) {
            s = s.add(1, unit);
          } // go to start


          s = s.startOf(unit);
        } else if (s.silent === false) {
          console.warn("no known unit '" + unit + "'");
        }

        return s;
      };

      var nearest_1 = nearest; //increment until dates are the same

      var climb = function climb(a, b, unit) {
        var i = 0;
        a = a.clone();

        while (a.isBefore(b)) {
          //do proper, expensive increment to catch all-the-tricks
          a = a.add(1, unit);
          i += 1;
        } //oops, we went too-far..


        if (a.isAfter(b, unit)) {
          i -= 1;
        }

        return i;
      }; // do a thurough +=1 on the unit, until they match
      // for speed-reasons, only used on day, month, week.


      var diffOne = function diffOne(a, b, unit) {
        if (a.isBefore(b)) {
          return climb(a, b, unit);
        } else {
          return climb(b, a, unit) * -1; //reverse it
        }
      };

      var one = diffOne; // 2020 - 2019 may be 1 year, or 0 years
      // - '1 year difference' means 366 days during a leap year

      var fastYear = function fastYear(a, b) {
        var years = b.year() - a.year(); // should we decrement it by 1?

        a = a.year(b.year());

        if (a.isAfter(b)) {
          years -= 1;
        }

        return years;
      }; // use a waterfall-method for computing a diff of any 'pre-knowable' units
      // compute years, then compute months, etc..
      // ... then ms-math for any very-small units


      var diff = function diff(a, b) {
        // an hour is always the same # of milliseconds
        // so these units can be 'pre-calculated'
        var msDiff = b.epoch - a.epoch;
        var obj = {
          milliseconds: msDiff,
          seconds: parseInt(msDiff / 1000, 10)
        };
        obj.minutes = parseInt(obj.seconds / 60, 10);
        obj.hours = parseInt(obj.minutes / 60, 10); //do the year

        var tmp = a.clone();
        obj.years = fastYear(tmp, b);
        tmp = a.add(obj.years, 'year'); //there's always 12 months in a year...

        obj.months = obj.years * 12;
        tmp = a.add(obj.months, 'month');
        obj.months += one(tmp, b, 'month'); // there's always atleast 52 weeks in a year..
        // (month * 4) isn't as close

        obj.weeks = obj.years * 52;
        tmp = a.add(obj.weeks, 'week');
        obj.weeks += one(tmp, b, 'week'); // there's always atleast 7 days in a week

        obj.days = obj.weeks * 7;
        tmp = a.add(obj.days, 'day');
        obj.days += one(tmp, b, 'day');
        return obj;
      };

      var waterfall = diff;

      var reverseDiff = function reverseDiff(obj) {
        Object.keys(obj).forEach(function (k) {
          obj[k] *= -1;
        });
        return obj;
      }; // this method counts a total # of each unit, between a, b.
      // '1 month' means 28 days in february
      // '1 year' means 366 days in a leap year


      var main = function main(a, b, unit) {
        b = fns.beADate(b, a); //reverse values, if necessary

        var reversed = false;

        if (a.isAfter(b)) {
          var tmp = a;
          a = b;
          b = tmp;
          reversed = true;
        } //compute them all (i know!)


        var obj = waterfall(a, b);

        if (reversed) {
          obj = reverseDiff(obj);
        } //return just the requested unit


        if (unit) {
          //make sure it's plural-form
          unit = fns.normalize(unit);

          if (/s$/.test(unit) !== true) {
            unit += 's';
          }

          if (unit === 'dates') {
            unit = 'days';
          }

          return obj[unit];
        }

        return obj;
      };

      var diff$1 = main; //our conceptual 'break-points' for each unit

      var qualifiers = {
        months: {
          almost: 10,
          over: 4
        },
        days: {
          almost: 25,
          over: 10
        },
        hours: {
          almost: 20,
          over: 8
        },
        minutes: {
          almost: 50,
          over: 20
        },
        seconds: {
          almost: 50,
          over: 20
        }
      }; //get number of hours/minutes... between the two dates

      function getDiff(a, b) {
        var isBefore = a.isBefore(b);
        var later = isBefore ? b : a;
        var earlier = isBefore ? a : b;
        earlier = earlier.clone();
        var diff = {
          years: 0,
          months: 0,
          days: 0,
          hours: 0,
          minutes: 0,
          seconds: 0
        };
        Object.keys(diff).forEach(function (unit) {
          if (earlier.isSame(later, unit)) {
            return;
          }

          var max = earlier.diff(later, unit);
          earlier = earlier.add(max, unit);
          diff[unit] = max;
        }); //reverse it, if necessary

        if (isBefore) {
          Object.keys(diff).forEach(function (u) {
            if (diff[u] !== 0) {
              diff[u] *= -1;
            }
          });
        }

        return diff;
      } // Expects a plural unit arg


      function pluralize(value, unit) {
        if (value === 1) {
          unit = unit.slice(0, -1);
        }

        return value + ' ' + unit;
      } //create the human-readable diff between the two dates


      var since = function since(start, end) {
        end = fns.beADate(end, start);
        var diff = getDiff(start, end);
        var isNow = Object.keys(diff).every(function (u) {
          return !diff[u];
        });

        if (isNow === true) {
          return {
            diff: diff,
            rounded: 'now',
            qualified: 'now',
            precise: 'now'
          };
        }

        var rounded;
        var qualified;
        var precise;
        var englishValues = []; //go through each value and create its text-representation

        Object.keys(diff).forEach(function (unit, i, units) {
          var value = Math.abs(diff[unit]);

          if (value === 0) {
            return;
          }

          var englishValue = pluralize(value, unit);
          englishValues.push(englishValue);

          if (!rounded) {
            rounded = qualified = englishValue;

            if (i > 4) {
              return;
            } //is it a 'almost' something, etc?


            var nextUnit = units[i + 1];
            var nextValue = Math.abs(diff[nextUnit]);

            if (nextValue > qualifiers[nextUnit].almost) {
              rounded = pluralize(value + 1, unit);
              qualified = 'almost ' + rounded;
            } else if (nextValue > qualifiers[nextUnit].over) qualified = 'over ' + englishValue;
          }
        }); //make them into a string

        precise = englishValues.splice(0, 2).join(', '); //handle before/after logic

        if (start.isAfter(end) === true) {
          rounded += ' ago';
          qualified += ' ago';
          precise += ' ago';
        } else {
          rounded = 'in ' + rounded;
          qualified = 'in ' + qualified;
          precise = 'in ' + precise;
        }

        return {
          diff: diff,
          rounded: rounded,
          qualified: qualified,
          precise: precise
        };
      };

      var since_1 = since; //https://www.timeanddate.com/calendar/aboutseasons.html
      // Spring - from March 1 to May 31;
      // Summer - from June 1 to August 31;
      // Fall (autumn) - from September 1 to November 30; and,
      // Winter - from December 1 to February 28 (February 29 in a leap year).

      var seasons = {
        north: [['spring', 2, 1], //spring march 1
        ['summer', 5, 1], //june 1
        ['fall', 8, 1], //sept 1
        ['autumn', 8, 1], //sept 1
        ['winter', 11, 1] //dec 1
        ],
        south: [['fall', 2, 1], //march 1
        ['autumn', 2, 1], //march 1
        ['winter', 5, 1], //june 1
        ['spring', 8, 1], //sept 1
        ['summer', 11, 1] //dec 1
        ]
      };
      var quarters = [null, [0, 1], //jan 1
      [3, 1], //apr 1
      [6, 1], //july 1
      [9, 1] //oct 1
      ];
      var units$2 = {
        minute: function minute(s) {
          walk_1(s, {
            second: 0,
            millisecond: 0
          });
          return s;
        },
        quarterhour: function quarterhour(s) {
          var minute = s.minutes();

          if (minute >= 45) {
            s = s.minutes(45);
          } else if (minute >= 30) {
            s = s.minutes(30);
          } else if (minute >= 15) {
            s = s.minutes(15);
          } else {
            s = s.minutes(0);
          }

          walk_1(s, {
            second: 0,
            millisecond: 0
          });
          return s;
        },
        hour: function hour(s) {
          walk_1(s, {
            minute: 0,
            second: 0,
            millisecond: 0
          });
          return s;
        },
        day: function day(s) {
          walk_1(s, {
            hour: 0,
            minute: 0,
            second: 0,
            millisecond: 0
          });
          return s;
        },
        week: function week(s) {
          var original = s.clone();
          s = s.day(s._weekStart); //monday

          if (s.isAfter(original)) {
            s = s.subtract(1, 'week');
          }

          walk_1(s, {
            hour: 0,
            minute: 0,
            second: 0,
            millisecond: 0
          });
          return s;
        },
        month: function month(s) {
          walk_1(s, {
            date: 1,
            hour: 0,
            minute: 0,
            second: 0,
            millisecond: 0
          });
          return s;
        },
        quarter: function quarter(s) {
          var q = s.quarter();

          if (quarters[q]) {
            walk_1(s, {
              month: quarters[q][0],
              date: quarters[q][1],
              hour: 0,
              minute: 0,
              second: 0,
              millisecond: 0
            });
          }

          return s;
        },
        season: function season(s) {
          var current = s.season();
          var hem = 'north';

          if (s.hemisphere() === 'South') {
            hem = 'south';
          }

          for (var i = 0; i < seasons[hem].length; i++) {
            if (seasons[hem][i][0] === current) {
              //winter goes between years
              var year = s.year();

              if (current === 'winter' && s.month() < 3) {
                year -= 1;
              }

              walk_1(s, {
                year: year,
                month: seasons[hem][i][1],
                date: seasons[hem][i][2],
                hour: 0,
                minute: 0,
                second: 0,
                millisecond: 0
              });
              return s;
            }
          }

          return s;
        },
        year: function year(s) {
          walk_1(s, {
            month: 0,
            date: 1,
            hour: 0,
            minute: 0,
            second: 0,
            millisecond: 0
          });
          return s;
        },
        decade: function decade(s) {
          s = s.startOf('year');
          var year = s.year();
          var decade = parseInt(year / 10, 10) * 10;
          s = s.year(decade);
          return s;
        },
        century: function century(s) {
          s = s.startOf('year');
          var year = s.year(); // near 0AD goes '-1 | +1'

          var decade = parseInt(year / 100, 10) * 100;
          s = s.year(decade);
          return s;
        }
      };
      units$2.date = units$2.day;

      var startOf = function startOf(a, unit) {
        var s = a.clone();
        unit = fns.normalize(unit);

        if (units$2[unit]) {
          return units$2[unit](s);
        }

        if (unit === 'summer' || unit === 'winter') {
          s = s.season(unit);
          return units$2.season(s);
        }

        return s;
      }; //piggy-backs off startOf


      var endOf = function endOf(a, unit) {
        var s = a.clone();
        unit = fns.normalize(unit);

        if (units$2[unit]) {
          s = units$2[unit](s); // startof

          s = s.add(1, unit);
          s = s.subtract(1, 'milliseconds');
          return s;
        }

        return s;
      };

      var startOf_1 = {
        startOf: startOf,
        endOf: endOf
      };

      var isDay = function isDay(unit) {
        if (days["short"]().find(function (s) {
          return s === unit;
        })) {
          return true;
        }

        if (days["long"]().find(function (s) {
          return s === unit;
        })) {
          return true;
        }

        return false;
      }; // return a list of the weeks/months/days between a -> b
      // returns spacetime objects in the timezone of the input


      var every = function every(start) {
        var unit = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
        var end = arguments.length > 2 ? arguments[2] : undefined;

        if (!unit || !end) {
          return [];
        } //cleanup unit param


        unit = fns.normalize(unit); //cleanup to param

        end = start.clone().set(end); //swap them, if they're backwards

        if (start.isAfter(end)) {
          var tmp = start;
          start = end;
          end = tmp;
        } //support 'every wednesday'


        var d = start.clone();

        if (isDay(unit)) {
          d = d.next(unit);
          unit = 'week';
        } else {
          d = d.next(unit);
        } //okay, actually start doing it


        var result = [];

        while (d.isBefore(end)) {
          result.push(d);
          d = d.add(1, unit);
        }

        return result;
      };

      var every_1 = every;

      var parseDst = function parseDst(dst) {
        if (!dst) {
          return [];
        }

        return dst.split('->');
      };

      var titleCase = function titleCase(str) {
        str = str[0].toUpperCase() + str.substr(1);
        str = str.replace(/\/gmt/, '/GMT');
        str = str.replace(/[\/_]([a-z])/gi, function (s) {
          return s.toUpperCase();
        });
        return str;
      }; //get metadata about this timezone


      var timezone = function timezone(s) {
        var zones = s.timezones;
        var tz = s.tz;

        if (zones.hasOwnProperty(tz) === false) {
          tz = find(s.tz, zones);
        }

        if (tz === null) {
          if (s.silent === false) {
            console.warn("Warn: could not find given or local timezone - '" + s.tz + "'");
          }

          return {
            current: {
              epochShift: 0
            }
          };
        }

        var found = zones[tz];
        var result = {
          name: titleCase(tz),
          hasDst: Boolean(found.dst),
          default_offset: found.offset,
          //do north-hemisphere version as default (sorry!)
          hemisphere: found.hem === 's' ? 'South' : 'North',
          current: {}
        };

        if (result.hasDst) {
          var arr = parseDst(found.dst);
          result.change = {
            start: arr[0],
            back: arr[1]
          };
        } //find the offsets for summer/winter times
        //(these variable names are north-centric)


        var summer = found.offset; // (july)

        var winter = summer; // (january) assume it's the same for now

        if (result.hasDst === true) {
          if (result.hemisphere === 'North') {
            winter = summer - 1;
          } else {
            //southern hemisphere
            winter = found.offset + 1;
          }
        } //find out which offset to use right now
        //use 'summer' time july-time


        if (result.hasDst === false) {
          result.current.offset = summer;
          result.current.isDST = false;
        } else if (summerTime(s.epoch, result.change.start, result.change.back, summer, winter) === true) {
          result.current.offset = summer;
          result.current.isDST = result.hemisphere === 'North'; //dst 'on' in winter in north
        } else {
          //use 'winter' january-time
          result.current.offset = winter;
          result.current.isDST = result.hemisphere === 'South'; //dst 'on' in summer in south
        }

        return result;
      };

      var timezone_1 = timezone;
      var units$3 = ['century', 'decade', 'year', 'month', 'date', 'day', 'hour', 'minute', 'second', 'millisecond']; //the spacetime instance methods (also, the API)

      var methods = {
        set: function set(input$1, tz) {
          var s = this.clone();
          s = input(s, input$1, null);

          if (tz) {
            this.tz = find(tz);
          }

          return s;
        },
        timezone: function timezone() {
          return timezone_1(this);
        },
        isDST: function isDST() {
          return timezone_1(this).current.isDST;
        },
        hasDST: function hasDST() {
          return timezone_1(this).hasDst;
        },
        offset: function offset() {
          return timezone_1(this).current.offset * 60;
        },
        hemisphere: function hemisphere() {
          return timezone_1(this).hemisphere;
        },
        format: function format(fmt) {
          return format_1(this, fmt);
        },
        unixFmt: function unixFmt(fmt) {
          return unixFmt_1(this, fmt);
        },
        startOf: function startOf(unit) {
          return startOf_1.startOf(this, unit);
        },
        endOf: function endOf(unit) {
          return startOf_1.endOf(this, unit);
        },
        leapYear: function leapYear() {
          var year = this.year();
          return fns.isLeapYear(year);
        },
        progress: function progress(unit) {
          return progress_1(this, unit);
        },
        nearest: function nearest(unit) {
          return nearest_1(this, unit);
        },
        diff: function diff(d, unit) {
          return diff$1(this, d, unit);
        },
        since: function since(d) {
          if (!d) {
            d = this.clone().set();
          }

          return since_1(this, d);
        },
        next: function next(unit) {
          var s = this.add(1, unit);
          return s.startOf(unit);
        },
        //the start of the previous year/week/century
        last: function last(unit) {
          var s = this.subtract(1, unit);
          return s.startOf(unit);
        },
        isValid: function isValid() {
          //null/undefined epochs
          if (!this.epoch && this.epoch !== 0) {
            return false;
          }

          return !isNaN(this.d.getTime());
        },
        //travel to this timezone
        "goto": function _goto(tz) {
          var s = this.clone();
          s.tz = find(tz, s.timezones); //science!

          return s;
        },
        //get each week/month/day between a -> b
        every: function every(unit, to) {
          return every_1(this, unit, to);
        },
        isAwake: function isAwake() {
          var hour = this.hour(); //10pm -> 8am

          if (hour < 8 || hour > 22) {
            return false;
          }

          return true;
        },
        isAsleep: function isAsleep() {
          return !this.isAwake();
        },
        //pretty-printing
        log: function log() {
          console.log('');
          console.log(format_1(this, 'nice-short'));
          return this;
        },
        logYear: function logYear() {
          console.log('');
          console.log(format_1(this, 'full-short'));
          return this;
        },
        json: function json() {
          var _this = this;

          return units$3.reduce(function (h, unit) {
            h[unit] = _this[unit]();
            return h;
          }, {});
        },
        debug: function debug() {
          var tz = this.timezone();
          var date = this.format('MM') + ' ' + this.format('date-ordinal') + ' ' + this.year();
          date += '\n     - ' + this.format('time');
          console.log('\n\n', date + '\n     - ' + tz.name + ' (' + tz.current.offset + ')');
          return this;
        },
        //alias of 'since' but opposite - like moment.js
        from: function from(d) {
          d = this.clone().set(d);
          return d.since(this);
        },
        fromNow: function fromNow() {
          var d = this.clone().set(Date.now());
          return d.since(this);
        },
        weekStart: function weekStart(input) {
          //accept a number directly
          if (typeof input === 'number') {
            this._weekStart = input;
            return this;
          }

          if (typeof input === 'string') {
            // accept 'wednesday'
            input = input.toLowerCase().trim();
            var num = days["short"]().indexOf(input);

            if (num === -1) {
              num = days["long"]().indexOf(input);
            }

            if (num === -1) {
              num = 1; //go back to default
            }

            this._weekStart = num;
          } else {
            console.warn('Spacetime Error: Cannot understand .weekStart() input:', input);
          }

          return this;
        }
      }; // aliases

      methods.inDST = methods.isDST;
      methods.round = methods.nearest;
      methods.each = methods.every;
      var methods_1 = methods; //these methods wrap around them.

      var isLeapYear$1 = fns.isLeapYear;

      var validate = function validate(n) {
        //handle number as a string
        if (typeof n === 'string') {
          n = parseInt(n, 10);
        }

        return n;
      };

      var order = ['year', 'month', 'date', 'hour', 'minute', 'second', 'millisecond']; //reduce hostile micro-changes when moving dates by millisecond

      var confirm = function confirm(s, tmp, unit) {
        var n = order.indexOf(unit);
        var arr = order.slice(n, order.length);

        for (var i = 0; i < arr.length; i++) {
          var want = tmp[arr[i]]();
          s[arr[i]](want);
        }

        return s;
      };

      var set = {
        milliseconds: function milliseconds(s, n) {
          n = validate(n);
          var current = s.millisecond();
          var diff = current - n; //milliseconds to shift by

          return s.epoch - diff;
        },
        seconds: function seconds(s, n) {
          n = validate(n);
          var diff = s.second() - n;
          var shift = diff * milliseconds.second;
          return s.epoch - shift;
        },
        minutes: function minutes(s, n) {
          n = validate(n);
          var old = s.clone();
          var diff = s.minute() - n;
          var shift = diff * milliseconds.minute;
          s.epoch -= shift; // check against a screw-up
          // if (old.hour() != s.hour()) {
          //   walkTo(old, {
          //     minute: n
          //   })
          //   return old.epoch
          // }

          confirm(s, old, 'second');
          return s.epoch;
        },
        hours: function hours(s, n) {
          n = validate(n);

          if (n >= 24) {
            n = 24;
          } else if (n < 0) {
            n = 0;
          }

          var old = s.clone();
          var diff = s.hour() - n;
          var shift = diff * milliseconds.hour;
          s.epoch -= shift; // oops, did we change the day?

          if (s.date() !== old.date()) {
            s = old.clone();

            if (diff > 1) {
              diff -= 1;
            }

            if (diff < 1) {
              diff += 1;
            }

            shift = diff * milliseconds.hour;
            s.epoch -= shift;
          }

          walk_1(s, {
            hour: n
          });
          confirm(s, old, 'minute');
          return s.epoch;
        },
        //support setting time by '4:25pm' - this isn't very-well developed..
        time: function time(s, str) {
          var m = str.match(/([0-9]{1,2})[:h]([0-9]{1,2})(:[0-9]{1,2})? ?(am|pm)?/);

          if (!m) {
            //fallback to support just '2am'
            m = str.match(/([0-9]{1,2}) ?(am|pm)/);

            if (!m) {
              return s.epoch;
            }

            m.splice(2, 0, '0'); //add implicit 0 minutes

            m.splice(3, 0, ''); //add implicit seconds
          }

          var h24 = false;
          var hour = parseInt(m[1], 10);
          var minute = parseInt(m[2], 10);

          if (hour > 12) {
            h24 = true;
          } //make the hour into proper 24h time


          if (h24 === false) {
            if (m[4] === 'am' && hour === 12) {
              //12am is midnight
              hour = 0;
            }

            if (m[4] === 'pm' && hour < 12) {
              //12pm is noon
              hour += 12;
            }
          } // handle seconds


          m[3] = m[3] || '';
          m[3] = m[3].replace(/:/, '');
          var sec = parseInt(m[3], 10) || 0;
          s = s.hour(hour);
          s = s.minute(minute);
          s = s.second(sec);
          s = s.millisecond(0);
          return s.epoch;
        },
        date: function date(s, n) {
          n = validate(n); //avoid setting february 31st

          if (n > 28) {
            var month = s.month();
            var max = monthLengths_1[month]; // support leap day in february

            if (month === 1 && n === 29 && isLeapYear$1(s.year())) {
              max = 29;
            }

            if (n > max) {
              n = max;
            }
          } //avoid setting < 0


          if (n <= 0) {
            n = 1;
          }

          walk_1(s, {
            date: n
          });
          return s.epoch;
        },
        //this one's tricky
        month: function month(s, n) {
          if (typeof n === 'string') {
            n = months.mapping()[n.toLowerCase()];
          }

          n = validate(n); //don't go past december

          if (n >= 12) {
            n = 11;
          }

          if (n <= 0) {
            n = 0;
          }

          var date = s.date(); //there's no 30th of february, etc.

          if (date > monthLengths_1[n]) {
            //make it as close as we can..
            date = monthLengths_1[n];
          }

          walk_1(s, {
            month: n,
            date: date
          });
          return s.epoch;
        },
        year: function year(s, n) {
          // support '97
          if (typeof n === 'string' && /^'[0-9]{2}$/.test(n)) {
            n = n.replace(/'/, '').trim();
            n = Number(n); // '89 is 1989

            if (n > 30) {
              //change this in 10y
              n = 1900 + n;
            } else {
              // '12 is 2012
              n = 2000 + n;
            }
          }

          n = validate(n);
          walk_1(s, {
            year: n
          });
          return s.epoch;
        },
        dayOfYear: function dayOfYear(s, n) {
          n = validate(n);
          var old = s.clone();
          n -= 1; //days are 1-based

          if (n <= 0) {
            n = 0;
          } else if (n >= 365) {
            n = 364;
          }

          s = s.startOf('year');
          s = s.add(n, 'day');
          confirm(s, old, 'hour');
          return s.epoch;
        }
      };
      var methods$1 = {
        millisecond: function millisecond(num) {
          if (num !== undefined) {
            var s = this.clone();
            s.epoch = set.milliseconds(s, num);
            return s;
          }

          return this.d.getMilliseconds();
        },
        second: function second(num) {
          if (num !== undefined) {
            var s = this.clone();
            s.epoch = set.seconds(s, num);
            return s;
          }

          return this.d.getSeconds();
        },
        minute: function minute(num) {
          if (num !== undefined) {
            var s = this.clone();
            s.epoch = set.minutes(s, num);
            return s;
          }

          return this.d.getMinutes();
        },
        hour: function hour(num) {
          var d = this.d;

          if (num !== undefined) {
            var s = this.clone();
            s.epoch = set.hours(s, num);
            return s;
          }

          return d.getHours();
        },
        //'3:30' is 3.5
        hourFloat: function hourFloat(num) {
          if (num !== undefined) {
            var s = this.clone();

            var _minute = num % 1;

            _minute = _minute * 60;

            var _hour = parseInt(num, 10);

            s.epoch = set.hours(s, _hour);
            s.epoch = set.minutes(s, _minute);
            return s;
          }

          var d = this.d;
          var hour = d.getHours();
          var minute = d.getMinutes();
          minute = minute / 60;
          return hour + minute;
        },
        // hour in 12h format
        hour12: function hour12(str) {
          var d = this.d;

          if (str !== undefined) {
            var s = this.clone();
            str = '' + str;
            var m = str.match(/^([0-9]+)(am|pm)$/);

            if (m) {
              var hour = parseInt(m[1], 10);

              if (m[2] === 'pm') {
                hour += 12;
              }

              s.epoch = set.hours(s, hour);
            }

            return s;
          } //get the hour


          var hour12 = d.getHours();

          if (hour12 > 12) {
            hour12 = hour12 - 12;
          }

          if (hour12 === 0) {
            hour12 = 12;
          }

          return hour12;
        },
        //some ambiguity here with 12/24h
        time: function time(str) {
          if (str !== undefined) {
            var s = this.clone();
            str = str.toLowerCase().trim();
            s.epoch = set.time(s, str);
            return s;
          }

          return "".concat(this.h12(), ":").concat(fns.zeroPad(this.minute())).concat(this.ampm());
        },
        // either 'am' or 'pm'
        ampm: function ampm(input) {
          var which = 'am';
          var hour = this.hour();

          if (hour >= 12) {
            which = 'pm';
          }

          if (typeof input !== 'string') {
            return which;
          } //okay, we're doing a setter


          var s = this.clone();
          input = input.toLowerCase().trim(); //ampm should never change the day
          // - so use `.hour(n)` instead of `.minus(12,'hour')`

          if (hour >= 12 && input === 'am') {
            //noon is 12pm
            hour -= 12;
            return s.hour(hour);
          }

          if (hour < 12 && input === 'pm') {
            hour += 12;
            return s.hour(hour);
          }

          return s;
        },
        //some hard-coded times of day, like 'noon'
        dayTime: function dayTime(str) {
          if (str !== undefined) {
            var times = {
              morning: '7:00am',
              breakfast: '7:00am',
              noon: '12:00am',
              lunch: '12:00pm',
              afternoon: '2:00pm',
              evening: '6:00pm',
              dinner: '6:00pm',
              night: '11:00pm',
              midnight: '23:59pm'
            };
            var s = this.clone();
            str = str || '';
            str = str.toLowerCase();

            if (times.hasOwnProperty(str) === true) {
              s = s.time(times[str]);
            }

            return s;
          }

          var h = this.hour();

          if (h < 6) {
            return 'night';
          }

          if (h < 12) {
            //until noon
            return 'morning';
          }

          if (h < 17) {
            //until 5pm
            return 'afternoon';
          }

          if (h < 22) {
            //until 10pm
            return 'evening';
          }

          return 'night';
        },
        //parse a proper iso string
        iso: function iso(num) {
          if (num !== undefined) {
            return this.set(num);
          }

          return this.format('iso');
        }
      };
      var _01Time = methods$1;
      var methods$2 = {
        // # day in the month
        date: function date(num) {
          if (num !== undefined) {
            var s = this.clone();
            s.epoch = set.date(s, num);
            return s;
          }

          return this.d.getDate();
        },
        //like 'wednesday' (hard!)
        day: function day(input) {
          if (input === undefined) {
            return this.d.getDay();
          }

          var original = this.clone();
          var want = input; // accept 'wednesday'

          if (typeof input === 'string') {
            input = input.toLowerCase();

            if (days.aliases.hasOwnProperty(input)) {
              want = days.aliases[input];
            } else {
              want = days["short"]().indexOf(input);

              if (want === -1) {
                want = days["long"]().indexOf(input);
              }
            }
          } //move approx


          var day = this.d.getDay();
          var diff = day - want;
          var s = this.subtract(diff, 'days'); //tighten it back up

          walk_1(s, {
            hour: original.hour(),
            minute: original.minute(),
            second: original.second()
          });
          return s;
        },
        //these are helpful name-wrappers
        dayName: function dayName(input) {
          if (input === undefined) {
            return days["long"]()[this.day()];
          }

          var s = this.clone();
          s = s.day(input);
          return s;
        },
        //either name or number
        month: function month(input) {
          if (input !== undefined) {
            var s = this.clone();
            s.epoch = set.month(s, input);
            return s;
          }

          return this.d.getMonth();
        }
      };
      var _02Date = methods$2;

      var clearMinutes = function clearMinutes(s) {
        s = s.minute(0);
        s = s.second(0);
        s = s.millisecond(1);
        return s;
      };

      var methods$3 = {
        // day 0-366
        dayOfYear: function dayOfYear(num) {
          if (num !== undefined) {
            var s = this.clone();
            s.epoch = set.dayOfYear(s, num);
            return s;
          } //days since newyears - jan 1st is 1, jan 2nd is 2...


          var sum = 0;
          var month = this.d.getMonth();
          var tmp; //count the num days in each month

          for (var i = 1; i <= month; i++) {
            tmp = new Date();
            tmp.setDate(1);
            tmp.setFullYear(this.d.getFullYear()); //the year matters, because leap-years

            tmp.setHours(1);
            tmp.setMinutes(1);
            tmp.setMonth(i);
            tmp.setHours(-2); //the last day of the month

            sum += tmp.getDate();
          }

          return sum + this.d.getDate();
        },
        //since the start of the year
        week: function week(num) {
          // week-setter
          if (num !== undefined) {
            var s = this.clone();
            s = s.month(0);
            s = s.date(1);
            s = s.day('monday');
            s = clearMinutes(s); //first week starts first Thurs in Jan
            // so mon dec 28th is 1st week
            // so mon dec 29th is not the week

            if (s.monthName() === 'december' && s.date() >= 28) {
              s = s.add(1, 'week');
            }

            num -= 1; //1-based

            s = s.add(num, 'weeks');
            return s;
          } //find-out which week it is


          var tmp = this.clone();
          tmp = tmp.month(0);
          tmp = tmp.date(1);
          tmp = clearMinutes(tmp);
          tmp = tmp.day('monday'); //don't go into last-year

          if (tmp.monthName() === 'december' && tmp.date() >= 28) {
            tmp = tmp.add(1, 'week');
          } // is first monday the 1st?


          var toAdd = 1;

          if (tmp.date() === 1) {
            toAdd = 0;
          }

          tmp = tmp.minus(1, 'second');
          var thisOne = this.epoch; //if the week technically hasn't started yet

          if (tmp.epoch > thisOne) {
            return 1;
          } //speed it up, if we can


          var i = 0;
          var skipWeeks = this.month() * 4;
          tmp.epoch += milliseconds.week * skipWeeks;
          i += skipWeeks;

          for (; i < 52; i++) {
            if (tmp.epoch > thisOne) {
              return i + toAdd;
            }

            tmp = tmp.add(1, 'week');
          }

          return 52;
        },
        //'january'
        monthName: function monthName(input) {
          if (input === undefined) {
            return months["long"]()[this.month()];
          }

          var s = this.clone();
          s = s.month(input);
          return s;
        },
        //q1, q2, q3, q4
        quarter: function quarter(num) {
          if (num !== undefined) {
            if (typeof num === 'string') {
              num = num.replace(/^q/i, '');
              num = parseInt(num, 10);
            }

            if (quarters[num]) {
              var s = this.clone();
              var _month = quarters[num][0];
              s = s.month(_month);
              s = s.date(1);
              s = s.startOf('day');
              return s;
            }
          }

          var month = this.d.getMonth();

          for (var i = 1; i < quarters.length; i++) {
            if (month < quarters[i][0]) {
              return i - 1;
            }
          }

          return 4;
        },
        //spring, summer, winter, fall
        season: function season(input) {
          var hem = 'north';

          if (this.hemisphere() === 'South') {
            hem = 'south';
          }

          if (input !== undefined) {
            var s = this.clone();

            for (var i = 0; i < seasons[hem].length; i++) {
              if (input === seasons[hem][i][0]) {
                s = s.month(seasons[hem][i][1]);
                s = s.date(1);
                s = s.startOf('day');
              }
            }

            return s;
          }

          var month = this.d.getMonth();

          for (var _i = 0; _i < seasons[hem].length - 1; _i++) {
            if (month >= seasons[hem][_i][1] && month < seasons[hem][_i + 1][1]) {
              return seasons[hem][_i][0];
            }
          }

          return 'winter';
        },
        //the year number
        year: function year(num) {
          if (num !== undefined) {
            var s = this.clone();
            s.epoch = set.year(s, num);
            return s;
          }

          return this.d.getFullYear();
        },
        //bc/ad years
        era: function era(str) {
          if (str !== undefined) {
            var s = this.clone();
            str = str.toLowerCase(); //TODO: there is no year-0AD i think. may have off-by-1 error here

            var year = s.d.getFullYear(); //make '1992' into 1992bc..

            if (str === 'bc' && year > 0) {
              s.epoch = set.year(s, year * -1);
            } //make '1992bc' into '1992'


            if (str === 'ad' && year < 0) {
              s.epoch = set.year(s, year * -1);
            }

            return s;
          }

          if (this.d.getFullYear() < 0) {
            return 'BC';
          }

          return 'AD';
        },
        // 2019 -> 2010
        decade: function decade(input) {
          if (input !== undefined) {
            input = String(input);
            input = input.replace(/([0-9])'?s$/, '$1'); //1950's

            input = input.replace(/([0-9])(th|rd|st|nd)/, '$1'); //fix ordinals

            if (!input) {
              console.warn('Spacetime: Invalid decade input');
              return this;
            } // assume 20th century?? for '70s'.


            if (input.length === 2 && /[0-9][0-9]/.test(input)) {
              input = '19' + input;
            }

            var year = Number(input);

            if (isNaN(year)) {
              return this;
            } // round it down to the decade


            year = Math.floor(year / 10) * 10;
            return this.year(year); //.startOf('decade')
          }

          return this.startOf('decade').year();
        },
        // 1950 -> 19+1
        century: function century(input) {
          if (input !== undefined) {
            if (typeof input === 'string') {
              input = input.replace(/([0-9])(th|rd|st|nd)/, '$1'); //fix ordinals

              input = input.replace(/([0-9]+) ?(b\.?c\.?|a\.?d\.?)/i, function (a, b, c) {
                if (c.match(/b\.?c\.?/i)) {
                  b = '-' + b;
                }

                return b;
              });
              input = input.replace(/c$/, ''); //20thC
            }

            var year = Number(input);

            if (isNaN(input)) {
              console.warn('Spacetime: Invalid century input');
              return this;
            } // there is no century 0


            if (year === 0) {
              year = 1;
            }

            if (year >= 0) {
              year = (year - 1) * 100;
            } else {
              year = (year + 1) * 100;
            }

            return this.year(year);
          } // century getter


          var num = this.startOf('century').year();
          num = Math.floor(num / 100);

          if (num < 0) {
            return num - 1;
          }

          return num + 1;
        },
        // 2019 -> 2+1
        millenium: function millenium(input) {
          if (input !== undefined) {
            if (typeof input === 'string') {
              input = input.replace(/([0-9])(th|rd|st|nd)/, '$1'); //fix ordinals

              input = Number(input);

              if (isNaN(input)) {
                console.warn('Spacetime: Invalid millenium input');
                return this;
              }
            }

            if (input > 0) {
              input -= 1;
            }

            var year = input * 1000; // there is no year 0

            if (year === 0) {
              year = 1;
            }

            return this.year(year);
          } // get the current millenium


          var num = Math.floor(this.year() / 1000);

          if (num >= 0) {
            num += 1;
          }

          return num;
        }
      };
      var _03Year = methods$3;
      var methods$4 = Object.assign({}, _01Time, _02Date, _03Year); //aliases

      methods$4.milliseconds = methods$4.millisecond;
      methods$4.seconds = methods$4.second;
      methods$4.minutes = methods$4.minute;
      methods$4.hours = methods$4.hour;
      methods$4.hour24 = methods$4.hour;
      methods$4.h12 = methods$4.hour12;
      methods$4.h24 = methods$4.hour24;
      methods$4.days = methods$4.day;

      var addMethods = function addMethods(Space) {
        //hook the methods into prototype
        Object.keys(methods$4).forEach(function (k) {
          Space.prototype[k] = methods$4[k];
        });
      };

      var query = addMethods;
      var isLeapYear$2 = fns.isLeapYear;

      var getMonthLength = function getMonthLength(month, year) {
        if (month === 1 && isLeapYear$2(year)) {
          return 29;
        }

        return monthLengths_1[month];
      }; //month is the one thing we 'model/compute'
      //- because ms-shifting can be off by enough


      var rollMonth = function rollMonth(want, old) {
        //increment year
        if (want.month > 0) {
          var years = parseInt(want.month / 12, 10);
          want.year = old.year() + years;
          want.month = want.month % 12;
        } else if (want.month < 0) {
          //decrement year
          var _years = Math.floor(Math.abs(want.month) / 13, 10);

          _years = Math.abs(_years) + 1;
          want.year = old.year() - _years; //ignore extras

          want.month = want.month % 12;
          want.month = want.month + 12;

          if (want.month === 12) {
            want.month = 0;
          }
        }

        return want;
      }; // briefly support day=-2 (this does not need to be perfect.)


      var rollDaysDown = function rollDaysDown(want, old, sum) {
        want.year = old.year();
        want.month = old.month();
        var date = old.date();
        want.date = date - Math.abs(sum);

        while (want.date < 1) {
          want.month -= 1;

          if (want.month < 0) {
            want.month = 11;
            want.year -= 1;
          }

          var max = getMonthLength(want.month, want.year);
          want.date += max;
        }

        return want;
      }; // briefly support day=33 (this does not need to be perfect.)


      var rollDaysUp = function rollDaysUp(want, old, sum) {
        var year = old.year();
        var month = old.month();
        var max = getMonthLength(month, year);

        while (sum > max) {
          sum -= max;
          month += 1;

          if (month >= 12) {
            month -= 12;
            year += 1;
          }

          max = getMonthLength(month, year);
        }

        want.month = month;
        want.date = sum;
        return want;
      };

      var _model = {
        months: rollMonth,
        days: rollDaysUp,
        daysBack: rollDaysDown
      }; // but briefly:
      // millisecond-math, and some post-processing covers most-things
      // we 'model' the calendar here only a little bit
      // and that usually works-out...

      var order$1 = ['millisecond', 'second', 'minute', 'hour', 'date', 'month'];
      var keep = {
        second: order$1.slice(0, 1),
        minute: order$1.slice(0, 2),
        quarterhour: order$1.slice(0, 2),
        hour: order$1.slice(0, 3),
        date: order$1.slice(0, 4),
        month: order$1.slice(0, 4),
        quarter: order$1.slice(0, 4),
        season: order$1.slice(0, 4),
        year: order$1,
        decade: order$1,
        century: order$1
      };
      keep.week = keep.hour;
      keep.season = keep.date;
      keep.quarter = keep.date; // Units need to be dst adjuested

      var dstAwareUnits = {
        year: true,
        quarter: true,
        season: true,
        month: true,
        week: true,
        day: true
      };
      var keepDate = {
        month: true,
        quarter: true,
        season: true,
        year: true
      };

      var addMethods$1 = function addMethods(SpaceTime) {
        SpaceTime.prototype.add = function (num, unit) {
          var s = this.clone();

          if (!unit || num === 0) {
            return s; //don't bother
          }

          var old = this.clone();
          unit = fns.normalize(unit); // support 'fortnight' alias

          if (unit === 'fortnight') {
            num *= 2;
            unit = 'week';
          } //move forward by the estimated milliseconds (rough)


          if (milliseconds[unit]) {
            s.epoch += milliseconds[unit] * num;
          } else if (unit === 'week') {
            s.epoch += milliseconds.day * (num * 7);
          } else if (unit === 'quarter' || unit === 'season') {
            s.epoch += milliseconds.month * (num * 3.1); //go a little too-far
          } else if (unit === 'quarterhour') {
            s.epoch += milliseconds.minute * 15 * num;
          } //now ensure our milliseconds/etc are in-line


          var want = {};

          if (keep[unit]) {
            keep[unit].forEach(function (u) {
              want[u] = old[u]();
            });
          }

          if (dstAwareUnits[unit]) {
            var diff = old.timezone().current.offset - s.timezone().current.offset;
            s.epoch += diff * 3600 * 1000;
          } //ensure month/year has ticked-over


          if (unit === 'month') {
            want.month = old.month() + num; //month is the one unit we 'model' directly

            want = _model.months(want, old);
          } //support coercing a week, too


          if (unit === 'week') {
            var sum = old.date() + num * 7;

            if (sum <= 28 && sum > 1) {
              want.date = sum;
            }
          } //support 25-hour day-changes on dst-changes
          else if (unit === 'date') {
              if (num < 0) {
                want = _model.daysBack(want, old, num);
              } else {
                //specify a naive date number, if it's easy to do...
                var _sum = old.date() + num; // ok, model this one too


                want = _model.days(want, old, _sum);
              } //manually punt it if we haven't moved at all..


              if (num !== 0 && old.isSame(s, 'day')) {
                want.date = old.date() + num;
              }
            } //ensure year has changed (leap-years)
            else if (unit === 'year') {
                var wantYear = old.year() + num;
                var haveYear = s.year();

                if (haveYear < wantYear) {
                  s.epoch += milliseconds.day;
                } else if (haveYear > wantYear) {
                  s.epoch += milliseconds.day;
                }
              } //these are easier
              else if (unit === 'decade') {
                  want.year = s.year() + 10;
                } else if (unit === 'century') {
                  want.year = s.year() + 100;
                } //keep current date, unless the month doesn't have it.


          if (keepDate[unit]) {
            var max = monthLengths_1[want.month];
            want.date = old.date();

            if (want.date > max) {
              want.date = max;
            }
          }

          walk_1(s, want);
          return s;
        }; //subtract is only add *-1


        SpaceTime.prototype.subtract = function (num, unit) {
          var s = this.clone();
          return s.add(num * -1, unit);
        }; //add aliases


        SpaceTime.prototype.minus = SpaceTime.prototype.subtract;
        SpaceTime.prototype.plus = SpaceTime.prototype.add;
      };

      var add = addMethods$1; //make a string, for easy comparison between dates

      var print = {
        millisecond: function millisecond(s) {
          return s.epoch;
        },
        second: function second(s) {
          return [s.year(), s.month(), s.date(), s.hour(), s.minute(), s.second()].join('-');
        },
        minute: function minute(s) {
          return [s.year(), s.month(), s.date(), s.hour(), s.minute()].join('-');
        },
        hour: function hour(s) {
          return [s.year(), s.month(), s.date(), s.hour()].join('-');
        },
        day: function day(s) {
          return [s.year(), s.month(), s.date()].join('-');
        },
        week: function week(s) {
          return [s.year(), s.week()].join('-');
        },
        month: function month(s) {
          return [s.year(), s.month()].join('-');
        },
        quarter: function quarter(s) {
          return [s.year(), s.quarter()].join('-');
        },
        year: function year(s) {
          return s.year();
        }
      };
      print.date = print.day;

      var addMethods$2 = function addMethods(SpaceTime) {
        SpaceTime.prototype.isSame = function (b, unit) {
          var tzAware = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
          var a = this;

          if (!unit) {
            return null;
          }

          if (typeof b === 'string' || typeof b === 'number') {
            b = new SpaceTime(b, this.timezone.name);
          } //support 'seconds' aswell as 'second'


          unit = unit.replace(/s$/, ''); // make them the same timezone for proper comparison

          if (tzAware === true && a.tz !== b.tz) {
            b = b.clone();
            b.tz = a.tz;
          }

          if (print[unit]) {
            return print[unit](a) === print[unit](b);
          }

          return null;
        };
      };

      var same = addMethods$2;

      var addMethods$3 = function addMethods(SpaceTime) {
        var methods = {
          isAfter: function isAfter(d) {
            d = fns.beADate(d, this);
            var epoch = fns.getEpoch(d);

            if (epoch === null) {
              return null;
            }

            return this.epoch > epoch;
          },
          isBefore: function isBefore(d) {
            d = fns.beADate(d, this);
            var epoch = fns.getEpoch(d);

            if (epoch === null) {
              return null;
            }

            return this.epoch < epoch;
          },
          isEqual: function isEqual(d) {
            d = fns.beADate(d, this);
            var epoch = fns.getEpoch(d);

            if (epoch === null) {
              return null;
            }

            return this.epoch === epoch;
          },
          isBetween: function isBetween(start, end) {
            var isInclusive = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
            start = fns.beADate(start, this);
            end = fns.beADate(end, this);
            var startEpoch = fns.getEpoch(start);

            if (startEpoch === null) {
              return null;
            }

            var endEpoch = fns.getEpoch(end);

            if (endEpoch === null) {
              return null;
            }

            if (isInclusive) {
              return this.isBetween(start, end) || this.isEqual(start) || this.isEqual(end);
            }

            return startEpoch < this.epoch && this.epoch < endEpoch;
          }
        }; //hook them into proto

        Object.keys(methods).forEach(function (k) {
          SpaceTime.prototype[k] = methods[k];
        });
      };

      var compare = addMethods$3;

      var addMethods$4 = function addMethods(SpaceTime) {
        var methods = {
          i18n: function i18n(data) {
            //change the day names
            if (fns.isObject(data.days)) {
              days.set(data.days);
            } //change the month names


            if (fns.isObject(data.months)) {
              months.set(data.months);
            } // change the the display style of the month / day names


            if (fns.isBoolean(data.useTitleCase)) {
              caseFormat.set(data.useTitleCase);
            }
          }
        }; //hook them into proto

        Object.keys(methods).forEach(function (k) {
          SpaceTime.prototype[k] = methods[k];
        });
      };

      var i18n = addMethods$4;
      var timezones = unpack; //fake timezone-support, for fakers (es5 class)

      var SpaceTime = function SpaceTime(input$1, tz) {
        var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; //the holy moment

        this.epoch = null; //the shift for the given timezone

        this.tz = find(tz, timezones); //whether to output warnings to console

        this.silent = options.silent || true; // favour british interpretation of 02/02/2018, etc

        this.british = options.dmy || options.british; //does the week start on sunday, or monday:

        this._weekStart = 1; //default to monday

        if (options.weekStart !== undefined) {
          this._weekStart = options.weekStart;
        } // the reference today date object, (for testing)


        this._today = {};

        if (options.today !== undefined) {
          this._today = options.today;
        } //add getter/setters


        Object.defineProperty(this, 'd', {
          //return a js date object
          get: function get() {
            var offset = quick(this); //every computer is somewhere- get this computer's built-in offset

            var bias = new Date(this.epoch).getTimezoneOffset() || 0; //movement

            var shift = bias + offset * 60; //in minutes

            shift = shift * 60 * 1000; //in ms
            //remove this computer's offset

            var epoch = this.epoch + shift;
            var d = new Date(epoch);
            return d;
          }
        }); //add this data on the object, to allow adding new timezones

        Object.defineProperty(this, 'timezones', {
          get: function get() {
            return timezones;
          },
          set: function set(obj) {
            timezones = obj;
            return obj;
          }
        }); //parse the various formats

        var tmp = input(this, input$1, tz);
        this.epoch = tmp.epoch;
      }; //(add instance methods to prototype)


      Object.keys(methods_1).forEach(function (k) {
        SpaceTime.prototype[k] = methods_1[k];
      }); // ¯\_(ツ)_/¯

      SpaceTime.prototype.clone = function () {
        return new SpaceTime(this.epoch, this.tz, {
          silent: this.silent,
          weekStart: this._weekStart,
          today: this._today
        });
      }; //return native date object at the same epoch


      SpaceTime.prototype.toLocalDate = function () {
        return new Date(this.epoch);
      }; //append more methods


      query(SpaceTime);
      add(SpaceTime);
      same(SpaceTime);
      compare(SpaceTime);
      i18n(SpaceTime);
      var spacetime = SpaceTime;

      var whereIts = function whereIts(a, b) {
        var start = new spacetime(null);
        var end = new spacetime(null);
        start = start.time(a); //if b is undefined, use as 'within one hour'

        if (b) {
          end = end.time(b);
        } else {
          end = start.add(59, 'minutes');
        }

        var startHour = start.hour();
        var endHour = end.hour();
        var tzs = Object.keys(start.timezones).filter(function (tz) {
          if (tz.indexOf('/') === -1) {
            return false;
          }

          var m = new spacetime(null, tz);
          var hour = m.hour(); //do 'calendar-compare' not real-time-compare

          if (hour >= startHour && hour <= endHour) {
            //test minutes too, if applicable
            if (hour === startHour && m.minute() < start.minute()) {
              return false;
            }

            if (hour === endHour && m.minute() > end.minute()) {
              return false;
            }

            return true;
          }

          return false;
        });
        return tzs;
      };

      var whereIts_1 = whereIts;
      var _version = '6.12.2';

      var main$1 = function main(input, tz, options) {
        return new spacetime(input, tz, options);
      }; // set all properties of a given 'today' object


      var setToday = function setToday(s) {
        var today = s._today || {};
        Object.keys(today).forEach(function (k) {
          s = s[k](today[k]);
        });
        return s;
      }; //some helper functions on the main method


      main$1.now = function (tz, options) {
        var s = new spacetime(new Date().getTime(), tz, options);
        s = setToday(s);
        return s;
      };

      main$1.today = function (tz, options) {
        var s = new spacetime(new Date().getTime(), tz, options);
        s = setToday(s);
        return s.startOf('day');
      };

      main$1.tomorrow = function (tz, options) {
        var s = new spacetime(new Date().getTime(), tz, options);
        s = setToday(s);
        return s.add(1, 'day').startOf('day');
      };

      main$1.yesterday = function (tz, options) {
        var s = new spacetime(new Date().getTime(), tz, options);
        s = setToday(s);
        return s.subtract(1, 'day').startOf('day');
      };

      main$1.extend = function (obj) {
        Object.keys(obj).forEach(function (k) {
          spacetime.prototype[k] = obj[k];
        });
        return this;
      }; //find tz by time


      main$1.whereIts = whereIts_1;
      main$1.version = _version; //aliases:

      main$1.plugin = main$1.extend;
      var src = main$1;
      return src;
    });
  });

  // these timezone abbreviations are wholly made-up by me, Spencer Kelly, with no expertise in geography
  // generated humbly from https://github.com/spencermountain/spacetime-informal

  var america = 'America/';
  var asia = 'Asia/';
  var europe = 'Europe/';
  var africa = 'Africa/';
  var aus = 'Australia/';
  var pac = 'Pacific/';
  var informal = {
    //europe
    'british summer time': europe + 'London',
    bst: europe + 'London',
    'british time': europe + 'London',
    'britain time': europe + 'London',
    'irish summer time': europe + 'Dublin',
    'irish time': europe + 'Dublin',
    ireland: europe + 'Dublin',
    'central european time': europe + 'Berlin',
    cet: europe + 'Berlin',
    'central european summer time': europe + 'Berlin',
    cest: europe + 'Berlin',
    'central europe': europe + 'Berlin',
    'eastern european time': europe + 'Riga',
    eet: europe + 'Riga',
    'eastern european summer time': europe + 'Riga',
    eest: europe + 'Riga',
    'eastern europe time': europe + 'Riga',
    'western european time': europe + 'Lisbon',
    // wet: europe+'Lisbon',
    'western european summer time': europe + 'Lisbon',
    // west: europe+'Lisbon',
    'western europe': europe + 'Lisbon',
    'turkey standard time': europe + 'Istanbul',
    trt: europe + 'Istanbul',
    'turkish time': europe + 'Istanbul',
    //africa
    etc: africa + 'Freetown',
    utc: africa + 'Freetown',
    'greenwich standard time': africa + 'Freetown',
    gmt: africa + 'Freetown',
    'east africa time': africa + 'Nairobi',
    // eat: africa+'Nairobi',
    'east african time': africa + 'Nairobi',
    'eastern africa time': africa + 'Nairobi',
    'central africa time': africa + 'Khartoum',
    // cat: africa+'Khartoum',
    'central african time': africa + 'Khartoum',
    'south africa standard time': africa + 'Johannesburg',
    sast: africa + 'Johannesburg',
    'southern africa': africa + 'Johannesburg',
    'south african': africa + 'Johannesburg',
    'west africa standard time': africa + 'Lagos',
    // wat: africa+'Lagos',
    'western africa time': africa + 'Lagos',
    'west african time': africa + 'Lagos',
    'australian central standard time': aus + 'Adelaide',
    acst: aus + 'Adelaide',
    'australian central daylight time': aus + 'Adelaide',
    acdt: aus + 'Adelaide',
    'australia central': aus + 'Adelaide',
    'australian eastern standard time': aus + 'Brisbane',
    aest: aus + 'Brisbane',
    'australian eastern daylight time': aus + 'Brisbane',
    aedt: aus + 'Brisbane',
    'australia east': aus + 'Brisbane',
    'australian western standard time': aus + 'Perth',
    awst: aus + 'Perth',
    'australian western daylight time': aus + 'Perth',
    awdt: aus + 'Perth',
    'australia west': aus + 'Perth',
    'australian central western standard time': aus + 'Eucla',
    acwst: aus + 'Eucla',
    'australia central west': aus + 'Eucla',
    'lord howe standard time': aus + 'Lord_Howe',
    lhst: aus + 'Lord_Howe',
    'lord howe daylight time': aus + 'Lord_Howe',
    lhdt: aus + 'Lord_Howe',
    'russian standard time': europe + 'Moscow',
    msk: europe + 'Moscow',
    russian: europe + 'Moscow',
    //america
    'central standard time': america + 'Chicago',
    'central time': america + 'Chicago',
    cst: america + 'Havana',
    'central daylight time': america + 'Chicago',
    cdt: america + 'Havana',
    'mountain standard time': america + 'Denver',
    'mountain time': america + 'Denver',
    mst: america + 'Denver',
    'mountain daylight time': america + 'Denver',
    mdt: america + 'Denver',
    'atlantic standard time': america + 'Halifax',
    'atlantic time': america + 'Halifax',
    ast: asia + 'Baghdad',
    'atlantic daylight time': america + 'Halifax',
    adt: america + 'Halifax',
    'eastern standard time': america + 'New_York',
    'eastern time': america + 'New_York',
    est: america + 'New_York',
    'eastern daylight time': america + 'New_York',
    edt: america + 'New_York',
    'pacific time': america + 'Los_Angeles',
    'pacific standard time': america + 'Los_Angeles',
    pst: america + 'Los_Angeles',
    'pacific daylight time': america + 'Los_Angeles',
    pdt: america + 'Los_Angeles',
    'alaskan standard time': america + 'Anchorage',
    'alaskan time': america + 'Anchorage',
    ahst: america + 'Anchorage',
    'alaskan daylight time': america + 'Anchorage',
    ahdt: america + 'Anchorage',
    'hawaiian standard time': pac + 'Honolulu',
    'hawaiian time': pac + 'Honolulu',
    hst: pac + 'Honolulu',
    'aleutian time': pac + 'Honolulu',
    'hawaii time': pac + 'Honolulu',
    'newfoundland standard time': america + 'St_Johns',
    'newfoundland time': america + 'St_Johns',
    nst: america + 'St_Johns',
    'newfoundland daylight time': america + 'St_Johns',
    ndt: america + 'St_Johns',
    'brazil time': america + 'Sao_Paulo',
    brt: america + 'Sao_Paulo',
    brasília: america + 'Sao_Paulo',
    brasilia: america + 'Sao_Paulo',
    'brazilian time': america + 'Sao_Paulo',
    'argentina time': america + 'Buenos_Aires',
    // art: a+'Buenos_Aires',
    'argentinian time': america + 'Buenos_Aires',
    'amazon time': america + 'Manaus',
    amt: america + 'Manaus',
    'amazonian time': america + 'Manaus',
    'easter island standard time': 'Chile/Easterisland',
    east: 'Chile/Easterisland',
    'easter island summer time': 'Chile/Easterisland',
    easst: 'Chile/Easterisland',
    'venezuelan standard time': america + 'Caracas',
    'venezuelan time': america + 'Caracas',
    vet: america + 'Caracas',
    'venezuela time': america + 'Caracas',
    'paraguay time': america + 'Asuncion',
    pyt: america + 'Asuncion',
    'paraguay summer time': america + 'Asuncion',
    pyst: america + 'Asuncion',
    'cuba standard time': america + 'Havana',
    'cuba time': america + 'Havana',
    'cuba daylight time': america + 'Havana',
    'cuban time': america + 'Havana',
    'bolivia time': america + 'La_Paz',
    // bot: a+'La_Paz',
    'bolivian time': america + 'La_Paz',
    'colombia time': america + 'Bogota',
    cot: america + 'Bogota',
    'colombian time': america + 'Bogota',
    'acre time': america + 'Eirunepe',
    // act: a+'Eirunepe',
    'peru time': america + 'Lima',
    // pet: a+'Lima',
    'chile standard time': america + 'Punta_Arenas',
    'chile time': america + 'Punta_Arenas',
    clst: america + 'Punta_Arenas',
    'chile summer time': america + 'Punta_Arenas',
    cldt: america + 'Punta_Arenas',
    'uruguay time': america + 'Montevideo',
    uyt: america + 'Montevideo',
    //asia
    ist: asia + 'Jerusalem',
    'arabic standard time': asia + 'Baghdad',
    'arabic time': asia + 'Baghdad',
    'arab time': asia + 'Baghdad',
    'iran standard time': asia + 'Tehran',
    'iran time': asia + 'Tehran',
    irst: asia + 'Tehran',
    'iran daylight time': asia + 'Tehran',
    irdt: asia + 'Tehran',
    iranian: asia + 'Tehran',
    'pakistan standard time': asia + 'Karachi',
    'pakistan time': asia + 'Karachi',
    pkt: asia + 'Karachi',
    'india standard time': asia + 'Kolkata',
    'indian time': asia + 'Kolkata',
    'indochina time': asia + 'Bangkok',
    ict: asia + 'Bangkok',
    'south east asia': asia + 'Bangkok',
    'china standard time': asia + 'Shanghai',
    ct: asia + 'Shanghai',
    'chinese time': asia + 'Shanghai',
    'alma-ata time': asia + 'Almaty',
    almt: asia + 'Almaty',
    'oral time': asia + 'Oral',
    'orat time': asia + 'Oral',
    'yakutsk time': asia + 'Yakutsk',
    yakt: asia + 'Yakutsk',
    'gulf standard time': asia + 'Dubai',
    'gulf time': asia + 'Dubai',
    gst: asia + 'Dubai',
    uae: asia + 'Dubai',
    'hong kong time': asia + 'Hong_Kong',
    hkt: asia + 'Hong_Kong',
    'western indonesian time': asia + 'Jakarta',
    wib: asia + 'Jakarta',
    'indonesia time': asia + 'Jakarta',
    'central indonesian time': asia + 'Makassar',
    wita: asia + 'Makassar',
    'israel daylight time': asia + 'Jerusalem',
    idt: asia + 'Jerusalem',
    'israel standard time': asia + 'Jerusalem',
    'israel time': asia + 'Jerusalem',
    israeli: asia + 'Jerusalem',
    'krasnoyarsk time': asia + 'Krasnoyarsk',
    krat: asia + 'Krasnoyarsk',
    'malaysia time': asia + 'Kuala_Lumpur',
    myt: asia + 'Kuala_Lumpur',
    'singapore time': asia + 'Singapore',
    sgt: asia + 'Singapore',
    'korea standard time': asia + 'Seoul',
    'korea time': asia + 'Seoul',
    kst: asia + 'Seoul',
    'korean time': asia + 'Seoul',
    'uzbekistan time': asia + 'Samarkand',
    uzt: asia + 'Samarkand',
    'vladivostok time': asia + 'Vladivostok',
    vlat: asia + 'Vladivostok',
    //indian
    'maldives time': 'Indian/Maldives',
    mvt: 'Indian/Maldives',
    'mauritius time': 'Indian/Mauritius',
    mut: 'Indian/Mauritius',
    // pacific
    'marshall islands time': pac + 'Kwajalein',
    mht: pac + 'Kwajalein',
    'samoa standard time': pac + 'Midway',
    sst: pac + 'Midway',
    'somoan time': pac + 'Midway',
    'chamorro standard time': pac + 'Guam',
    chst: pac + 'Guam',
    'papua new guinea time': pac + 'Bougainville',
    pgt: pac + 'Bougainville'
  }; //add the official iana zonefile names

  var iana = spacetime().timezones;
  var formal = Object.keys(iana).reduce(function (h, k) {
    h[k] = k;
    return h;
  }, {});

  var _timezones = Object.assign({}, informal, formal);

  var dates = ['weekday', 'summer', 'winter', 'autumn', 'some day', 'one day', 'all day', 'some point', 'eod', 'eom', 'eoy', 'standard time', 'daylight time', 'tommorrow'];

  var durations = ['centuries', 'century', 'day', 'days', 'decade', 'decades', 'hour', 'hours', 'hr', 'hrs', 'millisecond', 'milliseconds', 'minute', 'minutes', 'min', 'mins', 'month', 'months', 'seconds', 'sec', 'secs', 'week end', 'week ends', 'weekend', 'weekends', 'week', 'weeks', 'wk', 'wks', 'year', 'years', 'yr', 'yrs', 'quarter', 'quarters', 'qtr', 'qtrs', 'season', 'seasons'];

  var holidays = ['all hallows eve', 'all saints day', 'all sts day', 'april fools', 'armistice day', 'australia day', 'bastille day', 'boxing day', 'canada day', 'christmas eve', 'christmas', 'cinco de mayo', 'day of the dead', 'dia de muertos', 'dieciseis de septiembre', 'emancipation day', 'grito de dolores', 'groundhog day', 'halloween', 'harvey milk day', 'inauguration day', 'independence day', 'independents day', 'juneteenth', 'labour day', 'national freedom day', 'national nurses day', 'new years eve', 'new years', 'purple heart day', 'rememberance day', 'rosa parks day', 'saint andrews day', 'saint patricks day', 'saint stephens day', 'saint valentines day', 'st andrews day', 'st patricks day', 'st stephens day', 'st valentines day ', 'valentines day', 'valentines', 'veterans day', 'victoria day', 'womens equality day', 'xmas', // Fixed religious and cultural holidays
  // Catholic + Christian
  'epiphany', 'orthodox christmas day', 'orthodox new year', 'assumption of mary', 'all souls day', 'feast of the immaculate conception', 'feast of our lady of guadalupe', // Kwanzaa
  'kwanzaa', // Pagan / metal 🤘
  'imbolc', 'beltaine', 'lughnassadh', 'samhain', 'martin luther king day', 'mlk day', 'presidents day', 'mardi gras', 'tax day', 'commonwealth day', 'mothers day', 'memorial day', 'fathers day', 'columbus day', 'indigenous peoples day', 'canadian thanksgiving', 'election day', 'thanksgiving', 't-day', 'turkey day', 'black friday', 'cyber monday', // Astronomical religious and cultural holidays
  'ash wednesday', 'palm sunday', 'maundy thursday', 'good friday', 'holy saturday', 'easter', 'easter sunday', 'easter monday', 'orthodox good friday', 'orthodox holy saturday', 'orthodox easter', 'orthodox easter monday', 'ascension day', 'pentecost', 'whitsunday', 'whit sunday', 'whit monday', 'trinity sunday', 'corpus christi', 'advent', // Jewish
  'tu bishvat', 'tu bshevat', 'purim', 'passover', 'yom hashoah', 'lag baomer', 'shavuot', 'tisha bav', 'rosh hashana', 'yom kippur', 'sukkot', 'shmini atzeret', 'simchat torah', 'chanukah', 'hanukkah', // Muslim
  'isra and miraj', 'lailat al-qadr', 'eid al-fitr', 'id al-Fitr', 'eid ul-Fitr', 'ramadan', 'eid al-adha', 'muharram', 'the prophets birthday', 'ostara', 'march equinox', 'vernal equinox', 'litha', 'june solistice', 'summer solistice', 'mabon', 'september equinox', 'fall equinox', 'autumnal equinox', 'yule', 'december solstice', 'winter solstice', // Additional important holidays
  'chinese new year', 'diwali'];

  var times = ['noon', 'midnight', 'now', 'morning', 'tonight', 'evening', 'afternoon', 'night', 'breakfast time', 'lunchtime', 'dinnertime', 'sometime', 'midday', 'eod', 'oclock', 'oclock', 'all day', 'at night'];

  var data = [[dates, '#Date'], [durations, '#Duration'], [holidays, '#Holiday'], [times, '#Time'], [Object.keys(_timezones), '#Timezone']];
  var lex = {
    'a couple': 'Value'
  };
  data.forEach(function (a) {
    for (var i = 0; i < a[0].length; i++) {
      lex[a[0][i]] = a[1];
    }
  });
  var words = lex;

  var knownUnits = {
    second: true,
    minute: true,
    hour: true,
    day: true,
    week: true,
    weekend: true,
    month: true,
    season: true,
    quarter: true,
    year: true
  };
  var aliases = {
    wk: 'week',
    min: 'minute',
    sec: 'second',
    weekend: 'week' //for now...

  };

  var parseUnit = function parseUnit(m) {
    var unit = m.match('#Duration').text('normal');
    unit = unit.replace(/s$/, ''); // support shorthands like 'min'

    if (aliases.hasOwnProperty(unit)) {
      unit = aliases[unit];
    }

    return unit;
  }; //turn '5 weeks before' to {weeks:5}


  var parseShift = function parseShift(doc) {
    var result = {};
    var shift = doc.match('#DateShift+');

    if (shift.found === false) {
      return result;
    } // '5 weeks'


    shift.match('#Cardinal #Duration').forEach(function (ts) {
      var num = ts.match('#Cardinal').text('normal');
      num = parseFloat(num);

      if (num && typeof num === 'number') {
        var unit = parseUnit(ts);

        if (knownUnits[unit] === true) {
          result[unit] = num;
        }
      }
    }); //is it 2 weeks ago?  → -2

    if (shift.has('(before|ago|hence|back)$') === true) {
      Object.keys(result).forEach(function (k) {
        return result[k] *= -1;
      });
    }

    shift.remove('#Cardinal #Duration'); // supoprt '1 day after tomorrow'

    var m = shift.match('[<unit>#Duration] [<dir>(after|before)]');

    if (m.found) {
      var unit = m.groups('unit').text('reduced'); // unit = unit.replace(/s$/, '')

      var dir = m.groups('dir').text('reduced');

      if (dir === 'after') {
        result[unit] = 1;
      } else if (dir === 'before') {
        result[unit] = -1;
      }
    } // in half an hour


    m = shift.match('half (a|an) [#Duration]', 0);

    if (m.found) {
      var _unit = parseUnit(m);

      result[_unit] = 0.5;
    } // finally, remove it from our text


    doc.remove('#DateShift');
    return result;
  };

  var _01Shift = parseShift;

  /*
  a 'counter' is a Unit determined after a point
    * first hour of x
    * 7th week in x
    * last year in x
    * 
  unlike a shift, like "2 weeks after x"
  */
  var oneBased = {
    minute: true
  };

  var getCounter = function getCounter(doc) {
    // 7th week of
    var m = doc.match('[<num>#Value] [<unit>#Duration+] (of|in)');

    if (m.found) {
      var obj = m.groups();
      var num = obj.num.text('reduced');
      var unit = obj.unit.text('reduced');
      var found = {
        unit: unit,
        num: Number(num) || 0
      }; // 0-based or 1-based units

      if (!oneBased[unit]) {
        found.num -= 1;
      }

      doc = doc.remove(m);
      return found;
    } // first week of


    m = doc.match('[<dir>(first|initial|last|final)] [<unit>#Duration+] (of|in)');

    if (m.found) {
      var _obj = m.groups();

      var dir = _obj.dir.text('reduced');

      var _unit = _obj.unit.text('reduced');

      if (dir === 'initial') {
        dir = 'first';
      }

      if (dir === 'final') {
        dir = 'last';
      }

      var _found = {
        unit: _unit,
        dir: dir
      };
      doc = doc.remove(m);
      return _found;
    }

    return {};
  };

  var _02Counter = getCounter;

  var hardCoded = {
    daybreak: '7:00am',
    //ergh
    breakfast: '8:00am',
    morning: '9:00am',
    noon: '12:00pm',
    midday: '12:00pm',
    afternoon: '2:00pm',
    lunchtime: '12:00pm',
    evening: '6:00pm',
    dinnertime: '6:00pm',
    night: '8:00pm',
    eod: '10:00pm',
    midnight: '12:00am'
  };

  var halfPast = function halfPast(m, s) {
    var hour = m.match('#Cardinal$').text('reduced');
    var term = m.match('(half|quarter|25|15|10|5)');
    var mins = term.text('reduced');

    if (term.has('half')) {
      mins = '30';
    }

    if (term.has('quarter')) {
      mins = '15';
    }

    var behind = m.has('to'); // apply it

    s = s.hour(hour);
    s = s.startOf('hour'); // assume 'half past 5' is 5pm

    if (hour < 6) {
      s = s.ampm('pm');
    }

    if (behind) {
      s = s.subtract(mins, 'minutes');
    } else {
      s = s.add(mins, 'minutes');
    }

    return s;
  };

  var parseTime = function parseTime(doc, context) {
    var time = doc.match('(at|by|for|before|this)? #Time+');

    if (time.found) {
      doc.remove(time);
    } // get the main part of the time


    time = time.not('^(at|by|for|before|this)');
    time = time.not('sharp');
    time = time.not('on the dot');
    var s = spacetime.now(context.timezone);
    var now = s.clone(); // check for known-times (like 'today')

    var timeStr = time.text('reduced');

    if (hardCoded.hasOwnProperty(timeStr)) {
      return hardCoded[timeStr];
    } // '5 oclock'


    var m = time.match('^#Cardinal oclock (am|pm)?');

    if (m.found) {
      m = m.not('oclock');
      s = s.hour(m.text('reduced'));
      s = s.startOf('hour');

      if (s.isValid() && !s.isEqual(now)) {
        return s.time();
      }
    } // 'quarter to two'


    m = time.match('(half|quarter|25|15|10|5) (past|after|to) #Cardinal');

    if (m.found) {
      s = halfPast(m, s);

      if (s.isValid() && !s.isEqual(now)) {
        return s.time();
      }
    } // '4 in the evening'


    m = time.match('[<time>#Time] (in|at) the? [<desc>(morning|evening|night|nighttime)]');

    if (m.found) {
      var _str = m.groups('time').text('reduced');

      if (/^[0-9]{1,2}$/.test(_str)) {
        s = s.hour(_str); //3 in the morning
      } else {
        s = s.time(_str); // 3:30 in the morning
      }

      if (s.isValid() && !s.isEqual(now)) {
        var desc = m.groups('desc').text('reduced');

        if (desc === 'evening' || desc === 'night') {
          s = s.ampm('pm');
        }

        return s.time();
      }
    } // 'this morning at 4'


    m = time.match('this? [<desc>(morning|evening|tonight)] at [<time>(#Cardinal|#Time)]');

    if (m.found) {
      var g = m.groups();

      var _str2 = g.time.text('reduced');

      if (/^[0-9]{1,2}$/.test(_str2)) {
        s = s.hour(_str2); //3

        s = s.startOf('hour');
      } else {
        s = s.time(_str2); // 3:30
      }

      if (s.isValid() && !s.isEqual(now)) {
        var _desc = g.desc.text('reduced');

        if (_desc === 'morning') {
          s = s.ampm('am');
        }

        if (_desc === 'evening' || _desc === 'tonight') {
          s = s.ampm('pm');
        }

        return s.time();
      }
    } // 'at 4' -> '4'


    m = time.match('^#Cardinal$');

    if (m.found) {
      s = s.hour(m.text('reduced'));
      s = s.startOf('hour');

      if (s.isValid() && !s.isEqual(now)) {
        return s.time();
      }
    } // parse random a time like '4:54pm'


    var str = time.text('reduced');
    s = s.time(str);

    if (s.isValid() && !s.isEqual(now)) {
      return s.time();
    }

    return null;
  };

  var _03Time = parseTime;

  // interpret 'this halloween' or 'next june'
  var parseRelative = function parseRelative(doc) {
    // avoid parsing 'last month of 2019'
    // if (doc.has('^(this|current|next|upcoming|last|previous) #Duration')) {
    //   return null
    // }
    // parse 'this evening'
    // let m = doc.match('^(next|last|this)$')
    // if (m.found) {
    //   doc.remove(m)
    //   return doc.text('reduced')
    // }
    // but avoid parsing 'day after next'
    if (doc.has('(next|last|this)$')) {
      return null;
    }

    var rel = null;
    var m = doc.match('^this? (next|upcoming|coming)');

    if (m.found) {
      rel = 'next';
      doc.remove(m);
    }

    m = doc.match('^this? (last|previous)');

    if (m.found) {
      rel = 'last';
      doc.remove(m);
    }

    m = doc.match('^(this|current)');

    if (m.found) {
      rel = 'this';
      doc.remove(m);
    } // finally, remove it from our text
    // doc.remove('^(this|current|next|upcoming|last|previous)')


    return rel;
  };

  var _04Relative = parseRelative;

  // 'start of october', 'middle of june 1st'
  var parseSection = function parseSection(doc) {
    // start of 2019
    var m = doc.match('[(start|beginning) of] .', 0);

    if (m.found) {
      doc.remove(m);
      return 'start';
    } // end of 2019


    m = doc.match('[end of] .', 0);

    if (m.found) {
      doc.remove(m);
      return 'end';
    } // middle of 2019


    m = doc.match('[(middle|midpoint|center) of] .', 0);

    if (m.found) {
      doc.remove(m);
      return 'middle';
    }

    return null;
  };

  var _05Section = parseSection;

  var isOffset = /(\-?[0-9]+)h(rs)?/i;
  var isNumber = /(\-?[0-9]+)/;
  var utcOffset = /utc([\-+]?[0-9]+)/i;
  var gmtOffset = /gmt([\-+]?[0-9]+)/i;

  var toIana = function toIana(num) {
    num = Number(num);

    if (num > -13 && num < 13) {
      num = num * -1; //it's opposite!

      num = (num > 0 ? '+' : '') + num; //add plus sign

      return 'Etc/GMT' + num;
    }

    return null;
  };

  var parseOffset = function parseOffset(tz) {
    // '+5hrs'
    var m = tz.match(isOffset);

    if (m !== null) {
      return toIana(m[1]);
    } // 'utc+5'


    m = tz.match(utcOffset);

    if (m !== null) {
      return toIana(m[1]);
    } // 'GMT-5' (not opposite)


    m = tz.match(gmtOffset);

    if (m !== null) {
      var num = Number(m[1]) * -1;
      return toIana(num);
    } // '+5'


    m = tz.match(isNumber);

    if (m !== null) {
      return toIana(m[1]);
    }

    return null;
  };

  var parseTimezone = function parseTimezone(doc) {
    var m = doc.match('#Timezone+'); //remove prepositions

    m = m.remove('(in|for|by|near|at)');
    var str = m.text('reduced'); // remove it from our doc, either way

    doc.remove('#Timezone+'); // check our list of informal tz names

    if (_timezones.hasOwnProperty(str)) {
      return _timezones[str];
    }

    var tz = parseOffset(str);

    if (tz) {
      return tz;
    }

    return null;
  };

  var _06Timezone = parseTimezone;

  var Unit = /*#__PURE__*/function () {
    function Unit(input, unit, context, keepTime) {
      _classCallCheck(this, Unit);

      this.unit = unit || 'day';
      context = context || {};
      var today = {};

      if (context.today) {
        today = {
          date: context.today.date(),
          month: context.today.month(),
          year: context.today.year()
        };
      } // set it to the beginning of the given unit


      var d = spacetime(input, context.timezone, {
        today: today
      }); // set to beginning
      // if (d.isValid() && keepTime !== true) {
      //   d = d.startOf(this.unit)
      // }

      Object.defineProperty(this, 'd', {
        enumerable: false,
        writable: true,
        value: d
      });
      Object.defineProperty(this, 'context', {
        enumerable: false,
        writable: true,
        value: context
      });
    } // make a new one


    _createClass(Unit, [{
      key: "clone",
      value: function clone() {
        var d = new Unit(this.d, this.unit, this.context);
        return d;
      }
    }, {
      key: "log",
      value: function log() {
        console.log('--');
        this.d.log();
        console.log('\n');
        return this;
      }
    }, {
      key: "applyShift",
      value: function applyShift() {
        var _this = this;

        var obj = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
        Object.keys(obj).forEach(function (unit) {
          _this.d = _this.d.add(obj[unit], unit);
        });
        return this;
      }
    }, {
      key: "applyTime",
      value: function applyTime(str) {
        if (str) {
          this.d = this.d.time(str);
        } else {
          this.d = this.d.startOf('day'); //zero-out time
        }

        return this;
      }
    }, {
      key: "applyRel",
      value: function applyRel(rel) {
        if (rel === 'next') {
          return this.next();
        }

        if (rel === 'last') {
          return this.last();
        }

        return this;
      }
    }, {
      key: "applySection",
      value: function applySection(section) {
        if (section === 'start') {
          return this.start();
        }

        if (section === 'end') {
          return this.end();
        }

        if (section === 'middle') {
          return this.middle();
        }

        return this;
      }
    }, {
      key: "format",
      value: function format(fmt) {
        return this.d.format(fmt);
      }
    }, {
      key: "start",
      value: function start() {
        this.d = this.d.startOf(this.unit);
        return this;
      }
    }, {
      key: "end",
      value: function end() {
        this.d = this.d.endOf(this.unit);
        return this;
      }
    }, {
      key: "middle",
      value: function middle() {
        var diff = this.d.diff(this.d.endOf(this.unit));
        var minutes = Math.round(diff.minutes / 2);
        this.d = this.d.add(minutes, 'minutes');
        return this;
      } // the millescond before

    }, {
      key: "before",
      value: function before() {
        this.d = this.d.minus(1, this.unit);
        this.d = this.d.endOf(this.unit);
        return this;
      } // 'after 2019'

    }, {
      key: "after",
      value: function after() {
        this.d = this.d.add(1, this.unit);
        this.d = this.d.startOf(this.unit);
        return this;
      } // tricky: 'next june' 'next tuesday'

    }, {
      key: "next",
      value: function next() {
        this.d = this.d.add(1, this.unit);
        this.d = this.d.startOf(this.unit);
        return this;
      } // tricky: 'last june' 'last tuesday'

    }, {
      key: "last",
      value: function last() {
        this.d = this.d.minus(1, this.unit);
        this.d = this.d.startOf(this.unit);
        return this;
      }
    }]);

    return Unit;
  }();

  var Unit_1 = Unit;

  var Day = /*#__PURE__*/function (_Unit) {
    _inherits(Day, _Unit);

    var _super = _createSuper(Day);

    function Day(input, unit, context) {
      var _this;

      _classCallCheck(this, Day);

      _this = _super.call(this, input, unit, context);
      _this.unit = 'day';

      if (_this.d.isValid()) {
        _this.d = _this.d.startOf('day');
      }

      return _this;
    }

    return Day;
  }(Unit_1); // like 'feb 2'


  var CalendarDate = /*#__PURE__*/function (_Day) {
    _inherits(CalendarDate, _Day);

    var _super2 = _createSuper(CalendarDate);

    function CalendarDate(input, unit, context) {
      var _this2;

      _classCallCheck(this, CalendarDate);

      _this2 = _super2.call(this, input, unit, context);
      _this2.unit = 'day';

      if (_this2.d.isValid()) {
        _this2.d = _this2.d.startOf('day');
      }

      return _this2;
    }

    _createClass(CalendarDate, [{
      key: "next",
      value: function next() {
        this.d = this.d.add(1, 'year');
        return this;
      }
    }, {
      key: "last",
      value: function last() {
        this.d = this.d.minus(1, 'year');
        return this;
      }
    }]);

    return CalendarDate;
  }(Day);

  var WeekDay = /*#__PURE__*/function (_Day2) {
    _inherits(WeekDay, _Day2);

    var _super3 = _createSuper(WeekDay);

    function WeekDay(input, unit, context) {
      var _this3;

      _classCallCheck(this, WeekDay);

      _this3 = _super3.call(this, input, unit, context);
      _this3.unit = 'week'; // is the input just a weekday?

      if (typeof input === 'string') {
        _this3.d = spacetime(context.today, context.timezone);
        _this3.d = _this3.d.day(input); // assume a wednesday in the future

        if (_this3.d.isBefore(context.today)) {
          _this3.d = _this3.d.add(7, 'days');
        }
      } else {
        _this3.d = input;
      }

      _this3.weekDay = _this3.d.dayName();

      if (_this3.d.isValid()) {
        _this3.d = _this3.d.startOf('day');
      }

      return _this3;
    }

    _createClass(WeekDay, [{
      key: "clone",
      value: function clone() {
        //overloaded method
        return new WeekDay(this.d, this.unit, this.context);
      }
    }, {
      key: "end",
      value: function end() {
        //overloaded method
        this.d = this.d.endOf('day');
        return this;
      }
    }, {
      key: "next",
      value: function next() {
        this.d = this.d.add(7, 'days');
        this.d = this.d.day(this.weekDay);
        return this;
      }
    }, {
      key: "last",
      value: function last() {
        this.d = this.d.minus(7, 'days');
        this.d = this.d.day(this.weekDay);
        return this;
      }
    }]);

    return WeekDay;
  }(Day); // like 'haloween'


  var Holiday = /*#__PURE__*/function (_CalendarDate) {
    _inherits(Holiday, _CalendarDate);

    var _super4 = _createSuper(Holiday);

    function Holiday(input, unit, context) {
      var _this4;

      _classCallCheck(this, Holiday);

      _this4 = _super4.call(this, input, unit, context);
      _this4.unit = 'day';

      if (_this4.d.isValid()) {
        _this4.d = _this4.d.startOf('day');
      }

      return _this4;
    }

    return Holiday;
  }(CalendarDate);

  var _day = {
    Day: Day,
    WeekDay: WeekDay,
    CalendarDate: CalendarDate,
    Holiday: Holiday
  };

  var AnyMonth = /*#__PURE__*/function (_Unit) {
    _inherits(AnyMonth, _Unit);

    var _super = _createSuper(AnyMonth);

    function AnyMonth(input, unit, context) {
      var _this;

      _classCallCheck(this, AnyMonth);

      _this = _super.call(this, input, unit, context);
      _this.unit = 'month'; // set to beginning

      if (_this.d.isValid()) {
        _this.d = _this.d.startOf(_this.unit);
      }

      return _this;
    }

    return AnyMonth;
  }(Unit_1); // a specific month, like 'March'


  var Month = /*#__PURE__*/function (_Unit2) {
    _inherits(Month, _Unit2);

    var _super2 = _createSuper(Month);

    function Month(input, unit, context) {
      var _this2;

      _classCallCheck(this, Month);

      _this2 = _super2.call(this, input, unit, context);
      _this2.unit = 'month'; // set to beginning

      if (_this2.d.isValid()) {
        _this2.d = _this2.d.startOf(_this2.unit);
      }

      return _this2;
    }

    _createClass(Month, [{
      key: "next",
      value: function next() {
        this.d = this.d.add(1, 'year');
        this.d = this.d.startOf('month');
        return this;
      }
    }, {
      key: "last",
      value: function last() {
        this.d = this.d.minus(1, 'year');
        this.d = this.d.startOf('month');
        return this;
      }
    }]);

    return Month;
  }(Unit_1);

  var AnyQuarter = /*#__PURE__*/function (_Unit3) {
    _inherits(AnyQuarter, _Unit3);

    var _super3 = _createSuper(AnyQuarter);

    function AnyQuarter(input, unit, context) {
      var _this3;

      _classCallCheck(this, AnyQuarter);

      _this3 = _super3.call(this, input, unit, context);
      _this3.unit = 'quarter'; // set to beginning

      if (_this3.d.isValid()) {
        _this3.d = _this3.d.startOf(_this3.unit);
      }

      return _this3;
    }

    _createClass(AnyQuarter, [{
      key: "last",
      value: function last() {
        console.log(this.d.format());
        this.d = this.d.minus(1, 'quarter');
        console.log(this.d.format());
        this.d = this.d.startOf(this.unit);
        console.log(this.d.format());
        return this;
      }
    }]);

    return AnyQuarter;
  }(Unit_1);

  var Quarter = /*#__PURE__*/function (_Unit4) {
    _inherits(Quarter, _Unit4);

    var _super4 = _createSuper(Quarter);

    function Quarter(input, unit, context) {
      var _this4;

      _classCallCheck(this, Quarter);

      _this4 = _super4.call(this, input, unit, context);
      _this4.unit = 'quarter'; // set to beginning

      if (_this4.d.isValid()) {
        _this4.d = _this4.d.startOf(_this4.unit);
      }

      return _this4;
    }

    _createClass(Quarter, [{
      key: "next",
      value: function next() {
        this.d = this.d.add(1, 'year');
        this.d = this.d.startOf(this.unit);
        return this;
      }
    }, {
      key: "last",
      value: function last() {
        this.d = this.d.minus(1, 'year');
        this.d = this.d.startOf(this.unit);
        return this;
      }
    }]);

    return Quarter;
  }(Unit_1);

  var Season = /*#__PURE__*/function (_Unit5) {
    _inherits(Season, _Unit5);

    var _super5 = _createSuper(Season);

    function Season(input, unit, context) {
      var _this5;

      _classCallCheck(this, Season);

      _this5 = _super5.call(this, input, unit, context);
      _this5.unit = 'season'; // set to beginning

      if (_this5.d.isValid()) {
        _this5.d = _this5.d.startOf(_this5.unit);
      }

      return _this5;
    }

    _createClass(Season, [{
      key: "next",
      value: function next() {
        this.d = this.d.add(1, 'year');
        this.d = this.d.startOf(this.unit);
        return this;
      }
    }, {
      key: "last",
      value: function last() {
        this.d = this.d.minus(1, 'year');
        this.d = this.d.startOf(this.unit);
        return this;
      }
    }]);

    return Season;
  }(Unit_1);

  var Year = /*#__PURE__*/function (_Unit6) {
    _inherits(Year, _Unit6);

    var _super6 = _createSuper(Year);

    function Year(input, unit, context) {
      var _this6;

      _classCallCheck(this, Year);

      _this6 = _super6.call(this, input, unit, context);
      _this6.unit = 'year';

      if (_this6.d.isValid()) {
        _this6.d = _this6.d.startOf('year');
      }

      return _this6;
    }

    return Year;
  }(Unit_1);

  var _year = {
    AnyMonth: AnyMonth,
    Month: Month,
    Quarter: Quarter,
    AnyQuarter: AnyQuarter,
    Season: Season,
    Year: Year
  };

  var Week = /*#__PURE__*/function (_Unit) {
    _inherits(Week, _Unit);

    var _super = _createSuper(Week);

    function Week(input, unit, context) {
      var _this;

      _classCallCheck(this, Week);

      _this = _super.call(this, input, unit, context);
      _this.unit = 'week';

      if (_this.d.isValid()) {
        _this.d = _this.d.startOf('week');
      }

      return _this;
    }

    return Week;
  }(Unit_1); //may need some work


  var WeekEnd = /*#__PURE__*/function (_Unit2) {
    _inherits(WeekEnd, _Unit2);

    var _super2 = _createSuper(WeekEnd);

    function WeekEnd(input, unit, context) {
      var _this2;

      _classCallCheck(this, WeekEnd);

      _this2 = _super2.call(this, input, unit, context);
      _this2.unit = 'week';

      if (_this2.d.isValid()) {
        _this2.d = _this2.d.day('saturday');
        _this2.d = _this2.d.startOf('day');
      }

      return _this2;
    }

    _createClass(WeekEnd, [{
      key: "start",
      value: function start() {
        this.d = this.d.day('saturday').startOf('day');
        return this;
      } // end() {
      //   this.d = this.d.day('sunday').endOf('day')
      //   return this
      // }

    }, {
      key: "next",
      value: function next() {
        this.d = this.d.add(1, this.unit);
        this.d = this.d.startOf('weekend');
        return this;
      }
    }, {
      key: "last",
      value: function last() {
        this.d = this.d.minus(1, this.unit);
        this.d = this.d.startOf('weekend');
        return this;
      }
    }]);

    return WeekEnd;
  }(Unit_1);

  var _week = {
    Week: Week,
    WeekEnd: WeekEnd
  };

  var Hour = /*#__PURE__*/function (_Unit) {
    _inherits(Hour, _Unit);

    var _super = _createSuper(Hour);

    function Hour(input, unit, context) {
      var _this;

      _classCallCheck(this, Hour);

      _this = _super.call(this, input, unit, context, true);
      _this.unit = 'hour';

      if (_this.d.isValid()) {
        _this.d = _this.d.startOf('hour');
      }

      return _this;
    }

    return Hour;
  }(Unit_1);

  var Minute = /*#__PURE__*/function (_Unit2) {
    _inherits(Minute, _Unit2);

    var _super2 = _createSuper(Minute);

    function Minute(input, unit, context) {
      var _this2;

      _classCallCheck(this, Minute);

      _this2 = _super2.call(this, input, unit, context, true);
      _this2.unit = 'minute';

      if (_this2.d.isValid()) {
        _this2.d = _this2.d.startOf('minute');
      }

      return _this2;
    }

    return Minute;
  }(Unit_1);

  var Moment = /*#__PURE__*/function (_Unit3) {
    _inherits(Moment, _Unit3);

    var _super3 = _createSuper(Moment);

    function Moment(input, unit, context) {
      var _this3;

      _classCallCheck(this, Moment);

      _this3 = _super3.call(this, input, unit, context, true);
      _this3.unit = 'millisecond';
      return _this3;
    }

    return Moment;
  }(Unit_1);

  var _time = {
    Hour: Hour,
    Minute: Minute,
    Moment: Moment
  };

  var units = Object.assign({
    Unit: Unit_1
  }, _day, _year, _week, _time);

  var Day$1 = units.Day,
      Moment$1 = units.Moment,
      Hour$1 = units.Hour;
  var knownWord = {
    today: function today(context) {
      return new Day$1(context.today, null, context);
    },
    yesterday: function yesterday(context) {
      return new Day$1(context.today.minus(1, 'day'), null, context);
    },
    tomorrow: function tomorrow(context) {
      return new Day$1(context.today.plus(1, 'day'), null, context);
    },
    eom: function eom(context) {
      var d = context.today.endOf('month');
      d = d.startOf('day');
      return new Day$1(d, null, context);
    },
    // eod: (context) => {
    //   let d = context.today.endOf('day')
    //   d = d.startOf('hour').minus(4, 'hours') //rough
    //   return new Hour(d, null, context)
    // },
    eoy: function eoy(context) {
      var d = context.today.endOf('year');
      d = d.startOf('day');
      return new Day$1(d, null, context);
    }
  };
  knownWord.tommorrow = knownWord.tomorrow;
  knownWord.tmrw = knownWord.tomorrow;

  var today = function today(doc, context, section) {
    var unit = null; // is it empty?

    if (doc.found === false) {
      // do we have just a time?
      if (section.time !== null) {
        unit = new Moment$1(context.today, null, context); // choose today
      } //do we just have a shift?


      if (Object.keys(section.shift).length > 0) {
        if (section.shift.hour || section.shift.minute) {
          unit = new Moment$1(context.today, null, context); // choose now
        } else {
          unit = new Day$1(context.today, null, context); // choose today
        }
      }
    } // today, yesterday, tomorrow


    var str = doc.text('reduced');

    if (knownWord.hasOwnProperty(str) === true) {
      return knownWord[str](context);
    } // day after next


    if (str === 'next' && Object.keys(section.shift).length > 0) {
      return knownWord.tomorrow(context);
    }

    return unit;
  };

  var _01Today = today;

  var spacetimeHoliday = createCommonjsModule(function (module, exports) {
    (function (global, factory) {
       module.exports = factory(spacetime) ;
    })(commonjsGlobal, function (spacetime) {

      function _interopDefaultLegacy(e) {
        return e && _typeof(e) === 'object' && 'default' in e ? e : {
          'default': e
        };
      }

      var spacetime__default = /*#__PURE__*/_interopDefaultLegacy(spacetime); //yep,


      var jan = 'january';
      var feb = 'february';
      var mar = 'march';
      var apr = 'april';
      var may = 'may';
      var jun = 'june';
      var jul = 'july';
      var aug = 'august';
      var sep = 'september';
      var oct = 'october';
      var nov = 'november';
      var dec = 'december';
      var fixedHolidays = {
        'new years eve': [dec, 31],
        'new years': [jan, 1],
        'new years day': [jan, 1],
        'inauguration day': [jan, 20],
        'australia day': [jan, 26],
        'national freedom day': [feb, 1],
        'groundhog day': [feb, 2],
        'rosa parks day': [feb, 4],
        'valentines day': [feb, 14],
        'saint valentines day': [feb, 14],
        'st valentines day ': [feb, 14],
        'saint patricks day': [mar, 17],
        'st patricks day': [mar, 17],
        'april fools': [apr, 1],
        'april fools day': [apr, 1],
        'emancipation day': [apr, 16],
        'tax day': [apr, 15],
        //US
        'labour day': [may, 1],
        'cinco de mayo': [may, 5],
        'national nurses day': [may, 6],
        'harvey milk day': [may, 22],
        'victoria day': [may, 24],
        juneteenth: [jun, 19],
        'canada day': [jul, 1],
        'independence day': [jul, 4],
        'independents day': [jul, 4],
        'bastille day': [jul, 14],
        'purple heart day': [aug, 7],
        'womens equality day': [aug, 26],
        '16 de septiembre': [sep, 16],
        'dieciseis de septiembre': [sep, 16],
        'grito de dolores': [sep, 16],
        halloween: [oct, 31],
        'all hallows eve': [oct, 31],
        'day of the dead': [oct, 31],
        // Ranged holiday [nov, 2],
        'dia de muertos': [oct, 31],
        // Ranged holiday [nov, 2],
        'veterans day': [nov, 11],
        'st andrews day': [nov, 30],
        'saint andrews day': [nov, 30],
        'all saints day': [nov, 1],
        'all sts day': [nov, 1],
        'armistice day': [nov, 11],
        'rememberance day': [nov, 11],
        'christmas eve': [dec, 24],
        christmas: [dec, 25],
        xmas: [dec, 25],
        'boxing day': [dec, 26],
        'st stephens day': [dec, 26],
        'saint stephens day': [dec, 26],
        // Fixed religious and cultural holidays
        // Catholic + Christian
        epiphany: [jan, 6],
        'orthodox christmas day': [jan, 7],
        'orthodox new year': [jan, 14],
        'assumption of mary': [aug, 15],
        'all souls day': [nov, 2],
        'feast of the immaculate conception': [dec, 8],
        'feast of our lady of guadalupe': [dec, 12],
        // Kwanzaa
        kwanzaa: [dec, 26],
        // Ranged holiday [jan, 1],
        // Pagan / metal 🤘
        imbolc: [feb, 2],
        beltaine: [may, 1],
        lughnassadh: [aug, 1],
        samhain: [oct, 31]
      };

      var fixedDates = function fixedDates(str, normal, year, tz) {
        if (fixedHolidays.hasOwnProperty(str) || fixedHolidays.hasOwnProperty(normal)) {
          var arr = fixedHolidays[str] || fixedHolidays[normal] || [];
          var s = spacetime__default['default'].now(tz);
          s = s.year(year);
          s = s.startOf('year');
          s = s.month(arr[0]);
          s = s.date(arr[1]);

          if (s.isValid()) {
            return s;
          }
        }

        return null;
      };

      var _01FixedDates = fixedDates; //these are holidays on the 'nth weekday of month'

      var jan$1 = 'january';
      var feb$1 = 'february';
      var mar$1 = 'march'; // const apr = 'april'

      var may$1 = 'may';
      var jun$1 = 'june'; // const jul = 'july'
      // const aug = 'august'

      var sep$1 = 'september';
      var oct$1 = 'october';
      var nov$1 = 'november'; // const dec = 'december'

      var mon = 'monday'; // const tues = 'tuesday'
      // const wed = 'wednesday'

      var thurs = 'thursday';
      var fri = 'friday'; // const sat = 'saturday'

      var sun = 'sunday';
      var holidays = {
        'martin luther king day': [3, mon, jan$1],
        //[third monday in january],
        'presidents day': [3, mon, feb$1],
        //[third monday in february],
        'commonwealth day': [2, mon, mar$1],
        //[second monday in march],
        'mothers day': [2, sun, may$1],
        //[second Sunday in May],
        'fathers day': [3, sun, jun$1],
        //[third Sunday in June],
        'labor day': [1, mon, sep$1],
        //[first monday in september],
        'columbus day': [2, mon, oct$1],
        //[second monday in october],
        'canadian thanksgiving': [2, mon, oct$1],
        //[second monday in october],
        thanksgiving: [4, thurs, nov$1],
        // [fourth Thursday in November],
        'black friday': [4, fri, nov$1] //[fourth friday in november],
        // 'memorial day': [may], //[last monday in may],
        // 'us election': [nov], // [Tuesday following the first Monday in November],
        // 'cyber monday': [nov]
        // 'advent': [] // fourth Sunday before Christmas

      }; // add aliases

      holidays['turday day'] = holidays.thanksgiving;
      holidays['indigenous peoples day'] = holidays['columbus day'];
      holidays['mlk day'] = holidays['martin luther king day'];
      var calendarHolidays = holidays;

      var fixedDates$1 = function fixedDates(str, normal, year, tz) {
        if (calendarHolidays.hasOwnProperty(str) || calendarHolidays.hasOwnProperty(normal)) {
          var arr = calendarHolidays[str] || calendarHolidays[normal] || [];
          var s = spacetime__default['default'].now(tz);
          s = s.year(year); // [3rd, 'monday', 'january']

          s = s.month(arr[2]);
          s = s.startOf('month'); // make it january

          var month = s.month(); // make it the 1st monday

          s = s.day(arr[1]);

          if (s.month() !== month) {
            s = s.add(1, 'week');
          } // make it nth monday


          if (arr[0] > 1) {
            s = s.add(arr[0] - 1, 'week');
          }

          if (s.isValid()) {
            return s;
          }
        }

        return null;
      };

      var _02NthWeekday = fixedDates$1; // https://www.timeanddate.com/calendar/determining-easter-date.html

      var dates = {
        easter: 0,
        'ash wednesday': -46,
        // (46 days before easter)
        'palm sunday': 7,
        // (1 week before easter)
        'maundy thursday': -3,
        // (3 days before easter)
        'good friday': -2,
        // (2 days before easter)
        'holy saturday': -1,
        // (1 days before easter)
        'easter saturday': -1,
        // (1 day before easter)
        'easter monday': 1,
        // (1 day after easter)
        'ascension day': 39,
        // (39 days after easter)
        'whit sunday': 49,
        // / pentecost (49 days after easter)
        'whit monday': 50,
        // (50 days after easter)
        'trinity sunday': 65,
        // (56 days after easter)
        'corpus christi': 60,
        // (60 days after easter)
        'mardi gras': -47 //(47 days before easter)

      };
      dates['easter sunday'] = dates.easter;
      dates['pentecost'] = dates['whit sunday'];
      dates['whitsun'] = dates['whit sunday'];
      var easterHolidays = dates; // by John Dyer
      // based on the algorithm by Oudin (1940) from http://www.tondering.dk/claus/cal/easter.php

      var calcEaster = function calcEaster(year) {
        var f = Math.floor,
            // Golden Number - 1
        G = year % 19,
            C = f(year / 100),
            // related to Epact
        H = (C - f(C / 4) - f((8 * C + 13) / 25) + 19 * G + 15) % 30,
            // number of days from 21 March to the Paschal full moon
        I = H - f(H / 28) * (1 - f(29 / (H + 1)) * f((21 - G) / 11)),
            // weekday for the Paschal full moon
        J = (year + f(year / 4) + I + 2 - C + f(C / 4)) % 7,
            // number of days from 21 March to the Sunday on or before the Paschal full moon
        L = I - J,
            month = 3 + f((L + 40) / 44),
            date = L + 28 - 31 * f(month / 4);
        month = month === 4 ? 'April' : 'March';
        return month + ' ' + date;
      };

      var calcEaster_1 = calcEaster;

      var easterDates = function easterDates(str, normal, year, tz) {
        if (easterHolidays.hasOwnProperty(str) || easterHolidays.hasOwnProperty(normal)) {
          var days = easterHolidays[str] || easterHolidays[normal] || [];
          var date = calcEaster_1(year);

          if (!date) {
            return null; //no easter for this year
          }

          var e = spacetime__default['default'](date, tz);
          e = e.year(year);
          var s = e.add(days, 'day');

          if (s.isValid()) {
            return s;
          }
        }

        return null;
      };

      var _03EasterDates = easterDates; // http://www.astropixels.com/ephemeris/soleq2001.html
      // years 2000-2100

      var exceptions = {
        spring: [2003, 2007, 2044, 2048, 2052, 2056, 2060, 2064, 2068, 2072, 2076, 2077, 2080, 2081, 2084, 2085, 2088, 2089, 2092, 2093, 2096, 2097],
        summer: [2021, 2016, 2020, 2024, 2028, 2032, 2036, 2040, 2041, 2044, 2045, 2048, 2049, 2052, 2053, 2056, 2057, 2060, 2061, 2064, 2065, 2068, 2069, 2070, 2072, 2073, 2074, 2076, 2077, 2078, 2080, 2081, 2082, 2084, 2085, 2086, 2088, 2089, 2090, 2092, 2093, 2094, 2096, 2097, 2098, 2099],
        fall: [2002, 2003, 2004, 2006, 2007, 2010, 2011, 2014, 2015, 2018, 2019, 2022, 2023, 2026, 2027, 2031, 2035, 2039, 2043, 2047, 2051, 2055, 2059, 2092, 2096],
        winter: [2002, 2003, 2006, 2007, 2011, 2015, 2019, 2023, 2027, 2031, 2035, 2039, 2043, 2080, 2084, 2088, 2092, 2096]
      };
      var winter20th = [2080, 2084, 2088, 2092, 2096];

      var calcSeasons = function calcSeasons(year) {
        // most common defaults
        var res = {
          spring: 'March 20 ' + year,
          summer: 'June 21 ' + year,
          fall: 'Sept 22 ' + year,
          winter: 'Dec 21 ' + year
        };

        if (exceptions.spring.indexOf(year) !== -1) {
          res.spring = 'March 19 ' + year;
        }

        if (exceptions.summer.indexOf(year) !== -1) {
          res.summer = 'June 20 ' + year;
        }

        if (exceptions.fall.indexOf(year) !== -1) {
          res.fall = 'Sept 21 ' + year;
        } // winter can be 20th, 21st, or 22nd


        if (exceptions.winter.indexOf(year) !== -1) {
          res.winter = 'Dec 22 ' + year;
        }

        if (winter20th.indexOf(year) !== -1) {
          res.winter = 'Dec 20 ' + year;
        }

        return res;
      };

      var seasons = calcSeasons; // these are properly calculated in ./lib/seasons

      var dates$1 = {
        'spring equinox': 'spring',
        'summer solistice': 'summer',
        'fall equinox': 'fall',
        'winter solstice': 'winter'
      }; // aliases

      dates$1['march equinox'] = dates$1['spring equinox'];
      dates$1['vernal equinox'] = dates$1['spring equinox'];
      dates$1['ostara'] = dates$1['spring equinox'];
      dates$1['june solstice'] = dates$1['summer solistice'];
      dates$1['litha'] = dates$1['summer solistice'];
      dates$1['autumn equinox'] = dates$1['fall equinox'];
      dates$1['autumnal equinox'] = dates$1['fall equinox'];
      dates$1['september equinox'] = dates$1['fall equinox'];
      dates$1['sept equinox'] = dates$1['fall equinox'];
      dates$1['mabon'] = dates$1['fall equinox'];
      dates$1['december solstice'] = dates$1['winter solistice'];
      dates$1['dec solstice'] = dates$1['winter solistice'];
      dates$1['yule'] = dates$1['winter solistice'];
      var astroHolidays = dates$1;

      var astroDates = function astroDates(str, normal, year, tz) {
        if (astroHolidays.hasOwnProperty(str) || astroHolidays.hasOwnProperty(normal)) {
          var season = astroHolidays[str] || astroHolidays[normal];
          var seasons$1 = seasons(year);

          if (!season || !seasons$1 || !seasons$1[season]) {
            return null; // couldn't figure it out
          }

          var s = spacetime__default['default'](seasons$1[season], tz);

          if (s.isValid()) {
            return s;
          }
        }

        return null;
      };

      var _04Astronomical = astroDates;
      var dates$2 = {
        // Muslim holidays
        'isra and miraj': 'april 13',
        'lailat al-qadr': 'june 10',
        'eid al-fitr': 'june 15',
        'id al-Fitr': 'june 15',
        'eid ul-Fitr': 'june 15',
        ramadan: 'may 16',
        // Range holiday
        'eid al-adha': 'sep 22',
        muharram: 'sep 12',
        'prophets birthday': 'nov 21'
      };
      var lunarHolidays = dates$2;
      var dayDiff = -10.64;

      var lunarDates = function lunarDates(str, normal, year, tz) {
        if (lunarHolidays.hasOwnProperty(str) || lunarHolidays.hasOwnProperty(normal)) {
          var date = lunarHolidays[str] || lunarHolidays[normal] || [];

          if (!date) {
            return null;
          } // start at 2018


          var s = spacetime__default['default'](date + ' 2018', tz);
          var diff = year - 2018;
          var toAdd = diff * dayDiff;
          s = s.add(toAdd, 'day');
          s = s.startOf('day'); // now set the correct year

          s = s.year(year);

          if (s.isValid()) {
            return s;
          }
        }

        return null;
      };

      var _05LunarDates = lunarDates;
      var nowYear = spacetime__default['default'].now().year();

      var spacetimeHoliday = function spacetimeHoliday(str, year, tz) {
        year = year || nowYear;
        str = str || '';
        str = String(str);
        str = str.trim().toLowerCase();
        str = str.replace(/'s/, 's'); // 'mother's day'

        var normal = str.replace(/ day$/, '');
        normal = normal.replace(/^the /, '');
        normal = normal.replace(/^orthodox /, ''); //orthodox good friday
        // try easier, unmoving holidays

        var s = _01FixedDates(str, normal, year, tz);

        if (s !== null) {
          return s;
        } // try 'nth monday' holidays


        s = _02NthWeekday(str, normal, year, tz);

        if (s !== null) {
          return s;
        } // easter-based holidays


        s = _03EasterDates(str, normal, year, tz);

        if (s !== null) {
          return s;
        } // solar-based holidays


        s = _04Astronomical(str, normal, year, tz);

        if (s !== null) {
          return s;
        } // mostly muslim holidays


        s = _05LunarDates(str, normal, year, tz);

        if (s !== null) {
          return s;
        }

        return null;
      };

      var src = spacetimeHoliday;
      return src;
    });
  });

  var Holiday$1 = units.Holiday;

  var parseHoliday = function parseHoliday(doc, context) {
    var unit = null;
    var m = doc.match('[<holiday>#Holiday+] [<year>#Year?]');
    var year = context.today.year();

    if (m.groups('year').found) {
      year = Number(m.groups('year').text('reduced')) || year;
    }

    var str = m.groups('holiday').text('reduced');
    var s = spacetimeHoliday(str, year, context.timezone);

    if (s !== null) {
      // assume the year in the future..
      if (s.isBefore(context.today) && year === context.today.year()) {
        s = spacetimeHoliday(str, year + 1, context.timezone);
      }

      unit = new Holiday$1(s, null, context);
    }

    return unit;
  };

  var _02Holidays = parseHoliday;

  var Week$1 = units.Week,
      WeekEnd$1 = units.WeekEnd,
      AnyMonth$1 = units.AnyMonth,
      AnyQuarter$1 = units.AnyQuarter,
      Year$1 = units.Year,
      Season$1 = units.Season,
      WeekDay$1 = units.WeekDay,
      Day$2 = units.Day,
      Hour$2 = units.Hour,
      Minute$1 = units.Minute,
      Moment$2 = units.Moment;
  var mapping = {
    day: Day$2,
    hour: Hour$2,
    evening: Hour$2,
    second: Moment$2,
    milliscond: Moment$2,
    instant: Moment$2,
    minute: Minute$1,
    week: Week$1,
    weekend: WeekEnd$1,
    month: AnyMonth$1,
    quarter: AnyQuarter$1,
    year: Year$1,
    season: Season$1,
    // set aliases
    yr: Year$1,
    qtr: AnyQuarter$1,
    wk: Week$1,
    sec: Moment$2,
    hr: Hour$2
  };
  var matchStr = "^(".concat(Object.keys(mapping).join('|'), ")$"); // when a unit of time is spoken of as 'this month' - instead of 'february'

  var nextLast = function nextLast(doc, context) {
    //this month, last quarter, next year
    var m = doc.match(matchStr);

    if (m.found === true) {
      var str = m.text('reduced');

      if (mapping.hasOwnProperty(str)) {
        var Model = mapping[str];

        if (!Model) {
          return null;
        }

        var unit = new Model(null, str, context);
        return unit;
      }
    } //try this version - 'next friday, last thursday'


    m = doc.match('^#WeekDay$');

    if (m.found === true) {
      var _str = m.text('reduced');

      var _unit = new WeekDay$1(_str, null, context);

      return _unit;
    }

    return null;
  };

  var _03NextLast = nextLast;

  var Quarter$1 = units.Quarter,
      Season$2 = units.Season,
      Year$2 = units.Year;

  var fmtToday = function fmtToday(context) {
    return {
      date: context.today.date(),
      month: context.today.month(),
      year: context.today.year()
    };
  };

  var parseYearly = function parseYearly(doc, context) {
    // support 'summer 2002'
    var m = doc.match('(spring|summer|winter|fall|autumn) [<year>#Year?]');

    if (m.found) {
      var str = doc.text('reduced');
      var s = spacetime(str, context.timezone, {
        today: fmtToday(context)
      });
      var unit = new Season$2(s, null, context);

      if (unit.d.isValid() === true) {
        return unit;
      }
    } // support 'q4 2020'


    m = doc.match('[<q>#FinancialQuarter] [<year>#Year?]');

    if (m.found) {
      var _str = m.groups('q').text('reduced');

      var _s = spacetime(_str, context.timezone, {
        today: fmtToday(context)
      });

      if (m.groups('year')) {
        var year = Number(m.groups('year').text()) || context.today.year();
        _s = _s.year(year);
      }

      var _unit = new Quarter$1(_s, null, context);

      if (_unit.d.isValid() === true) {
        return _unit;
      }
    } // support '4th quarter 2020'


    m = doc.match('[<q>#Value] quarter (of|in)? [<year>#Year?]');

    if (m.found) {
      var q = m.groups('q').text('reduced');

      var _s2 = spacetime("q".concat(q), context.timezone, {
        today: fmtToday(context)
      });

      if (m.groups('year')) {
        var _year = Number(m.groups('year').text()) || context.today.year();

        _s2 = _s2.year(_year);
      }

      var _unit2 = new Quarter$1(_s2, null, context);

      if (_unit2.d.isValid() === true) {
        return _unit2;
      }
    } // support '2020'


    m = doc.match('^#Year$');

    if (m.found) {
      var _str2 = doc.text('reduced');

      var _s3 = spacetime(null, context.timezone, {
        today: fmtToday(context)
      });

      _s3 = _s3.year(_str2);

      var _unit3 = new Year$2(_s3, null, context);

      if (_unit3.d.isValid() === true) {
        return _unit3;
      }
    }

    return null;
  };

  var _04Yearly = parseYearly;

  var Day$3 = units.Day,
      CalendarDate$1 = units.CalendarDate,
      Month$1 = units.Month,
      Moment$3 = units.Moment; // parse things like 'june 5th 2019'
  // most of this is done in spacetime

  var parseExplicit = function parseExplicit(doc, context) {
    var impliedYear = context.today.year(); // 'fifth of june 1992'
    // 'june the fifth 1992'

    var m = doc.match('[<date>#Value] of? [<month>#Month] [<year>#Year]');

    if (!m.found) {
      m = doc.match('[<month>#Month] the? [<date>#Value] [<year>#Year]');
    }

    if (m.found) {
      var obj = {
        month: m.groups('month').text(),
        date: m.groups('date').text(),
        year: m.groups('year').text() || impliedYear
      };

      var _unit = new CalendarDate$1(obj, null, context);

      if (_unit.d.isValid() === true) {
        return _unit;
      }
    } // 'march 1992'


    m = doc.match('[<month>#Month] of? [<year>#Year]');

    if (m.found) {
      var _obj = {
        month: m.groups('month').text(),
        year: m.groups('year').text() || impliedYear
      };

      var _unit2 = new Month$1(_obj, null, context);

      if (_unit2.d.isValid() === true) {
        return _unit2;
      }
    } //no-years
    // 'fifth of june'


    m = doc.match('[<date>#Value] of? [<month>#Month]'); // 'june the fifth'

    if (!m.found) {
      m = doc.match('[<month>#Month] the? [<date>#Value]');
    }

    if (m.found) {
      var _obj2 = {
        month: m.groups('month').text(),
        date: m.groups('date').text(),
        year: context.today.year()
      };

      var _unit3 = new CalendarDate$1(_obj2, null, context); // assume 'feb' in the future


      if (_unit3.d.month() < context.today.month()) {
        _obj2.year += 1;
        _unit3 = new CalendarDate$1(_obj2, null, context);
      }

      if (_unit3.d.isValid() === true) {
        return _unit3;
      }
    } // support 'december'


    if (doc.has('#Month')) {
      var _obj3 = {
        month: doc.match('#Month').text(),
        date: 1,
        //assume 1st
        year: context.today.year()
      };

      var _unit4 = new Month$1(_obj3, null, context); // assume 'feb' in the future


      if (_unit4.d.month() < context.today.month()) {
        _obj3.year += 1;
        _unit4 = new Month$1(_obj3, null, context);
      }

      if (_unit4.d.isValid() === true) {
        return _unit4;
      }
    } // support 'thursday 21st'


    m = doc.match('#WeekDay [<date>#Value]');

    if (m.found) {
      var _obj4 = {
        month: context.today.month(),
        date: m.groups('date').text(),
        year: context.today.year()
      };

      var _unit5 = new CalendarDate$1(_obj4, null, context);

      if (_unit5.d.isValid() === true) {
        return _unit5;
      }
    } // support date-only 'the 21st'


    m = doc.match('the [<date>#Value]');

    if (m.found) {
      var _obj5 = {
        month: context.today.month(),
        date: m.groups('date').text(),
        year: context.today.year()
      };

      var _unit6 = new CalendarDate$1(_obj5, null, context);

      if (_unit6.d.isValid() === true) {
        // assume it's forward
        if (_unit6.d.isBefore(context.today)) {
          _unit6.d = _unit6.d.add(1, 'month');
        }

        return _unit6;
      }
    } // parse ISO as a Moment


    m = doc.match('/[0-9]{4}-[0-9]{2}-[0-9]{2}t[0-9]{2}:/');

    if (m.found) {
      var _str = doc.text('reduced');

      var _unit7 = new Moment$3(_str, null, context);

      if (_unit7.d.isValid() === true) {
        return _unit7;
      }
    }

    var str = doc.text('reduced'); // punt it to spacetime, for the heavy-lifting

    var unit = new Day$3(str, null, context); // did we find a date?

    if (unit.d.isValid() === false) {
      return null;
    }

    return unit;
  };

  var _05Explicit = parseExplicit;

  var Quarter$2 = units.Quarter,
      Season$3 = units.Season,
      Week$2 = units.Week,
      Day$4 = units.Day,
      Hour$3 = units.Hour,
      Minute$2 = units.Minute,
      Month$2 = units.Month,
      WeekEnd$2 = units.WeekEnd;
  var units$1 = {
    day: Day$4,
    week: Week$2,
    weekend: WeekEnd$2,
    month: Month$2,
    quarter: Quarter$2,
    season: Season$3,
    hour: Hour$3,
    minute: Minute$2
  };

  var applyCounter = function applyCounter(unit) {
    var counter = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
    var Unit = units$1[counter.unit];

    if (!Unit) {
      return unit;
    }

    var d = unit.d; // support 'first' or 0th

    if (counter.dir === 'first' || counter.num === 0) {
      d = unit.start().d;
      d = d.startOf(counter.unit);
    } else if (counter.dir === 'last') {
      d = d.endOf(unit.unit);
      d = d.startOf(counter.unit);
    } else if (counter.num) {
      // support 'nth week', eg.
      d = d.add(counter.num, counter.unit);
    }

    var u = new Unit(d, null, unit.context);

    if (u.d.isValid() === true) {
      return u;
    }

    return unit; //fallback
  };

  var addCounter = applyCounter;

  var tokens = {
    shift: _01Shift,
    counter: _02Counter,
    time: _03Time,
    relative: _04Relative,
    section: _05Section,
    timezone: _06Timezone
  };
  var parse = {
    today: _01Today,
    holiday: _02Holidays,
    nextLast: _03NextLast,
    yearly: _04Yearly,
    explicit: _05Explicit
  };
  var transform = {
    counter: addCounter
  };

  var parseDate = function parseDate(doc, context) {
    // quick normalization
    doc.match('[^the] !#Value', 0).remove(); // keep 'the 17th'
    //parse-out any sections

    var shift = tokens.shift(doc);
    var counter = tokens.counter(doc);
    var tz = tokens.timezone(doc);
    var time = tokens.time(doc, context);
    var section = tokens.section(doc, context);
    var rel = tokens.relative(doc); //set our new timezone

    if (tz) {
      context = Object.assign({}, context, {
        timezone: tz
      });
      var iso = context.today.format('iso-short');
      context.today = context.today["goto"](context.timezone).set(iso);
    }

    var unit = null; //'in two days'

    unit = unit || parse.today(doc, context, {
      shift: shift,
      time: time,
      rel: rel
    }); // 'this haloween'

    unit = unit || parse.holiday(doc, context); // 'this month'

    unit = unit || parse.nextLast(doc, context); // 'q2 2002'

    unit = unit || parse.yearly(doc, context); // 'this june 2nd'

    unit = unit || parse.explicit(doc, context); // doc.debug()

    if (!unit) {
      return null;
    } // 2 days after..


    if (shift) {
      unit.applyShift(shift); // if (shift.hour || shift.minute || shift.second) {
      //   console.log(shift)
      //   unit = new Hour(unit.d, null, unit.context)
      // }
    } // this/next/last


    if (rel) {
      unit.applyRel(rel);
    } // end of


    if (section) {
      unit.applySection(section);
    } // at 5:40pm


    if (time) {
      unit.applyTime(time);
    } // apply counter


    if (counter && counter.unit) {
      unit = transform.counter(unit, counter);
    } // debugging
    // console.log('\n\n=-=-=-=-=-=-=-=-=-=-=-=Date-=-=-=-=-=-=-=-=-=-=-=-=-\n')
    // console.log(`  shift:      ${JSON.stringify(shift)}`)
    // console.log(`  counter:   `, counter)
    // console.log(`  rel:        ${rel || '-'}`)
    // console.log(`  section:    ${section || '-'}`)
    // console.log(`  time:       ${time || '-'}`)
    // console.log(`  str:       '${doc.text()}'`)
    // console.log('  unit:     ', unit, '\n')
    // doc.debug()
    // console.log('=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n\n')


    return unit;
  };

  var parse_1 = parseDate;

  var punt = function punt(unit, context) {
    unit = unit.applyShift(context.punt);
    return unit;
  };

  var ranges = [{
    // two explicit dates - 'between friday and sunday'
    match: 'between [<start>*] and [<end>*]',
    parse: function parse(m, context) {
      var start = m.groups('start');
      start = parse_1(start, context);
      var end = m.groups('end');
      end = parse_1(end, context);

      if (start && end) {
        return {
          start: start,
          end: end.before()
        };
      }

      return null;
    }
  }, {
    // two months, no year - 'june 5 to june 7'
    match: '[<from>#Month #Value] (to|through|thru) [<to>#Month #Value] [<year>#Year?]',
    parse: function parse(m, context) {
      var res = m.groups();
      var start = res.from;

      if (res.year) {
        start = start.append(res.year);
      }

      start = parse_1(start, context);

      if (start) {
        var end = res.to;

        if (res.year) {
          end = end.append(res.year);
        }

        end = parse_1(end, context); // reverse the order?

        if (start.d.isAfter(end.d)) {
          var tmp = start;
          start = end;
          end = tmp;
        }

        return {
          start: start,
          end: end.end()
        };
      }

      return null;
    }
  }, {
    // one month, one year, first form - 'january 5 to 7 1998'
    match: '[<month>#Month] [<from>#Value] (to|through|thru) [<to>#Value] of? [<year>#Year]',
    parse: function parse(m, context) {
      var _m$groups = m.groups(),
          month = _m$groups.month,
          from = _m$groups.from,
          to = _m$groups.to,
          year = _m$groups.year;

      var year2 = year.clone();
      var start = from.prepend(month.text()).append(year.text());
      start = parse_1(start, context);

      if (start) {
        var end = to.prepend(month.text()).append(year2);
        end = parse_1(end, context);
        return {
          start: start,
          end: end.end()
        };
      }

      return null;
    }
  }, {
    // one month, one year, second form - '5 to 7 of january 1998'
    match: '[<from>#Value] (to|through|thru) [<to>#Value of? #Month of? #Year]',
    parse: function parse(m, context) {
      var to = m.groups('to');
      to = parse_1(to, context);

      if (to) {
        var fromDate = m.groups('to');
        var from = to.clone();
        from.d = from.d.date(fromDate.text('normal'));
        return {
          start: from,
          end: to.end()
        };
      }

      return null;
    }
  }, {
    // one month, no year - '5 to 7 of january'
    match: '[<from>#Value] (to|through|thru) [<to>#Value of? #Month]',
    parse: function parse(m, context) {
      var to = m.groups('to');
      to = parse_1(to, context);

      if (to) {
        var fromDate = m.groups('from');
        var from = to.clone();
        from.d = from.d.date(fromDate.text('normal'));
        return {
          start: from,
          end: to.end()
        };
      }

      return null;
    }
  }, {
    // one month, no year - 'january 5 to 7'
    match: '[<from>#Month #Value] (to|through|thru) [<to>#Value]',
    parse: function parse(m, context) {
      var from = m.groups('from');
      from = parse_1(from, context);

      if (from) {
        var toDate = m.groups('to');
        var to = from.clone();
        to.d = to.d.date(toDate.text('normal'));
        return {
          start: from,
          end: to.end()
        };
      }

      return null;
    }
  }, {
    // 'from A to B'
    match: 'from? [<from>*] (to|until|upto|through|thru) [<to>*]',
    parse: function parse(m, context) {
      var from = m.groups('from');
      var to = m.groups('to');
      from = parse_1(from, context);
      to = parse_1(to, context);

      if (from && to) {
        return {
          start: from,
          end: to.end()
        };
      }

      return null;
    }
  }, // {
  //   // 'A through B' (inclusive end)
  //   match: 'from? [<a>*] (through|thru) [<b>*]',
  //   parse: (m, context) => {
  //     let from = m.groups('a')
  //     let to = m.groups('b')
  //     from = parseDate(from, context)
  //     to = parseDate(to, context)
  //     if (from && to) {
  //       return {
  //         start: from,
  //         end: to.end(),
  //       }
  //     }
  //     return null
  //   },
  // },
  // {
  //   // 'A until B' (not inclusive end)
  //   match: 'from? [<a>*] (to|until|upto) [<b>*]',
  //   parse: (m, context) => {
  //     let from = m.groups('a')
  //     let to = m.groups('b')
  //     from = parseDate(from, context)
  //     to = parseDate(to, context)
  //     if (from && to) {
  //       return {
  //         start: from,
  //         end: to.end(),
  //       }
  //     }
  //     return null
  //   },
  // },
  {
    // 'before june'
    match: '^due? (by|before) [*]',
    group: 0,
    parse: function parse(m, context) {
      var unit = parse_1(m, context);

      if (unit) {
        var start = new Unit_1(context.today, null, context);

        if (start.d.isAfter(unit.d)) {
          start = unit.clone().applyShift({
            weeks: -2
          });
        } // end the night before


        var end = unit.clone().applyShift({
          day: -1
        });
        return {
          start: start,
          end: end.end()
        };
      }

      return null;
    }
  }, {
    // 'in june'
    match: '^(on|in|at|@) [*]',
    group: 0,
    parse: function parse(m, context) {
      var unit = parse_1(m, context);

      if (unit) {
        return {
          start: unit,
          end: unit.clone().end()
        };
      }

      return null;
    }
  }, {
    // 'after june'
    match: '^(after|following) [*]',
    group: 0,
    parse: function parse(m, context) {
      var unit = parse_1(m, context);

      if (unit) {
        unit = unit.after();
        return {
          start: unit.clone(),
          end: punt(unit.clone(), context)
        };
      }

      return null;
    }
  }, {
    // 'in june'
    match: '^(on|during|in|during) [*]',
    group: 0,
    parse: function parse(m, context) {
      var unit = parse_1(m, context);

      if (unit) {
        return {
          start: unit,
          end: unit.clone().end()
        };
      }

      return null;
    }
  }];

  var parseRange = function parseRange(doc, context) {
    // try each template in order
    for (var i = 0; i < ranges.length; i += 1) {
      var fmt = ranges[i];
      var m = doc.match(fmt.match);

      if (m.found) {
        if (fmt.group !== undefined) {
          m = m.groups(fmt.group);
        }

        var res = fmt.parse(m, context);

        if (res !== null) {
          // console.log(fmt.match)
          return res;
        }
      }
    } //else, try whole thing


    var unit = parse_1(doc, context);

    if (unit) {
      return {
        start: unit,
        end: unit.clone().end()
      };
    }

    return {
      start: null,
      end: null
    };
  };

  var _02Ranges = parseRange;

  var normalize$1 = function normalize(doc) {
    doc = doc.clone();

    if (!doc.numbers) {
      console.warn("Compromise: compromise-dates cannot find plugin dependency 'compromise-number'");
    } else {
      // convert 'two' to 2
      var num = doc.numbers();
      num.toNumber();
      num.toCardinal(false); // num.normalize()
    } // // expand 'aug 20-21'


    doc.contractions().expand(); // // remove adverbs

    doc.adverbs().remove(); // // 'week-end'

    doc.replace('week end', 'weekend').tag('Date'); // // 'a up to b'

    doc.replace('up to', 'upto').tag('Date'); // 'in a few years'

    var m = doc.match('in [a few] #Duration');

    if (m.found) {
      m.groups('0').replaceWith('2');
      m.tag('DateShift');
    }

    return doc;
  };

  var normalize_1 = normalize$1;

  var getDate = function getDate(doc, context) {
    // validate context a bit
    context = context || {};
    context.timezone = context.timezone || 'ETC/UTC';
    context.today = spacetime(context.today || null, context.timezone); //turn 'five' into 5..

    doc = normalize_1(doc); //interpret 'between [A] and [B]'...

    return _02Ranges(doc, context);
  };

  var find = getDate;

  var arr = [['mon', 'monday'], ['tue', 'tuesday'], ['tues', 'tuesday'], ['wed', 'wednesday'], ['thu', 'thursday'], ['thurs', 'thursday'], ['fri', 'friday'], ['sat', 'saturday'], ['sun', 'sunday'], ['jan', 'january'], ['feb', 'february'], ['mar', 'march'], ['apr', 'april'], ['jun', 'june'], ['jul', 'july'], ['aug', 'august'], ['sep', 'september'], ['sept', 'september'], ['oct', 'october'], ['nov', 'november'], ['dec', 'december']];
  arr = arr.map(function (a) {
    return {
      "short": a[0],
      "long": a[1]
    };
  });
  var _abbrevs = arr;

  var methods$1 = {
    /** overload the original json with noun information */
    json: function json(options) {
      var _this = this;

      var n = null;

      if (typeof options === 'number') {
        n = options;
        options = null;
      }

      options = options || {
        terms: false
      };
      var res = [];
      var format = options.format || 'iso';
      this.forEach(function (doc) {
        var json = doc.json(options)[0];
        var obj = find(doc, _this.context);
        var start = obj.start ? obj.start.format(format) : null;
        var end = obj.end ? obj.end.format(format) : null; // set iso strings to json result

        json.date = {
          start: start,
          end: end
        }; // add duration

        if (start && end) {
          json.date.duration = obj.start.d.diff(obj.end.d); // we don't need these

          delete json.date.duration.milliseconds;
          delete json.date.duration.seconds;
        }

        res.push(json);
      });

      if (n !== null) {
        return res[n];
      }

      return res;
    },

    /** render all dates according to a specific format */
    format: function format(fmt) {
      var _this2 = this;

      this.forEach(function (doc) {
        var obj = find(doc, _this2.context);
        var str = '';

        if (obj.start) {
          str = obj.start.format(fmt);

          if (obj.end) {
            var end = obj.start.format(fmt);

            if (str !== end) {
              str += ' to ' + end;
            }
          }

          doc.replaceWith(str, {
            keepTags: true,
            keepCase: false
          });
        }
      });
      return this;
    },

    /** replace 'Fri' with 'Friday', etc*/
    toLongForm: function toLongForm() {
      var _this3 = this;

      _abbrevs.forEach(function (a) {
        _this3.replace(a["short"], a["long"], true);
      });
      return this;
    },

    /** replace 'Friday' with 'Fri', etc*/
    toShortForm: function toShortForm() {
      var _this4 = this;

      _abbrevs.forEach(function (a) {
        _this4.replace(a["long"], a["short"], true);
      });
      return this;
    }
  };

  var opts = {
    punt: {
      weeks: 2
    }
  };

  var addMethods = function addMethods(Doc, world) {
    // our new tags
    world.addTags(_tags); // add info for the date plugin

    world.addWords(words); // run our tagger

    world.postProcess(_01Tagger);
    /**  */

    var Dates = /*#__PURE__*/function (_Doc) {
      _inherits(Dates, _Doc);

      var _super = _createSuper(Dates);

      function Dates(list, from, w) {
        var _this;

        _classCallCheck(this, Dates);

        _this = _super.call(this, list, from, w);
        _this.context = opts;
        return _this;
      }

      return Dates;
    }(Doc); //add-in methods


    Object.assign(Dates.prototype, methods$1);

    Doc.prototype.dates = function (n) {
      var context = {};

      if (n && _typeof(n) === 'object') {
        context = n;
        n = null;
      }

      context = Object.assign({}, context, opts); // let r = this.clauses()

      var dates = this.match('#Date+');

      if (typeof n === 'number') {
        dates = dates.get(n);
      }

      var d = new Dates(dates.list, this, this.world);

      if (context.today) {
        context.today = spacetime(context.today, context.timezone);
      }

      d.context = context;
      return d;
    };
  };

  var src = addMethods;

  return src;

})));
//# sourceMappingURL=compromise-dates.js.map
};
BundleModuleCode['nlp/compromise-numbers']=function (module,exports){
/* compromise-numbers 1.1.0 MIT */
(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  typeof define === 'function' && define.amd ? define(factory) :
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.compromiseNumbers = factory());
}(this, (function () { 'use strict';

  function _classCallCheck(instance, Constructor) {
    if (!(instance instanceof Constructor)) {
      throw new TypeError("Cannot call a class as a function");
    }
  }

  function _inherits(subClass, superClass) {
    if (typeof superClass !== "function" && superClass !== null) {
      throw new TypeError("Super expression must either be null or a function");
    }

    subClass.prototype = Object.create(superClass && superClass.prototype, {
      constructor: {
        value: subClass,
        writable: true,
        configurable: true
      }
    });
    if (superClass) _setPrototypeOf(subClass, superClass);
  }

  function _getPrototypeOf(o) {
    _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
      return o.__proto__ || Object.getPrototypeOf(o);
    };
    return _getPrototypeOf(o);
  }

  function _setPrototypeOf(o, p) {
    _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
      o.__proto__ = p;
      return o;
    };

    return _setPrototypeOf(o, p);
  }

  function _isNativeReflectConstruct() {
    if (typeof Reflect === "undefined" || !Reflect.construct) return false;
    if (Reflect.construct.sham) return false;
    if (typeof Proxy === "function") return true;

    try {
      Date.prototype.toString.call(Reflect.construct(Date, [], function () {}));
      return true;
    } catch (e) {
      return false;
    }
  }

  function _assertThisInitialized(self) {
    if (self === void 0) {
      throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
    }

    return self;
  }

  function _possibleConstructorReturn(self, call) {
    if (call && (typeof call === "object" || typeof call === "function")) {
      return call;
    }

    return _assertThisInitialized(self);
  }

  function _createSuper(Derived) {
    var hasNativeReflectConstruct = _isNativeReflectConstruct();

    return function _createSuperInternal() {
      var Super = _getPrototypeOf(Derived),
          result;

      if (hasNativeReflectConstruct) {
        var NewTarget = _getPrototypeOf(this).constructor;

        result = Reflect.construct(Super, arguments, NewTarget);
      } else {
        result = Super.apply(this, arguments);
      }

      return _possibleConstructorReturn(this, result);
    };
  }

  var tens = 'twenty|thirty|forty|fifty|sixty|seventy|eighty|ninety|fourty';
  var teens = 'eleven|twelve|thirteen|fourteen|fifteen|sixteen|seventeen|eighteen|nineteen'; // this is a bit of a mess

  var findNumbers = function findNumbers(doc, n) {
    var match = doc.match('#Value+'); //"50 83"

    if (match.has('#NumericValue #NumericValue')) {
      //a comma may mean two numbers
      if (match.has('#Value @hasComma #Value')) {
        match.splitAfter('@hasComma');
      } else if (match.has('#NumericValue #Fraction')) {
        match.splitAfter('#NumericValue #Fraction');
      } else {
        match = match.splitAfter('#NumericValue');
      }
    } //three-length


    if (match.has('#Value #Value #Value') && !match.has('#Multiple')) {
      //twenty-five-twenty
      if (match.has('(' + tens + ') #Cardinal #Cardinal')) {
        match = match.splitAfter('(' + tens + ') #Cardinal');
      }
    } //two-length ones


    if (match.has('#Value #Value')) {
      //june 21st 1992 is two seperate values
      if (match.has('#NumericValue #NumericValue')) {
        match = match.splitOn('#Year');
      } //sixty fifteen


      if (match.has('(' + tens + ') (' + teens + ')')) {
        match = match.splitAfter('(' + tens + ')');
      } //"72 82"


      var _double = match.match('#Cardinal #Cardinal');

      if (_double.found && !match.has('(point|decimal)')) {
        //not 'two hundred'
        if (!_double.has('#Cardinal (#Multiple|point|decimal)')) {
          //one proper way, 'twenty one', or 'hundred one'
          if (!_double.has('(' + tens + ') #Cardinal') && !_double.has('#Multiple #Value')) {
            // double = double.firstTerm()
            _double.terms().forEach(function (d) {
              match = match.splitOn(d);
            });
          }
        }
      } //seventh fifth


      if (match.match('#Ordinal #Ordinal').match('#TextValue').found && !match.has('#Multiple')) {
        //the one proper way, 'twenty first'
        if (!match.has('(' + tens + ') #Ordinal')) {
          match = match.splitAfter('#Ordinal');
        }
      } //fifth five


      if (match.has('#Ordinal #Cardinal')) {
        match = match.splitBefore('#Cardinal+');
      } //five 2017 (support '5 hundred', and 'twenty 5'


      if (match.has('#TextValue #NumericValue') && !match.has('(' + tens + '|#Multiple)')) {
        match = match.splitBefore('#NumericValue+');
      }
    } //5-8


    if (match.has('#NumberRange')) {
      match = match.splitAfter('#NumberRange');
    } //grab (n)th result


    if (typeof n === 'number') {
      match = match.get(n);
    }

    return match;
  };

  var find = findNumbers;

  //support global multipliers, like 'half-million' by doing 'million' then multiplying by 0.5
  var findModifiers = function findModifiers(str) {
    var mults = [{
      reg: /^(minus|negative)[\s\-]/i,
      mult: -1
    }, {
      reg: /^(a\s)?half[\s\-](of\s)?/i,
      mult: 0.5
    } //  {
    //   reg: /^(a\s)?quarter[\s\-]/i,
    //   mult: 0.25
    // }
    ];

    for (var i = 0; i < mults.length; i++) {
      if (mults[i].reg.test(str) === true) {
        return {
          amount: mults[i].mult,
          str: str.replace(mults[i].reg, '')
        };
      }
    }

    return {
      amount: 1,
      str: str
    };
  };

  var findModifiers_1 = findModifiers;

  var data = {
    ones: {
      zeroth: 0,
      first: 1,
      second: 2,
      third: 3,
      fourth: 4,
      fifth: 5,
      sixth: 6,
      seventh: 7,
      eighth: 8,
      ninth: 9,
      zero: 0,
      one: 1,
      two: 2,
      three: 3,
      four: 4,
      five: 5,
      six: 6,
      seven: 7,
      eight: 8,
      nine: 9
    },
    teens: {
      tenth: 10,
      eleventh: 11,
      twelfth: 12,
      thirteenth: 13,
      fourteenth: 14,
      fifteenth: 15,
      sixteenth: 16,
      seventeenth: 17,
      eighteenth: 18,
      nineteenth: 19,
      ten: 10,
      eleven: 11,
      twelve: 12,
      thirteen: 13,
      fourteen: 14,
      fifteen: 15,
      sixteen: 16,
      seventeen: 17,
      eighteen: 18,
      nineteen: 19
    },
    tens: {
      twentieth: 20,
      thirtieth: 30,
      fortieth: 40,
      fourtieth: 40,
      fiftieth: 50,
      sixtieth: 60,
      seventieth: 70,
      eightieth: 80,
      ninetieth: 90,
      twenty: 20,
      thirty: 30,
      forty: 40,
      fourty: 40,
      fifty: 50,
      sixty: 60,
      seventy: 70,
      eighty: 80,
      ninety: 90
    },
    multiples: {
      hundredth: 100,
      thousandth: 1000,
      millionth: 1e6,
      billionth: 1e9,
      trillionth: 1e12,
      quadrillionth: 1e15,
      quintillionth: 1e18,
      sextillionth: 1e21,
      septillionth: 1e24,
      hundred: 100,
      thousand: 1000,
      million: 1e6,
      billion: 1e9,
      trillion: 1e12,
      quadrillion: 1e15,
      quintillion: 1e18,
      sextillion: 1e21,
      septillion: 1e24,
      grand: 1000
    }
  };

  var isValid = function isValid(w, has) {
    if (data.ones.hasOwnProperty(w)) {
      if (has.ones || has.teens) {
        return false;
      }
    } else if (data.teens.hasOwnProperty(w)) {
      if (has.ones || has.teens || has.tens) {
        return false;
      }
    } else if (data.tens.hasOwnProperty(w)) {
      if (has.ones || has.teens || has.tens) {
        return false;
      }
    }

    return true;
  };

  var validate = isValid;

  var parseDecimals = function parseDecimals(arr) {
    var str = '0.';

    for (var i = 0; i < arr.length; i++) {
      var w = arr[i];

      if (data.ones.hasOwnProperty(w) === true) {
        str += data.ones[w];
      } else if (data.teens.hasOwnProperty(w) === true) {
        str += data.teens[w];
      } else if (data.tens.hasOwnProperty(w) === true) {
        str += data.tens[w];
      } else if (/^[0-9]$/.test(w) === true) {
        str += w;
      } else {
        return 0;
      }
    }

    return parseFloat(str);
  };

  var parseDecimals_1 = parseDecimals;

  //parse a string like "4,200.1" into Number 4200.1
  var parseNumeric = function parseNumeric(str) {
    //remove ordinal - 'th/rd'
    str = str.replace(/1st$/, '1');
    str = str.replace(/2nd$/, '2');
    str = str.replace(/3rd$/, '3');
    str = str.replace(/([4567890])r?th$/, '$1'); //remove prefixes

    str = str.replace(/^[$€¥£¢]/, ''); //remove suffixes

    str = str.replace(/[%$€¥£¢]$/, ''); //remove commas

    str = str.replace(/,/g, ''); //split '5kg' from '5'

    str = str.replace(/([0-9])([a-z\u00C0-\u00FF]{1,2})$/, '$1');
    return str;
  };

  var parseNumeric_1 = parseNumeric;

  var improperFraction = /^([0-9,\. ]+)\/([0-9,\. ]+)$/; //some numbers we know

  var casualForms = {
    // 'a few': 3,
    'a couple': 2,
    'a dozen': 12,
    'two dozen': 24,
    zero: 0
  }; // a 'section' is something like 'fifty-nine thousand'
  // turn a section into something we can add to - like 59000

  var section_sum = function section_sum(obj) {
    return Object.keys(obj).reduce(function (sum, k) {
      sum += obj[k];
      return sum;
    }, 0);
  }; //turn a string into a number


  var parse = function parse(str) {
    //convert some known-numbers
    if (casualForms.hasOwnProperty(str) === true) {
      return casualForms[str];
    } //'a/an' is 1


    if (str === 'a' || str === 'an') {
      return 1;
    }

    var modifier = findModifiers_1(str);
    str = modifier.str;
    var last_mult = null;
    var has = {};
    var sum = 0;
    var isNegative = false;
    var terms = str.split(/[ -]/);

    for (var i = 0; i < terms.length; i++) {
      var w = terms[i];
      w = parseNumeric_1(w);

      if (!w || w === 'and') {
        continue;
      }

      if (w === '-' || w === 'negative') {
        isNegative = true;
        continue;
      }

      if (w.charAt(0) === '-') {
        isNegative = true;
        w = w.substr(1);
      } //decimal mode


      if (w === 'point') {
        sum += section_sum(has);
        sum += parseDecimals_1(terms.slice(i + 1, terms.length));
        sum *= modifier.amount;
        return sum;
      } //improper fraction


      var fm = w.match(improperFraction);

      if (fm) {
        var num = parseFloat(fm[1].replace(/[, ]/g, ''));
        var denom = parseFloat(fm[2].replace(/[, ]/g, ''));

        if (denom) {
          sum += num / denom || 0;
        }

        continue;
      } //prevent mismatched units, like 'seven eleven'


      if (validate(w, has) === false) {
        return null;
      } //buildOut section, collect 'has' values


      if (/^[0-9\.]+$/.test(w)) {
        has['ones'] = parseFloat(w); //not technically right
      } else if (data.ones.hasOwnProperty(w) === true) {
        has['ones'] = data.ones[w];
      } else if (data.teens.hasOwnProperty(w) === true) {
        has['teens'] = data.teens[w];
      } else if (data.tens.hasOwnProperty(w) === true) {
        has['tens'] = data.tens[w];
      } else if (data.multiples.hasOwnProperty(w) === true) {
        var mult = data.multiples[w]; //something has gone wrong : 'two hundred five hundred'

        if (mult === last_mult) {
          return null;
        } //support 'hundred thousand'
        //this one is tricky..


        if (mult === 100 && terms[i + 1] !== undefined) {
          // has['hundreds']=
          var w2 = terms[i + 1];

          if (data.multiples[w2]) {
            mult *= data.multiples[w2]; //hundredThousand/hundredMillion

            i += 1;
          }
        } //natural order of things
        //five thousand, one hundred..


        if (last_mult === null || mult < last_mult) {
          sum += (section_sum(has) || 1) * mult;
          last_mult = mult;
          has = {};
        } else {
          //maybe hundred .. thousand
          sum += section_sum(has);
          last_mult = mult;
          sum = (sum || 1) * mult;
          has = {};
        }
      }
    } //dump the remaining has values


    sum += section_sum(has); //post-process add modifier

    sum *= modifier.amount;
    sum *= isNegative ? -1 : 1; //dont return 0, if it went straight-through

    if (sum === 0 && Object.keys(has).length === 0) {
      return null;
    }

    return sum;
  };

  var toNumber = parse;

  var parseNumeric$1 = function parseNumeric(str, p) {
    str = str.replace(/,/g, ''); //parse a numeric-number (easy)

    var arr = str.split(/^([^0-9]*)([0-9.,]*)([^0-9]*)$/);

    if (arr && arr[2] && p.terms().length < 2) {
      var num = parseFloat(arr[2] || str); //ensure that num is an actual number

      if (typeof num !== 'number') {
        num = null;
      } // strip an ordinal off the suffix


      var suffix = arr[3] || '';

      if (suffix === 'st' || suffix === 'nd' || suffix === 'rd' || suffix === 'th') {
        suffix = '';
      } // support M for million, k for thousand


      if (suffix === 'm' || suffix === 'M') {
        num *= 1000000;
        suffix = '';
      }

      if (suffix === 'k' || suffix === 'k') {
        num *= 1000;
        suffix = '';
      }

      return {
        prefix: arr[1] || '',
        num: num,
        suffix: suffix
      };
    }

    return null;
  }; // get a numeric value from this phrase


  var parseNumber = function parseNumber(p) {
    var str = p.text('reduced'); // is it in '3,123' format?

    var hasComma = /[0-9],[0-9]/.test(p.text('text')); // parse a numeric-number like '$4.00'

    var res = parseNumeric$1(str, p);

    if (res !== null) {
      res.hasComma = hasComma;
      return res;
    } //parse a text-numer (harder)


    var num = toNumber(str);
    return {
      hasComma: hasComma,
      prefix: '',
      num: num,
      suffix: ''
    };
  };

  var parse$1 = parseNumber;

  // handle 'one bottle', 'two bottles'
  var agreeUnits = function agreeUnits(agree, val, obj) {
    if (agree === false) {
      return;
    }

    var unit = val.lookAhead('^(#Unit|#Noun)'); // don't do these

    if (unit.has('(#Address|#Money|#Percent)') || val.has('#Ordinal')) {
      return;
    }

    if (obj.num === 1) {
      unit.nouns().toSingular();
    } else if (unit.has('#Singular')) {
      unit.nouns().toPlural();
    }
  };

  var _agreeUnits = agreeUnits;

  /**
   * turn big numbers, like 2.3e+22, into a string with a ton of trailing 0's
   * */
  var numToString = function numToString(n) {
    if (n < 1000000) {
      return String(n);
    }

    var str;

    if (typeof n === 'number') {
      str = n.toFixed(0);
    } else {
      str = n;
    }

    if (str.indexOf('e+') === -1) {
      return str;
    }

    return str.replace('.', '').split('e+').reduce(function (p, b) {
      return p + Array(b - p.length + 2).join(0);
    });
  };

  var _toString = numToString; // console.log(numToString(2.5e+22));

  /**
   * turns an integer/float into.ber, like 'fifty-five'
   */

  var tens_mapping = [['ninety', 90], ['eighty', 80], ['seventy', 70], ['sixty', 60], ['fifty', 50], ['forty', 40], ['thirty', 30], ['twenty', 20]];
  var ones_mapping = ['', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen'];
  var sequence = [[1e24, 'septillion'], [1e20, 'hundred sextillion'], [1e21, 'sextillion'], [1e20, 'hundred quintillion'], [1e18, 'quintillion'], [1e17, 'hundred quadrillion'], [1e15, 'quadrillion'], [1e14, 'hundred trillion'], [1e12, 'trillion'], [1e11, 'hundred billion'], [1e9, 'billion'], [1e8, 'hundred million'], [1e6, 'million'], [100000, 'hundred thousand'], [1000, 'thousand'], [100, 'hundred'], [1, 'one']]; //turn number into an array of magnitudes, like [[5, million], [2, hundred]]

  var breakdown_magnitudes = function breakdown_magnitudes(num) {
    var working = num;
    var have = [];
    sequence.forEach(function (a) {
      if (num >= a[0]) {
        var howmany = Math.floor(working / a[0]);
        working -= howmany * a[0];

        if (howmany) {
          have.push({
            unit: a[1],
            count: howmany
          });
        }
      }
    });
    return have;
  }; //turn numbers from 100-0 into their text


  var breakdown_hundred = function breakdown_hundred(num) {
    var arr = [];

    if (num > 100) {
      return arr; //something bad happened..
    }

    for (var i = 0; i < tens_mapping.length; i++) {
      if (num >= tens_mapping[i][1]) {
        num -= tens_mapping[i][1];
        arr.push(tens_mapping[i][0]);
      }
    } //(hopefully) we should only have 20-0 now


    if (ones_mapping[num]) {
      arr.push(ones_mapping[num]);
    }

    return arr;
  };
  /** print-out 'point eight nine'*/


  var handle_decimal = function handle_decimal(num) {
    var names = ['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine'];
    var arr = []; //parse it out like a string, because js math is such shit

    var str = _toString(num);
    var decimal = str.match(/\.([0-9]+)/);

    if (!decimal || !decimal[0]) {
      return arr;
    }

    arr.push('point');
    var decimals = decimal[0].split('');

    for (var i = 0; i < decimals.length; i++) {
      arr.push(names[decimals[i]]);
    }

    return arr;
  };
  /** turns an integer into a textual number */


  var to_text = function to_text(num) {
    // handle zero, quickly
    if (num === 0 || num === '0') {
      return 'zero'; // no?
    } //big numbers, north of sextillion, aren't gonna work well..
    //keep them small..


    if (num > 1e21) {
      num = _toString(num);
    }

    var arr = []; //handle negative numbers

    if (num < 0) {
      arr.push('minus');
      num = Math.abs(num);
    } //break-down into units, counts


    var units = breakdown_magnitudes(num); //build-up the string from its components

    for (var i = 0; i < units.length; i++) {
      var unit_name = units[i].unit;

      if (unit_name === 'one') {
        unit_name = ''; //put an 'and' in here

        if (arr.length > 1) {
          arr.push('and');
        }
      }

      arr = arr.concat(breakdown_hundred(units[i].count));
      arr.push(unit_name);
    } //also support decimals - 'point eight'


    arr = arr.concat(handle_decimal(num)); //remove empties

    arr = arr.filter(function (s) {
      return s;
    });

    if (arr.length === 0) {
      arr[0] = '';
    }

    return arr.join(' ');
  };

  var toText = to_text; // console.log(to_text(-1000.8));

  /**
   * turn a number like 5 into an ordinal like 5th
   */

  var numOrdinal = function numOrdinal(num) {
    if (!num && num !== 0) {
      return null;
    } //the teens are all 'th'


    var tens = num % 100;

    if (tens > 10 && tens < 20) {
      return String(num) + 'th';
    } //the rest of 'em


    var mapping = {
      0: 'th',
      1: 'st',
      2: 'nd',
      3: 'rd'
    };
    var str = _toString(num);
    var last = str.slice(str.length - 1, str.length);

    if (mapping[last]) {
      str += mapping[last];
    } else {
      str += 'th';
    }

    return str;
  };

  var numOrdinal_1 = numOrdinal;

  var irregulars = {
    one: 'first',
    two: 'second',
    three: 'third',
    five: 'fifth',
    eight: 'eighth',
    nine: 'ninth',
    twelve: 'twelfth',
    twenty: 'twentieth',
    thirty: 'thirtieth',
    forty: 'fortieth',
    fourty: 'fourtieth',
    fifty: 'fiftieth',
    sixty: 'sixtieth',
    seventy: 'seventieth',
    eighty: 'eightieth',
    ninety: 'ninetieth'
  };
  /**
   * convert a javascript number to 'twentieth' format
   * */

  var textOrdinal = function textOrdinal(num) {
    var words = toText(num).split(' '); //convert the last number to an ordinal

    var last = words[words.length - 1];

    if (irregulars.hasOwnProperty(last)) {
      words[words.length - 1] = irregulars[last];
    } else {
      words[words.length - 1] = last.replace(/y$/, 'i') + 'th';
    }

    return words.join(' ');
  };

  var textOrdinal_1 = textOrdinal;

  var prefixes = {
    '¢': 'cents',
    $: 'dollars',
    '£': 'pounds',
    '¥': 'yen',
    '€': 'euros',
    '₡': 'colón',
    '฿': 'baht',
    '₭': 'kip',
    '₩': 'won',
    '₹': 'rupees',
    '₽': 'ruble',
    '₺': 'liras'
  };
  var suffixes = {
    '%': 'percent',
    s: 'seconds',
    cm: 'centimetres',
    km: 'kilometres'
  };
  var _symbols = {
    prefixes: prefixes,
    suffixes: suffixes
  };

  var prefixes$1 = _symbols.prefixes;
  var suffixes$1 = _symbols.suffixes;
  var isCurrency = {
    usd: true,
    eur: true,
    jpy: true,
    gbp: true,
    cad: true,
    aud: true,
    chf: true,
    cny: true,
    hkd: true,
    nzd: true,
    kr: true,
    rub: true
  }; // convert $ to 'dollars', etc

  var prefixToText = function prefixToText(obj) {
    // turn 5% to 'five percent'
    if (prefixes$1.hasOwnProperty(obj.prefix)) {
      obj.suffix += prefixes$1[obj.prefix];
      obj.prefix = '';
    } //turn 5km to 'five kilometres'


    if (suffixes$1.hasOwnProperty(obj.suffix)) {
      obj.suffix = suffixes$1[obj.suffix];
    } //uppercase lost case for 'USD', etc


    if (isCurrency.hasOwnProperty(obj.suffix)) {
      obj.suffix = obj.suffix.toUpperCase();
    } // add a space, if it exists


    if (obj.suffix) {
      obj.suffix = ' ' + obj.suffix;
    }

    return obj;
  }; //business-logic for converting a cardinal-number to other forms


  var makeNumber = function makeNumber(obj, isText, isOrdinal) {
    var num = String(obj.num);

    if (isText) {
      obj = prefixToText(obj);

      if (isOrdinal) {
        //ordinal-text
        num = textOrdinal_1(num);
        return "".concat(obj.prefix || '').concat(num).concat(obj.suffix || '');
      } //cardinal-text


      num = toText(num);
      return "".concat(obj.prefix || '').concat(num).concat(obj.suffix || '');
    } //ordinal-number


    if (isOrdinal) {
      num = numOrdinal_1(num); // support '5th percent'

      obj = prefixToText(obj);
      return "".concat(obj.prefix || '').concat(num).concat(obj.suffix || '');
    } // support comma format


    if (obj.hasComma === true) {
      num = obj.num.toLocaleString();
    } // cardinal-number


    num = _toString(num); // support very large numbers

    return "".concat(obj.prefix || '').concat(num).concat(obj.suffix || '');
  };

  var makeNumber_1 = makeNumber;

  var methods = {
    /** overloaded json method with additional number information */
    json: function json(options) {
      var n = null;

      if (typeof options === 'number') {
        n = options;
        options = null;
      }

      options = options || {
        text: true,
        normal: true,
        trim: true,
        terms: true
      };
      var res = [];
      this.forEach(function (doc) {
        var json = doc.json(options)[0];
        var obj = parse$1(doc);
        json.prefix = obj.prefix;
        json.number = obj.num;
        json.suffix = obj.suffix;
        json.cardinal = makeNumber_1(obj, false, false);
        json.ordinal = makeNumber_1(obj, false, true);
        json.textCardinal = makeNumber_1(obj, true, false);
        json.textOrdinal = makeNumber_1(obj, true, true);
        res.push(json);
      });

      if (n !== null) {
        return res[n];
      }

      return res;
    },

    /** two of what? */
    units: function units() {
      var m = this.lookAhead('(#Unit|#Noun)+');
      m = m.splitAfter('@hasComma').first();
      m = m.not('#Pronoun');
      return m.first();
    },

    /** return only ordinal numbers */
    isOrdinal: function isOrdinal() {
      return this["if"]('#Ordinal');
    },

    /** return only cardinal numbers*/
    isCardinal: function isCardinal() {
      return this["if"]('#Cardinal');
    },

    /** convert to numeric form like '8' or '8th' */
    toNumber: function toNumber() {
      this.forEach(function (val) {
        var obj = parse$1(val);

        if (obj.num === null) {
          return;
        }

        var str = makeNumber_1(obj, false, val.has('#Ordinal'));
        val.replaceWith(str, true);
        val.tag('NumericValue');
      });
      return this;
    },

    /** add commas, or nicer formatting for numbers */
    toLocaleString: function toLocaleString() {
      this.forEach(function (val) {
        var obj = parse$1(val);

        if (obj.num === null) {
          return;
        }

        obj.num = obj.num.toLocaleString();
        var str = makeNumber_1(obj, false, val.has('#Ordinal'));
        val.replaceWith(str, true);
      });
      return this;
    },

    /** convert to text form - like 'eight' or 'eigth'*/
    toText: function toText() {
      this.forEach(function (val) {
        var obj = parse$1(val);

        if (obj.num === null) {
          return;
        }

        var str = makeNumber_1(obj, true, val.has('#Ordinal'));
        val.replaceWith(str, true);
        val.tag('TextValue');
      });
      return this;
    },

    /** convert to cardinal form, like 'eight', or '8' */
    toCardinal: function toCardinal(agree) {
      var m = this["if"]('#Ordinal');
      m.forEach(function (val) {
        var obj = parse$1(val);

        if (obj.num === null) {
          return;
        }

        var str = makeNumber_1(obj, val.has('#TextValue'), false); // a hack for number-ranges

        if (val.has('#NumberRange')) {
          var t = val.termList()[0];

          if (t.text && t.post === '') {
            t.post = ' ';
          }
        } // change the number text


        val.replaceWith(str, true);
        val.tag('Cardinal'); // turn unit into plural -> 'seven beers'

        _agreeUnits(agree, val, obj);
      });
      return this;
    },

    /** convert to ordinal form, like 'eighth', or '8th' */
    toOrdinal: function toOrdinal() {
      var _this = this;

      var m = this["if"]('#Cardinal');
      m.forEach(function (val) {
        var obj = parse$1(val);

        if (obj.num === null) {
          return;
        }

        var str = makeNumber_1(obj, val.has('#TextValue'), true); // a hack for number-ranges

        if (val.has('#NumberRange')) {
          var t = val.termList()[0];

          if (t.text && t.post === '') {
            t.post = ' ';
          }
        } // change the number text


        val.replaceWith(str, true);
        val.tag('Ordinal'); // turn unit into singular -> 'seventh beer'

        var unit = _this.lookAhead('^#Plural');

        if (unit.found) {
          unit.nouns().toSingular();
        }
      });
      return this;
    },

    /** return only numbers that are == n */
    isEqual: function isEqual(n) {
      return this.filter(function (val) {
        var num = parse$1(val).num;
        return num === n;
      });
    },

    /** return only numbers that are > n*/
    greaterThan: function greaterThan(n) {
      return this.filter(function (val) {
        var num = parse$1(val).num;
        return num > n;
      });
    },

    /** return only numbers that are < n*/
    lessThan: function lessThan(n) {
      return this.filter(function (val) {
        var num = parse$1(val).num;
        return num < n;
      });
    },

    /** return only numbers > min and < max */
    between: function between(min, max) {
      return this.filter(function (val) {
        var num = parse$1(val).num;
        return num > min && num < max;
      });
    },

    /** set these number to n */
    set: function set(n, agree) {
      if (n === undefined) {
        return this; // don't bother
      }

      if (typeof n === 'string') {
        n = toNumber(n);
      }

      this.forEach(function (val) {
        var obj = parse$1(val);
        obj.num = n;

        if (obj.num === null) {
          return;
        }

        var str = makeNumber_1(obj, val.has('#TextValue'), val.has('#Ordinal'));
        val = val.not('#Currency');
        val.replaceWith(str, true); // handle plural/singular unit

        _agreeUnits(agree, val, obj);
      });
      return this;
    },
    add: function add(n, agree) {
      if (!n) {
        return this; // don't bother
      }

      if (typeof n === 'string') {
        n = toNumber(n);
      }

      this.forEach(function (val) {
        var obj = parse$1(val);

        if (obj.num === null) {
          return;
        }

        obj.num += n;
        var str = makeNumber_1(obj, val.has('#TextValue'), val.has('#Ordinal'));
        val = val.not('#Currency');
        val.replaceWith(str, true); // handle plural/singular unit

        _agreeUnits(agree, val, obj);
      });
      return this;
    },

    /** decrease each number by n*/
    subtract: function subtract(n, agree) {
      return this.add(n * -1, agree);
    },

    /** increase each number by 1 */
    increment: function increment(agree) {
      this.add(1, agree);
      return this;
    },

    /** decrease each number by 1 */
    decrement: function decrement(agree) {
      this.add(-1, agree);
      return this;
    },

    /** return things like CCXX*/
    romanNumerals: function romanNumerals(n) {
      var m = this.match('#RomanNumeral').numbers();

      if (typeof n === 'number') {
        m = m.get(n);
      }

      return m;
    },

    /** split-apart suffix and number */
    normalize: function normalize() {
      var keep = {
        '%': true
      };
      this.forEach(function (val) {
        var obj = parse$1(val);

        if (obj.num !== null && obj.suffix && keep[obj.suffix] !== true) {
          var prefix = obj.prefix || '';
          val = val.replaceWith(prefix + obj.num + ' ' + obj.suffix);
          return;
        }
      });
      return this;
    },

    /** retrieve the parsed number */
    get: function get(n) {
      var arr = [];
      this.forEach(function (doc) {
        arr.push(parse$1(doc).num);
      });

      if (n !== undefined) {
        return arr[n];
      }

      return arr;
    }
  }; // aliases

  methods.toNice = methods.toLocaleString;
  methods.isBetween = methods.between;
  methods.minus = methods.subtract;
  methods.plus = methods.add;
  methods.equals = methods.isEqual;
  var methods_1 = methods;

  //from wikipedia's {{infobox currency}}, Dec 2020
  var currencies = [{
    dem: 'american',
    name: 'dollar',
    iso: 'usd',
    sub: 'cent',
    sym: ['$', 'US$', 'U$']
  }, {
    name: 'euro',
    iso: 'eur',
    sub: 'cent',
    sym: ['€']
  }, {
    dem: 'british',
    name: 'pound',
    iso: 'gbp',
    sub: 'penny',
    alias: {
      sterling: true
    },
    sym: ['£']
  }, {
    name: 'renminbi',
    iso: 'cny',
    sub: 'yuán',
    plural: 'yuán',
    alias: {
      yuan: true
    },
    sym: ['元'] //'¥'

  }, {
    dem: 'japanese',
    name: 'yen',
    iso: 'jpy',
    sub: 'sen',
    sym: ['¥', '円', '圓']
  }, // kr
  {
    dem: 'swedish',
    name: 'krona',
    iso: 'sek',
    sub: 'öre',
    alias: {
      ore: true,
      kronor: true
    },
    sym: ['kr']
  }, {
    dem: 'estonian',
    name: 'kroon',
    iso: 'eek',
    sub: 'sent',
    sym: ['kr']
  }, {
    dem: 'norwegian',
    name: 'krone',
    iso: 'nok',
    sub: 'øre',
    sym: ['kr']
  }, {
    dem: 'icelandic',
    name: 'króna',
    iso: 'isk',
    sym: ['kr']
  }, {
    dem: 'danish',
    name: 'krone',
    iso: 'dkk',
    sub: 'øre',
    sym: ['kr.']
  }, // {
  //   dem: 'scandinavian',
  //   name: 'Monetary Union',
  //   sub: 'øre',
  //   sym: ['kr.'],
  // },
  // 'k'
  {
    dem: 'zambian',
    name: 'kwacha',
    iso: 'zmw',
    sub: 'ngwee',
    sym: ['K']
  }, {
    dem: 'malawian',
    name: 'kwacha',
    iso: 'mwk',
    sub: 'tambala',
    sym: ['K']
  }, // misc
  {
    dem: 'greek',
    name: 'drachma',
    iso: 'grd',
    sub: 'leptοn',
    sym: ['Δρχ.', 'Δρ.', '₯']
  }, {
    dem: 'eastern caribbean',
    name: 'dollar',
    iso: 'xcd',
    sub: 'cent',
    sym: ['$']
  }, {
    dem: 'finnish',
    name: 'markka',
    iso: 'fim',
    sub: 'penni',
    sym: ['mk']
  }, {
    dem: 'polish',
    name: 'złoty',
    iso: 'pln',
    sub: 'grosz',
    sym: ['zł']
  }, {
    dem: 'slovenian',
    name: 'tolar',
    iso: 'sit',
    sub: 'stotin',
    sym: []
  }, {
    dem: 'australian',
    name: 'dollar',
    iso: 'aud',
    sub: 'cent',
    sym: ['$', 'A$', 'AU$']
  }, {
    dem: 'deutsche',
    name: 'mark',
    iso: 'dem',
    sub: 'pfennig',
    sym: ['DM']
  }, {
    dem: 'thai',
    name: 'baht',
    iso: 'thb',
    sub: 'satang',
    sym: ['฿']
  }, {
    dem: 'canadian',
    name: 'dollar',
    iso: 'cad',
    sub: 'cent',
    sym: ['$', 'Can$', 'C$', 'CA$', 'CAD']
  }, {
    dem: 'mexican',
    name: 'peso',
    iso: 'mxn',
    sub: 'centavo',
    sym: ['$', 'Mex$']
  }, {
    dem: 'spanish',
    name: 'peseta',
    iso: 'esp',
    sub: 'céntimo',
    sym: ['Pta']
  }, {
    dem: 'new zealand',
    name: 'dollar',
    iso: 'nzd',
    sub: 'cent',
    sym: ['$', 'NZ$']
  }, {
    dem: 'chilean',
    name: 'peso',
    iso: 'clp',
    sub: 'Centavo',
    sym: ['Cifrão', '$']
  }, {
    dem: 'nigerian',
    name: 'naira',
    iso: 'ngn',
    sub: 'kobo',
    sym: ['₦']
  }, {
    dem: 'austrian',
    name: 'schilling',
    iso: 'ats',
    sub: 'groschen',
    sym: ['S', 'öS']
  }, {
    dem: 'guatemalan',
    name: 'quetzal',
    iso: 'gtq',
    sub: 'centavo',
    sym: ['Q']
  }, {
    dem: 'philippine',
    name: 'peso',
    iso: 'php',
    sub: 'sentimo',
    sym: ['₱']
  }, {
    dem: 'hungarian',
    name: 'forint',
    iso: 'huf',
    sub: 'fillér',
    sym: ['Ft']
  }, {
    dem: 'russian',
    name: 'ruble',
    iso: 'rub',
    sub: 'kopeyka',
    sym: ['₽', 'руб', 'р.']
  }, {
    dem: 'kuwaiti',
    name: 'dinar',
    iso: 'kwd',
    sub: 'fils',
    sym: ['د.ك', 'KD']
  }, {
    dem: 'israeli',
    name: 'new shekel',
    iso: 'ils',
    sub: 'agora',
    sym: ['₪']
  }, {
    dem: 'latvian',
    name: 'lats',
    iso: 'lvl',
    sub: 'santīms',
    sym: ['Ls']
  }, {
    dem: 'kazakhstani',
    name: 'tenge',
    iso: 'kzt',
    sub: 'tıyn',
    sym: ['₸']
  }, {
    dem: 'iraqi',
    name: 'dinar',
    iso: 'iqd',
    sub: 'fils',
    sym: ['د.ع']
  }, {
    dem: 'bahamian',
    name: 'dollar',
    iso: 'bsd',
    sub: 'cent',
    sym: ['$', 'B$']
  }, {
    dem: 'seychellois',
    name: 'rupee',
    iso: 'scr',
    sub: 'cent',
    sym: ['SCR', 'SR']
  }, {
    dem: 'albanian',
    name: 'lek',
    iso: 'all',
    sub: 'qindarkë',
    sym: ['L']
  }, {
    dem: 'bulgarian',
    name: 'lev',
    iso: 'bgn',
    sub: 'stotinka',
    sym: ['лв.']
  }, {
    dem: 'irish',
    name: 'pound',
    iso: 'iep',
    sym: ['£', 'IR£']
  }, {
    name: 'cfp franc',
    iso: 'xpf',
    sym: ['f']
  }, {
    dem: 'south african',
    name: 'rand',
    iso: 'zar',
    sub: 'cent',
    sym: ['R']
  }, {
    dem: 'south korean',
    name: 'won',
    iso: 'krw',
    sub: 'jeon',
    plural: 'won',
    sym: ['₩']
  }, {
    dem: 'north korean',
    name: 'won',
    iso: 'kpw',
    sub: 'chon',
    plural: 'won',
    sym: ['₩']
  }, {
    dem: 'portuguese',
    name: 'escudo',
    iso: 'pte',
    sub: 'centavo',
    sym: []
  }, {
    dem: 'ghanaian',
    name: 'cedi',
    iso: 'ghs',
    sub: 'pesewa',
    sym: ['GH₵']
  }, {
    dem: 'hong kong',
    name: 'dollar',
    iso: 'hkd',
    sub: '毫',
    sym: ['$']
  }, {
    dem: 'new taiwan',
    name: 'dollar',
    iso: 'twd',
    sub: 'dime',
    sym: ['NT$']
  }, {
    dem: 'east german',
    name: 'mark',
    iso: 'ddm',
    sub: 'pfennig',
    sym: ['M']
  }, {
    dem: 'namibian',
    name: 'dollar',
    iso: 'nad',
    sub: 'cent',
    sym: ['$']
  }, {
    dem: 'malaysian',
    name: 'ringgit',
    iso: 'myr',
    sub: 'sen',
    sym: ['RM']
  }, {
    dem: 'swiss',
    name: 'franc',
    iso: 'chf',
    sym: ['Rp.']
  }, {
    dem: 'panamanian',
    name: 'balboa',
    iso: 'pab',
    sub: 'centésimo',
    sym: ['B/.']
  }, {
    dem: 'indonesian',
    name: 'rupiah',
    iso: 'idr',
    sub: 'sen',
    sym: ['Rp']
  }, {
    dem: 'brunei',
    name: 'dollar',
    iso: 'bnd',
    sub: 'sen',
    sym: ['$', 'B$']
  }, {
    dem: 'venezuelan',
    name: 'bolívar',
    iso: 'vef',
    sub: 'céntimo',
    sym: ['Bs.F', 'Bs.']
  }, {
    dem: 'macedonian',
    name: 'denar',
    iso: 'mkd',
    sub: 'deni',
    sym: ['den']
  }, {
    dem: 'mauritanian',
    name: 'ouguiya',
    iso: 'mru',
    sub: 'khoums',
    sym: ['UM']
  }, {
    dem: 'argentine',
    name: 'peso',
    iso: 'ars',
    sub: 'centavo',
    sym: ['$']
  }, {
    dem: 'libyan',
    name: 'dinar',
    iso: 'lyd',
    sub: 'dirham',
    sym: ['LD', 'ل.د']
  }, {
    dem: 'jordanian',
    name: 'dinar',
    iso: 'jod',
    sub: 'dirham',
    sym: ['د.أ']
  }, {
    dem: 'french',
    name: 'franc',
    iso: 'frf',
    sub: 'centime',
    sym: ['F', 'Fr', 'FF', '₣']
  }, {
    dem: 'syrian',
    name: 'pound',
    iso: 'syp',
    sub: 'piastre',
    sym: ['LS', '£S']
  }, {
    dem: 'belize',
    name: 'dollar',
    iso: 'bzd',
    sub: 'cent',
    sym: ['$']
  }, {
    dem: 'saudi',
    name: 'riyal',
    iso: 'sar',
    sub: 'halalah',
    sym: ['SAR', 'ر.س', ' ﷼']
  }, {
    dem: 'surinamese',
    name: 'dollar',
    iso: 'srd',
    sub: 'cent',
    sym: ['$']
  }, {
    dem: 'singapore',
    name: 'dollar',
    iso: 'sgd',
    sub: 'cent',
    sym: ['S$', '$']
  }, {
    dem: 'nepalese',
    name: 'rupee',
    iso: 'npr',
    sub: 'Paisa',
    sym: ['रु ₨', 'Re']
  }, {
    dem: 'macanese',
    name: 'pataca',
    iso: 'mop',
    sub: 'ho',
    sym: ['MOP$']
  }, {
    dem: 'nicaraguan',
    name: 'córdoba',
    iso: 'nio',
    sub: 'centavo',
    sym: ['C$']
  }, {
    dem: 'bangladeshi',
    name: 'taka',
    iso: 'bdt',
    sub: 'poysha',
    sym: ['৳']
  }, {
    dem: 'indian',
    name: 'rupee',
    iso: 'inr',
    sub: 'paisa',
    sym: ['₹']
  }, {
    dem: 'maldivian',
    name: 'rufiyaa',
    iso: 'mvr',
    sub: 'laari',
    sym: ['Rf', 'MRf', 'MVR', '.ރ ']
  }, {
    dem: 'sri lankan',
    name: 'rupee',
    iso: 'lkr',
    sub: 'Cents',
    sym: ['Rs', 'රු', 'ரூ']
  }, {
    dem: 'bhutanese',
    name: 'ngultrum',
    iso: 'btn',
    sub: 'chhertum',
    sym: ['Nu.']
  }, {
    dem: 'turkish',
    name: 'lira',
    iso: 'try',
    sub: 'new kuruş',
    sym: ['YTL']
  }, {
    dem: 'serbian',
    name: 'dinar',
    iso: 'rsd',
    sub: 'para',
    sym: ['din', 'дин']
  }, {
    dem: 'bosnia and herzegovina',
    name: 'convertible mark',
    iso: 'bam',
    sub: 'Fening/Pfenig',
    sym: ['KM']
  }, {
    dem: 'botswana',
    name: 'pula',
    iso: 'bwp',
    sub: 'thebe',
    sym: ['p']
  }, {
    dem: 'swazi',
    name: 'lilangeni',
    iso: 'szl',
    sub: 'cent',
    sym: ['L', 'E']
  }, {
    dem: 'lithuanian',
    name: 'litas',
    iso: 'ltl',
    sub: 'centas',
    sym: ['Lt', 'ct']
  }, {
    dem: 'mauritian',
    name: 'rupee',
    iso: 'mur',
    sub: 'cent',
    sym: ['₨']
  }, {
    dem: 'pakistani',
    name: 'rupee',
    iso: 'pkr',
    sub: 'Paisa',
    sym: ['₨']
  }, {
    dem: 'maltese',
    name: 'lira',
    iso: 'mtl',
    sub: 'cent',
    sym: ['₤', 'Lm']
  }, {
    dem: 'cypriot',
    name: 'pound',
    iso: 'cyp',
    sub: 'cent',
    sym: ['£']
  }, {
    dem: 'moldovan',
    name: 'leu',
    iso: 'mdl',
    sub: 'ban',
    sym: ['l']
  }, {
    dem: 'croatian',
    name: 'kuna',
    iso: 'hrk',
    sub: 'lipa',
    sym: ['kn']
  }, {
    dem: 'afghan',
    name: 'afghani',
    iso: 'afn',
    sub: 'pul',
    sym: ['؋', 'Af', 'Afs']
  }, {
    dem: 'ecuadorian',
    name: 'sucre',
    iso: 'ecs',
    sub: 'centavo',
    sym: ['S/.']
  }, {
    dem: 'sierra leonean',
    name: 'leone',
    iso: 'sll',
    sub: 'cent',
    sym: ['Le']
  } // {
  //
  //   name: 'European Currency Unit',
  //   iso: 'xeu',
  //   sym: ['₠'],
  // },
  // {
  //
  //   name: 'Special drawing rights',
  //   iso: 'xdr',
  //   sym: ['SDR'],
  // },
  // {
  //
  //   name: 'Unidad de Valor Constante',
  //   iso: 'ecv',
  // },
  ];

  var symbols = {};
  currencies.forEach(function (o) {
    o.sym.forEach(function (str) {
      symbols[str] = symbols[str] || o.iso;
    });
    symbols[o.iso] = symbols[o.iso] || o.iso;
  }); // parse 'australian dollars'

  var getNamedCurrency = function getNamedCurrency(doc) {
    var m = doc.match('#Currency+');
    m.nouns().toSingular(); // 'dollars'➔'dollar'

    var str = m.text('reduced');
    return currencies.find(function (o) {
      // 'mexcan peso'
      if (str === "".concat(o.dem, " ").concat(o.name)) {
        return o;
      } // 'CAD'


      if (str === o.iso) {
        return o;
      } // 'cent'


      if (str === o.sub) {
        return o;
      } // 'peso'


      if (str === o.name) {
        return o;
      } // any other alt names


      if (o.alias && o.alias[str] === true) {
        return o;
      }

      return false;
    });
  }; // turn '£' into GBP


  var getBySymbol = function getBySymbol(obj) {
    // do suffix first, for '$50CAD'
    if (obj.suffix && symbols.hasOwnProperty(obj.suffix)) {
      return currencies.find(function (o) {
        return o.iso === symbols[obj.suffix];
      });
    } // parse prefix for '£50'


    if (obj.prefix && symbols.hasOwnProperty(obj.prefix)) {
      return currencies.find(function (o) {
        return o.iso === symbols[obj.prefix];
      });
    }

    return null;
  };

  var parseMoney = function parseMoney(doc) {
    var res = parse$1(doc);
    var found = getBySymbol(res) || getNamedCurrency(doc) || {};
    var sym = '';

    if (found && found.sym) {
      sym = found.sym[0];
    }

    return {
      num: res.num,
      iso: found.iso,
      demonym: found.dem,
      currency: found.name,
      plural: found.plural,
      symbol: sym
    };
  };

  var parse$2 = parseMoney;

  var titleCase = function titleCase() {
    var str = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
    return str.replace(/\w\S*/g, function (txt) {
      return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
    });
  };

  var moneyMethods = {
    /** which currency is this money in? */
    currency: function currency(n) {
      var arr = [];
      this.forEach(function (doc) {
        var found = parse$2(doc);

        if (found) {
          arr.push(found);
        }
      });

      if (typeof n === 'number') {
        return arr[n];
      }

      return arr;
    },

    /** overloaded json method with additional number information */
    json: function json(options) {
      var n = null;

      if (typeof options === 'number') {
        n = options;
        options = null;
      }

      options = options || {
        text: true,
        normal: true,
        trim: true,
        terms: true
      };
      var res = [];
      this.forEach(function (doc) {
        var json = doc.json(options)[0];
        var obj = parse$2(doc);
        json.number = obj.num;

        if (obj.iso) {
          json.iso = obj.iso.toUpperCase();
          json.symbol = obj.symbol;
          json.currency = titleCase(obj.demonym) + ' ' + titleCase(obj.currency);
        } // 'thirty pounds'


        json.textFmt = makeNumber_1(obj, true, false);

        if (obj.currency) {
          var str = obj.currency;

          if (obj.num !== 1) {
            str = obj.plural || str + 's';
          }

          json.textFmt += ' ' + str;
        }

        res.push(json);
      });

      if (n !== null) {
        return res[n] || {};
      }

      return res;
    }
  };
  var methods$1 = moneyMethods;

  var endS = /s$/;

  var slashForm = function slashForm(m) {
    var str = m.text('reduced');
    var found = str.match(/^([-+]?[0-9]+)\/([-+]?[0-9]+)(st|nd|rd|th)?s?$/);

    if (found && found[1] && found[0]) {
      return {
        numerator: Number(found[1]),
        denominator: Number(found[2])
      };
    }

    return null;
  }; // parse '4 out of 4'


  var textForm1 = function textForm1(m) {
    var found = m.match('[<num>#Value+] out of every? [<den>#Value+]');

    if (found.found !== true) {
      return null;
    }

    var _found$groups = found.groups(),
        num = _found$groups.num,
        den = _found$groups.den;

    num = num.numbers().get(0);
    den = den.numbers().get(0);

    if (typeof num === 'number' && typeof den === 'number') {
      return {
        numerator: num,
        denominator: den
      };
    }

    return null;
  }; // parse 'a third'


  var textForm2 = function textForm2(m) {
    var found = m.match('[<num>(#Cardinal|a)+] [<den>#Ordinal+]');

    if (found.found !== true) {
      return null;
    }

    var _found$groups2 = found.groups(),
        num = _found$groups2.num,
        den = _found$groups2.den; // quick-support for 'a third'


    if (num.has('a')) {
      num = 1;
    } else {
      num = num.numbers().get(0);
    } // turn 'thirds' into third


    var str = den.text('reduced');

    if (endS.test(str)) {
      str = str.replace(endS, '');
      den.replaceWith(str);
    } // support 'one half' as '1/2'


    if (den.has('half')) {
      den = 2;
    } else {
      den = den.numbers().get(0);
    }

    if (typeof num === 'number' && typeof den === 'number') {
      return {
        numerator: num,
        denominator: den
      };
    }

    return null;
  };

  var parseFraction = function parseFraction(m) {
    return slashForm(m) || textForm1(m) || textForm2(m) || null;
  };

  var parse$3 = parseFraction;

  var methods$2 = {
    /** overloaded json method with additional number information */
    json: function json(options) {
      var n = null;

      if (typeof options === 'number') {
        n = options;
        options = null;
      }

      options = options || {
        text: true,
        normal: true,
        trim: true,
        terms: true
      };
      var res = [];
      this.forEach(function (m) {
        var json = m.json(options)[0];
        var found = parse$3(m) || {};
        json.numerator = found.numerator;
        json.denominator = found.denominator;
        res.push(json);
      });

      if (n !== null) {
        return res[n] || {};
      }

      return res;
    },

    /** change 'four out of 10' to 4/10 */
    normalize: function normalize() {
      var _this = this;

      this.forEach(function (m) {
        var found = parse$3(m);

        if (found && typeof found.numerator === 'number' && typeof found.denominator === 'number') {
          var str = "".concat(found.numerator, "/").concat(found.denominator);

          _this.replace(m, str);
        }
      });
      return this;
    }
  };
  var methods_1$1 = methods$2;

  var here = 'number-tag';
  var multiples = '(hundred|thousand|million|billion|trillion|quadrillion|quintillion|sextillion|septillion)'; //support 'two thirds'
  // (do this conservatively)

  var ordinals = ['half', 'third', 'fourth', 'quarter', 'fifth', 'sixth', 'seventh', 'eighth', 'ninth', 'tenth', 'hundredth', 'thousandth', 'millionth']; // add plural forms

  var len = ordinals.length;

  for (var i = 0; i < len; i += 1) {
    ordinals.push(ordinals[i] + 's');
  }

  ordinals = "(".concat(ordinals.join('|'), ")"); // improved tagging for numbers

  var tagger = function tagger(doc) {
    doc.match(multiples).tag('#Multiple', here); //  in the 400s

    doc.match('the [/[0-9]+s$/]').tag('#Plural', here); //half a million

    doc.match('half a? #Value').tag('Value', 'half-a-value'); //(quarter not ready)
    //five and a half

    doc.match('#Value and a (half|quarter)').tag('Value', 'value-and-a-half'); //one hundred and seven dollars

    doc.match('#Money and #Money #Currency?').tag('Money', 'money-and-money'); // $5.032 is invalid money

    doc.match('#Money').not('#TextValue').match('/\\.[0-9]{3}$/').unTag('#Money', 'three-decimal money'); // cleanup currency false-positives

    doc.ifNo('#Value').match('#Currency #Verb').unTag('Currency', 'no-currency'); // 6 dollars and 5 cents

    doc.match('#Value #Currency [and] #Value (cents|ore|centavos|sens)', 0).tag('Money', here); // maybe currencies

    var m = doc.match('[<num>#Value] [<currency>(mark|rand|won|rub|ore)]');
    m.group('num').tag('Money', here);
    m.group('currency').tag('Currency', here); // fraction - '3 out of 5'

    doc.match('#Cardinal+ out of every? #Cardinal').tag('Fraction', here); // fraction - 'a third of a slice'

    m = doc.match("[(#Cardinal|a) ".concat(ordinals, "] of (a|an|the)"), 0).tag('Fraction', here); // tag 'thirds' as a ordinal

    m.match('.$').tag('Ordinal', 'plural-ordinal');
  };

  var tagger_1 = tagger;

  var tags = {
    Fraction: {
      isA: ['Value', 'NumericValue']
    },
    Multiple: {
      isA: 'Value'
    }
  };

  var ambig = {
    mark: true,
    sucre: true,
    leone: true,
    afghani: true,
    rand: true,
    "try": true,
    mop: true,
    won: true,
    all: true,
    rub: true,
    eek: true,
    sit: true,
    bam: true,
    npr: true,
    leu: true
  };
  var lex = {
    kronor: 'Currency'
  };
  currencies.forEach(function (o) {
    if (o.iso && !ambig[o.iso]) {
      lex[o.iso] = ['Acronym', 'Currency'];
    }

    var name = o.name;

    if (name && !ambig[name]) {
      lex[name] = 'Currency';
      lex[name + 's'] = 'Currency';
    }

    if (o.dem) {
      var dem = o.dem;
      lex["".concat(dem, " ").concat(name)] = 'Currency';
      lex["".concat(dem, " ").concat(name, "s")] = 'Currency';
    }
  });
  var lexicon = lex;

  /** adds .numbers() method */

  var plugin = function plugin(Doc, world) {
    // add money words to our lexicon
    world.addWords(lexicon); // add tags to our tagset

    world.addTags(tags); // additional tagging before running the number-parser

    world.postProcess(tagger_1);
    /** a list of number values, and their units */

    var Numbers = /*#__PURE__*/function (_Doc) {
      _inherits(Numbers, _Doc);

      var _super = _createSuper(Numbers);

      function Numbers() {
        _classCallCheck(this, Numbers);

        return _super.apply(this, arguments);
      }

      return Numbers;
    }(Doc);

    Object.assign(Numbers.prototype, methods_1);
    /** a number and a currency */

    var Money = /*#__PURE__*/function (_Numbers) {
      _inherits(Money, _Numbers);

      var _super2 = _createSuper(Money);

      function Money() {
        _classCallCheck(this, Money);

        return _super2.apply(this, arguments);
      }

      return Money;
    }(Numbers);

    Object.assign(Money.prototype, methods$1);

    var Fraction = /*#__PURE__*/function (_Numbers2) {
      _inherits(Fraction, _Numbers2);

      var _super3 = _createSuper(Fraction);

      function Fraction() {
        _classCallCheck(this, Fraction);

        return _super3.apply(this, arguments);
      }

      return Fraction;
    }(Numbers);

    Object.assign(Fraction.prototype, methods_1$1);
    var docMethods = {
      /** find all numbers and values */
      numbers: function numbers(n) {
        var m = find(this, n);
        return new Numbers(m.list, this, this.world);
      },

      /** return '4%' or 'four percent' etc*/
      percentages: function percentages(n) {
        var m = this.match('#Percent+');
        m = m.concat(this.match('[#Cardinal] percent', 0));

        if (typeof n === 'number') {
          m = m.eq(n);
        }

        return new Numbers(m.list, this, this.world);
      },

      /** return '3 out of 5' or '3/5' etc**/
      fractions: function fractions(n) {
        var m = this.match('#Fraction+');

        if (typeof n === 'number') {
          m = m.eq(n);
        }

        return new Fraction(m.list, this, this.world);
      },

      /** number + currency pair */
      money: function money() {
        var m = this.splitOn('(#Money|#Currency)+');
        m = m["if"]('#Money')["if"]('#Value');
        return new Money(m.list, this, this.world);
      }
    }; // aliases

    docMethods.values = docMethods.numbers;
    docMethods.percents = docMethods.percentages;
    Object.assign(Doc.prototype, docMethods);
    return Doc;
  };

  var src = plugin;

  return src;

})));
//# sourceMappingURL=compromise-numbers.js.map
};
BundleModuleCode['nlp/compromise-sentences']=function (module,exports){
/* compromise-sentences 0.1.1 MIT */
(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  typeof define === 'function' && define.amd ? define(factory) :
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.compromiseSentences = factory());
}(this, (function () { 'use strict';

  function _classCallCheck(instance, Constructor) {
    if (!(instance instanceof Constructor)) {
      throw new TypeError("Cannot call a class as a function");
    }
  }

  function _inherits(subClass, superClass) {
    if (typeof superClass !== "function" && superClass !== null) {
      throw new TypeError("Super expression must either be null or a function");
    }

    subClass.prototype = Object.create(superClass && superClass.prototype, {
      constructor: {
        value: subClass,
        writable: true,
        configurable: true
      }
    });
    if (superClass) _setPrototypeOf(subClass, superClass);
  }

  function _getPrototypeOf(o) {
    _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
      return o.__proto__ || Object.getPrototypeOf(o);
    };
    return _getPrototypeOf(o);
  }

  function _setPrototypeOf(o, p) {
    _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
      o.__proto__ = p;
      return o;
    };

    return _setPrototypeOf(o, p);
  }

  function _isNativeReflectConstruct() {
    if (typeof Reflect === "undefined" || !Reflect.construct) return false;
    if (Reflect.construct.sham) return false;
    if (typeof Proxy === "function") return true;

    try {
      Date.prototype.toString.call(Reflect.construct(Date, [], function () {}));
      return true;
    } catch (e) {
      return false;
    }
  }

  function _assertThisInitialized(self) {
    if (self === void 0) {
      throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
    }

    return self;
  }

  function _possibleConstructorReturn(self, call) {
    if (call && (typeof call === "object" || typeof call === "function")) {
      return call;
    }

    return _assertThisInitialized(self);
  }

  function _createSuper(Derived) {
    var hasNativeReflectConstruct = _isNativeReflectConstruct();

    return function _createSuperInternal() {
      var Super = _getPrototypeOf(Derived),
          result;

      if (hasNativeReflectConstruct) {
        var NewTarget = _getPrototypeOf(this).constructor;

        result = Reflect.construct(Super, arguments, NewTarget);
      } else {
        result = Super.apply(this, arguments);
      }

      return _possibleConstructorReturn(this, result);
    };
  }

  var tags = {
    // Phrase: {},
    NounPhrase: {
      // isA: 'Phrase',
      notA: ['VerbPhrase', 'AdjectivePhrase'],
      color: 'blue'
    },
    VerbPhrase: {
      // isA: 'Phrase',
      notA: ['AdjectivePhrase', 'NounPhrase'],
      color: 'green'
    },
    AdjectivePhrase: {
      // isA: 'Phrase',
      notA: ['VerbPhrase', 'NounPhrase'],
      color: 'magenta'
    },
    Subordinate: {
      // isA: 'Phrase',
      notA: [] // color: '',

    }
  };

  var tagger = function tagger(doc) {
    doc.match('#Noun').tag('NounPhrase');
    doc.match('#Verb').tag('VerbPhrase'); // NounPhrase

    doc.match('(this|that|those|these)').tag('NounPhrase');
    doc.match('#Adjective+ #NounPhrase').tagSafe('NounPhrase');
    doc.match('#NounPhrase #Adjective+').tagSafe('NounPhrase'); // numbers

    doc.match('#Value #NounPhrase').tag('NounPhrase'); // (determiners)

    doc.match('#Determiner #NounPhrase').tag('NounPhrase');
    doc.match('#Determiner #Adverb+? #Adjective+ #NounPhrase').tag('NounPhrase');
    doc.match('(many|most|all|one|some|plenty) of #NounPhrase').tag('NounPhrase');
    doc.match('such a #NounPhrase').tag('NounPhrase'); // VerbPhrase

    doc.match('#VerbPhrase #Adverb+').tagSafe('VerbPhrase');
    doc.match('#Adverb+ #VerbPhrase').tagSafe('VerbPhrase');
    doc.match('#Auxiliary+ #VerbPhrase').tagSafe('VerbPhrase');
    doc.match('#VerbPhrase no').tagSafe('VerbPhrase');
    doc.match('not #VerbPhrase').tagSafe('VerbPhrase'); // claiming that

    doc.match('#VerbPhrase [that]', 0).unTag('NounPhrase'); // (conjunctions)

    doc.match('#VerbPhrase #Conjunction #VerbPhrase').tagSafe('VerbPhrase'); // nouns

    doc.match('(who|what|which)').tag('NounPhrase'); // Adjective

    doc.match('#Adverb+ #Adjective').tagSafe('AdjectivePhrase');
    doc.match('#Adjective').tagSafe('AdjectivePhrase'); // missing

    doc.match('#Value').tagSafe('NounPhrase');
    doc.match('#Date').tagSafe('NounPhrase');
    doc.match('#Date at #Date').tagSafe('NounPhrase');
  };

  var tagger_1 = tagger;

  /** add a word to the start of this sentence */
  var prepend = function prepend(str) {
    this.forEach(function (doc) {
      // repair the titlecase
      var firstTerms = doc.match('^.');
      firstTerms.not('#ProperNoun').toLowerCase(); // actually add the word

      firstTerms._prepend(str); // add a titlecase


      firstTerms.terms(0).toTitleCase();
    });
    return this;
  };
  /** add a word to the end of this sentence */


  var append_1 = function append_1(str) {
    var hasEnd = /[.?!]\s*$/.test(str);
    this.forEach(function (doc) {
      var end = doc.match('.$');
      var lastTerm = end.termList(0);
      var punct = lastTerm.post;

      if (hasEnd === true) {
        punct = '';
      } // add punctuation to the end


      end._append(str + punct); // remove punctuation from the former last-term


      lastTerm.post = ' ';
    });
    return this;
  };

  var append = {
    prepend: prepend,
    append: append_1
  };

  // if a clause starts with these, it's not a main clause
  var subordinate = "(after|although|as|because|before|if|since|than|that|though|when|whenever|where|whereas|wherever|whether|while|why|unless|until|once)";
  var relative = "(that|which|whichever|who|whoever|whom|whose|whomever)"; //try to remove secondary clauses

  var mainClause = function mainClause(og) {
    var m = og.clone(true);

    if (m.length === 1) {
      return m;
    } // if there's no verb?


    m = m["if"]('#Verb');

    if (m.length === 1) {
      return m;
    } // this is a signal for subordinate-clauses


    m = m.ifNo(subordinate);
    m = m.ifNo('^even (if|though)');
    m = m.ifNo('^so that');
    m = m.ifNo('^rather than');
    m = m.ifNo('^provided that');

    if (m.length === 1) {
      return m;
    } // relative clauses


    m = m.ifNo(relative);

    if (m.length === 1) {
      return m;
    }

    m = m.ifNo('(despite|during|before|through|throughout)');

    if (m.length === 1) {
      return m;
    } // did we go too far?


    if (m.length === 0) {
      m = og;
    } // choose the first one?


    return m.eq(0);
  };

  var mainClause_1 = mainClause;

  var parse = function parse(doc) {
    var clauses = doc.clauses();
    var main = mainClause_1(clauses);
    var nouns = main.match('#Determiner? (#Noun|#Adjective)+')["if"]('#Noun');
    var verb = main.verbs().eq(0); // match('(do|will)? not? #Verb+ not?').eq(0)

    return {
      subject: nouns.eq(0),
      verb: verb,
      object: verb.lookAhead('.*')
    };
  };

  var parse_1 = parse;

  /** overload the original json with noun information */

  var json_1 = function json_1(options) {
    var n = null;

    if (typeof options === 'number') {
      n = options;
      options = null;
    }

    options = options || {
      text: true,
      normal: true,
      trim: true,
      terms: true
    };
    var res = [];
    this.forEach(function (doc) {
      var json = doc._json(options)[0];

      var obj = parse_1(doc);
      json.subject = obj.subject.json(options)[0];
      json.verb = obj.verb.json(options)[0];
      json.object = obj.object.json(options)[0];
      res.push(json);
    });

    if (n !== null) {
      return res[n];
    }

    return res;
  };

  var json = {
    json: json_1
  };

  /** he walks -> he did not walk */

  var toNegative = function toNegative() {
    this.forEach(function (doc) {
      var obj = parse_1(doc);
      var vb = obj.verb.clone();
      vb = vb.verbs().toNegative();
      obj.verb.replaceWith(vb, false);
    });
    return this;
  };
  /** he doesn't walk -> he walks */


  var toPositive = function toPositive() {
    this.forEach(function (doc) {
      var obj = parse_1(doc);
      var vb = obj.verb.clone();
      vb = vb.verbs().toPositive();
      obj.verb.replaceWith(vb, false);
    });
    return this;
  };

  var negative = {
    toNegative: toNegative,
    toPositive: toPositive
  };

  //is this sentence asking a question?
  var isQuestion = function isQuestion(doc) {
    var endPunct = doc.post();
    var clauses = doc.clauses();

    if (/\?/.test(endPunct) === true) {
      return true;
    } // Has ellipsis at the end means it's probably not a question
    // e.g., Is this just fantasy...


    if (/\.\.$/.test(doc.out('text'))) {
      return false;
    } // Starts with question word, but has a comma, so probably not a question
    // e.g., Why are we caught in a land slide, no escape from reality


    if (doc.has('^#QuestionWord') && doc.has('#Comma')) {
      return false;
    } // Starts with a #QuestionWord
    // e.g., What open your eyes look up to the skies and see


    if (doc.has('^#QuestionWord')) {
      return true;
    } // Second word is a #QuestionWord
    // e.g., I'm what a poor boy
    // case ts.has('^\w+\s#QuestionWord'):
    // return true;
    // is it, do you - start of sentence
    // e.g., Do I need no sympathy


    if (doc.has('^(do|does|did|is|was|can|could|will|would|may) #Noun')) {
      return true;
    } // these are a little more loose..
    // e.g., Must I be come easy come easy go


    if (doc.has('^(have|must) you')) {
      return true;
    } // Clause starts with a question word
    // e.g., Anyway the wind blows, what doesn't really matter to me


    if (clauses.has('^#QuestionWord')) {
      return true;
    } //is wayne gretskzy alive


    if (clauses.has('(do|does|is|was) #Noun+ #Adverb? (#Adjective|#Infinitive)$')) {
      return true;
    } // Probably not a question


    return false;
  };

  var isQuestion_1 = isQuestion;

  /** return sentences ending with '?' */

  var isQuestion_1$1 = function isQuestion_1$1() {
    return this.filter(function (d) {
      return isQuestion_1(d);
    });
  };
  /** return sentences ending with '!' */


  var isExclamation = function isExclamation() {
    return this.filter(function (doc) {
      var term = doc.lastTerm().termList(0);
      return term.hasPost('!');
    });
  };
  /** return sentences with neither a question or an exclamation */


  var isStatement = function isStatement() {
    return this.filter(function (doc) {
      var term = doc.lastTerm().termList(0);
      return !term.hasPost('?') && !term.hasPost('!');
    });
  };
  /** 'he is.' -> 'he is!' */


  var toExclamation = function toExclamation() {
    this.post('!');
    return this;
  };
  /** 'he is.' -> 'he is?' */


  var toQuestion = function toQuestion() {
    this.post('?');
    return this;
  };
  /** 'he is?' -> 'he is.' */


  var toStatement = function toStatement() {
    this.post('.');
    return this;
  };

  var questions = {
    isQuestion: isQuestion_1$1,
    isExclamation: isExclamation,
    isStatement: isStatement,
    toExclamation: toExclamation,
    toQuestion: toQuestion,
    toStatement: toStatement
  };

  var useParticiple = function useParticiple(vb) {
    if (vb.has('(could|should|would|may|can|must)')) {
      return true;
    }

    return false;
  };
  /** he walks -> he walked */


  var toPastTense = function toPastTense() {
    this.forEach(function (doc) {
      if (doc.has('#PastTense')) {
        return;
      }

      var obj = parse_1(doc);
      var vb = obj.verb.clone(); // support 'he could drive' -> 'he could have driven'

      if (useParticiple(vb)) {
        vb = vb.verbs().toParticiple();
        obj.verb.replaceWith(vb, false);
      } else {
        //   //do a normal conjugation
        vb = vb.verbs().toPastTense();
        obj.verb.replaceWith(vb, false);
      } // // trailing gerund/future/present are okay, but 'walked and eats' is not


      if (obj.object && obj.object.found && obj.object.has('#PresentTense')) {
        var verbs = obj.object.verbs();
        verbs["if"]('#PresentTense').verbs().toPastTense();
      }
    });
    return this;
  };
  /** he drives -> he has driven */


  var toParticiple = function toParticiple() {
    this.forEach(function (doc) {
      if (doc.has('has #Participle')) {
        return;
      }

      var obj = parse_1(doc);
      var vb = obj.verb.clone();
      vb = vb.verbs().toParticiple();
      obj.verb.replaceWith(vb, false); // trailing gerund/future/present are okay, but 'walked and eats' is not

      if (obj.object && obj.object.found && obj.object.has('#PresentTense')) {
        var verbs = obj.object.verbs();
        verbs["if"]('#PresentTense').verbs().toParticiple();
      }
    });
    return this;
  };
  /** he walked -> he walks */


  var toPresentTense = function toPresentTense() {
    this.forEach(function (doc) {
      var obj = parse_1(doc);
      var isPlural = obj.verb.lookBehind('(i|we) (#Adverb|#Verb)?$').found;
      var vb = obj.verb.clone(); // 'i look', not 'i looks'

      if (isPlural) {
        //quick hack for copula verb - be/am
        if (vb.has('(is|was|am|be)')) {
          vb = vb.replace('will? (is|was|am|be)', 'am');
        } else {
          vb = vb.verbs().toInfinitive();
        }
      } else {
        //'he looks'
        vb = vb.verbs().toPresentTense();
      }

      obj.verb.replaceWith(vb, false); // future is okay, but 'walks and ate' -> 'walks and eats'

      if (obj.object && obj.object.found && obj.object.has('#PastTense')) {
        var verbs = obj.object.verbs();
        verbs["if"]('#PastTense').verbs().toPresentTense();
      }
    });
    return this;
  };
  /**he walked -> he will walk */


  var toFutureTense = function toFutureTense() {
    this.forEach(function (doc) {
      var obj = parse_1(doc);
      var vb = obj.verb.clone();
      vb = vb.verbs().toFutureTense();
      obj.verb.replaceWith(vb, false); //Present is okay, but 'will walk and ate' -> 'will walk and eat'

      if (obj.object && obj.object.found && obj.object.has('(#PastTense|#PresentTense)')) {
        var verbs = obj.object.verbs();
        verbs["if"]('(#PastTense|#PresentTense)').verbs().toInfinitive();
      }
    });
    return this;
  };
  /** the main noun of the sentence */


  var subjects = function subjects() {
    return this.map(function (doc) {
      var res = parse_1(doc);
      return res.subject;
    });
  };
  /** return sentences that are in passive-voice */


  var isPassive = function isPassive() {
    return this["if"]('was #Adverb? #PastTense #Adverb? by'); //haha
  };

  var tense = {
    toPastTense: toPastTense,
    toParticiple: toParticiple,
    toPresentTense: toPresentTense,
    toFutureTense: toFutureTense,
    subjects: subjects,
    isPassive: isPassive
  };

  var phrases_1 = function phrases_1() {
    var arr = [];
    this.forEach(function (s) {
      s = s.splitOn('#VerbPhrase+');
      s = s.splitOn('#NounPhrase+');
      s = s.splitOn('#AdjectivePhrase+');
      arr = arr.concat(s.list);
    });
    return this.buildFrom(arr);
  };

  var phrases = {
    phrases: phrases_1
  };

  var methods = Object.assign({}, append, json, negative, questions, tense, phrases);

  var plugin = function plugin(Doc, world) {
    // our new tags
    world.addTags(tags); // run our tagger

    world.postProcess(tagger_1);
    /**  */

    var Sentences = /*#__PURE__*/function (_Doc) {
      _inherits(Sentences, _Doc);

      var _super = _createSuper(Sentences);

      function Sentences(list, from, w) {
        _classCallCheck(this, Sentences);

        list = list.map(function (p) {
          return p.clone(true);
        });
        return _super.call(this, list, from, w);
      }

      return Sentences;
    }(Doc); // add some aliases


    methods.questions = methods.isQuestion;
    methods.exclamations = methods.isExclamation;
    methods.statements = methods.isStatement; // keep backups of these methods

    methods._prepend = Sentences.prototype.prepend;
    methods._append = Sentences.prototype.append;
    methods._json = Sentences.prototype.json;
    Object.assign(Sentences.prototype, methods);
    /** create a new Sentences object */

    Sentences.prototype.buildFrom = function (list) {
      list = list.map(function (p) {
        return p.clone(true);
      });
      var doc = new Sentences(list, this, this.world);
      return doc;
    };
    /** create a new Doc object */


    Sentences.prototype.toDoc = function () {
      return Doc.prototype.buildFrom(this.list);
    };
    /** overload original sentences() method and return Sentence class**/


    Doc.prototype.sentences = function (n) {
      var arr = [];
      this.list.forEach(function (p) {
        arr.push(p.fullSentence());
      });
      var s = new Sentences(arr, this, this.world);

      if (typeof n === 'number') {
        s = s.get(n);
      }

      return s;
    };

    return Doc;
  };

  var src = plugin;

  return src;

})));
//# sourceMappingURL=compromise-sentences.js.map
};
BundleModuleCode['nlp/efrt']=function (module,exports){
(function (global, factory) {
	typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
	typeof define === 'function' && define.amd ? define(factory) :
	(global = global || self, global.efrt = factory());
}(this, function () { 'use strict';

	var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};

	function createCommonjsModule(fn, module) {
		return module = { exports: {} }, fn(module, module.exports), module.exports;
	}

	var commonPrefix = function(w1, w2) {
	  var len = Math.min(w1.length, w2.length);
	  while (len > 0) {
	    var prefix = w1.slice(0, len);
	    if (prefix === w2.slice(0, len)) {
	      return prefix
	    }
	    len -= 1;
	  }
	  return ''
	};

	/* Sort elements and remove duplicates from array (modified in place) */
	var unique = function(a) {
	  a.sort();
	  for (var i = 1; i < a.length; i++) {
	    if (a[i - 1] === a[i]) {
	      a.splice(i, 1);
	    }
	  }
	};

	var fns = {
	  commonPrefix: commonPrefix,
	  unique: unique
	};

	var Histogram = function() {
	  this.counts = {};
	};

	var methods = {
	  init: function(sym) {
	    if (this.counts[sym] === undefined) {
	      this.counts[sym] = 0;
	    }
	  },
	  add: function(sym, n) {
	    if (n === undefined) {
	      n = 1;
	    }
	    this.init(sym);
	    this.counts[sym] += n;
	  },
	  countOf: function(sym) {
	    this.init(sym);
	    return this.counts[sym]
	  },
	  highest: function(top) {
	    var sorted = [];
	    var keys = Object.keys(this.counts);
	    for (var i = 0; i < keys.length; i++) {
	      var sym = keys[i];
	      sorted.push([sym, this.counts[sym]]);
	    }
	    sorted.sort(function(a, b) {
	      return b[1] - a[1]
	    });
	    if (top) {
	      sorted = sorted.slice(0, top);
	    }
	    return sorted
	  }
	};
	Object.keys(methods).forEach(function(k) {
	  Histogram.prototype[k] = methods[k];
	});
	var histogram = Histogram;

	var BASE = 36;

	var seq = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
	var cache = seq.split('').reduce(function(h, c, i) {
	  h[c] = i;
	  return h
	}, {});

	// 0, 1, 2, ..., A, B, C, ..., 00, 01, ... AA, AB, AC, ..., AAA, AAB, ...
	var toAlphaCode = function(n) {
	  if (seq[n] !== undefined) {
	    return seq[n]
	  }
	  var places = 1;
	  var range = BASE;
	  var s = '';

	  for (; n >= range; n -= range, places++, range *= BASE) {}
	  while (places--) {
	    var d = n % BASE;
	    s = String.fromCharCode((d < 10 ? 48 : 55) + d) + s;
	    n = (n - d) / BASE;
	  }
	  return s
	};

	var fromAlphaCode = function(s) {
	  if (cache[s] !== undefined) {
	    return cache[s]
	  }
	  var n = 0;
	  var places = 1;
	  var range = BASE;
	  var pow = 1;

	  for (; places < s.length; n += range, places++, range *= BASE) {}
	  for (var i = s.length - 1; i >= 0; i--, pow *= BASE) {
	    var d = s.charCodeAt(i) - 48;
	    if (d > 10) {
	      d -= 7;
	    }
	    n += d * pow;
	  }
	  return n
	};

	var encoding = {
	  toAlphaCode: toAlphaCode,
	  fromAlphaCode: fromAlphaCode
	};

	var config = {
      SYM_SEP: '|',
	  NODE_SEP: ';',
	  KEY_VAL: ':',
	  STRING_SEP: ',',
	  TERMINAL_PREFIX: '!',
	  BASE: 36
	};

	// Return packed representation of Trie as a string.

	// Return packed representation of Trie as a string.
	//
	// Each node of the Trie is output on a single line.
	//
	// For example Trie("the them there thesis this"):
	// {
	//    "th": {
	//      "is": 1,
	//      "e": {
	//        "": 1,
	//        "m": 1,
	//        "re": 1,
	//        "sis": 1
	//      }
	//    }
	//  }
	//
	// Would be reperesented as:
	//
	// th0
	// e0is
	// !m,re,sis
	//
	// The line begins with a '!' iff it is a terminal node of the Trie.
	// For each string property in a node, the string is listed, along
	// with a (relative!) line number of the node that string references.
	// Terminal strings (those without child node references) are
	// separated by ',' characters.

	var nodeLine = function(self, node) {
	  var line = '',
	    sep = '';

	  if (self.isTerminal(node)) {
	    line += config.TERMINAL_PREFIX;
	  }

	  var props = self.nodeProps(node);
	  for (var i = 0; i < props.length; i++) {
	    var prop = props[i];
	    if (typeof node[prop] === 'number') {
	      line += sep + prop;
	      sep = config.STRING_SEP;
	      continue
	    }
	    if (self.syms[node[prop]._n]) {
	      line += sep + prop + self.syms[node[prop]._n];
	      sep = '';
	      continue
	    }
	    var ref = encoding.toAlphaCode(node._n - node[prop]._n - 1 + self.symCount);
	    // Large reference to smaller string suffix -> duplicate suffix
	    if (node[prop]._g && ref.length >= node[prop]._g.length && node[node[prop]._g] === 1) {
	      ref = node[prop]._g;
	      line += sep + prop + ref;
	      sep = config.STRING_SEP;
	      continue
	    }
	    line += sep + prop + ref;
	    sep = '';
	  }
	  return line
	};

	var analyzeRefs = function(self, node) {
	  if (self.visited(node)) {
	    return
	  }
	  var props = self.nodeProps(node, true);
	  for (var i = 0; i < props.length; i++) {
	    var prop = props[i];
	    var ref = node._n - node[prop]._n - 1;
	    // Count the number of single-character relative refs
	    if (ref < config.BASE) {
	      self.histRel.add(ref);
	    }
	    // Count the number of characters saved by converting an absolute
	    // reference to a one-character symbol.
	    self.histAbs.add(node[prop]._n, encoding.toAlphaCode(ref).length - 1);
	    analyzeRefs(self, node[prop]);
	  }
	};

	var symbolCount = function(self) {
	  self.histAbs = self.histAbs.highest(config.BASE);
	  var savings = [];
	  savings[-1] = 0;
	  var best = 0,
	    sCount = 0;
	  var defSize = 3 + encoding.toAlphaCode(self.nodeCount).length;
	  for (var sym = 0; sym < config.BASE; sym++) {
	    if (self.histAbs[sym] === undefined) {
	      break
	    }
	    savings[sym] =
	      self.histAbs[sym][1] -
	      defSize -
	      self.histRel.countOf(config.BASE - sym - 1) +
	      savings[sym - 1];
	    if (savings[sym] >= best) {
	      best = savings[sym];
	      sCount = sym + 1;
	    }
	  }
	  return sCount
	};

	var numberNodes = function(self, node) {
	  // Topological sort into nodes array
	  if (node._n !== undefined) {
	    return
	  }
	  var props = self.nodeProps(node, true);
	  for (var i = 0; i < props.length; i++) {
	    numberNodes(self, node[props[i]]); //recursive
	  }
	  node._n = self.pos++;
	  self.nodes.unshift(node);
	};

	var pack = function(self) {
	  self.nodes = [];
	  self.nodeCount = 0;
	  self.syms = {};
	  self.symCount = 0;
	  self.pos = 0;
	  // Make sure we've combined all the common suffixes
	  self.optimize();

	  self.histAbs = new histogram();
	  self.histRel = new histogram();

	  numberNodes(self, self.root);
	  self.nodeCount = self.nodes.length;

	  self.prepDFS();
	  analyzeRefs(self, self.root);
	  self.symCount = symbolCount(self);
	  for (var sym = 0; sym < self.symCount; sym++) {
	    self.syms[self.histAbs[sym][0]] = encoding.toAlphaCode(sym);
	  }
	  for (var i = 0; i < self.nodeCount; i++) {
	    self.nodes[i] = nodeLine(self, self.nodes[i]);
	  }
	  // Prepend symbols
	  for (var sym = self.symCount - 1; sym >= 0; sym--) {
	    self.nodes.unshift(
	      encoding.toAlphaCode(sym) +
	        config.KEY_VAL +
	        encoding.toAlphaCode(self.nodeCount - self.histAbs[sym][0] - 1)
	    );
	  }

	  return self.nodes.join(config.NODE_SEP)
	};

	var pack_1 = pack;

	var NOT_ALLOWED = new RegExp('[0-9A-Z,;!:|¦]'); //characters banned from entering the trie

	var methods$1 = {
	  // Insert words from one big string, or from an array.
	  insertWords: function(words) {
	    if (words === undefined) {
	      return
	    }
	    if (typeof words === 'string') {
	      words = words.split(/[^a-zA-Z]+/);
	    }
	    for (var i = 0; i < words.length; i++) {
	      words[i] = words[i].toLowerCase();
	    }
	    fns.unique(words);
	    for (var i = 0; i < words.length; i++) {
	      if (words[i].match(NOT_ALLOWED) === null) {
	        this.insert(words[i]);
	      }
	    }
	  },

	  insert: function(word) {
	    this._insert(word, this.root);
	    var lastWord = this.lastWord;
	    this.lastWord = word;

	    var prefix = fns.commonPrefix(word, lastWord);
	    if (prefix === lastWord) {
	      return
	    }

	    var freeze = this.uniqueNode(lastWord, word, this.root);
	    if (freeze) {
	      this.combineSuffixNode(freeze);
	    }
	  },

	  _insert: function(word, node) {
	    var prefix, next;

	    // Duplicate word entry - ignore
	    if (word.length === 0) {
	      return
	    }

	    // Do any existing props share a common prefix?
	    var keys = Object.keys(node);
	    for (var i = 0; i < keys.length; i++) {
	      var prop = keys[i];
	      prefix = fns.commonPrefix(word, prop);
	      if (prefix.length === 0) {
	        continue
	      }
	      // Prop is a proper prefix - recurse to child node
	      if (prop === prefix && typeof node[prop] === 'object') {
	        this._insert(word.slice(prefix.length), node[prop]);
	        return
	      }
	      // Duplicate terminal string - ignore
	      if (prop === word && typeof node[prop] === 'number') {
	        return
	      }
	      next = {};
	      next[prop.slice(prefix.length)] = node[prop];
	      this.addTerminal(next, word = word.slice(prefix.length));
	      delete node[prop];
	      node[prefix] = next;
	      this.wordCount++;
	      return
	    }

	    // No shared prefix.  Enter the word here as a terminal string.
	    this.addTerminal(node, word);
	    this.wordCount++;
	  },

	  // Add a terminal string to node.
	  // If 2 characters or less, just add with value == 1.
	  // If more than 2 characters, point to shared node
	  // Note - don't prematurely share suffixes - these
	  // terminals may become split and joined with other
	  // nodes in this part of the tree.
	  addTerminal: function(node, prop) {
	    if (prop.length <= 1) {
	      node[prop] = 1;
	      return
	    }
	    var next = {};
	    node[prop[0]] = next;
	    this.addTerminal(next, prop.slice(1));
	  },

	  // Well ordered list of properties in a node (string or object properties)
	  // Use nodesOnly==true to return only properties of child nodes (not
	  // terminal strings.
	  nodeProps: function(node, nodesOnly) {
	    var props = [];
	    for (var prop in node) {
	      if (prop !== '' && prop[0] !== '_') {
	        if (!nodesOnly || typeof node[prop] === 'object') {
	          props.push(prop);
	        }
	      }
	    }
	    props.sort();
	    return props
	  },

	  optimize: function() {
	    this.combineSuffixNode(this.root);
	    this.prepDFS();
	    this.countDegree(this.root);
	    this.prepDFS();
	    this.collapseChains(this.root);
	  },

	  // Convert Trie to a DAWG by sharing identical nodes
	  combineSuffixNode: function(node) {
	    // Frozen node - can't change.
	    if (node._c) {
	      return node
	    }
	    // Make sure all children are combined and generate unique node
	    // signature for this node.
	    var sig = [];
	    if (this.isTerminal(node)) {
	      sig.push('!');
	    }
	    var props = this.nodeProps(node);
	    for (var i = 0; i < props.length; i++) {
	      var prop = props[i];
	      if (typeof node[prop] === 'object') {
	        node[prop] = this.combineSuffixNode(node[prop]);
	        sig.push(prop);
	        sig.push(node[prop]._c);
	      } else {
	        sig.push(prop);
	      }
	    }
	    sig = sig.join('-');

	    var shared = this.suffixes[sig];
	    if (shared) {
	      return shared
	    }
	    this.suffixes[sig] = node;
	    node._c = this.cNext++;
	    return node
	  },

	  prepDFS: function() {
	    this.vCur++;
	  },

	  visited: function(node) {
	    if (node._v === this.vCur) {
	      return true
	    }
	    node._v = this.vCur;
	    return false
	  },

	  countDegree: function(node) {
	    if (node._d === undefined) {
	      node._d = 0;
	    }
	    node._d++;
	    if (this.visited(node)) {
	      return
	    }
	    var props = this.nodeProps(node, true);
	    for (var i = 0; i < props.length; i++) {
	      this.countDegree(node[props[i]]);
	    }
	  },

	  // Remove intermediate singleton nodes by hoisting into their parent
	  collapseChains: function(node) {
	    var prop, props, child, i;
	    if (this.visited(node)) {
	      return
	    }
	    props = this.nodeProps(node);
	    for (i = 0; i < props.length; i++) {
	      prop = props[i];
	      child = node[prop];
	      if (typeof child !== 'object') {
	        continue
	      }
	      this.collapseChains(child);
	      // Hoist the singleton child's single property to the parent
	      if (child._g !== undefined && (child._d === 1 || child._g.length === 1)) {
	        delete node[prop];
	        prop += child._g;
	        node[prop] = child[child._g];
	      }
	    }
	    // Identify singleton nodes
	    if (props.length === 1 && !this.isTerminal(node)) {
	      node._g = prop;
	    }
	  },

	  isTerminal: function(node) {
	    return !!node['']
	  },

	  // Find highest node in Trie that is on the path to word
	  // and that is NOT on the path to other.
	  uniqueNode: function(word, other, node) {
	    var props = this.nodeProps(node, true);
	    for (var i = 0; i < props.length; i++) {
	      var prop = props[i];
	      if (prop === word.slice(0, prop.length)) {
	        if (prop !== other.slice(0, prop.length)) {
	          return node[prop]
	        }
	        return this.uniqueNode(word.slice(prop.length), other.slice(prop.length), node[prop])
	      }
	    }
	    return undefined
	  },

	  pack: function() {
	    return pack_1(this)
	  }
	};

	/*
	 A JavaScript implementation of a Trie search datastructure.
	Each node of the Trie is an Object that can contain the following properties:
	      '' - If present (with value == 1), the node is a Terminal Node - the prefix
	          leading to this node is a word in the dictionary.
	      numeric properties (value == 1) - the property name is a terminal string
	          so that the prefix + string is a word in the dictionary.
	      Object properties - the property name is one or more characters to be consumed
	          from the prefix of the test string, with the remainder to be checked in
	          the child node.
	      '_c': A unique name for the node (starting from 1), used in combining Suffixes.
	      '_n': Created when packing the Trie, the sequential node number
	          (in pre-order traversal).
	      '_d': The number of times a node is shared (it's in-degree from other nodes).
	      '_v': Visited in DFS.
	      '_g': For singleton nodes, the name of it's single property.
	 */
	var Trie = function(words) {
	  this.root = {};
	  this.lastWord = '';
	  this.suffixes = {};
	  this.suffixCounts = {};
	  this.cNext = 1;
	  this.wordCount = 0;
	  this.insertWords(words);
	  this.vCur = 0;
	};
	Object.keys(methods$1).forEach(function(k) {
	  Trie.prototype[k] = methods$1[k];
	});
	var trie = Trie;

	var isArray = function(input) {
	  return Object.prototype.toString.call(input) === '[object Array]'
	};

	var handleFormats = function(input) {
	  //null
	  if (input === null || input === undefined) {
	    return {}
	  }
	  //string
	  if (typeof input === 'string') {
	    return input.split(/ +/g).reduce(function(h, str) {
	      h[str] = true;
	      return h
	    }, {})
	  }
	  //array
	  if (isArray(input)) {
	    return input.reduce(function(h, str) {
	      h[str] = true;
	      return h
	    }, {})
	  }
	  //object
	  return input
	};

	//turn an array into a compressed string
	var pack$1 = function(obj) {
	  obj = handleFormats(obj);
	  //pivot into categories:
	  var flat = Object.keys(obj).reduce(function(h, k) {
	    var val = obj[k];
	    //array version-
	    //put it in several buckets
	    if (isArray(val)) {
	      for (var i = 0; i < val.length; i++) {
	        h[val[i]] = h[val[i]] || [];
	        h[val[i]].push(k);
	      }
	      return h
	    }
	    //normal string/boolean version
	    if (h.hasOwnProperty(val) === false) {
	      //basically h[val]=[]  - support reserved words
	      Object.defineProperty(h, val, {
	        writable: true,
	        enumerable: true,
	        configurable: true,
	        value: []
	      });
	    }
	    h[val].push(k);
	    return h
	  }, {});
	  //pack each into a compressed string
	  Object.keys(flat).forEach(function(k) {
	    var t = new trie(flat[k]);
	    flat[k] = t.pack();
	  });
	  // flat = JSON.stringify(flat, null, 0);

	  return Object.keys(flat)
	    .map(function (k) {
	      return k + ':' + flat[k]
	    })
	    .join('|')

	  // return flat;
	};
	var pack_1$1 = pack$1;

	//the symbols are at the top of the array.
	var symbols = function(t) {
	  //... process these lines
	  var reSymbol = new RegExp('([0-9A-Z]+):([0-9A-Z]+)');
	  for (var i = 0; i < t.nodes.length; i++) {
	    var m = reSymbol.exec(t.nodes[i]);
	    if (!m) {
	      t.symCount = i;
	      break
	    }
	    t.syms[encoding.fromAlphaCode(m[1])] = encoding.fromAlphaCode(m[2]);
	  }
	  //remove from main node list
	  t.nodes = t.nodes.slice(t.symCount, t.nodes.length);
	};

	// References are either absolute (symbol) or relative (1 - based)
	var indexFromRef = function(trie, ref, index) {
	  var dnode = encoding.fromAlphaCode(ref);
	  if (dnode < trie.symCount) {
	    return trie.syms[dnode]
	  }
	  return index + dnode + 1 - trie.symCount
	};

	var toArray = function(trie) {
	  var all = [];
	  var crawl = function (index, pref) {
	    var node = trie.nodes[index];
	    if (node[0] === '!') {
	      all.push(pref);
	      node = node.slice(1); //ok, we tried. remove it.
	    }
	    var matches = node.split(/([A-Z0-9,]+)/g);
	    for (var i = 0; i < matches.length; i += 2) {
	      var str = matches[i];
	      var ref = matches[i + 1];
	      if (!str) {
	        continue
	      }

	      var have = pref + str;
	      //branch's end
	      if (ref === ',' || ref === undefined) {
	        all.push(have);
	        continue
	      }
	      var newIndex = indexFromRef(trie, ref, index);
	      crawl(newIndex, have);
	    }
	  };
	  crawl(0, '');
	  return all
	};

	//PackedTrie - Trie traversal of the Trie packed-string representation.
	var unpack = function(str) {
	  var trie = {
	    nodes: str.split(';'), //that's all ;)!
	    syms: [],
	    symCount: 0
	  };
	  //process symbols, if they have them
	  if (str.match(':')) {
	    symbols(trie);
	  }
	  return toArray(trie)
	};

	var unpack_1 = unpack;

	var unpack_1$1 = function(str) {
	  //turn the weird string into a key-value object again
	  var obj = str.split('|').reduce(function (h, s) {
	    var arr = s.split(':');
	    h[arr[0]] = arr[1];
	    return h
	  }, {});
	  var all = {};
	  Object.keys(obj).forEach(function(cat) {
	    var arr = unpack_1(obj[cat]);
	    //special case, for botched-boolean
	    if (cat === 'true') {
	      cat = true;
	    }
	    for (var i = 0; i < arr.length; i++) {
	      var k = arr[i];
	      if (all.hasOwnProperty(k) === true) {
	        if (Array.isArray(all[k]) === false) {
	          all[k] = [all[k], cat];
	        } else {
	          all[k].push(cat);
	        }
	      } else {
	        all[k] = cat;
	      }
	    }
	  });
	  return all
	};

    // Create a fast symbol lexer from packed string (about 2-5 times slower than unpacked hash table)
    var lexer = function (packed) {
      var lex={};
      var symbols = packed.split(config.SYM_SEP);
      function lexit (treestr) {
        var levels = treestr.split(';');
//        print(levels);
        return function (text) {
          var scannerOff=0,level=0,jump,shift,startOff=0;
          for(var textOff=0;;) {
            var current = levels[level];
// print(level,textOff,scannerOff,text[textOff],current[scannerOff],/[0-9A-Z]/.test(current[scannerOff]));
            if (current[scannerOff]==undefined) return true; // terminal; all chars consumed
            if (current[scannerOff]==',' && text[textOff]==undefined) return true; // terminal; all chars consumed
            if (current[scannerOff]==',') scannerOff++;
            if (/[0-9A-Z]/.test(current[scannerOff])) {
              jump = 0;
              // BASE36 encoding !!!
              var code='';
              while(/[0-9A-Z]/.test(current[scannerOff])) {
                code += current[scannerOff++];
              }
              
              level += (fromAlphaCode(code)+1); // delta
              scannerOff=0;
              startOff=textOff;
              jump=undefined;
              continue;
            }
            if (current[scannerOff]=='!' && text[textOff]==undefined) return true;
            else if (current[scannerOff]=='!') scannerOff++;
            if (current[scannerOff]==text[textOff]) {
              textOff++;scannerOff++;
            } else {
              // skip to next pattern on current level (starts after comma or jump number)
              while (current[scannerOff]!=undefined && !(/[0-9A-Z]/.test(current[scannerOff])) && current[scannerOff]!=',') 
               scannerOff++;
              if (current[scannerOff]==',') scannerOff++;
              else while (current[scannerOff]!=undefined && (/[0-9A-Z]/.test(current[scannerOff]))) scannerOff++;
              if (current[scannerOff]==undefined) return false; // no matching; end of pattern list on this level
              textOff=startOff;
            }
          }
          return text[textOff]==undefined && 
                 (current[scannerOff]==undefined||current[scannerOff]==','||current[scannerOff]=='!');
        }
      }
      symbols.forEach(function (line) {
        var tokens=line.split(':');
        lex[tokens[0]]=lexit(tokens[1]);
      });
      return lex;
    };
    
	var src = createCommonjsModule(function (module) {
	var efrt = {
      lexer : lexer,
	  pack: pack_1$1,
	  unpack: unpack_1$1
	};

	//and then all-the-exports...
	if (typeof self !== 'undefined') {
	  self.efrt = efrt; // Web Worker
	} else if (typeof window !== 'undefined') {
	  window.efrt = efrt; // Browser
	} else if (typeof commonjsGlobal !== 'undefined') {
	  commonjsGlobal.efrt = efrt; // NodeJS
	}
	//then for some reason, do this too!
	{
	  module.exports = efrt;
	}
	});

	return src;

}));
};
BundleModuleCode['ml/ml']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2006-2022 BSSLAB
 **    $CREATED:     8-2-16 by sbosse.
 **    $VERSION:     1.17.1
 **
 **    $INFO:
 **
 **  JavaScript AIOS Machine Learning API
 **
 ** type algorithm = {'dti','dt','id3','c45','kmeans','knn','knn2','mlp','slp','rl','svm','txt','cnn'}
 **
 **
 ** id3: Symbolic Decision Tree algorithm
 ** -------------------------------------
 **
 ** typeof @options = {
 **   algorithm='id3',
 **   data:{x1:number,x2:number,..,y:*} []
 **   target:string is e.g. 'y'
 **   features: string [] is e.g. ['x1','x2',..]
 ** }
 **
 ** ice: decision tree algorithm supporting numbers with eps intervals (hybrid C45/ID3)
 ** -------------------------------------
 **
 ** General feature variable set:
 **
 ** typeof @options = {
 **   algorithm='dt',
 **   data:{x1:number,x2:number,..,y:*} [],
 **   target:string is e.g. 'y',
 **   features: string [] is e.g. ['x1','x2',..],
 **   eps:number is e.g. '5',
 ** }
 ** 
 ** dti: interval decision tree algorithm
 ** -------------------------------------
 **
 ** General feature variable set:
 **
 ** typeof @options = {
 **   algorithm='dti',
 **   data:{x1:number,x2:number,..,y:*} []
 **   target:string is e.g. 'y'
 **   features: string [] is e.g. ['x1','x2',..]
 **   eps:number is e.g. '5',
 **   maxdepth:number,
 ** }
 ** 
 ** Or vector feature variables (i.e., features=[0,1,2,...n-1], target=n):
 **
 ** typeof @options = {
 **   algorithm='dti',
 **   x:* [] [],
 **   y:* [],
 **   eps:number is e.g. '5',
 **   maxdepth:number,
 ** }
 **
 ** knn: k-Nearest-Neighbour Algorithm
 ** ----------------------------------
 **
 ** typeof @options = {
 **   algorithm='knn',
 **   x: number [][], 
 **   y: * []
 ** }
 **
 ** mlp: multi layer perceptron Algorithm
 ** ----------------------------------
 **
 ** typeof @options = {
 **   algorithm='mlp',
 **   x: number [][], 
 **   y: number [] [] | * [],
 **   hidden_layers?:number [],
 **   lr?:number,
 **   epochs?:number,
 **   labels?:string [], 
 **   features?: string [], 
 **   normalize?, 
 **   verbose?:number
 ** }
 **
 **
 ** cnn: Convolutional Neural Network for numerial (2D) data
 ** -------------------------------------
 **
 ** General feature variable set:
 **
 ** typeof @options = {
 **   algorithm='cnn',
 **   data:{x:[]|[][],y:'a} []
 **   layers: layer [],
 **   trainer:trainer,
 ** }
 ** type layer = 
 **  {type:'input', out_sx:number, out_sy:number, out_depth:number} | // Input Layer
 **  {type:'conv', sx:number, filters:number, stride:number, pad:number, activation:string} | // Convolution Layer
 **  {type:'pool', sx:number, stride:number} | // Pooling Layer
 **  {type:'softmax', num_classes:number} | // Classifier Layers
 **  {type:'svm', num_classes:number| // Classifier Layers
 **  {type:'fc', num_neurons:number, activation:string} // Fully Connected Layer
 **
 ** typeof activation = 'relu'| 'maxout' | 'sigmoid' | 'tanh' ..
 **
 ** type trainer = 
 **  {method: 'sgd', learning_rate:number,  momentum: number, batch_size:number, l2_decay:number} |
 **  {method: 'adadelta', learning_rate:number,  eps: number, ro:number, batch_size:number, l2_decay:number} |
 **  {method: 'adam', learning_rate:number, eps: number, beta1: number, beta2: number, batch_size: number, l2_decay:number} |
 **  ..
 **
 ** text: text analysis (similarity checking)
 ** -----------------------------------------
 **   classify(model,string) -> {match:number [0..1],string:string }
 **   learn({algorithm:ML.TXT, data:string []]) -> model
 **   test({algorithm:ML.TXT,string:string}|model,string) -> number [0..1]
 **   similarity(string,string) -> number [0..1]
 ** 
 **    $ENDOFINFO
 */
var Io = Require('com/io');
var Comp = Require('com/compat');


var ICE = Require('ml/ice'); // ICE ID3/C45 eps
var DTI = Require('ml/dti');
var KNN = Require('ml/knn');
var KMN = Require('ml/kmeans');
var SVM = Require('ml/svm');
var MLP = Require('ml/mlp');
var ID3 = Require('ml/id3');
var C45 = Require('ml/C45');
var TXT = Require('ml/text');
var RF  = Require('ml/rf');
var RL  = Require('ml/rl');
var STAT= Require('ml/stats');
var CNN = Require('ml/cnn');
var ANN = Require('ml/ann');
var PCA = Require('ml/pca');

var current=none;
var Aios=none;

var options = {
  version: '1.17.1'
}

// Some definitions
var ML = {
  // Algorithms
  ANN:'ann',    // neataptic NN 
  C45:'c45',
  CNN:'cnn',
  ICE:'ice',   // ICE ID3/C45 eps
  DTI:'dti',
  ID3:'id3',
  KMN:'kmeans',
  KNN:'knn',
  KNN2:'knn2',
  MLP:'mlp',
  RF:'rf',    // Random Forest
  RL:'rl',    // Reinforcement Leerner
  SLP:'slp',  // Synonym for MLP (but single layer)
  SVM:'svm',
  TXT:'txt',
  // Some Functions
  EUCL:'euclidean',
  PEAR:'pearson',
  
  // RL agents
  DPAgent:'DPAgent',
  TDAgent:'TDAgent',
  DQNAgent:'DQNAgent',
};

/**
 * Computes Log with base-2
 * @private
 */
function log2(n) {
  return Math.log(n) / Math.log(2);
}

function obj2Array(row,features) {
  return features.map(function (attr) { return row[attr] });
}
function objSlice(row,features) {
  var o = {};
  features.forEach(function (attr) { o[attr]=row[attr] });
  return o;
}

// transform [v][] -> v[]
function relax(mat) {
  if (Comp.obj.isMatrix(mat) && mat[0].length==1) return mat.map(function (row) { return row[0]})
  else return mat;
}

// transform v[] -> [v][]
function wrap(mat) {
  if (!Comp.obj.isMatrix(mat)) return mat.map(function (v) { return [v]})
  else return mat
}

/* Common data transformation between different formats
**
** 1a. need='xy':   data={$x:'a,$y:'b}[] -> {x:{$x} [], y:'b[]}
** 1b. need='xy':   data=('a|'b)[][] -> {x:'a [][], y:'b[]}
** 1c. need='xry':  data=('a|'b)[][] -> {x:{$x} [], y:'b[]}
** 1c. need='io':   data=number[][] -> {input:number, output:number} []
** 1d. need='io':   data={$x:number,$y:number}[] -> {input:number, output:number} []
** 2. need='xmy':   data={$x:'a,$y:'b}[] -> {x:'a [][], y:'b[]}
** 3. need='d':     data={x:'a[][],y:'b[]}} -> {data:{$x:'a,$y:'b}[][]}
** 4. need='dm':    data={x:'a[][],y;'b[]} -> {data:('a|'b)[][]}
** 5. need='m':     data={$x:'a}[] -> 'a [][]
** 6. need='a':     data={$x:'a} -> 'a []

** typeof options = {
**   scale:   {k:number, off:number, shift:number} is transformation of input data,
**   xscale:  {k:number, off:number, shift:number} is transformation of input data,
**   yscale:  {k:number, off:number, shift:number} is transformation of output data,
**   features : string [] is feature variable list,
**   target: string is output variable,
**
**/
function scale(vrow,scala) {
  if (!scala) return vrow;
  if (typeof vrow == 'number') {
    if (typeof scala.k == 'number')
      return scala.shift+(vrow-scala.off)*scala.k
    else
      return scala.shift+(vrow-scala.off[0])*scala.k[0];
  }
  if (typeof scala.k == 'number')
    return vrow.map(function (col,i) { 
      return scala.shift+(col-scala.off)*scala.k })
  else
    return vrow.map(function (col,i) { 
      return scala.shift+(col-scala.off[i])*scala.k[i] })
}

function unscale(vrow,scala) {
  if (!scala) return vrow;
  if (typeof vrow == 'number') {
    if (typeof scala.k == 'number')
      return (vrow-scala.shift)/scala.k+scala.off
    else
      return (vrow-scala.shift)/scala.k[0]+scala.off[0]
  }
}

function preprocess(data,need,options) {
  var row,x,y,_data;
  options=options||{};
  var scala=options.scale || options.xscale;
  function array(data) {
    return Comp.obj.isArray(data)?data:[data]
  } 
  if (Comp.obj.isArray(data)) {
    row=data[0];
    switch (need) {
      case 'xy':
      case 'xry':
        if (options.target!=undefined && options.features!=undefined) {
          if (Comp.obj.isArray(row) && need=='xy') {
            if (Number(options.target)==row.length-1) {
              x=data.map(function (row) { return scale(row.slice(0,options.target),scala) });
              y=data.map(function (row) { return row[options.target] })
            }
          } else  if (Comp.obj.isObj(row)) {
            if (typeof options.target == 'string') {
              x=data.map(function (row) { return scale(objSlice(row,options.features),scala) });
              y=data.map(function (row) { return row[options.target] });
            }
          }
        }
        if (x && y) return {x:x,y:y}
        break;
      case 'a':
        if (Comp.obj.isArray(data) && typeof data[0] != 'object') return {data:data};  
        if (Comp.obj.isObject(data) && options.features!=undefined) {
          return { data:data.map(function (row) { 
                    return scale(objSlice(row,options.features),scala) })};
        }
        break;
      case 'm':
       if (Comp.obj.isMatrix(data)) return {data:data};
        if (Comp.obj.isObject(row) && options.features!=undefined) {
          return { data:data.map(function (row) { 
                    return scale(obj2Array(row,options.features),scala) })};
        }
       break;  
      case 'xmy':
        if (Comp.obj.isObject(row) && options.features!=undefined && options.target!=undefined) {
          return { x:data.map(function (row) { 
                      return scale(obj2Array(row,options.features),scala) }),
                   y:data.map(function (row) { return row[options.target]})};
        }
       break;  
      case 'io':
        if (Comp.obj.isArray(row) && options.target!=undefined) {
          // number [][] 
          if (Number(options.target)==row.length-1) {
            _data=data.map(function (row) { return { input:scale(row.slice(0,options.target),scala),
                                                     output:array(row[options.target]) }});
            return _data
          } 
        } else if (Comp.obj.isObject(row) && options.target!=undefined && options.features!=undefined) {
          _data=data.map(function (row) { return { input:scale(obj2Array(row,options.features),scala),
                                                   output:array(row[options.target]) }});
          return _data
        }

        break;
    }
  } else if (data.x && data.y) {
    if (Comp.obj.isArray(data.x) && Comp.obj.isArray(data.y)) {
      row=data.x[0];
      switch (need) {
        case 'io':
        if (Comp.obj.isArray(row)) {
          // number [][] 
          _data=data.x.map(function (row, rowi) { return { input:scale(row,scala),
                                                           output:array(data.y[rowi]) }});
          return _data          
        } 
        if (Comp.obj.isObject(row) && options.features!=undefined) {
          _data=data.x.map(function (row, rowi) { return { input:scale(obj2Array(row,options.features),scala),
                                                           output:array(data.y[rowi]) }});
          return _data          
        }
        break;
        case 'xm':
          if (Comp.obj.isArray(row)) return data.x;
          break;
        case 'xmy':
          if (Comp.obj.isArray(row)) return { x:data.x, y:data.y};
          break;
        case 'xmya':
          if (Comp.obj.isArray(row)) return { x:data.x, y:data.y.map(array)};
          break;
        case 'd':
          return data.x.map(function (row,rowi) {
            var newrow={};
            if (options.features && options.target) {
              options.features.forEach(function (f,coli) {
                newrow[f]=row[coli];
              });
              newrow[options.target]=data.y[rowi];
            } else {
              row.forEach(function (col,f) {
                newrow[String(f)]=col;                
              });
              newrow[String(row.length)]=data.y[rowi];
            }
            return newrow;
          })
          break;
      } 
    }   
  }
}



// Agent AIOS API
var  ml = {
  // only RL
  action : function (model,arg) {
    switch (model.algorithm) {
      // Selects and returns next action from set of actions
      case ML.RL:
        switch (model.kind) {
          case ML.DQNAgent:
            // arg == state array
            return model.actions[RL.DQNAgent.code.act(model,arg)];   
            break;
          case ML.DPAgent:
            // arg == state (integer number)
            return model.actions[RL.DPAgent.code.act(model,arg)];   
            break;
          case ML.TDAgent:
            // arg == state (integer number)
            return model.actions[RL.TDAgent.code.act(model,arg)];   
            break;
        }
        break;   
    }
  },
  /** Classification (prediction): Apply sample data to learned model.
   *  Returns prediction result.
   *
   */ 
  classify: function (model,samples) {
    var x,solutions,result;
    switch (model.algorithm) {
    
      case ML.ANN:
        if (Comp.obj.isArray(samples)) 
          return samples.map(function (sample) { 
            return model.network.activate(sample) 
          });
        else
          return model.network.activate(samples);

      case ML.CNN:
        if (Comp.obj.isMatrix(samples))
          return samples.map(function (sample) {
            return CNN.predict(model,sample);
          });
        else
          return CNN.predict(model,samples);
        break;

      case ML.C45:
        // Sample row format: [x1,x2,..,xn]
        if (Comp.obj.isMatrix(samples)) {
          return samples.map(function (sample) {
            return C45.classify(model,sample);
          });
        } else if (Comp.obj.isArray(samples) && !Comp.obj.isObj(samples[0])) {
          return C45.classify(model,samples);
        } else if (Comp.obj.isArray(samples) &&  Comp.obj.isObj(samples[0])) {
          return samples.map(function (sample) {
            return C45.classify(model,sample); 
          });
        } else if (Comp.obj.isObj(samples)) {
          return C45.classify(model,samples);
        }
        break;

      case ML.DT:
      case ML.ICE:
        if (Comp.obj.isMatrix(samples) ||
            Comp.obj.isArray(samples) && Comp.obj.isObj(samples[0])) 
          return samples.map(function (sample) { 
            return ICE.predict(model,sample) 
          });
        else 
          return ICE.predict(model,samples);

      case ML.DTI:
        if (Comp.obj.isMatrix(samples)) 
          return samples.map(function (sample) { 
            return DTI.predict(model,sample) 
          });
        else
          return DTI.predict(model,samples);

      case ML.ID3:
        if (Comp.obj.isArray(samples)) 
          return samples.map(function (sample) { 
            return ID3.predict(model,sample) 
          });
        else
          return ID3.predict(model,samples);

      case ML.KNN:
        if (Comp.obj.isMatrix(samples))
          return KNN.predict(model,samples);        
        else if (Comp.obj.isArray(samples) && Comp.obj.isObj(samples[0]))
          return KNN.predict(model,samples.map(function (sample) { 
            return obj2Array(sample,model.features)}));
        else if (Comp.obj.isObj(samples))
          return KNN.predict(model,obj2Array(samples,model.features));
        else
          return KNN.predict(model,samples);
        break;

      case ML.KNN2:
        if (Comp.obj.isMatrix(samples))
          return samples.map(function (sample) {
            return KNN.predict2(model,sample);
          });
        else if (Comp.obj.isArray(samples) && Comp.obj.isObj(samples[0]))
          return samples.map(function (sample) {
             return KNN.predict2(model,obj2Array(sample,model.features))
            })
        else if (Comp.obj.isObj(samples))
          return KNN.predict2(model,obj2Array(samples,model.features));
        else
          return KNN.predict2(model,samples);
        break;

      case ML.KMN:
        return model.clusters
        break;

      case ML.RF:
        if (model.labels) {
          if (Comp.obj.isMatrix(samples)) {
            return samples.map(function (sample) {
              return model.rfs.map(function (rf) {
                return RF.code.predictOne(rf,sample);
              }).map(function (v,i) {
                return { value:model.labels[i], prob:v }
              })
            });
          } else if (Comp.obj.isArray(samples) && typeof samples[0] == 'number') {
            return model.rfs.map(function (rf) {
              return RF.code.predictOne(rf,samples);
            }).map(function (v,i) {
                return { value:model.labels[i], prob:v }
            })
          } // TODO
        } else {
          // Sample row format: [x1,x2,..,xn]
          if (Comp.obj.isMatrix(samples)) {
            return samples.map(function (sample) {
              return RF.code.predictOne(model,sample);
            });
          } else if (Comp.obj.isArray(samples) && typeof samples[0] == 'number') {
            return RF.predictOne(model,samples);
          } // TODO
        }
        // preprocess(samples,'m')
        break;
                
      case ML.SVM:
        if (!model._labels) {
          // Single SVM 
          if (Comp.obj.isMatrix(samples))
            return samples.map(function (sample) {
              return SVM.code.predict(model,sample);
            });
          else
            return SVM.code.predict(model,samples);
        } else {
          // Multi SVM
          if (Comp.obj.isMatrix(samples))
            return samples.map(function (sample) {
              solutions=model.svms.map(function (svm,index) { 
                if (svm.threshold==false)
                  return SVM.code.predict(svm,sample)
                else
                  return SVM.code.predict(svm,sample); 
              });
              return solutions.map(function (v,i) { return { value:model._labels[i], prob:v } });
            });
          else {
            solutions=model.svms.map(function (svm,index) { 
                if (svm.threshold==false)
                  return SVM.code.predict(svm,samples)
                else
                  return SVM.code.predict(svm,samples)==1; 
            })
            return solutions.map(function (v,i) { return { value:model._labels[i], prob:v } });
          }
        }
        break;
        
      case ML.SLP:
      case ML.MLP:
        if (Comp.obj.isMatrix(samples)) {
          x=samples;          
          if (model.xscale) 
            x=x.map(function (row) { return scale(row,model.xscale) });
          result = model.labels?MLP.code.predict(model,x).map(function (r) {
            var o={};
            r.forEach(function (v,i) { o[model.labels[i]]=v });
            return o;
          }):relax(MLP.code.predict(model,x));
        } else if (Comp.obj.isArray(samples) && typeof samples[0] == 'number') {
          x=samples;
          if (model.xscale) 
            x=scale(x,model.xscale);
          result = model.labels?MLP.code.predict(model,[x]).map(function (r) {
            var o={};
            r.forEach(function (v,i) { o[model.labels[i]]=v });
            return o;
          })[0]:relax(MLP.code.predict(model,[x])[0]);
        } else if (Comp.obj.isArray(samples) && typeof samples[0] == 'object') {
          x=samples.map(function (sample) { return model.features.map(function (f) { return sample[f] }) });
          if (model.xscale) 
            x=x.map(function (row) { return scale(row,model.xscale) });
          result = model.labels?MLP.code.predict(model,x).map(function (r) {
            var o={};
            r.forEach(function (v,i) { o[model.labels[i]]=v });
            return o;
          }):relax(MLP.code.predict(model,x));
        } else if (Comp.obj.isObj(samples) && model.features) {
          x=model.features.map(function (f) { return samples[f] });
          if (model.xscale) 
            x=scale(x,model.xscale);
          result = model.labels?MLP.code.predict(model,[x]).map(function (r) {
            var o={};
            r.forEach(function (v,i) { o[model.labels[i]]=v });
            return o;
          })[0]:relax(MLP.code.predict(model,[x])[0]); 
        }
        if (Comp.obj.isArray(result)) {
          return model.yscale?result.map(function (y) { return unscale(y,model.yscale) }):result;
        } else {
          return result;
        }
        break;
        
       case ML.TXT:
        // typeof options = {data: string []}
        if (Comp.obj.isArray(samples))
          return samples.map(function (sample) { return TXT.classify(model,sample) });
        else
          return TXT.classify(model,samples);
        break;

   }
  },
  
  compact: function (model) {
    switch (model.algorithm) {
      case ML.DTI:
      default:
        return DTI.compactTree(model);
    }
  },
  
  depth: function (model) {
    switch (model.algorithm) {
      case ML.DTI:
        return DTI.depth(model);
      case ML.DT:
      case ML.ICE:
        return ICE.depth(model);
      case ML.C45:
        return C45.depth(model);
      case ML.ID3:
        return ID3.depth(model);
    }
  },
  
  
  evaluate: function (model,target,samples) {
    switch (model.algorithm) {
      case ML.DTI:
      default:
        return DTI.evaluate(model,target,samples);
    }
  },

  info: function (model) {
    switch (model.algorithm) {
      case ML.C45:
        return C45.info(model);
      case ML.DT:
      case ML.ICE:
        return ICE.info(model);
      case ML.ID3:
        return ID3.info(model);
    }
  },
  /** Learning: Create a classification model from training data (or an empty model that can be updated)
   *
   */
  learn: function (options) {
    var model,data,data2,x,y,features,featureTypes,test,target,
        result,cols,n_ins,n_outs,x,y,xscale,xoffset,xshift,yscale,yoffset,yshift,key,err,
        t0=Io.time();
    if (options==_) options={};
    switch (options.algorithm) {
    
      case ML.ANN:
        // typeof options = { x,y,features?,target?,layers:number [], trainerror:number}
        data = preprocess(options,'io',options);
        model={};
        model.algorithm=options.algorithm
        if (!options.layers) options.layers=[]
        if (data)
          model.network = new ANN.Network(options.layers[0],options.layers[options.layers.length-1]);
        else throw 'ML.learn.ANN: Invalid options';
        model.network.evolve(data,options);
        model.time=Io.time()-t0;
        return model;
        break;      
        

      case ML.CNN:
        // typeof options = {x:[][],y:[],..}
        model = CNN.create(options);
        model.algorithm=options.algorithm;
        model.time=Io.time()-t0;
        return model;
        break;

      case ML.C45:
        // typeof options = {data: {}[], target:string, features: string []} |
        //                  {data: [][], target?:string, features?: string []} |
        //                  {x: number [][], y:[]} |
        //                  {data: {x,y}[] }
        var model = C45.create();
        if (options.x && options.y) {
          features=options.x[0].map(function (col,i) { return String(i) }); 
          featureTypes=options.x[0].map(function (col,i) { return 'number' });
          data=options.x.map(function (row,i) { row=row.slice(); row.push(options.y[i]); return row});
          target='y';
        } else if (options.data && Comp.obj.isMatrix(options.data)) {
          data=options.data;
          features=options.features||options.data[0].slice(0,-1).map(function (col,i) { return String(i) });
          featureTypes=options.data[0].slice(0,-1).map(function (col,i) { return typeof col == 'number'?'number':'category' });
          target=options.target||'y';
        } else if (options.data && Comp.obj.isObj(options.data[0]) && options.data[0].x && options.data[0].y!=undefined) {
          data=options.data.map(function (row) { return row.x.concat(row.y) });
          features=options.features||options.data[0].x.slice(0,-1).map(function (col,i) { return String(i) });
          featureTypes=options.data[0].x.slice(0,-1).map(function (col,i) { return typeof col == 'number'?'number':'category' });
          target=options.target||'y';
        } else if (options.data && Comp.obj.isArray(options.data) && Comp.obj.isObj(options.data[0]) && 
                   options.target && options.features) {
          rowNames=Comp.obj.isArray(options.target)?options.features.concat(options.target):
                                                    options.features.concat([options.target]);
          data=options.data.map(function (row) { return obj2Array(row,rowNames) })
          features=options.features;
          featureTypes=data[0].slice(0,-1).map(function (col,i) { return typeof col == 'number'?'number':'category' });
          target=options.target;
        } else throw 'ML.learn.C45: Invalid options';

        C45.train(model,{
          data: data,
          target: target,
          features: features,
          featureTypes: featureTypes
        });
        model.algorithm=options.algorithm
        model.time=Io.time()-t0;
        return model;
        break;


      case ML.DTI:
        // typeof options = {data: {}[], target:string, features: string [], eps;number, maxdepth} |
        //                   {x: number [][], y:[], eps;number, maxdepth}
        if (options.eps==_) options.eps=0;
        if (options.maxdepth==_) options.maxdepth=20;
        if (options.data && options.target && options.features)
          model = DTI.create(options);
        else if (options.x && options.y) {
          if (options.x.length != options.y.length) throw 'ML.learn.DTI: X and Y vector have different length';
          data=options.x.map(function (row,i) { row=row.slice(); row.push(options.y[i]); return row});
          features=Comp.array.init(data[0].length-1,function (i) { return String(i)});
          target=String(data[0].length-1);
          // console.log(data,features,target)
          model = DTI.create({
            data:data,
            features:features,
            target:target,
            eps:options.eps,
            maxdepth:options.maxdepth
          });
        } else throw 'ML.learn.DTI: Invalid options';
        model.algorithm=options.algorithm;
        model.time=Io.time()-t0;
        return model;


      case ML.ICE:
      case ML.DT:
        if (options.eps==_) options.eps=0;
        if (options.data && options.target && options.features)
          model = ICE.create(options);                  
        else if (options.x && options.y) {
          if (options.x.length != options.y.length) throw 'ML.learn.ICE: X and Y vector have different length';
          data=options.x.map(function (row,i) { row=row.slice(); row.push(options.y[i]); return row});
          features=Comp.array.init(data[0].length-1,function (i) { return String(i)});
          target=String(data[0].length-1);
          model = ICE.create({
            data:data,
            features:features,
            target:target,
            eps:options.eps,
          });
        } else throw 'ML.learn.ICE: Invalid options';
        model.algorithm=options.algorithm;
        model.eps=options.eps;
        model.time=Io.time()-t0;
        return model;
        break;      

      case ML.ID3:
        if (options.data && options.target && options.features)
          model = ID3.createTree(options.data,options.target,
                                 options.features);
        else throw 'ML.learn.ID3: Invalid options';
        model.algorithm=options.algorithm
        model.time=Io.time()-t0;
        return model;
        break;      
          
      case ML.KNN:
        // typeof @options = {data: {}[]|[][], distance?:function|string,k?:number}
        // typeof @options = {x:number [][], y:number [], 
        //                    distance?:function|string,k?:number}
        if (options.features && options.target) target=options.target,features = options.features;
        else {
          features = [];
          if (options.data) {
            for(key in options.data[0]) features.push(key);
            target = features.pop()
          } else if (options.x) {
            for(key in options.x[0]) features.push('x'+key);
            target='y';
          }
        }
        if (options.data && Comp.obj.isObj(options.data[0])) {
          x = options.data.map(function (row) { return obj2Array(row,features) });
          y = options.data.map(function (row) { return row[target] })
        } else if (options.data && Comp.obj.isMatrix(options.data)) {
          x = options.data,map(function (row) { return row.slice(0,row.length-1) });
          y = options.data,map(function (row) { return row[row.length-1] });
        } else if (options.x && options.y) {
          x = options.x;
          y = options.y;
        }
        model = KNN.create(
          x,
          y,
          {
            distance:options.distance,
            k:options.k
          });
        model.algorithm = options.algorithm
        model.features  = features
        model.target    = target
        model.time=Io.time()-t0;
        return model;
        break;

      case ML.KNN2:
        // typeof @options = {data: {}[]|[][], distance?:function|string,k?:number}
        // typeof @options = {x:number [][], y:number [], 
        //                    distance?:function|string,k?:number}
        if (options.features && options.target) target=options.target,features = options.features;
        else {
          features = [];
          if (options.data) {
            for(key in options.data[0]) features.push(key);
            target = features.pop()
          } else if (options.x) {
            for(key in options.x[0]) features.push('x'+key);
            target='y';
          }
        }
        if (options.data && Comp.obj.isObj(options.data[0])) {
          x = options.data.map(function (row) { return obj2Array(row,features) });
          y = options.data.map(function (row) { return row[target] })
        } else if (options.data && Comp.obj.isMatrix(options.data)) {
          x = options.data,map(function (row) { return row.slice(0,row.length-1) });
          y = options.data,map(function (row) { return row[row.length-1] });
        } else if (options.x && options.y) {
          x = options.x;
          y = options.y;
        }
        model = KNN.create2(
          {
            x : x,
            y : y,
            distance:options.distance,
            k:options.k
          });
        model.algorithm=options.algorithm
        model.features = features
        model.target = target
        model.time=Io.time()-t0;
        return model;
        break;
        
      case ML.KMN:
        if (options.data && Comp.obj.isMatrix(options.data)) {
          data=options.data;
        } 
        model = KMN.cluster({
          data:data,
          k:options.k,
          distance:options.distance,
          epochs:options.epochs,
        })
        model.algorithm=options.algorithm
        model.data = data
        model.time=Io.time()-t0;
        return model;
        break;
                
      case ML.RF:
        var model={};
        // Single Binary RF (y={-1,1}) or Multi-RF (y:string is in labels)
        // typeof options = {data: {}[], target:string, features: string []} |
        //                  {data: [][], target?:string, features?: string []} |
        //                  {x: number [][], y: {-1,1} []} |
        //                  {data: {x,y}[] }
        //                  {data: {x,y}[], labels: string [] }
        if (!options.x || !options.y) throw 'ML.learn.RF: Invalid options';
        // data=preprocess(data,'xmy',{features:features,target:target})
        data={x:options.x,y:options.y}; // TODO 
        if (options.labels) {
          // multi-RF
          model.labels = options.labels;
          model.rfs = model.labels.map (function (label) { return RF() });
          model.rfs.forEach (function (rf,i) {
            var y = data.y.map(function (label) { return label==model.labels[i]?1:-1} );
            RF.code.train(rf,options.x,y,{
              numTrees:options.numTrees,
              maxDepth:options.maxDepth,
              numTries:options.numTries,
              type:options.weakType,
            });
          });
        } else {
          model = RF();
          features=options.x[0].map(function (col,i) { return String(i) }); 
          target='y';
        
          RF.code.train(model,
            options.x,
            options.y,
            {
              numTrees:options.numTrees,
              maxDepth:options.maxDepth,
              numTries:options.numTries,
              type:options.weakType,
            });    
        }
        model.algorithm=options.algorithm
        model.time=Io.time()-t0;
        return model;
        break;

      case ML.RL:
        // Create learner instance
        model = {}
        options.environment=checkOptions(options.environment,{});
        options.environment.getMaxNumActions=
          checkOption(options.environment.getMaxNumActions,
                      function () { return options.actions.length })
        options.environment.getNumStates=
          checkOption(options.environment.getNumStates,
                      function () { return options.states.length })
        var allowedActions=checkOption(options.environment.allowedActions, function () { return options.actions });
        options.environment.allowedActions=
          // Ensure that allowedActions return number array!
          function (state) { 
            return allowedActions(state).map(function (a) {
              return options.actions.indexOf(a)
            })
          }  
        var nextState = options.environment.nextState;
        if (nextState) {
          options.environment.nextState = function (state,action) {
            return nextState(state,options.actions[action])
          }
        }
        switch (options.kind) {
          case ML.DQNAgent:                          
            model = RL.DQNAgent(
              options.environment,  
              {
                alpha:options.alpha,gamma:options.gamma,epsilon:options.epsilon,
                experience_add_every:options.experience_add_every,
                experience_size:options.experience_size,
                learning_steps_per_iteration:options.learning_steps_per_iteration,
                tderror_clamp:options.tderror_clamp,
                num_hidden_units:options.num_hidden_units,
                update:options.update,
               }
            )
            break;
          case ML.DPAgent:
            model = RL.DPAgent(
              options.environment,  
              {alpha:options.alpha,beta:options.beta,gamma:options.gamma,
               epsilon:options.epsilon,lambda:options.lambda}
            )
            break;
          case ML.TDAgent:
            model = RL.TDAgent(
              options.environment,  
              // specs
              {alpha:options.alpha,beta:options.beta,gamma:options.gamma,
               epsilon:options.epsilon,lambda:options.lambda,
               replacing_traces:options.replacing_traces,
               smooth_policy_update:options.smooth_policy_update,
               update:options.update,
               planN:options.planN}
            )
            break;
        }
        model.algorithm = options.algorithm;
        model.kind      = options.kind;
        if (options.actions)  model.actions   = options.actions;
        if (options.states)   model.states    = options.states;
        if (options.rewards)  model.rewards   = options.rewards;
        return model;
        break;



      case ML.SLP:
      case ML.MLP:
        // typeof options = {x: number [][], 
        //                   y: number number [][] | string [],
        //                   hidden_layers?:[],epochs?:number,
        //                   labels?:string [], features?: string [], 
        //                   regression?,
        //                   normalize?, bipolar?, eps?:number | number [], verbose?}
        //
        // y and MLP(learn) requires [[p1,p2,..],[p1,p2,..],..] with 0>=p>=1
        //                                                           p:label probability
        x=options.x;
        if (Comp.obj.isArray(options.x) && typeof options.x[0] == 'number') 
          x=wrap(options.x);
        else if (!Comp.obj.isMatrix(options.x) && Comp.obj.isArray(options.x) && typeof options.x[0] == 'object' && options.features) {
          x=options.x.map(function (o) {
            return options.features.map(function (f) { return o[f] }); 
          });
        } 
        if (Comp.obj.isMatrix(options.y)) 
          y=options.y;
        else if (Comp.obj.isArray(options.y) && typeof options.y[0] == 'number') 
          y=wrap(options.y);        
        else if (Comp.obj.isArray(options.y) && options.labels) {
          y=options.y.map(function (l1) {
            return options.labels.map(function (l2) {
              return l1==l2?1:0;
            });
          });
        } else throw 'ML.learn.MLP: invalid options';
        if (options.normalize) {
          // normalize each variable independently!?
          var max=x[0].map(function (col) { return col}),
              min=x[0].map(function (col) { return col});
          x.forEach(function (row) { row.forEach(function (col,i) { 
            max[i]=Math.max(max[i],col);
            min[i]=Math.min(min[i],col) }) });
          xshift=options.bipolar?-1:0;
          xscale=max.map(function (x,i) { return (xshift?2:1)/((x-min[i])==0?1:x-min[i])});
          xoffset=min;
          x=x.map(function (row) { return row.map(function (col,i) { return xshift+(col-xoffset[i])*xscale[i] }) });
          if (options.regression) {
            // scale y, too, [0,1]
            max=y[0].map(function (col) { return col});
            min=y[0].map(function (col) { return col});
            y.forEach(function (row) { row.forEach(function (col,i) { 
              max[i]=Math.max(max[i],col);
              min[i]=Math.min(min[i],col) }) });
          
            yshift=options.bipolar?-1:0;
            yscale=max.map(function (x,i) { return (yshift?2:1)/((x-min[i])==0?1:x-min[i])});
            yoffset=min;
            y=y.map(function (row) { return row.map(function (col,i) { return yshift+(col-yoffset[i])*yscale[i] }) });
          }
        }

        model = MLP({
          input   : x,
          output  : y,
          n_ins   : x[0].length,
          n_outs  : y[0].length,
          hidden_layer_sizes:options.algorithm==ML.SLP?[]:(options.hidden_layers||[])
        });
        model.algorithm=options.algorithm;
        model.labels=options.labels;
        model.features=options.features;
        model.xscale=options.normalize?{k:xscale,off:xoffset,shift:xshift}:undefined;
        model.yscale=options.normalize&&options.regression?{k:yscale,off:yoffset,shift:yshift}:undefined;
        model.nOutputs=y[0].length;
        
        MLP.code.set(model,'log level',options.verbose||0); // 0 : nothing, 1 : info, 2 : warning.
        if (options.epochs) MLP.code.train(model,{
          epochs : options.epochs
        });
        model.time=Io.time()-t0;
        return model;
        break;

      case ML.SVM:
        // typeof options = {x: number [][], 
        //                   y: ({-1,1}|string) [],
        //                   labels?:string|number [],
        //                   threshold?:number|false,
        //                   C?:numer,tol?:number,max_passes?:number,alpha_tol?:number,kernel?:{}}
        
        // If classes then multi-SVM (one for each class to be separated)!
        if (!options.labels) {
          model = SVM({
            x:options.x,
            y:options.y,
            threshold:options.threshold,
          });
          model.algorithm=options.algorithm
          SVM.code.train(model,{
            C:options.C||1.0,
            tol:options.tol||1e-4,
            max_passes:options.max_passes||20,
            alpha_tol:options.alpha_tol||1e-5,
            kernel:options.kernel
          });
        } else {
          model={};
          model.algorithm=options.algorithm;
          model._labels=options.labels;
          model.svms=options.labels.map(function (cl) {
            return SVM({
              x:options.x,
              y:options.y.map(function (y) { return y==cl?1:-1 }),
              threshold:options.threshold,
            });
          });
          
          model.svms.forEach(function (svm) {
            SVM.code.train(svm,{
              C:options.C||1.0,
              tol:options.tol||1e-4,
              max_passes:options.max_passes||20,
              alpha_tol:options.alpha_tol||1e-5,
              kernel:options.kernel
            });
          });
          // Create one SVM for each class
          // Transform y vector          
        }
        model.time=Io.time()-t0;
        return model;
        break;

      case ML.TXT:
        // typeof options = {data: string []}
        model = TXT.create(options.data,{
        });
        model.algorithm=options.algorithm
        return model;
        break;
    }
  },

  // add noise to numerical data to create synthetic data
  noise: function (data,noise) {
    if (Comp.obj.isMatrix(data)) {
      return data.map(function (row) {
        return row.map(function (v,i) {
          if (typeof noise == 'number')
            return v+(Math.random()-0.5)*noise;
          else return v+(Math.random()-0.5)*noise[i]
        })
      })
    } else if (Comp.obj.isArray(data) && Comp.obj.isObject(data[0])) {
      return data.map(function (row) {
        var o={};
        for (var p in row) {
          if (typeof noise == 'number')
            o[p] = row[p]+(Math.random()-0.5)*noise;
          else o[p] = row[p]+(Math.random()-0.5)*noise[p]
        }
        return o;
      })      
    }
  },

  preprocess:preprocess,

  print: function (model,indent,compact) {
    switch (model.algorithm) {
      case ML.DTI:
        return DTI.print(model,indent,compact);
      case ML.DT:
      case ML.ICE:
        return ICE.print(model,indent);
      case ML.C45:
        return C45.print(model,indent);
      case ML.ID3:
        return ID3.print(model,indent);
    }
  },
  
  // Only text module
  similarity : TXT.similarity,
  
  stats : STAT,
  
  // Check model consistency
  test: function (model,samples) {
    var x,y,data,res,p=0.0;
    switch (model.algorithm) {
    
      case ML.ANN:
        data=preprocess(samples,'xmya',{features:model.features,target:model.target});
        // TODO
        break;
        
      case ML.C45:
        // Sample row format: [x1,x2,..,y]
        if (Comp.obj.isMatrix(samples)) {
          samples.forEach(function (sample) {
            x=sample.slice(0,sample.length-1);
            y=sample[sample.length-1];
            res= C45.classify(model,x);
            if (res==y) p += 1;
          });
          return p/samples.length;
        } else if (Comp.obj.isArray(samples)) {
          x=samples.slice(0,samples.length-1);
          y=samples[samples.length-1];
          res = C45.classify(model,x);
          return res==y?1.0:0.0
        } else if (Comp.obj.isObj(samples) && model.features) {
        }
        break;

      case ML.TXT:
        var model = model.string?{ data : [model.string] }:model;
        if (Comp.obj.isArray(samples))
          return samples.map(function (sample) { 
            return TXT.classify(model,sample).match
          });
        else
          return TXT.classify(model,samples).match;
        break;

        
    }
  },
  

  /** Update a learned model
   *
   */
  update: function (model,options) {
    switch (model.algorithm||options.algorithm) {
    
      case ML.CNN:
        break;

      case ML.DTI:
        // typeof @options = {data: number [][], target:string, features: string [], eps?:number, maxdepth?:number} |
        //                   {x: number [][], y:[], eps?:number, maxdepth?:number}
        if (options.eps==_) options.eps=0;
        if (options.maxdepth==_) options.maxdepth=20;
        if (options.data && options.target && options.features)
          model = DTI.update(model,options);
        else if (options.x && options.y) {
          if (options.x.length != options.y.length) throw 'ML.update.DTI: X and Y vector have different length';
          data=options.x.slice();
          data=data.map(function (row,i) {row.push(options.y[i]); return row});
          features=Comp.array.init(data[0].length-1,function (i) { return String(i)});
          target=String(data[0].length-1);
          console.log(data,features,target)
          model = DTI.update(model,{
            data:data,
            features:features,
            target:target,
            eps:options.eps,
            maxdepth:options.maxdepth
          });
        } else throw 'ML.update.DTI: Invalid options';
          
        model.algorithm=options.algorithm;
        return model;
        break;
        
      case ML.MLP:
        return MLP.code.train(model,{
          epochs : options.epochs||1
        });
        break;

      case ML.RL:
        switch (model.kind) {
          case ML.DQNAgent:
            return RL.DQNAgent.code.learn(model,options);
            break;
          case ML.DPAgent:  
            return RL.DPAgent.code.learn(model,options);
            break;
          case ML.TDAgent:
            return RL.TDAgent.code.learn(model,options);
            break;
        }
        break;
        
    }
  },
  ML:ML,
};
  
ICE.ml=ml;
CNN.ml=ml;
ml.predict=ml.classify;
ml.learner=ml.learn;
ml.train=function (model,options) {
  if (model.algorithm) return ml.update(model,options);
  else return ml.learn(model);
}
ml.best=ml.stats.utils.best;

module.exports = {
  agent:ml,
  classify:ml.classify,
  column:ml.column,
  compact:ml.compact,
  depth:ml.depth,
  entropy:STAT.entropy,
  entropyN:STAT.entropyN,
  entropyDep:STAT.entropyDep,
  entropyT:STAT.entropyT,
  evaluate:ml.evaluate,
  info:ml.info,
  learn:ml.learn,
  noise:ml.noise,
  options:options,
  pca:PCA,
  preprocess:preprocess,
  print:ml.print,
  stats:STAT,
  test:ml.test,
  unique:ml.unique,
  update:ml.update,
  ML:ML,
  current:function (module) { current=module.current; Aios=module; }
}
};
BundleModuleCode['ml/ice']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Ankit Kuwadekar, Stefan Bosse
 **    $INITIAL:     (C) 2014, Ankit Kuwadekar
 **    $MODIFIED:    (C) 2006-2018 bLAB by sbosse
 **    $VERSION:     1.3.2
 **
 **    $INFO:
 **
 ** ICE: C45/ID3 Decision Tree Algorithm supporting feature variables with eps intervals
 **
 ** Portable model
 **
 ** New:
 **        typeof eps = number | [epsx1:number,epsx2:number,..]
 **
 **    $ENDOFINFO
 */
var Io = Require('com/io');
var Comp = Require('com/compat');
var current=none;
var Aios=none;
var that;

/**
 * Map of valid tree node types
 * @constant
 * @static
 */
var NODE_TYPES = {
  RESULT: 'result',
  FEATURE: 'feature',
  FEATURE_VALUE: 'feature_value'
};

var NL ='\n'

/**
 * Creates a new tree
 */
function createTree(data, target, features, eps) {
  var ml = that.ml;
  var targets = ml.stats.unique(ml.stats.utils.column(data, target));
  if (targets.length == 1) {
    return {
      type: NODE_TYPES.RESULT,
      name: targets[0],
    };
  }

  if (features.length == 0) {
    var topTarget = ml.stats.mostCommon(targets);
    return {
      type: NODE_TYPES.RESULT,
      name: topTarget,
    };
  }

  
  var split = ml.stats.splitEps(data,features,target,targets,eps);
  var bestFeature = split.feature;
  var index = features.indexOf(bestFeature);
  var remainingFeatures = split.remainingFeatures;
  var remainingEps = 
    typeof eps == 'number'?eps:remainingFeatures.map(function (v) { return eps[features.indexOf(v)] });
  var possibleValues = split.possibleValues;

  var node = {
    type: NODE_TYPES.FEATURE,
    name: bestFeature,
    index: index,
    eps: that.ml.stats.utils.selectEps(eps,index)
  };

  node.vals = split.choices.map(function (c) {
    var child_node = {
      val : c.val,
      eps : that.ml.stats.utils.selectEps(eps,index),
      type: NODE_TYPES.FEATURE_VALUE
    };

    child_node.child = createTree(c.data, target, remainingFeatures, remainingEps);
    return child_node;
    
  })
  return node;
}


function depth(model) {
  switch (model.type) {
    case NODE_TYPES.RESULT: return 1;
    case NODE_TYPES.FEATURE: 
      return 1+Comp.array.max(model.vals.map(function (val) {
        return depth(val);
      }));
    case NODE_TYPES.FEATURE_VALUE: 
      return 1+depth(model.child);   
  }
  return 0;
}

function info(model) {
  var vl = vars(model);
  return {
    depth:depth(model),
    nodes:vl.length,
    vars:vl.unique(),
  }
}

function predictEps(model,sample,prob,eps) {
  var root = model;
  if (!prob) prob=1;
  while (root.type !== NODE_TYPES.RESULT) {
    var attr = root.name;
    var sampleVal = sample[attr];
    // kNN approximation
    var childNode = null;
    root.vals.forEach(function(node) {
      var fit=Math.abs(node.val-sampleVal);
      if (!childNode || fit < childNode.fit) childNode={fit:fit,node:node};
    });
    if (childNode){
      // with fit quality propagation
      prob = prob * (1-Math.abs(childNode.fit/that.ml.stats.utils.selectEps(eps,root.index))/4) 
      root = childNode.node.child;
    } else {
      root = root.vals[0].child;
    }
  }
  return {value:root.name,prob:prob};
};


function printModel(model,indent) {
  var line='',sep;
  if (indent==undefined) indent=0;
  if (!model) return '';
  var sp = function () {var s=''; for(var i=0;i<indent;i++) s+=' '; return s};
  switch (model.type) {
    case NODE_TYPES.RESULT: 
      return sp()+'-> '+model.name+NL;
    case NODE_TYPES.FEATURE:
      line=sp()+'$'+model.name+'?'+NL;
      model.vals.forEach(function (v) {
        line += printModel(v,indent+2);
      }); 
      return line;
    case NODE_TYPES.FEATURE_VALUE: 
      line=sp()+'=['+(model.val-model.eps)+','+(model.val+model.eps)+']'+NL;
      return line+printModel(model.child,indent+2); 
  }
  return 'model?';
}

function vars(model) {
  switch (model.type) {
    case NODE_TYPES.RESULT: return [];
    case NODE_TYPES.FEATURE: 
      return [model.name].concat(Comp.array.flatten(model.vals.map(vars)));
    case NODE_TYPES.FEATURE_VALUE: 
      return vars(model.child);   
  }
  return [];
}

that = module.exports = {
  create: function (options) {
    return createTree(options.data,
                      options.target,
                      options.features,
                      options.eps)
  },
  depth:depth,
  info:info,
  ml:{},
  predict:function (model,sample) {
    return predictEps(model,sample,1,model.eps)
  },
  print:printModel,
}
};
BundleModuleCode['ml/dti']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2006-2018 bLAB
 **    $CREATED:     03-03-16 by sbosse.
 **    $VERSION:     1.4.2
 **
 **    $INFO:
 **
 ** Interval Decision Tree Learner
 **
 ** Modified ID3-based Decision Tree Algorithm that wraps all data with 2-eps intervals and uses
 ** interval instead single value arithmetic for entropy calculation and feature selection.
 ** The classification bases on a nearest-neighbourhood look-up of best matching results.
 **
 ** Two different algorithms are supported:
 **
 **   1. Static (using learn), the DTI learner using attribute selection based on entropy.
 **      The training data must be available in advance.
 **   2. Dynamic (using update), the DTI learrner using attribute selection based on significance.
 **      The training data is applied sequentielly (stream learning) updating the model.
 **
 **   Though in principle the both algrotihms can be mixed (first static, then dynamic updating), 
 **   the resulting model will have poor classification quality. Either use static or only dynamic
 **   (stream) learning.
 **   
 ** Portable model
 **
 **    $ENDOFINFO
 */
var Io = Require('com/io');
var Comp = Require('com/compat');
var current=none;
var Aios=none;
var min = Comp.pervasives.min;
var max = Comp.pervasives.max;

/**
 * Map of valid tree node types
 * @constant
 * @static
 */
var NODE_TYPES = {
  RESULT: 'result',
  FEATURE: 'feature',
  FEATURE_VALUE: 'feature_value'
};


function Result(key) {
  return {
    type:NODE_TYPES.RESULT,
    name:key
  }
}

function Feature(name,vals) {
  return {
    type:NODE_TYPES.FEATURE,
    name:name,
    vals:vals
  }
}

// A value can be a scalar or a range {a,b} object
function Value(val,child) {
  return {
    type:NODE_TYPES.FEATURE_VALUE,
    val:val,
    child:child
  }
}

/** Add a new training set with optional data set merging and value interval expansion.
 * 
 */
function add_training_set(data,set,merge) {
  if (merge) {
    // Merge a data set with an existing for a specific key; create value ranges
  } else
    data.push(set);  
} 


/**
 * Computes Log with base-2
 * @private
 */
function log2(n) {
  return Math.log(n) / Math.log(2);
}




function results(model) {
  var line='',sep;
  if (!model) return '';
  switch (model.type) {
    case NODE_TYPES.RESULT: 
      return model.name;
    case NODE_TYPES.FEATURE:
      sep='';
      line='';
      Comp.array.iter(model.vals,function (v) {
        line += sep+results(v);
        sep=',';
      }); 
      return line;
    case NODE_TYPES.FEATURE_VALUE: 
      return results(model.child);   
  }
  return 'result?';
}


/**
 * Finds element with highest occurrence in a list
 * @private
 */
function mostCommon(list) {
  var elementFrequencyMap = {};
  var largestFrequency = -1;
  var mostCommonElement = null;

  list.forEach(function(element) {
    var elementFrequency = (elementFrequencyMap[element] || 0) + 1;
    elementFrequencyMap[element] = elementFrequency;

    if (largestFrequency < elementFrequency) {
      mostCommonElement = element;
      largestFrequency = elementFrequency;
    }
  });

  return mostCommonElement;
}

function addVal(v1,v2) {
  if (v1.a!=undefined) {
    if (v2.a!=undefined) return {a:v1.a+v2.a,b:v1.b+v2.b};
    else return {a:v1.a+v2,b:v2.b+v2};
  } else if (v2.a!=undefined) return {a:v2.a+v1,b:v2.b+v1};
  else return v1+v2;
}

function lowerBound(v) {
  if (v.a==undefined) return v; else return v.a;
}

function upperBound(v) {
  if (v.b==undefined) return v; else return v.b;
}

function equal(v1,v2) {
  return (v1==v2 ||
          (upperBound(v1) == upperBound(v2) &&
          (lowerBound(v1) == lowerBound(v2))))
}

function overlap(v1,v2) {
  return (upperBound(v1) >= lowerBound(v2) && upperBound(v1) <= upperBound(v2)) ||
         (upperBound(v2) >= lowerBound(v1) && upperBound(v2) <= upperBound(v1))
}

function containsVal(vl,v) {
  for (var i in vl) {
    var v2=vl[i];
    if (overlap(v,v2)) return true;
  }
  return false;
}

function centerVal(v) {
  if (v.a==undefined) return v; else return (v.a+v.b)/2;
}

function distanceVal (v1,v2) {
  return Math.abs(centerVal(v1)-centerVal(v2));
}

function Bounds(vl,v) {
  if (vl.length==0) return {a:v,b:v};
  else if (v==undefined) return {a:Min(vl),b:Max(vl)};
  else return {a:Min([Min(vl),v]),b:Max([Max(vl),v])};
}

function Min(vals) {
  var min=none;
  Comp.array.iter(vals, function (val) {
    if (min==none) min=(val.a==undefined?val:val.a);
    else min=val.a==undefined?(val<min?val:min):(val.a<min?val.a:min);
  });
  return min;
}

function Max(vals) {
  var max=none;
  Comp.array.iter(vals,function (val) {
    if (max==none) max=(val.b==undefined?val:val.b);
    else max=(val.b==undefined?(val>max?val:max):(val.b>max?val.a:max));
  });
  return max;
}

// Return interval of a value x with a<=x_center-eps, b>=x_center+eps
function epsVal(x,eps) {
  if (x.a == undefined) return {a:x-eps,b:x+eps};
  else if ((x.b-x.a) < 2*eps) return {a:centerVal(x)-eps,b:centerVal(x)+eps}; 
  else return x;
}
/** Filter out unique values that are spaced at least by eps
 *
 */
function uniqueEps(data,eps) {
  var results=[];
  Comp.array.iter(data,function (x) {
    var found;
    if (!results.length) results.push(x);
    else {
      Comp.array.iter(results,function (y,i) {
        if (found) return;
        found = Math.abs(centerVal(x)-centerVal(y))<eps;
        if (found) // create new overlapping value with +-eps extensions 
          results[i]={a:Min([x,y])-eps,b:Max([x,y])+eps}
      }); 
      if (!found) results.push(x);
    }
  });
  return results;
}

/** Compact tree, merge nodes and intervals.
 ** adjust=true: Adjust overlapping feature variable value intervals!!!
 */

function compactTree(model,adjust) {
  var i,j,vi,vj,_vals,merged;
  function target(model) {
    var line;
    switch (model.type) {
      case NODE_TYPES.RESULT: 
        return model.name;
      case NODE_TYPES.FEATURE:      
        line = model.name+'?'+target;
        Comp.array.iter(model.vals,function (v) {
          line += target(v);
        }); 
        return line;  
      case NODE_TYPES.FEATURE_VALUE: 
        line='='+(model.val.a==undefined?model.val:'['+model.val.a+','+model.val.b+']')+NL;
        return line+target(model.child); 
    }
  }
  if (!model) return model;
  switch (model.type) {
    case NODE_TYPES.RESULT: 
      return model;
      break;
    case NODE_TYPES.FEATURE:
      _vals=[];
      // 1. Merge
      for (i in model.vals) {
        vi=model.vals[i];
        assert((vi.type==NODE_TYPES.FEATURE_VALUE)||'vi.type==NODE_TYPES.FEATURE_VALUE');
        merged=false;
        loopj: for(j in _vals) {
          vj=_vals[j];
          if (target(vi.child)==target(vj.child)) {
            merged=true;
            vj.val={a:Min([vi.val,vj.val]),b:Max([vi.val,vj.val])}
            break loopj;
          }
        }
        if (!merged) {
          _vals.push(vi);
          vi.child=compactTree(vi.child);
        }
      }
      // 2. Adjust overlapping value intervals!
      if (adjust) {
        // TODO: approach too simple!!!! 
        for (i in _vals) {
          i=Comp.pervasives.int_of_string(i);
          if (_vals[i+1]) {
            if (upperBound(_vals[i].val) > lowerBound(_vals[i+1].val)) {
              if (_vals[i].val.b) _vals[i].val.b=lowerBound(_vals[i+1].val)-1;
              else _vals[i+1].val.a=upperBound(_vals[i].val)+1;
            }
          }
        }
      }
      
      model.vals=_vals;
      return model;
      break;
    case NODE_TYPES.FEATURE_VALUE:
      return model;
      break;
  }
}



/** Creates a new tree from training data (data)
 *
 *  data is {x1:v1,x2:v2,..,y:vn} []
 *  target is classification key name
 *  features is ['x1','x2,',..]  w/o target variable
 *  eps is interval applied to all data values
 *
 */
function createTree(data, target, features, options) {
  var _newS,child_node,bounds;
      
  var targets = Comp.array.unique(Comp.array.pluck(data, target));
  // console.log(targets)  
  if (options.maxdepth==undefined) options.maxdepth=1;
  if (options.maxdepth==0) return Result('-');
  // console.log(data);
  // console.log(features);

  //Aios.aios.log('createTree:'+targets.length);
  //try {Aios.aios.CP();} catch (e) {throw 'DTI.createTree: '+options.maxdepth };
  if (Aios) Aios.aios.CP();
  if (targets.length == 1) return Result(targets[0]);

  if (features.length == 0) {
    var topTarget = mostCommon(targets);
    return Result(topTarget)
  }
  var bestFeatures = getBestFeatures(data, target, features, options.eps);
  var bestFeature = bestFeatures[0];

  var remainingFeatures = Comp.array.filtermap(bestFeatures,function (feat) {
    if (feat.name!=bestFeature.name) return feat.name;
    else return none;
  });
/*  
  var possibleValues = Comp.array.sort(Comp.array.pluck(data, bestFeature.name), function (x,y) {
    if (upperBound(x) < lowerBound(y)) return -1; else return 1; // increasing value order
  });
*/
  var possibleValues = getPossibleVals(data,bestFeature.name);
  
  var vals=[];
  
  //console.log(bestFeatures);
  //console.log(possibleValues);
  var partitions=partitionVals(possibleValues,options.eps);
  // Aios.aios.log(partitions);
  //console.log(bestFeatures);
  //console.log(possibleValues);
  if (partitions.length==1) {
    // no further 2*eps separation possible, find best feature by largest distance
    // resort best feature list with respect to value deviation
    bestFeatures.sort(function (ef1,ef2) {
      if (ef1.d > ef2.d) return -1; else return 1;
    });
    bestFeature = bestFeatures[0];
    possibleValues = getPossibleVals(data,bestFeature.name);
    Comp.array.iter(mergeVals(possibleValues),function (val,i) {

      _newS = data.filter(function(x) {
        // console.log(x[bestFeature.name],val,overlap(val,x[bestFeature.name]))
        
        return overlap(val,x[bestFeature.name]);
      });
      child_node = Value(val);
      options.maxdepth--;
      child_node.child = createTree(_newS, target, remainingFeatures, options);
      //console.log(_newS);
      vals.push(child_node);
    })    
    
  } else Comp.array.iter(partitions,function (partition,i) {
    
    _newS = data.filter(function(x) {
      // console.log(x[bestFeature.name],v,overlap(x[bestFeature.name],v))
      return containsVal(partition,x[bestFeature.name]);
    });
    bounds = Bounds(partition);
    child_node = Value(options.eps==0?{a:bounds.a,b:bounds.b}:{a:bounds.a-options.eps,b:bounds.b+options.eps});
      options.maxdepth--;
    child_node.child = createTree(_newS, target, remainingFeatures, options);
    //console.log(_newS);
    vals.push(child_node);
  });
  
  return Feature(bestFeature.name,vals);
}

/** Return the depth of the tree
 *
 */
function depth(model) {
  switch (model.type) {
    case NODE_TYPES.RESULT: return 0;
    case NODE_TYPES.FEATURE: 
      return 1+Comp.array.max(model.vals,function (val) {
        return depth(val);
      });
    case NODE_TYPES.FEATURE_VALUE: 
      return depth(model.child);   
  }
  return 0;
}

/** Computes entropy of a list with 2-epsilon intervals
 *
 */

function entropyEps(vals,eps) {
  // TODO: overlapping value intervals
  var uniqueVals = Comp.array.unique(vals);
  var probs = uniqueVals.map(function(x) {
    return probEps(x, vals, eps)
  });

  var logVals = probs.map(function(p) {
    return -p * log2(p)
  });

  return logVals.reduce(function(a, b) {
    return a + b
  }, 0);
}

function entropyEps2(vals,eps) {
  // TODO: overlapping value intervals
  var uniqueVals = uniqueEps(vals,eps);
  var probs = uniqueVals.map(function(x) {
    return probEps2(x, vals, eps)
  });

  var logVals = probs.map(function(p) {
    return -p * log2(p)
  });

  return logVals.reduce(function(a, b) {
    return a + b
  }, 0);
}


function getBestFeatures(data,target,features,eps) {
  var bestfeatures=[];
  function deviation(vals) {
    var n = vals.length;
    var mu=Comp.array.sum(vals,function (val) {
      return (lowerBound(val)+upperBound(val))/2;
    })/n;
    var dev=Comp.array.sum(vals,function (val) {
      return Math.pow(((lowerBound(val)+upperBound(val))/2)-mu,2);
    })/n;
    return dev;
  }
  for (var feature in features) {
    if (features[feature]==undefined) throw 'DTI.getBestFeatures: invalid feature vector';
    var vals=Comp.array.pluck(data, features[feature]).map(function (val) {return val==undefined?0:val});
    var e = entropyEps(vals,eps);
    var d = deviation(vals);
    var min = Min(vals);
    var max = Max(vals);
    bestfeatures.push({e:e,d:d,range:{a:min,b:max},name:features[feature]});
  }
  bestfeatures.sort(function (ef1,ef2) {
    if (ef1.e > ef2.e) return -1; else return 1;
  });
  return bestfeatures;
}

/** Find in one data set the most significant feature variable (i.e., with highest value)
 */
function getSignificantFeature(data,features) {
  var f,sig;
  for (f in features) {
    if (sig==undefined || sig.val < data[features[f]]) sig={name:features[f],val:data[features[f]]};
  }
  return sig;
}

function getPossibleVals(data,feature) {
  return Comp.array.sort(Comp.array.pluck(data, feature), function (x,y) {
    if (upperBound(x) < lowerBound(y)) return -1; else return 1; // increasing value order
  });
}

/** Merge values and intervals
 */
function mergeVals(vals) {
  var _vals,
      merged,i,j;
  for (i in vals) {
    var vi = vals[i];
    if (!_vals) _vals=[vi];
    else {
      // Find overlapping values and merge
      merged=false;
      loopj: for (j in _vals) {
        var vj = _vals[j];
        if (equal(vi,vj)) {
          merged=true;
          break loopj;          
        }
        else if (overlap(vi,vj)) {
          merged=true;
          _vals[j]={a:Min([vi,vj]),b:Max([vi,vj])};
          break loopj;
        }
      }
      if (!merged) _vals.push(vi);
    }
  }
  //Aios.aios.log(_vals);
  return _vals||[];
}

/**
 * Predicts class for sample
 */
function nearestVal(vals,sample,fun) {
  var best=none;
  for (var v in vals) {
    var d=fun?distanceVal(fun(vals[v]),sample):distanceVal(vals[v],sample);
    if (best==none) 
      best={v:vals[v],d:d};
    else if (best.d > d)
      best={v:vals[v],d:d};    
  }
  if (best) return best.v;
  else return none;
}


/** Parttition an ordered set of values
 *  Each partition of values has at least 2*eps distance to the next partition.
 *
 */
function partitionVals(vals,eps) {
  var last=none;
  var partitions=[];
  var partition=[];
  for(var i in vals) {
    var val0=vals[i];
    var val1=vals[i-1];

    if (val1==undefined) partition.push(val0);
    else if ( upperBound(val0) < upperBound(addVal(val1,2*eps))) partition.push(val0);    
    else {
      partitions.push(partition);
      partition=[val0];
    }
  }
  if (partition.length>0) partitions.push(partition);
  return partitions;
}

/** Make a predicition with sample data
 *
 */
function predict(model,sample) {
  var root = model;
  while (root && root.type !== NODE_TYPES.RESULT) {
    var attr = root.name;
    var sampleVal = sample[attr];
    var childNode = nearestVal(root.vals,sampleVal,function (node) {
      return node.val;
    });

    if (childNode){
      root = childNode.child;
    } else {
      root = none;
    }
  }
  if (root) return root.name||root.val;
  else return none;
};

/** Print the tree
 *
 */
function print(model,indent, compact) {
  var line='',sep;
  if (compact) return results(model);
  if (indent==undefined) indent=0;
  if (!model) return '';
  var sp = function () {return Comp.string.create(indent);};
  switch (model.type) {
    case NODE_TYPES.RESULT: 
      return sp()+'-> '+model.name+NL;
    case NODE_TYPES.FEATURE:
      line=sp()+'$'+model.name+'?'+NL;
      Comp.array.iter(model.vals,function (v) {
        line += print(v,indent+2);
      }); 
      return line;
    case NODE_TYPES.FEATURE_VALUE: 
      line=sp()+'='+(model.val.a==undefined?model.val:'['+model.val.a+','+model.val.b+']')+NL;
      return line+print(model.child,indent+2); 
  }
  return 'model?';
}

/**
 * Computes probability of of a given value existing in a given list
 * with additional 2*epsilon interval, only applicable to numerical values.
 */
function probEps(value, list, eps) {
  // TODO: ranges
  var occurrences = Comp.array.filter(list, function(element) {
    return (element >= (value-eps)) && (element <= (value+eps));
  });

  var numOccurrences = occurrences.length;
  var numElements = list.length;
  return numOccurrences / numElements;
}

function probEps2(value, list, eps) {
  // TODO: ranges
  var occurrences = Comp.array.filter(list, function(element) {
    return overlap(epsVal(value), epsVal(element));
  });

  var numOccurrences = occurrences.length;
  var numElements = list.length;
  return numOccurrences / numElements;
}

/** Incremental update of the model with new training set(s). Can be executed with an empty model.
 *  The current tree can be week for a new training set (new target).
 *  This can result in a classification of the new target with insignificant variables.
 *  Therefore, the last tree node must be exapnded with an additional strong (most significant)
 *  variable of the new data set (but it is still a heuristic for future updates). 
 */
function updateTree(model,data, target, features, options) {
  var eps = options.eps,
      maxdepth = options.maxdepth,
      verbose = options.verbose;
  var featuresINm={},   // All current tree feature variables and their value interval
      results=[],       // All current tree result leafs
      set,i,v,feature,remainingFeatures,exists,sigFeature;
  // 1. Analysis of existing model
 
  var analyze = function (model,feature) {
    var feature2;
    if (!model) return;
    switch (model.type) {
      case NODE_TYPES.RESULT:
        if (!Comp.array.contains(results,model.name)) results.push(model.name); 
        break;
      case NODE_TYPES.FEATURE:
        feature2={name:model.name};
        if (!featuresINm[model.name]) featuresINm[model.name]=feature2;
        Comp.array.iter(model.vals,function (v) { analyze(v,featuresINm[model.name]) });
        break;
      case NODE_TYPES.FEATURE_VALUE:
        if (!feature.val) feature.val={
          a:(model.val.a==undefined?model.val:model.val.a),
          b:(model.val.a==undefined?model.val:model.val.b)
        }; else {
          feature.val.a=min(feature.val.a,
                            (model.val.a==undefined?model.val:model.val.a));
          feature.val.b=max(feature.val.b,
                            (model.val.a==undefined?model.val:model.val.b));
        }                  
        analyze(model.child);
        break; 
    }   
  }

  
  analyze(model);
  // console.log(featuresINm);
  // console.log(results);
  
  exists=Comp.array.contains(results,data[target]);

  
  // 2a. Empty model, add first training set with two significant feature variable nodes
  function init(set) {
    set=data[i];
      sigFeature1=getSignificantFeature(set,features);
      remainingFeatures=Comp.array.filter(features,function (feat) {
        return sigFeature1.name!=feat;
      });
      sigFeature2=getSignificantFeature(set,remainingFeatures);

      featuresINm[sigFeature1.name]={name:sigFeature1.name,
                                    val:{a:sigFeature1.val-eps,b:sigFeature1.val+eps}};
      featuresINm[sigFeature2.name]={name:sigFeature2.name,
                                    val:{a:sigFeature2.val-eps,b:sigFeature2.val+eps}};
      results.push(set[target]);
      model=Feature(sigFeature1.name,[
                    Value({a:set[sigFeature1.name]-eps,b:set[sigFeature1.name]+eps},
                          Feature(sigFeature2.name,[
                                 Value({a:sigFeature2.val-eps,b:sigFeature2.val+eps},
                                       Result(set[target])) 
                                  ]))]);
      return model;
  }
  
  remainingFeatures=Comp.array.filter(features,function (feat) {
    return !featuresINm[feat];
  });
  
  // 2b. Update the tree with the new training set
  var update = function (model,set,feature) {
    var feature2,p;
    if (!model) return;
    switch (model.type) {
    
      case NODE_TYPES.RESULT:
        if (model.name != set[target] && verbose)
          console.log('Cannot insert new training set '+set[target]+' in tree. No more separating variables!');
        break;
        
      case NODE_TYPES.FEATURE:
        // console.log(set[target]+': '+ model.name+'='+set[model.name]);
        if (set[model.name]<(featuresINm[model.name].val.a-eps) ||
            set[model.name]>(featuresINm[model.name].val.b+eps)) {
          // add new training set; done
          // the current decision tree can  be week, thus add another strong variable node, too! 
          sigFeature=getSignificantFeature(set,remainingFeatures);
          featuresINm[sigFeature.name]={name:sigFeature.name,
                                        val:{a:sigFeature.val-eps,b:sigFeature.val+eps}};
          featuresINm[model.name].val.a=min(featuresINm[model.name].val.a,set[model.name]-eps);
          featuresINm[model.name].val.b=max(featuresINm[model.name].val.b,set[model.name]+eps);
          if (!Comp.array.contains(results,set[target])) results.push(set[target]);

          model.vals.push(Value({a:set[model.name]-eps,b:set[model.name]+eps},
                          Feature(sigFeature.name,[
                            Value({a:sigFeature.val-eps,b:sigFeature.val+eps},
                                  Result(set[target]))
                          ])));
          model.vals=Comp.array.sort(model.vals,function (v1,v2) {return (lowerBound(v1.val)<lowerBound(v2.val))?-1:1});  
        } else {
          // go deeper, but extend the interval of the best matching child node with new data variable
          Comp.array.iter_break(model.vals,function (fv) {
            // console.log(model.name,fv.val,overlap(fv.val,{a:set[model.name]-eps,b:set[model.name]+eps})) 
            if (overlap(fv.val,{a:set[model.name]-eps,b:set[model.name]+eps})) {
              fv.val.a=min(lowerBound(fv.val),set[model.name]-eps);
              fv.val.b=max(upperBound(fv.val),set[model.name]+eps);
              update(fv,set,model.name);
              return true;
            } else return false;
          });
        }
        break;
        
      case NODE_TYPES.FEATURE_VALUE:
        update(model.child,set);
        break; 
    }   
  }

  for (i in data) {
    set=data[i];
    if (model==undefined || model.type==undefined)
      model=init(set);
    else
      update(model,set);
  }
  return model;
}

module.exports =  {
  NODE_TYPES:NODE_TYPES,
  compactTree:compactTree,
  create:function (options) {
    // type options = {data number [][], target:string, features: string [], eps;number, maxdepth}
    return createTree(options.data,options.target,options.features,options)
  },
  depth:depth,
  entropy:entropyEps,
  evaluate:function evaluate(model,target,samples){},
  predict:predict,
  print:print,
  results:results,
  update:function (model,options) {
    // type options = {data number [][], target:string, features: string [], eps:number, maxdepth}
    return updateTree(model,options.data,options.target,options.features,options)
  },
  current:function (module) { current=module.current; Aios=module;}
};


};
BundleModuleCode['ml/knn']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Ankit Kuwadekar, Stefan Bosse
 **    $INITIAL:     (C) 2014, Ankit Kuwadekar
 **    $MODIFIED:    (C) 2006-2019 bLAB by sbosse
 **    $VERSION:     1.2.1
 **
 **    $INFO:
 **
 ** KNN: k-nearest-neighbour Algorithm
 ** A General purpose k-nearest neighbor classifier algorithm based on the 
 ** k-d tree Javascript library develop by Ubilabs.
 **
 ** Portable models (KNN/KNN2)
 **
 **    $ENDOFINFO
 */
var options = {
  version:'1.2.1'
}
var Comp = Require('com/compat');
var math = Require('ml/math');
var euclideanDistance = math.euclidean;

/*
 * Original code from:
 *
 * k-d Tree JavaScript - V 1.01
 *
 * https://github.com/ubilabs/kd-tree-javascript
 *
 * @author Mircea Pricop <pricop@ubilabs.net>, 2012
 * @author Martin Kleppe <kleppe@ubilabs.net>, 2012
 * @author Ubilabs http://ubilabs.net, 2012
 * @license MIT License <http://www.opensource.org/licenses/mit-license.php>
 */

function Node(obj, dimension, parent) {
    var N = {}
    N.obj = obj;
    N.left = null;
    N.right = null;
    N.parent = parent;
    N.dimension = dimension;
    return N;
}

/* KDTree
 *
 */

function KDTree(points, metric) {
    // if (!(this instanceof KDTree)) return new KDTree(points, metric);
    // If points is not an array, assume we're loading a pre-built tree
    var K ={}
    if (!Array.isArray(points)) {
        K.dimensions = points.dimensions;
        K.root = points;
        restoreParent(K.root);
    } else {
        K.dimensions = new Array(points[0].length);
        for (var i = 0; i < K.dimensions.length; i++) {
            K.dimensions[i] = i;
        }
        K.root = buildTree(points, 0, null, K.dimensions);
    }
    K.metric = metric;
    return K;
}

// Convert to a JSON serializable structure; this just requires removing
// the `parent` property
KDTree.code = {
  nearest : function(K, point, maxNodes, maxDistance) {
    var metric = K.metric;
    var dimensions = K.dimensions;
    var i;

    var bestNodes = BinaryHeap(
        function (e) {
            return -e[1];
        }
    );

    function nearestSearch(node) {
        var dimension   = dimensions[node.dimension];
        var ownDistance = metric(point, node.obj);
        var linearPoint = {};
        var bestChild,
            linearDistance,
            otherChild,
            i;
        function saveNode(node, distance) {
            BinaryHeap.code.push(bestNodes,[node, distance]);
            if (BinaryHeap.code.size(bestNodes) > maxNodes) {
                BinaryHeap.code.pop(bestNodes);
            }
        }

        for (i = 0; i < dimensions.length; i += 1) {
            if (i === node.dimension) {
                linearPoint[dimensions[i]] = point[dimensions[i]];
            } else {
                linearPoint[dimensions[i]] = node.obj[dimensions[i]];
            }
        }

        linearDistance = metric(linearPoint, node.obj);
        if (node.right === null && node.left === null) {
            if (BinaryHeap.code.size(bestNodes) < maxNodes || ownDistance < BinaryHeap.code.peek(bestNodes)[1]) {
                saveNode(node, ownDistance);
            }
            return;
        }

        if (node.right === null) {
            bestChild = node.left;
        } else if (node.left === null) {
            bestChild = node.right;
        } else {
            if (point[dimension] < node.obj[dimension]) {
                bestChild = node.left;
            } else {
                bestChild = node.right;
            }
        }

        nearestSearch(bestChild);

        if (BinaryHeap.code.size(bestNodes) < maxNodes || ownDistance < BinaryHeap.code.peek(bestNodes)[1]) {
            saveNode(node, ownDistance);
        }

        if (BinaryHeap.code.size(bestNodes) < maxNodes || Math.abs(linearDistance) < BinaryHeap.code.peek(bestNodes)[1]) {
            if (bestChild === node.left) {
                otherChild = node.right;
            } else {
                otherChild = node.left;
            }
            if (otherChild !== null) {
                nearestSearch(otherChild);
            }
        }
    }

    if (maxDistance) {
        for (i = 0; i < maxNodes; i += 1) {
            BinaryHeap.code.push(bestNodes,[null, maxDistance]);
        }
    }

    if (K.root) {
        nearestSearch(K.root);
    }

    var result = [];
    for (i = 0; i < Math.min(maxNodes, bestNodes.content.length); i += 1) {
        if (bestNodes.content[i][0]) {
            result.push([bestNodes.content[i][0].obj, bestNodes.content[i][1]]);
        }
    }
    return result;
  }
}

function buildTree(points, depth, parent, dimensions) {
    var dim = depth % dimensions.length;

    if (points.length === 0) {
        return null;
    }
    if (points.length === 1) {
        return Node(points[0], dim, parent);
    }

    points.sort(function (a, b) { a[dimensions[dim]] - b[dimensions[dim]]});

    var median  = Math.floor(points.length / 2);
    var node    = Node(points[median], dim, parent);
    node.left   = buildTree(points.slice(0, median), depth + 1, node, dimensions);
    node.right  = buildTree(points.slice(median + 1), depth + 1, node, dimensions);

    return node;
}

function restoreParent(root) {
    if (root.left) {
        root.left.parent = root;
        restoreParent(root.left);
    }

    if (root.right) {
        root.right.parent = root;
        restoreParent(root.right);
    }
}
/** BinaryHeap
 *
 */
 
// Binary heap implementation from:
// http://eloquentjavascript.net/appendix2.html
function BinaryHeap (scoreFunction) {
  var B={}
    //if (!(this instanceof BinaryHeap)) return new BinaryHeap (scoreFunction);
  B.content = [];
  B.scoreFunction = scoreFunction;
  return B;
}


BinaryHeap.code = {
  push : function(B,element) {
    // Add the new element to the end of the array.
    B.content.push(element);
    // Allow it to bubble up.
    BinaryHeap.code.bubbleUp(B,B.content.length - 1);
  },
  pop : function(B) {
    // Store the first element so we can return it later.
    var result = B.content[0];
    // Get the element at the end of the array.
    var end = B.content.pop();
    // If there are any elements left, put the end element at the
    // start, and let it sink down.
    if (B.content.length > 0) {
        B.content[0] = end;
        BinaryHeap.code.sinkDown(B,0);
    }
    return result;
  },
  peek : function(B) {
    return B.content[0];
  },
  size : function(B) {
    return B.content.length;
  },
  bubbleUp : function(B,n) {
    // Fetch the element that has to be moved.
    var element = B.content[n];
    // When at 0, an element can not go up any further.
    while (n > 0) {
        // Compute the parent element's index, and fetch it.
        var parentN = Math.floor((n + 1) / 2) - 1;
        var parent = B.content[parentN];
        // Swap the elements if the parent is greater.
        if (B.scoreFunction(element) < B.scoreFunction(parent)) {
            B.content[parentN] = element;
            B.content[n] = parent;
            // Update 'n' to continue at the new position.
            n = parentN;
        } else { // Found a parent that is less, no need to move it further.
            break;
        }
    }
  },
  sinkDown : function(B,n) {
    // Look up the target element and its score.
    var length = B.content.length;
    var element = B.content[n];
    var elemScore = B.scoreFunction(element);

    while (true) {
        // Compute the indices of the child elements.
        var child2N = (n + 1) * 2;
        var child1N = child2N - 1;
        // This is used to store the new position of the element,
        // if any.
        var swap = null;
        // If the first child exists (is inside the array)...
        if (child1N < length) {
            // Look it up and compute its score.
            var child1 = B.content[child1N];
            var child1Score = B.scoreFunction(child1);
            // If the score is less than our element's, we need to swap.
            if (child1Score < elemScore) {
                swap = child1N;
            }
        }
        // Do the same checks for the other child.
        if (child2N < length) {
            var child2 = B.content[child2N];
            var child2Score = B.scoreFunction(child2);
            if (child2Score < (swap === null ? elemScore : child1Score)) {
                swap = child2N;
            }
        }

        // If the element needs to be moved, swap it, and continue.
        if (swap !== null) {
            B.content[n] = B.content[swap];
            B.content[swap] = element;
            n = swap;
        } else {
            // Otherwise, we are done.
            break;
        }
    }
  }
}

/** KNN
 *
 */

/**
 ** typeof @dataset = number [] []
 ** typeof @labels  = number []
 ** typeof @options = { distance?:function, k?:number }
 */
function KNN(dataset, labels, options) {
    var L = {}
    if (!options) options={};
    if (dataset === true) {
        var model = labels;
        L.kdTree = KDTree(model.kdTree, options);
        L.k = model.k;
        L.classes = new Set(model.classes);
        L.isEuclidean = model.isEuclidean;
        return L;
    }
    var classes = new Set(labels);

    var distance = getDistanceFunction(options.distance),
        k = options.k||classes.size + 1;

    var points = new Array(dataset.length);
    for (var i = 0; i < points.length; ++i) {
        points[i] = dataset[i].slice();
    }

    for (i = 0; i < labels.length; ++i) {
        points[i].push(labels[i]);
    }

    L.kdTree = KDTree(points, distance);
    L.k = k;
    L.distance = distance;
    L.classes = classes;
    L.isEuclidean = distance === euclideanDistance;
    return L;
}


/**
 * Predicts the output given the matrix to predict.
 * @param {Array} dataset
 * @return {Array} predictions
 */
KNN.code = {
  predict : function(L,dataset) {
    if (Array.isArray(dataset)) {
        if (typeof dataset[0] === 'number') {
            return getSinglePrediction(L, dataset);
        } else if (Array.isArray(dataset[0]) && typeof dataset[0][0] === 'number') {
            var predictions = new Array(dataset.length);
            for (var i = 0; i < dataset.length; i++) {
                predictions[i] = getSinglePrediction(L, dataset[i]);
            }
            return predictions;
        }
    }
    throw new TypeError('dataset to predict must be an array or a matrix');
  }
}

function getSinglePrediction(knn, currentCase) {
    var nearestPoints = KDTree.code.nearest(knn.kdTree, currentCase, knn.k);
    var pointsPerClass = {};
    var predictedClassMin = null;
    var predictedClassMax = null;
    var predictedClassDistance = 0;
    var maxPoints = -1;
    var minDistance = 1E30;
    
    var lastElement = nearestPoints[0][0].length - 1;
    //for (var element of knn.classes) {
    //    pointsPerClass[element] = 0;
    //}
    forof(knn.classes,function (element) {
      pointsPerClass[element] = 0;
    });
    for (var i = 0; i < nearestPoints.length; ++i) {
        var currentClass = nearestPoints[i][0][lastElement];
        var currentPoints = ++pointsPerClass[currentClass];
        // Either use majority of points matching a class or the nearest points
        if (currentPoints > maxPoints) {
            predictedClassMax = currentClass;
            predictedClassDistance = predictedClassDistance+nearestPoints[i][1];
            maxPoints = currentPoints;
        }
        if (nearestPoints[i][1] < minDistance) {
            predictedClassMin = currentClass;
            minDistance = nearestPoints[i][1];
        }
    }
    predictedClassDistance /= maxPoints;
    return maxPoints>2?predictedClassMax:predictedClassMin;
}



/** Create a simple KNN (2)
 *
 * typeof @options = {x:number [] [],y: number []}
 *
 */
var KNN2 = function (options) {
  var model={}
  // if (!(this instanceof KNN2)) return new KNN2(options);
  model.x       = options.x;
  model.y       = options.y;
  model.target  = options.y;
  model.k       = options.k || 3
  model.distance = getDistanceFunction(options.distance);
  model.weightf =  getWeightedFunction(options.weightf);
  return model
}

/** Make a prediction
 *  
 */
KNN2.code = {
  predict : function (model,data) {
    var x = data;
    var k = model.k;
    var weightf = model.weightf;
    var distance = model.distance;
    var distanceList = [];
    var i;
    for(i=0; i<model.x.length; i++)
        distanceList.push([distance(x,model.x[i]),i]);
    distanceList.sort(function(a,b) {return a[0]-b[0];});
    var avg = 0.0;
    var totalWeight = 0, weight;
    for(i=0; i<k; i++) {
        var dist = distanceList[i][0];
        var idx = distanceList[i][1];
        weight = weightf(dist);
        avg += weight * model.y[idx];
        totalWeight += weight;
    }

    avg /= totalWeight;
    return avg;
  }
}

function getWeightedFunction(options) {
    if(typeof options === 'undefined') {
        return function(x) {
            var sigma = 10.0;
            return Math.exp(-1.*x*x/(2*sigma*sigma));
        }
    } else if(typeof options === 'function') {
        return options;
    } else if(options === 'gaussian') {
        return function(x) {
            var sigma = options.sigma;
            return Math.exp(-1.*x*x/(2*sigma*sigma));
        }
    } else if(options === 'none') {
        return function(dist) {
            return 1.0;
        }
    }
}

function getDistanceFunction(options) {
    if(typeof options === 'undefined') {
        return math.euclidean;
    } else if (typeof options === 'function') {
        return options;
    } else if (options === 'euclidean') {
        return math.euclidean;
    } else if (options === 'pearson') {
        return math.pearson;
    } else 
        throw new TypeError('distance opions invalid: '+options);;      
}

module.exports={
  create    : KNN,
  predict   : KNN.code.predict,
  create2   : KNN2,
  predict2  : KNN2.code.predict,
}
};
BundleModuleCode['ml/math']=function (module,exports){
/**
 * Created by joonkukang on 2014. 1. 12..
 */
var m = module.exports;

m.randn = function() {
    // generate random guassian distribution number. (mean : 0, standard deviation : 1)
    var v1, v2, s;

    do {
        v1 = 2 * Math.random() - 1;   // -1.0 ~ 1.0 까지의 값
        v2 = 2 * Math.random() - 1;   // -1.0 ~ 1.0 까지의 값
        s = v1 * v1 + v2 * v2;
    } while (s >= 1 || s == 0);

    s = Math.sqrt( (-2 * Math.log(s)) / s );
    return v1 * s;
}

m.shape = function(mat) {
    var row = mat.length;
    var col = mat[0].length;
    return [row,col];
};

m.addVec = function(vec1, vec2) {
    if(vec1.length === vec2.length) {
        var result = [];
        var i;
        for(i=0;i<vec1.length;i++)
            result.push(vec1[i]+vec2[i]);
        return result;
    } else {
        throw new Error("Length Error : not same.")
    }
}

m.minusVec = function(vec1,vec2) {
    if(vec1.length === vec2.length) {
        var result = [];
        var i;
        for(i=0;i<vec1.length;i++)
            result.push(vec1[i]-vec2[i]);
        return result;
    } else {
        throw new Error("Length Error : not same.")
    }
};

m.addMatScalar = function(mat,scalar) {
    var row = m.shape(mat)[0];
    var col = m.shape(mat)[1];
    var i , j,result = [];
    for(i=0 ; i<row ; i++) {
        var rowVec = [];
        for(j=0 ; j<col ; j++) {
            rowVec.push(mat[i][j] + scalar);
        }
        result.push(rowVec);
    }
    return result;
}

m.addMatVec = function(mat,vec) {
    if(mat[0].length === vec.length) {
        var result = [];
        var i;
        for(i=0;i<mat.length;i++)
            result.push(m.addVec(mat[i],vec));
        return result;
    } else {
        throw new Error("Length Error : not same.")
    }
}

m.minusMatVec = function(mat,vec) {
    if(mat[0].length === vec.length) {
        var result = [];
        var i;
        for(i=0;i<mat.length;i++)
            result.push(m.minusVec(mat[i],vec));
        return result;
    } else {
        throw new Error("Length Error : not same.")
    }
}

m.addMat = function (mat1, mat2) {
    if ((mat1.length === mat2.length) && (mat1[0].length === mat2[0].length)) {
        var result = new Array(mat1.length);
        for (var i = 0; i < mat1.length; i++) {
            result[i] = new Array(mat1[i].length);
            for (var j = 0; j < mat1[i].length; j++) {
                result[i][j] = mat1[i][j] + mat2[i][j];
            }
        }
        return result;
    } else {
        throw new Error('Matrix mismatch.');
    }
};

m.minusMat = function(mat1, mat2) {
    if ((mat1.length === mat2.length) && (mat1[0].length === mat2[0].length)) {
        var result = new Array(mat1.length);
        for (var i = 0; i < mat1.length; i++) {
            result[i] = new Array(mat1[i].length);
            for (var j = 0; j < mat1[i].length; j++) {
                result[i][j] = mat1[i][j] - mat2[i][j];
            }
        }
        return result;
    } else {
        throw new Error('Matrix mismatch.');
    }
}

m.transpose = function (mat) {
    var result = new Array(mat[0].length);
    for (var i = 0; i < mat[0].length; i++) {
        result[i] = new Array(mat.length);
        for (var j = 0; j < mat.length; j++) {
            result[i][j] = mat[j][i];
        }
    }
    return result;
};

m.dotVec = function (vec1, vec2) {
    if (vec1.length === vec2.length) {
        var result = 0;
        for (var i = 0; i < vec1.length; i++) {
            result += vec1[i] * vec2[i];
        }
        return result;
    } else {
        throw new Error("Vector mismatch");
    }
};

m.outerVec = function (vec1,vec2) {
    var mat1 = m.transpose([vec1]);
    var mat2 = [vec2];
    return m.mulMat(mat1,mat2);
};

m.mulVecScalar = function(vec,scalar) {
    var i, result = [];
    for(i=0;i<vec.length;i++)
        result.push(vec[i]*scalar);
    return result;
};

m.mulMatScalar = function(mat,scalar) {
    var row = m.shape(mat)[0];
    var col = m.shape(mat)[1];
    var i , j,result = [];
    for(i=0 ; i<row ; i++) {
        var rowVec = [];
        for(j=0 ; j<col ; j++) {
            rowVec.push(mat[i][j] * scalar);
        }
        result.push(rowVec);
    }
    return result;
};

m.mulMatElementWise = function(mat1, mat2) {
    if (mat1.length === mat2.length && mat1[0].length === mat2[0].length) {
        var result = new Array(mat1.length);

        for (var x = 0; x < mat1.length; x++) {
            result[x] = new Array(mat1[0].length);
        }

        for (var i = 0; i < result.length; i++) {
            for (var j = 0; j < result[i].length; j++) {
                result[i][j] = mat1[i][j] * mat2[i][j]
            }
        }
        return result;
    } else {
        throw new Error("Matrix shape error : not same");
    }
};

m.mulMat = function (mat1, mat2) {
    if (mat1[0].length === mat2.length) {
        var result = new Array(mat1.length);

        for (var x = 0; x < mat1.length; x++) {
            result[x] = new Array(mat2[0].length);
        }


        var mat2_T = m.transpose(mat2);
        for (var i = 0; i < result.length; i++) {
            for (var j = 0; j < result[i].length; j++) {
                result[i][j] = m.dotVec(mat1[i],mat2_T[j]);
            }
        }
        return result;
    } else {
        throw new Error("Array mismatch");
    }
};

m.sumVec = function(vec) {
    var sum = 0;
    var i = vec.length;
    while (i--) {
        sum += vec[i];
    }
    return sum;
};

m.sumMat = function(mat) {
    var sum = 0;
    var i = mat.length;
    while (i--) {
        for(var j=0;j<mat[0].length;j++)
          sum += mat[i][j];
    }
    return sum;
};

m.sumMatAxis = function(mat,axis) {
    // default axis 0;
    // axis 0 : mean of col vector . axis 1 : mean of row vector
    if(axis === 1) {
        var row = m.shape(mat)[0];
        var i ;
        var result = [];
        for(i=0 ; i<row; i++)
            result.push(m.sumVec(mat[i]));
        return result;
    } else {
        mat_T = m.transpose(mat);
        return m.sumMatAxis(mat_T,1);
    }
};

m.meanVec = function(vec) {
    return 1. * m.sumVec(vec) / vec.length;
};

m.meanMat = function(mat) {
    var row = mat.length;
    var col = mat[0].length;
    return 1. * m.sumMat(mat) / (row * col);
};

m.meanMatAxis = function(mat,axis) {
    // default axis 0;
    // axis 0 : mean of col vector . axis 1 : mean of row vector
    if(axis === 1) {
        var row = m.shape(mat)[0];
        var i ;
        var result = [];
        for(i=0 ; i<row; i++)
            result.push(m.meanVec(mat[i]));
        return result;
    } else {
        mat_T = m.transpose(mat);
        return m.meanMatAxis(mat_T,1);
    }
};

m.squareVec = function(vec) {
    var squareVec = [];
    var i;
    for(i=0;i<vec.length;i++) {
        squareVec.push(vec[i]*vec[i]);
    }
    return squareVec;
};

m.squareMat = function(mat) {
    var squareMat = [];
    var i;
    for(i=0;i<mat.length;i++) {
        squareMat.push(m.squareVec(mat[i]));
    }
    return squareMat;
};

m.minVec = function(vec) {
    var min = vec[0];
    var i = vec.length;
    while (i--) {
        if (vec[i] < min)
            min = vec[i];
    }
    return min;
};

m.maxVec = function(vec) {
    var max = vec[0];
    var i = vec.length;
    while (i--) {
        if (vec[i] > max)
            max = vec[i];
    }
    return max;
}

m.minMat = function(mat) {
    var min = mat[0][0];
    var i = mat.length;
    while (i--) {
        for(var j=0;j<mat[0].length;j++) {
            if(mat[i][j] < min)
                min = mat[i][j];
        }
    }
    return min;
};

m.maxMat = function(mat) {
    var max = mat[0][0];
    var i = mat.length;
    while (i--) {
        for(var j=0;j<mat[0].length;j++) {
            if(mat[i][j] < max)
                max = mat[i][j];
        }
    }
    return max;
};

m.zeroVec = function(n) {
    var vec = [];
    while(vec.length < n)
        vec.push(0);
    return vec;
};

m.zeroMat = function(row,col) {
    var mat = [];
    while(mat.length < row)
        mat.push(m.zeroVec(col));
    return mat;
};

m.oneVec = function(n) {
    var vec = [];
    while(vec.length < n)
        vec.push(1);
    return vec;
};

m.oneMat = function(row,col) {
    var mat = [];
    while(mat.length < row)
        mat.push(m.oneVec(col));
    return mat;
};

m.randVec = function(n,lower,upper) {
    lower = (typeof lower !== 'undefined') ? lower : 0;
    upper = (typeof upper !== 'undefined') ? upper : 1;
    var vec = [];
    while(vec.length < n)
        vec.push(lower + (upper-lower) * Math.random());
    return vec;
};

m.randMat = function(row,col,lower,upper) {
    lower = (typeof lower !== 'undefined') ? lower : 0;
    upper = (typeof upper !== 'undefined') ? upper : 1;
    var mat = [];
    while(mat.length < row)
        mat.push(m.randVec(col,lower,upper));
    return mat;
};

m.randnVec = function(n,mean,sigma) {
    var vec = [];
    while(vec.length < n)
        vec.push(mean+sigma* m.randn());
    return vec;
};

m.randnMat = function(row,col,mean,sigma) {
    var mat = [];
    while(mat.length < row)
        mat.push(m.randnVec(col,mean,sigma));
    return mat;
};

m.identity = function (n) {
    var result = new Array(n);

    for (var i = 0; i < n ; i++) {
        result[i] = new Array(n);
        for (var j = 0; j < n; j++) {
            result[i][j] = (i === j) ? 1 : 0;
        }
    }

    return result;
};

m.sigmoid = function(x) {
    var sigmoid = (1. / (1 + Math.exp(-x)))
    if(sigmoid ==1) {
     //   console.warn("Something Wrong!! Sigmoid Function returns 1. Probably javascript float precision problem?\nSlightly Controlled value to 1 - 1e-14")
        sigmoid = 0.99999999999999; // Javascript Float Precision Problem.. This is a limit of javascript.
    } else if(sigmoid ==0) {
      //  console.warn("Something Wrong!! Sigmoid Function returns 0. Probably javascript float precision problem?\nSlightly Controlled value to 1e-14")
        sigmoid = 1e-14;
    }
    return sigmoid; // sigmoid cannot be 0 or 1;;
};

m.dSigmoid = function(x){
    a = m.sigmoid(x);
    return a * (1. - a);
};

m.probToBinaryMat = function(mat) {
    var row = m.shape(mat)[0];
    var col = m.shape(mat)[1];
    var i,j;
    var result = [];

    for(i=0;i<row;i++) {
        var rowVec = [];
        for(j=0;j<col;j++) {
            if(Math.random() < mat[i][j])
                rowVec.push(1);
            else
                rowVec.push(0);
        }
        result.push(rowVec);
    }
    return result;
};

m.activateVec = function(vec,activation) {
    var i, result = [];
    for(i=0;i<vec.length;i++)
        result.push(activation(vec[i]));
    return result;
};

m.activateMat = function(mat,activation) {
    var row = m.shape(mat)[0];
    var col = m.shape(mat)[1];
    var i, j,result = [];
    for(i=0;i<row;i++) {
        var rowVec = [];
        for(j=0;j<col;j++)
            rowVec.push(activation(mat[i][j]));
        result.push(rowVec);
    }
    return result;
};

m.activateTwoVec = function(vec1, vec2,activation) {
    if (vec1.length === vec2.length) {
        var result = new Array(vec1.length);
        for (var i = 0; i < result.length; i++) {
            result[i] = activation(vec1[i],vec2[i]);
        }
        return result;
    } else {
        throw new Error("Matrix shape error : not same");
    }
};

m.activateTwoMat = function(mat1, mat2,activation) {
    if (mat1.length === mat2.length && mat1[0].length === mat2[0].length) {
        var result = new Array(mat1.length);

        for (var x = 0; x < mat1.length; x++) {
            result[x] = new Array(mat1[0].length);
        }

        for (var i = 0; i < result.length; i++) {
            for (var j = 0; j < result[i].length; j++) {
                result[i][j] = activation(mat1[i][j],mat2[i][j]);
            }
        }
        return result;
    } else {
        throw new Error("Matrix shape error : not same");
    }
};

m.fillVec = function(n,value) {
    var vec = [];
    while(vec.length < n)
        vec.push(value);
    return vec;
};

m.fillMat = function(row,col,value) {
    var mat = [];
    while(mat.length < row) {
        var rowVec = [];
        while(rowVec.length < col)
            rowVec.push(value);
        mat.push(rowVec);
    }
    return mat;
};

m.softmaxVec = function(vec) {
    var max = m.maxVec(vec);
    var preSoftmaxVec = m.activateVec(vec,function(x) {return Math.exp(x - max);})
    return m.activateVec(preSoftmaxVec,function(x) {return x/ m.sumVec(preSoftmaxVec)})
};

m.softmaxMat = function(mat) {
    var result=[], i;
    for(i=0 ; i<mat.length ; i++)
        result.push(m.softmaxVec(mat[i]));
    return result;
};

m.randInt = function(min,max) {
  var rand = Math.random() * (max - min + 0.9999) + min
  return Math.floor(rand);
}

m.normalizeVec = function(vec) {
    var i;
    var newVec = [],tot = 0;
    for(i=0; i<vec.length; i++)
        tot += vec[i];
    for(i=0; i<vec.length;i++)
        newVec.push(1.*vec[i]/tot);
    return newVec;
};

m.euclidean = function(x1,x2) {
    var i;
    var distance = 0;
    for(i=0 ; i<x1.length; i++) {
        var dx = x1[i] - x2[i];
        distance += dx * dx;
    }
    return Math.sqrt(distance);
};

m.pearson = function(x, y)
{
    var xy = [];
    var x2 = [];
    var y2 = [];

    for(var i=0; i<x.length; i++)
    {
        xy.push(x[i] * y[i]);
        x2.push(x[i] * x[i]);
        y2.push(y[i] * y[i]);
    }

    var sum_x = 0;
    var sum_y = 0;
    var sum_xy = 0;
    var sum_x2 = 0;
    var sum_y2 = 0;

    for(var i=0; i<x.length; i++)
    {
        sum_x += x[i];
        sum_y += y[i];
        sum_xy += xy[i];
        sum_x2 += x2[i];
        sum_y2 += y2[i];
    }

    var step1 = (x.length * sum_xy) - (sum_x * sum_y);
    var step2 = (x.length * sum_x2) - (sum_x * sum_x);
    var step3 = (x.length * sum_y2) - (sum_y * sum_y);
    var step4 = Math.sqrt(step2 * step3);
    var answer = step1 / step4;

    return answer;
};

m.getNormVec = function(vec) {
    var i;
    var sqsum = 0;
    for(i=0; i<vec.length; i++)
        sqsum += vec[i] * vec[i];
    return Math.sqrt(sqsum);
}

m.gaussian = function(x, sigma) {
    sigma = sigma || 10.0;
    return Math.exp(-1.*x*x/(2*sigma*sigma));
}

m.meanVecs = function(vecs) {
    var sum = m.zeroVec(vecs[0].length);
    var i;
    for(i=0; i<vecs.length; i++)
        sum = m.addVec(sum,vecs[i]);
    return m.activateVec(sum,function(x) {return 1.*x/vecs.length;});
};

m.covarianceVecs = function(vecs) {
    var mat = m.zeroMat(vecs[0].length,vecs[0].length);
    var meanVec = m.meanVecs(vecs);
    var i;
    for(i=0; i<vecs.length; i++) {
        var a = m.minusVec(vecs[i],meanVec);
        mat = m.addMat(mat, m.mulMat(m.transpose([a]),[a]));
    }
    return m.activateMat(mat,function(x) { return 1.*x/(vecs.length-1);});
};

m.shuffle = function(arr){
    var o = [];
    for(var i=0;i<arr.length;i++)
        o.push(arr[i]); // deep copy
    for(var j, x, i = o.length; i; j = parseInt(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
    return o;
};

m.range = function(start, end, step) {
    var ret = [];
    if(typeof step === "undefined")
        step = 1;
    if(typeof end === "undefined") {
        end = start;
        start = 0;
    }
    for(var i=start;i<end;i+=step)
        ret.push(i);
    return ret;
};
// For CRBM
/*
m.phi = function(mat,vec,low,high) {
    var i;
    var result = [];
    for(i=0;i<mat.length;i++) {
        result.push(m.activateTwoVec(mat[i],vec,function(x,y){return low+(high-low)* m.sigmoid(x*y);}))
    }
    return result;
}
*/
};
BundleModuleCode['ml/kmeans']=function (module,exports){
/**
 * Created by joonkukang on 2014. 1. 16..
 */
var math = Require('ml/math')
var Kmeans = module.exports;

Kmeans.cluster = function(options) {
    var data = options['data'];
    var k = options['k'];
    var distance = getDistanceFunction(options['distance']);
    var epochs = options['epochs'];
    var init_using_data = options['init_using_data'];
    if(typeof init_using_data === "undefined");
        init_using_data = true;
    var means = getRandomMeans(data,k, init_using_data);

    var epoch, i, j, l;
    var clusters = [];
    for(i=0 ; i<k ; i++)
        clusters.push([]);

    for(epoch=0 ; epoch<epochs ; epoch++) {
        clusters = [];
        for(i=0 ; i<k ; i++)
            clusters.push([]);

        // Find which centroid is the closest for each row
        for(i=0 ; i<data.length ; i++) {
            var bestmatch = 0;
            for(j=0 ; j<k ; j++) {
                if(distance(means[j],data[i]) < distance(means[bestmatch],data[i])) bestmatch = j;
            }
            clusters[bestmatch].push(i);
        }

        // Move the centroids to the average of their members
        for(i=0 ; i<k ; i++) {
            var avgs = [];
            for(j=0 ; j<data[0].length ; j++)
                avgs.push(0.0);
            if(clusters[i].length > 0) {
                for(j=0 ; j<clusters[i].length ; j++) {
                    for(l=0 ; l<data[0].length ; l++) {
                        avgs[l] += data[clusters[i][j]][l];
                    }
                }
                for(j=0 ; j<data[0].length ; j++) {
                    avgs[j] /= clusters[i].length;
                }
                means[i] = avgs;
            }
        }
    }
    return {
        clusters : clusters,
        means : means
    };
}

var getRandomMeans = function(data,k, init_using_data) {
    var clusters = [];
    if(init_using_data) {
        var cluster_index = math.range(data.length);
        cluster_index = math.shuffle(cluster_index);
        for(i=0 ; i<k ; i++) {
            clusters.push(data[cluster_index[i]]);
        }
    } else {
        var i,j;
        var ranges = [];
        for(i=0 ; i<data[0].length ; i++) {
            var min = data[0][i] , max = data[0][i];
            for(j=0 ; j<data.length ; j++) {
                if(data[j][i] < min) min = data[j][i];
                if(data[j][i] > max) max = data[j][i];
            }
            ranges.push([min,max]);
        }
        for(i=0 ; i<k ; i++) {
            var cluster = [];
            for(j=0 ; j<data[0].length;j++) {
                cluster.push(Math.random() * (ranges[j][1] - ranges[j][0]) + ranges[j][0]);
            }
            clusters.push(cluster);
        }
    }
    return clusters;
}


function getDistanceFunction(options) {
    if(typeof options === 'undefined') {
        return math.euclidean;
    } else if (typeof options === 'function') {
        return options;
    } else if (options['type'] === 'euclidean') {
        return math.euclidean;
    } else if (options['type'] === 'pearson') {
        return math.pearson;
    }
}
};
BundleModuleCode['ml/svm']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     joonkukang, Stefan Bosse
 **    $INITIAL:     (C) 2014, joonkukang
 **    $MODIFIED:    (C) 2006-2018 bLAB by sbosse
 **    $VERSION:     1.1.3
 **
 **    $INFO:
 **
 ** Support Vector Machine Algrotihm
 **
 ** 1. References : http://cs229.stanford.edu/materials/smo.pdf . simplified smo algorithm 
 ** 2. https://github.com/karpathy/svmjs
 ** 
 ** Portable model
 **
 **    $ENDOFINFO
 */

var math = Require('ml/math');

/**
 * type options = {x: number [] [], y: number []}
 */
var SVM = function (options) {
    var L = {};
    L.x = options.x;
    L.y = options.y;
    return L
};

SVM.code = {
  train : function (L,options) {
    var self = L;
    var C = options.C || 1.0;
    var tol = options.tol || 1e-4;
    var maxPasses = options.max_passes || 20;
    var alphatol = options.alpha_tol || 1e-5;

    L.options={kernel:options.kernel,iterations:maxPasses,alpha_tol:alphatol, C:C, tol:tol };
    self.kernel = getKernel(options.kernel);
    self.alphas = math.zeroVec(self.x.length);
    self.b = 0;
    var passes = 0, i;
    var count=0;
    while(passes < maxPasses) {
        var numChangedAlphas = 0;

        for(i=0; i<self.x.length; i++) {

            var E_i = SVM.code.f(self,self.x[i]) - self.y[i];

            if((self.y[i] * E_i < -tol && self.alphas[i] < C) || (self.y[i] * E_i > tol && self.alphas[i] >0)) {

                // Randomly selects j (i != j)
                var j = math.randInt(0,self.x.length-1);
                if(i==j) j = (j+1) % self.x.length;

                var E_j = SVM.code.f(self,self.x[j]) - self.y[j];
                var alpha_i_old = self.alphas[i], alpha_j_old = self.alphas[j];

                // Compute L,H
                var L,H;
                if(self.y[i] !== self.y[j]) {
                    L = Math.max(0, self.alphas[j] - self.alphas[i]);
                    H = Math.min(C, C + self.alphas[j] - self.alphas[i]);
                } else {
                    L = Math.max(0, self.alphas[j] + self.alphas[i] - C);
                    H = Math.min(C, self.alphas[j] + self.alphas[i]);
                }

                if(L === H)
                    continue;

                // Compute ETA
                var ETA = 2 * self.kernel(self.x[i],self.x[j]) - self.kernel(self.x[i],self.x[i]) - self.kernel(self.x[j],self.x[j]);
                if(ETA >= 0)
                    continue;

                // Clip new value to alpha_j
                self.alphas[j] -= 1.*self.y[j] * (E_i - E_j) / ETA;
                if(self.alphas[j] > H)
                    self.alphas[j] = H;
                else if(self.alphas[j] < L)
                    self.alphas[j] = L;

                if(Math.abs(self.alphas[j] - alpha_j_old) < alphatol)
                    continue;

                // Clip new value to alpha_i
                self.alphas[i] += self.y[i] * self.y[j] * (alpha_j_old - self.alphas[j]);

                // update b
                var b1 = self.b - E_i - self.y[i] * (self.alphas[i] - alpha_i_old) * self.kernel(self.x[i],self.x[i])
                                - self.y[j] * (self.alphas[j] - alpha_j_old) * self.kernel(self.x[i],self.x[j]);
                var b2 = self.b - E_j - self.y[i] * (self.alphas[i] - alpha_i_old) * self.kernel(self.x[i],self.x[j])
                                - self.y[j] * (self.alphas[j] - alpha_j_old) * self.kernel(self.x[j],self.x[j]);

                if(0 < self.alphas[i] && self.alphas[i] < C)
                    self.b = b1;
                else if(0 < self.alphas[j] && self.alphas[j] < C)
                    self.b = b2;
                else
                    self.b = (b1+b2)/2.0;

                numChangedAlphas ++ ;
            } // end-if
        } // end-for
        if(numChangedAlphas == 0)
            passes++;
        else
            passes = 0;
    }
  },
  
  predict : function(L,x) {
    var self = L;
    this.kernel = getKernel(L.options.kernel); // update kernel
    if(SVM.code.f(L,x) >= 0)
        return 1;
    else
        return -1;
  },

  f : function(L,x) {
    var self = L;
    var f = 0, j;
    for(j=0; j<self.x.length; j++)
        f += self.alphas[j] * self.y[j] * self.kernel(self.x[j],x);
    f += self.b;
    return f;
  }
}

function getKernel (options) {
    if(typeof options === 'undefined') {
        return function(x,y) {
            var sigma = 1.0;
            return Math.exp(-1.*Math.pow(math.getNormVec(math.minusVec(x,y)),2)/(2*sigma*sigma));
        }
    } else if (typeof options === 'function') {
        return options;
    } else if (options['type'] === 'gaussian') {
        return function(x,y) {
            var sigma = options['sigma'];
            return Math.exp(-1.*Math.pow(math.getNormVec(math.minusVec(x,y)),2)/(2*sigma*sigma));
        }
    } else if (options['type'] === 'linear') {
        return function(x,y) {
            return math.dotVec(x,y);
        }
    } else if (options['type'] === 'polynomial') {
        return function(x,y) {
            var c = options['c'];
            var d = options['d'];
            return Math.pow(math.dotVec(x,y) + c, d);
        }
    } else if (options['type'] === 'rbf') {
        return function(v1, v2) {
          var s=0;
          var sigma = options.sigma||options.rbfsigma || 0.5;
          for(var q=0;q<v1.length;q++) { s += (v1[q] - v2[q])*(v1[q] - v2[q]); } 
          return Math.exp(-s/(2.0*sigma*sigma));
        }
    }
}


var SVM2 = function (options) {
    var L = {};
    L.data = options.x;
    L.labels = options.y;
    L.threshold=checkOption(options.threshold,0);
    return L
};

SVM2.code = {

  // data is NxD array of floats. labels are 1 or -1.
  train: function(L, options) {
    var data = L.data,labels=L.labels;

    // parameters
    options = options || {};
    var C = options.C || 1.0; // C value. Decrease for more regularization
    var tol = options.tol || 1e-4; // numerical tolerance. Don't touch unless you're pro
    var alphatol = options.alphatol || options.alpha_tol || 1e-7; // non-support vectors for space and time efficiency are truncated. To guarantee correct result set this to 0 to do no truncating. If you want to increase efficiency, experiment with setting this little higher, up to maybe 1e-4 or so.
    var maxiter = options.maxiter || 10000; // max number of iterations
    var numpasses = options.numpasses || options.max_passes || 10; // how many passes over data with no change before we halt? Increase for more precision.

    // instantiate kernel according to options. kernel can be given as string or as a custom function
    var kernel = linearKernel;
    L.kernelType = "linear";
    L.options={kernel:options.kernel};
    if("kernel" in options) {
      if  (typeof options.kernel == 'object') {
        kernel = getKernel(options.kernel);
        L.kernelType=options.kernel.type;
        L.rbfSigma = options.kernel.sigma || options.kernel.rbfsigma;
      } else if (typeof options.kernel == 'function') {
        // assume kernel was specified as a function. Let's just use it
        L.kernelType = "custom";
        kernel = options.kernel;
      }
    }
    L.options.C=C;
    L.options.tol=tol;
    L.options.alphatol=alphatol;
    L.options.iterations=numpasses;
    
    // initializations
    L.kernel = kernel;
    L.N = data.length; var N = L.N;
    L.D = data[0].length; var D = L.D;
    L.alpha = zeros(N);
    L.b = 0.0;
    L.usew_ = false; // internal efficiency flag

    // Cache kernel computations to avoid expensive recomputation.
    // This could use too much memory if N is large.
    if (options.memoize) {
      L.kernelResults = new Array(N);
      for (var i=0;i<N;i++) {
        L.kernelResults[i] = new Array(N);
        for (var j=0;j<N;j++) {
          L.kernelResults[i][j] = kernel(data[i],data[j]);
        }
      }
    }

    // run SMO algorithm
    var iter = 0;
    var passes = 0;
    while(passes < numpasses && iter < maxiter) {

      var alphaChanged = 0;
      for(var i=0;i<N;i++) {

        var Ei= SVM2.code.marginOne(L, data[i]) - labels[i];
        if( (labels[i]*Ei < -tol && L.alpha[i] < C)
         || (labels[i]*Ei > tol && L.alpha[i] > 0) ){

          // alpha_i needs updating! Pick a j to update it with
          var j = i;
          while(j === i) j= randi(0, L.N);
          var Ej= SVM2.code.marginOne(L, data[j]) - labels[j];

          // calculate L and H bounds for j to ensure we're in [0 C]x[0 C] box
          ai= L.alpha[i];
          aj= L.alpha[j];
          var Lb = 0; var Hb = C;
          if(labels[i] === labels[j]) {
            Lb = Math.max(0, ai+aj-C);
            Hb = Math.min(C, ai+aj);
          } else {
            Lb = Math.max(0, aj-ai);
            Hb = Math.min(C, C+aj-ai);
          }

          if(Math.abs(Lb - Hb) < 1e-4) continue;

          var eta = 2*SVM2.code.kernelResult(L, i,j) - SVM2.code.kernelResult(L, i,i) - SVM2.code.kernelResult(L, j,j);
          if(eta >= 0) continue;

          // compute new alpha_j and clip it inside [0 C]x[0 C] box
          // then compute alpha_i based on it.
          var newaj = aj - labels[j]*(Ei-Ej) / eta;
          if(newaj>Hb) newaj = Hb;
          if(newaj<Lb) newaj = Lb;
          if(Math.abs(aj - newaj) < 1e-4) continue; 
          L.alpha[j] = newaj;
          var newai = ai + labels[i]*labels[j]*(aj - newaj);
          L.alpha[i] = newai;

          // update the bias term
          var b1 = L.b - Ei - labels[i]*(newai-ai)*SVM2.code.kernelResult(L, i,i)
                   - labels[j]*(newaj-aj)*SVM2.code.kernelResult(L, i,j);
          var b2 = L.b - Ej - labels[i]*(newai-ai)*SVM2.code.kernelResult(L, i,j)
                   - labels[j]*(newaj-aj)*SVM2.code.kernelResult(L, j,j);
          L.b = 0.5*(b1+b2);
          if(newai > 0 && newai < C) L.b= b1;
          if(newaj > 0 && newaj < C) L.b= b2;

          alphaChanged++;

        } // end alpha_i needed updating
      } // end for i=1..N

      iter++;
      //console.log("iter number %d, alphaChanged = %d", iter, alphaChanged);
      if(alphaChanged == 0) passes++;
      else passes= 0;

    } // end outer loop

    // if the user was using a linear kernel, lets also compute and store the
    // weights. This will speed up evaluations during testing time
    if(L.kernelType === "linear") {

      // compute weights and store them
      L.w = new Array(L.D);
      for(var j=0;j<L.D;j++) {
        var s= 0.0;
        for(var i=0;i<L.N;i++) {
          s+= L.alpha[i] * labels[i] * data[i][j];
        }
        L.w[j] = s;
        L.usew_ = true;
      }
    } else {

      // okay, we need to retain all the support vectors in the training data,
      // we can't just get away with computing the weights and throwing it out

      // But! We only need to store the support vectors for evaluation of testing
      // instances. So filter here based on L.alpha[i]. The training data
      // for which L.alpha[i] = 0 is irrelevant for future. 
      var newdata = [];
      var newlabels = [];
      var newalpha = [];
      for(var i=0;i<L.N;i++) {
        //console.log("alpha=%f", L.alpha[i]);
        if(L.alpha[i] > alphatol) {
          newdata.push(L.data[i]);
          newlabels.push(L.labels[i]);
          newalpha.push(L.alpha[i]);
        }
      }

      // store data and labels
      L.data = newdata;
      L.labels = newlabels;
      L.alpha = newalpha;
      L.N = L.data.length;
      // console.log("filtered training data from %d to %d support vectors.", data.length, L.data.length);
    }

    var trainstats = {};
    trainstats.iters= iter;
    trainstats.passes= passes;
    return trainstats;
  }, 

  // inst is an array of length D. Returns margin of given example
  // this is the core prediction function. All others are for convenience mostly
  // and end up calling this one somehow.
  marginOne: function(L,inst) {

    var f = L.b;
    // if the linear kernel was used and w was computed and stored,
    // (i.e. the svm has fully finished training)
    // the internal class variable usew_ will be set to true.
    if(L.usew_) {

      // we can speed this up a lot by using the computed weights
      // we computed these during train(). This is significantly faster
      // than the version below
      for(var j=0;j<L.D;j++) {
        f += inst[j] * L.w[j];
      }

    } else {

      for(var i=0;i<L.N;i++) {
        f += L.alpha[i] * L.labels[i] * L.kernel(inst, L.data[i]);
      }
    }
    return f;
  },

  predict: function(L,inst) { 
    L.kernel=getKernel(L.options.kernel); // update kernel
    var result = SVM2.code.marginOne(L,inst);
    if (L.threshold===false) return result;
    else return  result > L.threshold ? 1 : -1; 
  },

  // data is an NxD array. Returns array of margins.
  margins: function(L,data) {

    // go over support vectors and accumulate the prediction. 
    var N = data.length;
    var margins = new Array(N);
    for(var i=0;i<N;i++) {
      margins[i] = SVM2.code.marginOne(L,data[i]);
    }
    return margins;

  },

  kernelResult: function(L, i, j) {
    if (L.kernelResults) {
      return L.kernelResults[i][j];
    }
    return L.kernel(L.data[i], L.data[j]);
  },

  // data is NxD array. Returns array of 1 or -1, predictions
  predictN: function(L,data) {
    L.kernel=getKernel(L.options.kernel); // update kernel
    var margs = SVM2.code.margins(L, data);
    for(var i=0;i<margs.length;i++) {
      if (L.threshold!=false)
        margs[i] = margs[i] > L.threshold ? 1 : -1;
    }
    return margs;
  },

  // THIS FUNCTION IS NOW DEPRECATED. WORKS FINE BUT NO NEED TO USE ANYMORE. 
  // LEAVING IT HERE JUST FOR BACKWARDS COMPATIBILITY FOR A WHILE.
  // if we trained a linear svm, it is possible to calculate just the weights and the offset
  // prediction is then yhat = sign(X * w + b)
  getWeights: function(L) {

    // DEPRECATED
    var w= new Array(L.D);
    for(var j=0;j<L.D;j++) {
      var s= 0.0;
      for(var i=0;i<L.N;i++) {
        s+= L.alpha[i] * L.labels[i] * L.data[i][j];
      }
      w[j]= s;
    }
    return {w: w, b: L.b};
  },

  toJSON: function(L) {

    if(L.kernelType === "custom") {
      console.log("Can't save this SVM because it's using custom, unsupported kernel...");
      return {};
    }

    json = {}
    json.N = L.N;
    json.D = L.D;
    json.b = L.b;

    json.kernelType = L.kernelType;
    if(L.kernelType === "linear") { 
      // just back up the weights
      json.w = L.w; 
    }
    if(L.kernelType === "rbf") { 
      // we need to store the support vectors and the sigma
      json.rbfSigma = L.rbfSigma; 
      json.data = L.data;
      json.labels = L.labels;
      json.alpha = L.alpha;
    }

    return json;
  },

  fromJSON: function(L,json) {

    this.N = json.N;
    this.D = json.D;
    this.b = json.b;

    this.kernelType = json.kernelType;
    if(this.kernelType === "linear") { 

      // load the weights! 
      this.w = json.w; 
      this.usew_ = true; 
      this.kernel = linearKernel; // this shouldn't be necessary
    }
    else if(this.kernelType == "rbf") {

      // initialize the kernel
      this.rbfSigma = json.rbfSigma; 
      this.kernel = makeRbfKernel(this.rbfSigma);

      // load the support vectors
      this.data = json.data;
      this.labels = json.labels;
      this.alpha = json.alpha;
    } else {
      console.log("ERROR! unrecognized kernel type." + this.kernelType);
    }
  }
}

// Kernels
function makeRbfKernel(sigma) {
  return function(v1, v2) {
    var s=0;
    for(var q=0;q<v1.length;q++) { s += (v1[q] - v2[q])*(v1[q] - v2[q]); } 
    return Math.exp(-s/(2.0*sigma*sigma));
  }
}

function linearKernel(v1, v2) {
  var s=0; 
  for(var q=0;q<v1.length;q++) { s += v1[q] * v2[q]; } 
  return s;
}

// Misc utility functions
// generate random floating point number between a and b
function randf(a, b) {
  return Math.random()*(b-a)+a;
}

// generate random integer between a and b (b excluded)
function randi(a, b) {
   return Math.floor(Math.random()*(b-a)+a);
}

// create vector of zeros of length n
function zeros(n) {
  var arr= new Array(n);
  for(var i=0;i<n;i++) { arr[i]= 0; }
  return arr;
}

module.exports = SVM2
};
BundleModuleCode['ml/mlp']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     joonkukang, Stefan Bosse
 **    $INITIAL:     (C) 2014, joonkukang
 **    $MODIFIED:    (C) 2006-2022 bLAB by sbosse
 **    $VERSION:     1.3.2
 **
 **    $INFO:
 **
 ** Multilayer Perceptron Artificial Neural Network
 **
 ** References : http://cs229.stanford.edu/materials/smo.pdf . simplified smo algorithm 
 **
 ** Portable model
 **
 **    $ENDOFINFO
 */
/**
 */
var math = Require('ml/math');
var HiddenLayer = Require('ml/HiddenLayer');

var MLP = function (settings) {
    var L = {}
    var self = L;
    self.x = settings.input||settings.x;
    self.y = settings.output||settings.y;
    self.sigmoidLayers = [];
    self.nLayers = settings.hidden_layer_sizes.length;
    self.settings = {
        'log level' : 1, // 0 : nothing, 1 : info, 2: warn
        hidden_layers : settings.hidden_layer_sizes
    };
    var i;
    for(i=0 ; i<self.nLayers+1 ; i++) {
        var inputSize, layerInput;
        if(i == 0)
            inputSize = settings.n_ins;
        else
            inputSize = settings.hidden_layer_sizes[i-1];

        if(i == 0)
            layerInput = self.x;
        else
            layerInput = HiddenLayer.code.sampleHgivenV(self.sigmoidLayers[self.sigmoidLayers.length-1]);

        var sigmoidLayer;
        if(i == self.nLayers) {
            sigmoidLayer = HiddenLayer({
                'input' : layerInput,
                'n_in' : inputSize,
                'n_out' : settings.n_outs,
                'activation' : math.sigmoid,
                'W' : (typeof settings.w_array === 'undefined')? undefined : settings.w_array[i],
                'b' : (typeof settings.b_array === 'undefined')? undefined : settings.b_array[i]
            });
        } else {
            sigmoidLayer = HiddenLayer({
                'input' : layerInput,
                'n_in' : inputSize,
                'n_out' : settings.hidden_layer_sizes[i],
                'activation' : math.sigmoid,
                'W' : (typeof settings.w_array === 'undefined')? undefined : settings.w_array[i],
                'b' : (typeof settings.b_array === 'undefined')? undefined : settings.b_array[i]
            });
        }
        self.sigmoidLayers.push(sigmoidLayer);
    }
    return L
};

MLP.code = {
  train : function(L,settings) { try {
    var self = L;
    var t0=Date.now();
    settings=settings||{}
    if (settings.input||settings.x) self.x = settings.input||settings.x;
    if (settings.output||settings.y) self.y = settings.output||settings.y;
    var epochs = 1000;
    if(typeof settings.epochs !== 'undefined')
        epochs = settings.epochs;
    self.settings.iterations=epochs;
    
    var epoch;
    var currentProgress = 1;
    for(epoch=0 ; epoch < epochs ; epoch++) {

        // Feed Forward
        var i;
        var layerInput = [];
        layerInput.push(self.x);
        for(i=0; i<self.nLayers+1 ; i++) {
            layerInput.push(HiddenLayer.code.output(self.sigmoidLayers[i],layerInput[i]));
        }
        var output = layerInput[self.nLayers+1];
        // Back Propagation
        var delta = new Array(self.nLayers + 1);
        delta[self.nLayers] = math.mulMatElementWise(math.minusMat(self.y, output),
            math.activateMat(HiddenLayer.code.linearOutput(self.sigmoidLayers[self.nLayers],layerInput[self.nLayers]), math.dSigmoid));

        /*
         self.nLayers = 3 (3 hidden layers)
         delta[3] : ouput layer
         delta[2] : 3rd hidden layer, delta[0] : 1st hidden layer
         */
        for(i = self.nLayers - 1; i>=0 ; i--) {
            delta[i] = math.mulMatElementWise(HiddenLayer.code.backPropagate(self.sigmoidLayers[i+1],delta[i+1]),
                math.activateMat(HiddenLayer.code.linearOutput(self.sigmoidLayers[i],layerInput[i]), math.dSigmoid));
        }
        // Update Weight, Bias
        for(var i=0; i<self.nLayers+1 ; i++) {
            var deltaW = math.activateMat(math.mulMat(math.transpose(layerInput[i]),delta[i]),function(x){return 1. * x / self.x.length;})
            var deltaB = math.meanMatAxis(delta[i],0);
            self.sigmoidLayers[i].W = math.addMat(self.sigmoidLayers[i].W,deltaW);
            self.sigmoidLayers[i].b = math.addVec(self.sigmoidLayers[i].b,deltaB);
        }

        if(self.settings['log level'] > 0) {
            var progress = (1.*epoch/epochs)*100;
            if(progress > currentProgress) {
                console.log("MLP",progress.toFixed(0),"% Completed.");
                currentProgress+=8;
            }
        }
    }
    var crossentropy = MLP.code.getReconstructionCrossEntropy(L);
    if(self.settings['log level'] > 0)
        console.log("MLP Final Cross Entropy : ",crossentropy);
    var t1=Date.now();
    return {
      time:t1-t0,
      epochs:epochs,
      loss:crossentropy,
    }; } catch (e) { console.log (e) }
  },
  getReconstructionCrossEntropy : function(L) {
    var self = L;
    var reconstructedOutput = MLP.code.predict(L,self.x);
    var a = math.activateTwoMat(self.y,reconstructedOutput,function(x,y){
        return x*Math.log(y);
    });

    var b = math.activateTwoMat(self.y,reconstructedOutput,function(x,y){
        return (1-x)*Math.log(1-y);
    });

    var crossEntropy = -math.meanVec(math.sumMatAxis(math.addMat(a,b),1));
    return crossEntropy
  },
  predict : function(L,x) {
    var self = L;
    var output = x;
    for(i=0; i<self.nLayers+1 ; i++) {
        output = HiddenLayer.code.output(self.sigmoidLayers[i],output);
    }
    return output;
  },
  set : function(L,property,value) {
    var self = L;
    self.settings[property] = value;
  }
}
module.exports = MLP
};
BundleModuleCode['ml/HiddenLayer']=function (module,exports){
/**
 * Created by joonkukang on 2014. 1. 12..
 */
var math = Require('ml/math');
var HiddenLayer = module.exports = function (settings) {
    var L = {}
    var self = L;
    self.input = settings['input'];

    if(typeof settings['W'] === 'undefined') {
        var a = 1. / settings['n_in'];
        settings['W'] = math.randMat(settings['n_in'],settings['n_out'],-a,a);
    }
    if(typeof settings['b'] === 'undefined')
        settings['b'] = math.zeroVec(settings['n_out']);
    if(typeof settings['activation'] === 'undefined')
        settings['activation'] = math.sigmoid;

    self.W = settings['W'];
    self.b = settings['b'];
    self.activation = settings['activation'];
    return L;
}

HiddenLayer.code = {
  output : function(L,input) {
    var self = L;
    if(typeof input !== 'undefined')
        self.input = input;

    var linearOutput = math.addMatVec(math.mulMat(self.input,self.W),self.b);
    return math.activateMat(linearOutput,self.activation);
  },
  linearOutput : function(L,input) { // returns the value before activation.
    var self = L;
    if(typeof input !== 'undefined')
        self.input = input;

    var linearOutput = math.addMatVec(math.mulMat(self.input,self.W),self.b);
    return linearOutput;
  },
  backPropagate : function (L,input) { // example+num * n_out matrix
    var self = L;
    if(typeof input === 'undefined')
        throw new Error("No BackPropagation Input.")

    var linearOutput = math.mulMat(input, math.transpose(self.W));
    return linearOutput;
  },
  sampleHgivenV : function(L,input) {
    var self = L;
    if(typeof input !== 'undefined')
        self.input = input;

    var hMean = HiddenLayer.code.output(self);
    var hSample = math.probToBinaryMat(hMean);
    return hSample;
  }
}
};
BundleModuleCode['ml/id3']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Ankit Kuwadekar, Stefan Bosse
 **    $INITIAL:     (C) 2014, Ankit Kuwadekar
 **    $MODIFIED:    (C) 2006-2018 bLAB by sbosse
 **    $VERSION:     1.3.1
 **
 **    $INFO:
 **
 ** ID3 Decision Tree Algorithm supporting categorical values only
 ** Portable model
 **
 ** New
 **   predict with nn selection
 **
 **    $ENDOFINFO
 */
var Io = Require('com/io');
var Comp = Require('com/compat');
var current=none;
var Aios=none;


/**
 * Map of valid tree node types
 * @constant
 * @static
 */
var NODE_TYPES = {
  RESULT: 'result',
  FEATURE: 'feature',
  FEATURE_VALUE: 'feature_value'
};

function isEqual(a,b) { return a==b }

/**
 * Predicts class for sample
 */
function predict(model,sample) {
  var root = model;
  while (root.type !== NODE_TYPES.RESULT) {
    var attr = root.name;
    var sampleVal = sample[attr];
    var childNode = Comp.array.min(root.vals, function(node) {
      if (typeof node.value == 'number' && typeof sampleVal == 'number')  
        return Math.pow(node.value - sampleVal,2);
      else
        return node.value == sampleVal? 0:1;
    });
    if (childNode){
      root = childNode.child;
    } else {
      root = root.vals[0].child;
    }
  }
  return root.value;
};

/**
 * Evalutes prediction accuracy on samples
 */
function evaluate(model,target,samples) {

   var total = 0;
   var correct = 0;

   Comp.array.iter(samples, function(s) {
     total++;
     var pred = predict(model,s);
     var actual = s[target];
     if (isEqual(pred,actual)) {
       correct++;
     }
   });

   return correct / total;
};

/**
 * Creates a new tree
 */
function createTree(data, target, features) {
  var targets = Comp.array.unique(Comp.array.pluck(data, target));
  
  if (targets.length == 1) {
    return {
      type:   NODE_TYPES.RESULT,
      value:  targets[0],
      name:   targets[0],
      // alias: targets[0] + randomUUID()
    };
  }

  if (features.length == 0) {
    var topTarget = mostCommon(targets);
    return {
      type:   NODE_TYPES.RESULT,
      value:  topTarget,
      name:   topTarget,
      // alias: topTarget + randomUUID()
    };
  }

  var bestFeature = maxGain(data, target, features);
  var remainingFeatures = Comp.array.without(features, bestFeature);
  var possibleValues = Comp.array.unique(Comp.array.pluck(data, bestFeature));

  var node = {
    name: bestFeature,
    // alias: bestFeature + randomUUID()
  };

  node.type = NODE_TYPES.FEATURE;
  node.vals = Comp.array.map(possibleValues, function(v) {
    var _newS = data.filter(function(x) {
      return x[bestFeature] == v
    });

    var child_node = {
      value: v,
      // alias: v + randomUUID(),
      type: NODE_TYPES.FEATURE_VALUE
    };

    child_node.child = createTree(_newS, target, remainingFeatures);
    return child_node;
  });

  return node;
}

/**
 * Computes Max gain across features to determine best split
 * @private
 */
function maxGain(data, target, features) {
  var gains=[];
  var maxgain= Comp.array.max(features, function(element) {
    var g = gain(data, target, element);
    gains.push(element+':'+g);
    return g;
  });
  return maxgain;
}

/**
 * Computes entropy of a list
 * @private
 */
function entropy(vals) {
  var uniqueVals = Comp.array.unique(vals);
  var probs = uniqueVals.map(function(x) {
    return prob(x, vals)
  });

  var logVals = probs.map(function(p) {
    return -p * log2(p)
  });

  return logVals.reduce(function(a, b) {
    return a + b
  }, 0);
}

/**
 * Computes gain
 * @private
 */
function gain(data, target, feature) {
  var attrVals = Comp.array.unique(Comp.array.pluck(data, feature));
  var setEntropy = entropy(Comp.array.pluck(data, target));
  var setSize = data.length;

  var entropies = attrVals.map(function(n) {
    var subset = data.filter(function(x) {
      return x[feature] === n
    });

    return (subset.length / setSize) * entropy(Comp.array.pluck(subset, target));
  });

  // var entropyData = entropyV(Comp.array.pluck(data, feature),eps);
  // console.log('Feat '+feature+':'+entropyData);
  var sumOfEntropies = entropies.reduce(function(a, b) {
    return a + b
  }, 0);
  return setEntropy - sumOfEntropies;
}

/**
 * Computes probability of of a given value existing in a given list
 * @private
 */
function prob(value, list) {
  var occurrences = Comp.array.filter(list, function(element) {
    return element === value
  });

  var numOccurrences = occurrences.length;
  var numElements = list.length;
  return numOccurrences / numElements;
}

/**
 * Computes Log with base-2
 * @private
 */
function log2(n) {
  return Math.log(n) / Math.log(2);
}

/**
 * Finds element with highest occurrence in a list
 * @private
 */
function mostCommon(list) {
  var elementFrequencyMap = {};
  var largestFrequency = -1;
  var mostCommonElement = null;

  list.forEach(function(element) {
    var elementFrequency = (elementFrequencyMap[element] || 0) + 1;
    elementFrequencyMap[element] = elementFrequency;

    if (largestFrequency < elementFrequency) {
      mostCommonElement = element;
      largestFrequency = elementFrequency;
    }
  });

  return mostCommonElement;
}

/**
 * Generates random UUID
 * @private
 */
function randomUUID() {
  return "_r" + Math.random().toString(32).slice(2);
}

function depth(model) {
  switch (model.type) {
    case NODE_TYPES.RESULT: return 1;
    case NODE_TYPES.FEATURE: 
      return 1+Comp.array.max(model.vals.map(function (val) {
        return depth(val);
      }));
    case NODE_TYPES.FEATURE_VALUE: 
      return 1+depth(model.child);   
  }
  return 0;
}


function info(model) {
  var vl = vars(model);
  return {
    depth:depth(model),
    nodes:vl.length,
    vars:vl.unique(),
  }
}


function print(model,indent) {
  var NL = '\n',
      line='',sep,
      sp = function () {return Comp.string.create(indent);};
  if (indent==undefined) indent=0;
  switch (model.type) {
    case NODE_TYPES.RESULT: 
      return ' -> '+model.name;
    case NODE_TYPES.FEATURE:
      line=NL+sp()+'($'+model.name+'?'+NL;
      sep='';
      Comp.array.iter(model.vals,function (v) {
        line += sep+print(v,indent+2)+NL;
        sep='';
      }); 
      return line+sp()+')';
    case NODE_TYPES.FEATURE_VALUE: 
      return sp()+model.value+':'+print(model.child,indent+2);   
  }
  return 0;
}

function vars(model) {
  switch (model.type) {
    case NODE_TYPES.RESULT: return [];
    case NODE_TYPES.FEATURE: 
      return [model.name].concat(Comp.array.flatten(model.vals.map(vars)));
    case NODE_TYPES.FEATURE_VALUE: 
      return vars(model.child);   
  }
  return [];
}

module.exports =  {
  NODE_TYPES:NODE_TYPES,
  createTree:createTree,
  depth:depth,
  entropy:entropy,
  evaluate:evaluate,
  info:info,
  predict:predict,
  print:print,
  current:function (module) { current=module.current; Aios=module;}
};

};
BundleModuleCode['ml/C45']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     ?, Stefan Bosse
 **    $INITIAL:     (C) ?
 **    $MODIFIED:    (C) 2006-2018 bLAB by sbosse
 **    $VERSION:     1.1.6
 **
 **    $INFO:
 **
 ** C45 Decision Tree ML Algorithm
 **
 ** Portable model
 **
 **    $ENDOFINFO
 */
'use strict';
var Io = Require('com/io');
var Comp = Require('com/compat');
var current=none;
var Aios=none;

var NODE_TYPES = {
  RESULT: 'result',
  FEATURE_NUMBER: 'feature_number',     // Number value node (cut split)
  FEATURE_VALUE: 'feature_value',       // Category value
  FEATURE_CATEGORY: 'feature_category'  // Symbolic variable node (split)
};

function unique(col) {
  var u = {}, a = [];
  for(var i = 0, l = col.length; i < l; ++i){
    if(u.hasOwnProperty(col[i])) {
      continue;
    }
    a.push(col[i]);
    u[col[i]] = 1;
  }
  return a;
}

function find(col, pred) {
  var value;
  col.forEach(function(item) {
    var result = pred(item);
    if (result) {
      value = item;
    }
  });
  return value;
}

function max(array, fn) {
  var max = -Infinity;
  var index;
  for (var i = 0; i < array.length; i++) {
    var result = fn(array[i]);
    if (result >= max) {
      max = result;
      index = i;
    }
  }
  return typeof index !== 'undefined' ? array[index] : max;
}

function sortBy(col, fn) {
 col = [].slice.call(col);
 return col.sort(fn);
}

var C45 = {
  create: function () {
    return {
      features : [],
      targets: [],
      model: null
    }
  },
  /**
   * train
   *
   * @param {object} options
   * @param {array} options.data - training data
   * @param {string} options.target - class label
   * @param {array} options.features - features names
   * @param {array} options.featureTypes - features type (ie 'category', 'number')
   */
  train: function(model,options) {
    var data = options.data,
        target = options.target,
        features = options.features,
        featureTypes = options.featureTypes;
    featureTypes.forEach(function(f) {
      if (['number','category'].indexOf(f) === -1) {
        throw new Error('C4.5: Unrecognized option!');
      }
    });

    var targets = unique(data.map(function(d) {
      return d[d.length-1];
    }));
    
    model.features = features;
    model.targets = targets;
    // model is the generated tree structure
    model.model = C45._c45(model, data, target, features, featureTypes, 0);
  },

  _c45: function(model, data, target, features, featureTypes, depth) {
    var targets = unique(data.map(function(d) {
      return d[d.length-1];
    }));

    if (!targets.length) {
      return {
        type: 'result',
        value: 'none data',
        name: 'none data'
      };
    }

    if (targets.length === 1) {
      return {
        type: 'result',
        value: targets[0],
        name: targets[0]
      };
    }

    if (!features.length) {
      var topTarget = C45.mostCommon(targets);
      return {
        type: 'result',
        value: topTarget,
        name: topTarget
      };
    }

    var bestFeatureData = C45.maxGain(model, data, target, features, featureTypes);
    var bestFeature = bestFeatureData.feature;

    var remainingFeatures = features.slice(0);
    remainingFeatures.splice(features.indexOf(bestFeature), 1);

    if (featureTypes[model.features.indexOf(bestFeature)] === 'category') {
      var possibleValues = unique(data.map(function(d) {
        return d[model.features.indexOf(bestFeature)];
      }));
      var node = {
        name: bestFeature,
        type: 'feature_category',
        values: possibleValues.map(function(v) {
          var newData = data.filter(function(x) {
            return x[model.features.indexOf(bestFeature)] === v;
          });
          var childNode = {
            name: v,
            type: 'feature_value',
            child: C45._c45(model, newData, target, remainingFeatures, featureTypes, depth+1)
          };
          return childNode;
        })
      };
    } else if (featureTypes[model.features.indexOf(bestFeature)] === 'number') {
      var possibleValues = unique(data.map(function(d) {
        return d[model.features.indexOf(bestFeature)];
      }));
      var node = {
        name: bestFeature,
        type: 'feature_number',
        cut: bestFeatureData.cut,
        values: []
      };

      var newDataRight = data.filter(function(x) {
        return parseFloat(x[model.features.indexOf(bestFeature)]) > bestFeatureData.cut;
      });
      var childNodeRight = {
        name: bestFeatureData.cut.toString(),
        type: 'feature_value',
        child: C45._c45(model, newDataRight, target, remainingFeatures, featureTypes, depth+1)
      };
      node.values.push(childNodeRight);

      var newDataLeft = data.filter(function(x) {
        return parseFloat(x[model.features.indexOf(bestFeature)]) <= bestFeatureData.cut;
      });
      var childNodeLeft = {
        name: bestFeatureData.cut.toString(),
        type: 'feature_value',
        child: C45._c45(model, newDataLeft, target, remainingFeatures, featureTypes, depth+1),
      };
      node.values.push(childNodeLeft);
    }
    return node;
  },


  classify: function (model,sample) {
    // root is feature (attribute) containing all sub values
    var childNode, featureName, sampleVal;
    var root = model.model;

    if (typeof root === 'undefined') {
      callback(new Error('model is undefined'));
    }

    while (root.type != NODE_TYPES.RESULT) {

      if (root.type == NODE_TYPES.FEATURE_NUMBER) {
        // feature number attribute
        featureName = root.name;
        sampleVal = parseFloat(sample[featureName]);
        if (sampleVal <= root.cut) {
          childNode = root.values[1];
        } else {
          childNode = root.values[0];
        }
      } else if (root.type == NODE_TYPES.FEATURE_CATEGORY) {
        // feature category attribute
        featureName = root.name;
        sampleVal = sample[featureName];

        // sub value , containing n childs
        childNode = find(root.values, function(x) {
          return x.name === sampleVal;
        });
      }

      // non trained feature
      if (typeof childNode === 'undefined') {
        return 'unknown';
      }
      root = childNode.child;
    }
    return root.value;
  },

  conditionalEntropy: function(model, data, feature, cut, target) {
    var subset1 = data.filter(function(x) {
      return parseFloat(x[model.features.indexOf(feature)]) <= cut;
    });
    var subset2 = data.filter(function(x) {
      return parseFloat(x[model.features.indexOf(feature)]) > cut;
    });
    var setSize = data.length;
    return subset1.length/setSize * C45.entropy(model,
      subset1.map(function(d) {
        return d[d.length-1];
      })
    ) + subset2.length/setSize*C45.entropy(model,
      subset2.map(function(d) {
        return d[d.length-1];
      })
    );
  },

  count: function(target, targets) {
    return targets.filter(function(t) {
      return t === target;
    }).length;
  },

  entropy: function(model, vals) {
    var uniqueVals = unique(vals);
    var probs = uniqueVals.map(function(x) {
      return C45.prob(x, vals);
    });
    var logVals = probs.map(function(p) {
      return -p * C45.log2(p);
    });
    return logVals.reduce(function(a, b) {
      return a + b;
    }, 0);
  },

  gain: function(model, data, target, features, feature, featureTypes) {
    var setEntropy = C45.entropy(model, data.map(function(d) {
      return d[d.length-1];
    }));
    if (featureTypes[model.features.indexOf(feature)] === 'category') {
      var attrVals = unique(data.map(function(d) {
        return d[model.features.indexOf(feature)];
      }));
      var setSize = data.length;
      var entropies = attrVals.map(function(n) {
        var subset = data.filter(function(x) {
          return x[feature] === n;
        });
        return (subset.length/setSize) * C45.entropy(model,
          subset.map(function(d) {
            return d[d.length-1];
          })
        );
      });
      var sumOfEntropies = entropies.reduce(function(a, b) {
        return a + b;
      }, 0);
      return {
        feature: feature,
        gain: setEntropy - sumOfEntropies,
        cut: 0
      };
    } else if (featureTypes[model.features.indexOf(feature)] === 'number') {
      var attrVals = unique(data.map(function(d) {
        return d[model.features.indexOf(feature)];
      }));
      var gainVals = attrVals.map(function(cut) {
        var cutf = parseFloat(cut);
        var gain = setEntropy - C45.conditionalEntropy(model, data, feature, cutf, target);
        return {
            feature: feature,
            gain: gain,
            cut: cutf
        };
      });
      var maxgain = max(gainVals, function(e) {
        return e.gain;
      });
      return maxgain;
    }
  },

  log2: function(n) {
    return Math.log(n) / Math.log(2);
  },
  
  maxGain: function(model, data, target, features, featureTypes) {
    var g45 = features.map(function(feature) {
      return C45.gain(model, data, target, features, feature, featureTypes);
    });
    return max(g45, function(e) {
      return e.gain;
    });
  },


  mostCommon: function(targets) {
    return sortBy(targets, function(target) {
      return C45.count(target, targets);
    }).reverse()[0];
  },

  /** Print the tree
  *
  */
  print: function (model,indent) {
    var NL = '\n',
        line='',sep;
    if (indent==undefined) indent=0;
    if (!model) return '';
    var sp = function () {return Comp.string.create(indent);};
    switch (model.type) {
      case NODE_TYPES.RESULT: 
        return sp()+'-> '+model.name+NL;
      case NODE_TYPES.FEATURE_CATEGORY:
        line=sp()+'$'+model.name+'?'+NL;
        Comp.array.iter(model.values,function (v) {
          line += C45.print(v,indent+2);
        }); 
        return line;
      case NODE_TYPES.FEATURE_NUMBER:
        line = sp()+'$'+model.name+'>'+model.cut+'?'+NL;
        if (model.values[0].type==NODE_TYPES.FEATURE_VALUE)
          line = line+C45.print(model.values[0].child,indent+2);
        else
          line = line+C45.print(model.values[0],indent+2);
        line = line+sp()+'$'+model.name+'<='+model.cut+'?'+NL;
        if (model.values[0].type==NODE_TYPES.FEATURE_VALUE)
          line = line+C45.print(model.values[1].child,indent+2);
        else
          line = line+C45.print(model.values[1],indent+2);
        return line;
      case NODE_TYPES.FEATURE_VALUE:
        line=sp()+''+model.name+NL;
        line += C45.print(model.child,indent+2);
        return line;
    }
    return 'model?';
  },

  prob: function(target, targets) {
    return C45.count(target,targets)/targets.length;
  },

};

module.exports = {
  classify:C45.classify,
  create:C45.create,
  entropy:C45.entropy,
  log2:C45.log2,
  print:function (model,indent) { return C45.print(model.model,indent) },
  unique:unique,
  train:C45.train,
  current:function (module) { current=module.current; Aios=module;}  
}
};
BundleModuleCode['ml/text']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2006-2019 BSSLAB
 **    $CREATED:     5-3-19 by sbosse.
 **    $VERSION:     1.1.1
 **
 **    $INFO:
 **
 **  JavaScript AIOS Machine Learning API: Text analysis
 **
 ** Portable model
 **
 **    $ENDOFINFO
 */
'use strict';
var Io = Require('com/io');
var Comp = Require('com/compat');
var current=none;
var Aios=none;

function similarity(s1, s2) {
  var longer = s1;
  var shorter = s2;
  if (s1.length < s2.length) {
    longer = s2;
    shorter = s1;
  }
  var longerLength = longer.length;
  if (longerLength == 0) {
    return 1.0;
  }
  return (longerLength - editDistance(longer, shorter)) / parseFloat(longerLength);
}
function editDistance(s1, s2) {
  s1 = s1.toLowerCase();
  s2 = s2.toLowerCase();

  var costs = new Array();
  for (var i = 0; i <= s1.length; i++) {
    var lastValue = i;
    for (var j = 0; j <= s2.length; j++) {
      if (i == 0)
        costs[j] = j;
      else {
        if (j > 0) {
          var newValue = costs[j - 1];
          if (s1.charAt(i - 1) != s2.charAt(j - 1))
            newValue = Math.min(Math.min(newValue, lastValue),
              costs[j]) + 1;
          costs[j - 1] = lastValue;
          lastValue = newValue;
        }
      }
    }
    if (i > 0)
      costs[s2.length] = lastValue;
  }
  return costs[s2.length];
}


// Create a model
function create(strings,options) {
  return {
    data:strings
  }
}

// Classify one sample; return best matching string
function classify(model,sample) {
  var matches = model.data.map(function (h) {
    return {
      match:similarity(h,sample),
      string:h
    }
  }).sort(function (a,b) {
    if (a.match < b.match) return 1; else return -1;
  });
  return matches[0];
}

module.exports = {
  classify:classify,
  create:create,
  similarity:similarity,
  current:function (module) { current=module.current; Aios=module;}  
}
};
BundleModuleCode['ml/rf']=function (module,exports){
// MIT License
// Random Forest Trees (only binary classifier)
// Andrej Karpathy
// @blab+ 
// https://github.com/karpathy/forestjs


var RandomForest = function(options) {
  var L = {};
  return L
}

RandomForest.code = {

  /*
  data is 2D array of size N x D of examples
  labels is a 1D array of labels (only -1 or 1 for now). In future will support multiclass or maybe even regression
  options.numTrees can be used to customize number of trees to train (default = 100)
  options.maxDepth is the maximum depth of each tree in the forest (default = 4)
  options.numTries is the number of random hypotheses generated at each node during training (default = 10)
  options.trainFun is a function with signature "function myWeakTrain(data, labels, ix, options)". Here, ix is a list of 
                   indeces into data of the instances that should be payed attention to. Everything not in the list 
                   should be ignored. This is done for efficiency. The function should return a model where you store 
                   variables. (i.e. model = {}; model.myvar = 5;) This will be passed to testFun.
  options.testFun is a function with signature "funtion myWeakTest(inst, model)" where inst is 1D array specifying an example,
                   and model will be the same model that you return in options.trainFun. For example, model.myvar will be 5.
                   see decisionStumpTrain() and decisionStumpTest() downstairs for example.
  */
  train: function(L, data, labels, options) {
    options = options || {};
    L.options = options;
    
    L.numTrees = options.numTrees || 100;

    // initialize many trees and train them all independently
    L.trees= new Array(L.numTrees);
    for(var i=0;i<L.numTrees;i++) {
      L.trees[i] = DecisionTree();
      DecisionTree.code.train(L.trees[i],data, labels, options);
    }
  },

  /*
  inst is a 1D array of length D of an example. 
  returns the probability of label 1, i.e. a number in range [0, 1]
  */
  predictOne: function(L, inst) {

    // have each tree predict and average out all votes
    var dec=0;
    for(var i=0;i<L.numTrees;i++) {
      dec += DecisionTree.code.predictOne(L.trees[i],inst);
    }
    dec /= L.numTrees;
    return dec;
  },

  // convenience function. Here, data is NxD array. 
  // returns probabilities of being 1 for all data in an array.
  predict: function(L, data) {

    var probabilities= new Array(data.length);
    for(var i=0;i<data.length;i++) {
      probabilities[i]= RandomForest.code.predictOne(L,data[i]);
    }
    return probabilities;

  }

}

// represents a single decision tree
var DecisionTree = function(options) {
  var L = {};
  return L
}

DecisionTree.code = {

  train: function(L, data, labels, options) {

    options = options || {};
    var maxDepth = options.maxDepth || 4;
    var weakType = options.type || 0;

    
    var trainFun= decisionStumpTrain;
    var testFun= decisionStumpTest;

    if(options.trainFun) trainFun = options.trainFun;
    if(options.testFun) testFun = options.testFun;

    if(weakType == 0) {
      // Default
      trainFun  = decisionStumpTrain;
      testFun   = decisionStumpTest;
    }
    if(weakType) {
      trainFun  = decision2DStumpTrain;
      L.testFun = testFun = decision2DStumpTest;
    }

    // initialize various helper variables
    var numInternals= Math.pow(2, maxDepth)-1;
    var numNodes= Math.pow(2, maxDepth + 1)-1;
    var ixs= new Array(numNodes);
    for(var i=1;i<ixs.length;i++) ixs[i]=[];
    ixs[0]= new Array(labels.length);
    for(var i=0;i<labels.length;i++) ixs[0][i]= i; // root node starts out with all nodes as relevant
    var models = new Array(numInternals);

    // train
    for(var n=0; n < numInternals; n++) {

      // few base cases
      var ixhere= ixs[n];
      if(ixhere.length == 0) { continue; }
      if(ixhere.length == 1) { ixs[n*2+1] = [ixhere[0]]; continue; } // arbitrary send it down left

      // learn a weak model on relevant data for this node
      var model= trainFun(data, labels, ixhere);
      models[n]= model; // back it up model

      // split the data according to the learned model
      var ixleft=[];
      var ixright=[];
      for(var i=0; i<ixhere.length;i++) {
          var label= testFun(data[ixhere[i]], model);
          if(label === 1) ixleft.push(ixhere[i]);
          else ixright.push(ixhere[i]);
      }
      ixs[n*2+1]= ixleft;
      ixs[n*2+2]= ixright;
    }

    // compute data distributions at the leafs
    var leafPositives = new Array(numNodes);
    var leafNegatives = new Array(numNodes);
    for(var n=numInternals; n < numNodes; n++) {
      var numones= 0;
      for(var i=0;i<ixs[n].length;i++) {
          if(labels[ixs[n][i]] === 1) numones+=1;
      }
      leafPositives[n]= numones;
      leafNegatives[n]= ixs[n].length-numones;
    }

    // back up important prediction variables for predicting later
    L.models= models;
    L.leafPositives = leafPositives;
    L.leafNegatives = leafNegatives;
    L.maxDepth= maxDepth;
    // L.trainFun= trainFun;
    // L.testFun= testFun;
  }, 

  // returns probability that example inst is 1.
  predictOne: function(L, inst) { 
      var testFun = L.testFun||decisionStumpTest;
      var n=0;
      for(var i=0;i<L.maxDepth;i++) {
          var dir= testFun(inst, L.models[n]);
          if(dir === 1) n= n*2+1; // descend left
          else n= n*2+2; // descend right
      }

      return (L.leafPositives[n] + 0.5) / (L.leafNegatives[n] + 1.0); // bayesian smoothing!
  }
}

// returns model
function decisionStumpTrain(data, labels, ix, options) {

  options = options || {};
  var numtries = options.numTries || 10;

  // choose a dimension at random and pick a best split
  var ri= randi(0, data[0].length);
  var N= ix.length;

  // evaluate class entropy of incoming data
  var H= entropy(labels, ix);
  var bestGain=0; 
  var bestThr= 0;
  for(var i=0;i<numtries;i++) {

      // pick a random splitting threshold
      var ix1= ix[randi(0, N)];
      var ix2= ix[randi(0, N)];
      while(ix2==ix1) ix2= ix[randi(0, N)]; // enforce distinctness of ix2

      var a= Math.random();
      var thr= data[ix1][ri]*a + data[ix2][ri]*(1-a);

      // measure information gain we'd get from split with thr
      var l1=1, r1=1, lm1=1, rm1=1; //counts for Left and label 1, right and label 1, left and minus 1, right and minus 1
      for(var j=0;j<ix.length;j++) {
          if(data[ix[j]][ri] < thr) {
            if(labels[ix[j]]==1) l1++;
            else lm1++;
          } else {
            if(labels[ix[j]]==1) r1++;
            else rm1++;
          }
      }
      var t= l1+lm1;  // normalize the counts to obtain probability estimates
      l1=l1/t;
      lm1=lm1/t;
      t= r1+rm1;
      r1=r1/t;
      rm1= rm1/t;

      var LH= -l1*Math.log(l1) -lm1*Math.log(lm1); // left and right entropy
      var RH= -r1*Math.log(r1) -rm1*Math.log(rm1);

      var informationGain= H - LH - RH;
      //console.log("Considering split %f, entropy %f -> %f, %f. Gain %f", thr, H, LH, RH, informationGain);
      if(informationGain > bestGain || i === 0) {
          bestGain= informationGain;
          bestThr= thr;
      }
  }

  model= {};
  model.thr= bestThr;
  model.ri= ri;
  return model;
}

// returns a decision for a single data instance
function decisionStumpTest(inst, model) {
  if(!model) {
      // this is a leaf that never received any data... 
      return 1;
  }
  return inst[model.ri] < model.thr ? 1 : -1;

}

// returns model. Code duplication with decisionStumpTrain :(
function decision2DStumpTrain(data, labels, ix, options) {

  options = options || {};
  var numtries = options.numTries || 10;

  // choose a dimension at random and pick a best split
  var N= ix.length;

  var ri1= 0;
  var ri2= 1;
  if(data[0].length > 2) {
    // more than 2D data. Pick 2 random dimensions
    ri1= randi(0, data[0].length);
    ri2= randi(0, data[0].length);
    while(ri2 == ri1) ri2= randi(0, data[0].length); // must be distinct!
  }

  // evaluate class entropy of incoming data
  var H= entropy(labels, ix);
  var bestGain=0; 
  var bestw1, bestw2, bestthr;
  var dots= new Array(ix.length);
  for(var i=0;i<numtries;i++) {

      // pick random line parameters
      var alpha= randf(0, 2*Math.PI);
      var w1= Math.cos(alpha);
      var w2= Math.sin(alpha);

      // project data on this line and get the dot products
      for(var j=0;j<ix.length;j++) {
        dots[j]= w1*data[ix[j]][ri1] + w2*data[ix[j]][ri2];
      }

      // we are in a tricky situation because data dot product distribution
      // can be skewed. So we don't want to select just randomly between
      // min and max. But we also don't want to sort as that is too expensive
      // let's pick two random points and make the threshold be somewhere between them.
      // for skewed datasets, the selected points will with relatively high likelihood
      // be in the high-desnity regions, so the thresholds will make sense
      var ix1= ix[randi(0, N)];
      var ix2= ix[randi(0, N)];
      while(ix2==ix1) ix2= ix[randi(0, N)]; // enforce distinctness of ix2
      var a= Math.random();
      var dotthr= dots[ix1]*a + dots[ix2]*(1-a);

      // measure information gain we'd get from split with thr
      var l1=1, r1=1, lm1=1, rm1=1; //counts for Left and label 1, right and label 1, left and minus 1, right and minus 1
      for(var j=0;j<ix.length;j++) {
          if(dots[j] < dotthr) {
            if(labels[ix[j]]==1) l1++;
            else lm1++;
          } else {
            if(labels[ix[j]]==1) r1++;
            else rm1++;
          }
      }
      var t= l1+lm1; 
      l1=l1/t;
      lm1=lm1/t;
      t= r1+rm1;
      r1=r1/t;
      rm1= rm1/t;

      var LH= -l1*Math.log(l1) -lm1*Math.log(lm1); // left and right entropy
      var RH= -r1*Math.log(r1) -rm1*Math.log(rm1);

      var informationGain= H - LH - RH;
      //console.log("Considering split %f, entropy %f -> %f, %f. Gain %f", thr, H, LH, RH, informationGain);
      if(informationGain > bestGain || i === 0) {
          bestGain= informationGain;
          bestw1= w1;
          bestw2= w2;
          bestthr= dotthr;
      }
  }

  model= {};
  model.w1= bestw1;
  model.w2= bestw2;
  model.dotthr= bestthr;
  return model;
}

// returns label for a single data instance
function decision2DStumpTest(inst, model) {
  if(!model) {
      // this is a leaf that never received any data... 
      return 1;
  }
  return inst[0]*model.w1 + inst[1]*model.w2 < model.dotthr ? 1 : -1;

}

// Misc utility functions
function entropy(labels, ix) {
  var N= ix.length;
  var p=0.0;
  for(var i=0;i<N;i++) {
      if(labels[ix[i]]==1) p+=1;
  }
  p=(1+p)/(N+2); // let's be bayesian about this
  q=(1+N-p)/(N+2);
  return (-p*Math.log(p) -q*Math.log(q));
}

// generate random floating point number between a and b
function randf(a, b) {
  return Math.random()*(b-a)+a;
}

// generate random integer between a and b (b excluded)
function randi(a, b) {
   return Math.floor(Math.random()*(b-a)+a);
}

module.exports = RandomForest
};
BundleModuleCode['ml/rl']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Ankit Kuwadekar, Stefan Bosse
 **    $INITIAL:     (C) 2015, Andrej Karpathy
 **    $MODIFIED:    (C) 2006-2019 bLAB by sbosse
 **    $VERSION:     1.1.2
 **
 **    $INFO:
 **
 ** Reinforcement Learning module that implements several common RL algorithms.
 ** Portable models (TDAgent/DPAgent/DQNAgent)
 **
 **    $ENDOFINFO
 */
"use strict";

var options = {
  version:'1.1.2'
}
var Io = Require('com/io')
var R = module.exports; // the Recurrent library


// Utility fun
function assert(condition, message) {
  // from http://stackoverflow.com/questions/15313418/javascript-assert
  if (!condition) {
    message = message || "Assertion failed";
    if (typeof Error !== "undefined") {
      throw new Error(message);
    }
    throw message; // Fallback
  }
}

// Random numbers utils
var return_v = false;
var v_val = 0.0;
var gaussRandom = function() {
  if(return_v) { 
    return_v = false;
    return v_val; 
  }
  var u = 2*Math.random()-1;
  var v = 2*Math.random()-1;
  var r = u*u + v*v;
  if(r == 0 || r > 1) return gaussRandom();
  var c = Math.sqrt(-2*Math.log(r)/r);
  v_val = v*c; // cache this
  return_v = true;
  return u*c;
}
var randf = function(a, b) { return Math.random()*(b-a)+a; }
var randi = function(a, b) { return Math.floor(Math.random()*(b-a)+a); }
var randn = function(mu, std){ return mu+gaussRandom()*std; }

// helper function returns array of zeros of length n
// and uses typed arrays if available
var zeros = function(n) {
  if(typeof(n)==='undefined' || isNaN(n)) { return []; }
  if(typeof ArrayBuffer === 'undefined') {
    // lacking browser support
    var arr = new Array(n);
    for(var i=0;i<n;i++) { arr[i] = 0; }
    return arr;
  } else {
    return new Float64Array(n);
  }
}

// Mat holds a matrix
var Mat = function(n,d) {
  var M = {}
  // n is number of rows d is number of columns
  M.n = n;
  M.d = d;
  M.w = zeros(n * d);
  M.dw = zeros(n * d);
  return M;
}

Mat.code = {
  get: function(M,row, col) { 
    // slow but careful accessor function
    // we want row-major order
    var ix = (M.d * row) + col;
    assert(ix >= 0 && ix < M.w.length);
    return M.w[ix];
  },
  set: function(M, row, col, v) {
    // slow but careful accessor function
    var ix = (M.d * row) + col;
    assert(ix >= 0 && ix < M.w.length);
    M.w[ix] = v; 
  },
  setFrom: function(M, arr) {
    for(var i=0,n=arr.length;i<n;i++) {
      M.w[i] = arr[i]; 
    }
  },
  setColumn: function(M, m, i) {
    for(var q=0,n=m.w.length;q<n;q++) {
      M.w[(M.d * q) + i] = m.w[q];
    }
  },
  toJSON: function(M) {
    var json = {};
    json['n'] = M.n;
    json['d'] = M.d;
    json['w'] = M.w;
    return json;
  },
  fromJSON: function(M, json) {
    M.n = json.n;
    M.d = json.d;
    M.w = zeros(M.n * M.d);
    M.dw = zeros(M.n * M.d);
    for(var i=0,n=M.n * M.d;i<n;i++) {
      M.w[i] = json.w[i]; // copy over weights
    }
  }
}

var copyMat = function(b) {
  var a = Mat(b.n, b.d);
  Mat.code.setFrom(a, b.w);
  return a;
}

var copyNet = function(net) {
  // nets are (k,v) pairs with k = string key, v = Mat()
  var new_net = {};
  for(var p in net) {
    if(net.hasOwnProperty(p)){
      new_net[p] = copyMat(net[p]);
    }
  }
  return new_net;
}

var updateMat = function(m, alpha) {
  // updates in place
  for(var i=0,n=m.n*m.d;i<n;i++) {
    if(m.dw[i] !== 0) {
      m.w[i] += - alpha * m.dw[i];
      m.dw[i] = 0;
    }
  }
}

var updateNet = function(net, alpha) {
  for(var p in net) {
    if(net.hasOwnProperty(p)){
      updateMat(net[p], alpha);
    }
  }
}

var netToJSON = function(net) {
  var j = {};
  for(var p in net) {
    if(net.hasOwnProperty(p)){
      j[p] = Mat.code.toJSON(net[p]);
    }
  }
  return j;
}
var netFromJSON = function(j) {
  var net = {};
  for(var p in j) {
    if(j.hasOwnProperty(p)){
      net[p] = Mat(1,1); // not proud of this
      Mat.code.fromJSON(net[p],j[p]);
    }
  }
  return net;
}
var netZeroGrads = function(net) {
  for(var p in net) {
    if(net.hasOwnProperty(p)){
      var mat = net[p];
      gradFillConst(mat, 0);
    }
  }
}
var netFlattenGrads = function(net) {
  var n = 0;
  for(var p in net) { 
   if(net.hasOwnProperty(p)) { 
    var mat = net[p]; n += mat.dw.length; 
  }}
  var g = Mat(n, 1);
  var ix = 0;
  for(var p in net) {
    if(net.hasOwnProperty(p)){
      var mat = net[p];
      for(var i=0,m=mat.dw.length;i<m;i++) {
        g.w[ix] = mat.dw[i];
        ix++;
      }
    }
  }
  return g;
}

// return Mat but filled with random numbers from gaussian
var RandMat = function(n,d,mu,std) {
  var m = Mat(n, d);
  fillRandn(m,mu,std);
  //fillRand(m,-std,std); // kind of :P
  return m;
}

// Mat utils
// fill matrix with random gaussian numbers
var fillRandn = function(m, mu, std) { for(var i=0,n=m.w.length;i<n;i++) { m.w[i] = randn(mu, std); } }
var fillRand = function(m, lo, hi) { for(var i=0,n=m.w.length;i<n;i++) { m.w[i] = randf(lo, hi); } }
var gradFillConst = function(m, c) { for(var i=0,n=m.dw.length;i<n;i++) { m.dw[i] = c } }



// Transformer definitions
var Graph = function(needs_backprop) {
  var G = {}
  if(typeof needs_backprop === 'undefined') { needs_backprop = true; }
  G.needs_backprop = needs_backprop;

  // this will store a list of functions that perform backprop,
  // in their forward pass order. So in backprop we will go
  // backwards and evoke each one
  G.backprop = [];
  return G
}
Graph.code = {
  backward: function(G) {
    for(var i=G.backprop.length-1;i>=0;i--) {
      G.backprop[i](); // tick!
    }
  },
  rowPluck: function(G, m, ix) {
    // pluck a row of m with index ix and return it as col vector
    assert(ix >= 0 && ix < m.n);
    var d = m.d;
    var out = Mat(d, 1);
    for(var i=0,n=d;i<n;i++){ out.w[i] = m.w[d * ix + i]; } // copy over the data

    if(G.needs_backprop) {
      var backward = function() {
        for(var i=0,n=d;i<n;i++){ m.dw[d * ix + i] += out.dw[i]; }
      }
      G.backprop.push(backward);
    }
    return out;
  },
  tanh: function(G, m) {
    // tanh nonlinearity
    var out = Mat(m.n, m.d);
    var n = m.w.length;
    for(var i=0;i<n;i++) { 
      out.w[i] = Math.tanh(m.w[i]);
    }

    if(G.needs_backprop) {
      var backward = function() {
        for(var i=0;i<n;i++) {
          // grad for z = tanh(x) is (1 - z^2)
          var mwi = out.w[i];
          m.dw[i] += (1.0 - mwi * mwi) * out.dw[i];
        }
      }
      G.backprop.push(backward);
    }
    return out;
  },
  sigmoid: function(G, m) {
    // sigmoid nonlinearity
    var out = Mat(m.n, m.d);
    var n = m.w.length;
    for(var i=0;i<n;i++) { 
      out.w[i] = sig(m.w[i]);
    }

    if(G.needs_backprop) {
      var backward = function() {
        for(var i=0;i<n;i++) {
          // grad for z = tanh(x) is (1 - z^2)
          var mwi = out.w[i];
          m.dw[i] += mwi * (1.0 - mwi) * out.dw[i];
        }
      }
      G.backprop.push(backward);
    }
    return out;
  },
  relu: function(G, m) {
    var out = Mat(m.n, m.d);
    var n = m.w.length;
    for(var i=0;i<n;i++) { 
      out.w[i] = Math.max(0, m.w[i]); // relu
    }
    if(G.needs_backprop) {
      var backward = function() {
        for(var i=0;i<n;i++) {
          m.dw[i] += m.w[i] > 0 ? out.dw[i] : 0.0;
        }
      }
      G.backprop.push(backward);
    }
    return out;
  },
  mul: function(G, m1, m2) {
    // multiply matrices m1 * m2
    assert(m1.d === m2.n, 'matmul dimensions misaligned');

    var n = m1.n;
    var d = m2.d;
    var out = Mat(n,d);
    for(var i=0;i<m1.n;i++) { // loop over rows of m1
      for(var j=0;j<m2.d;j++) { // loop over cols of m2
        var dot = 0.0;
        for(var k=0;k<m1.d;k++) { // dot product loop
          dot += m1.w[m1.d*i+k] * m2.w[m2.d*k+j];
        }
        out.w[d*i+j] = dot;
      }
    }

    if(G.needs_backprop) {
      var backward = function() {
        for(var i=0;i<m1.n;i++) { // loop over rows of m1
          for(var j=0;j<m2.d;j++) { // loop over cols of m2
            for(var k=0;k<m1.d;k++) { // dot product loop
              var b = out.dw[d*i+j];
              m1.dw[m1.d*i+k] += m2.w[m2.d*k+j] * b;
              m2.dw[m2.d*k+j] += m1.w[m1.d*i+k] * b;
            }
          }
        }
      }
      G.backprop.push(backward);
    }
    return out;
  },
  add: function(G, m1, m2) {
    assert(m1.w.length === m2.w.length);

    var out = Mat(m1.n, m1.d);
    for(var i=0,n=m1.w.length;i<n;i++) {
      out.w[i] = m1.w[i] + m2.w[i];
    }
    if(G.needs_backprop) {
      var backward = function() {
        for(var i=0,n=m1.w.length;i<n;i++) {
          m1.dw[i] += out.dw[i];
          m2.dw[i] += out.dw[i];
        }
      }
      G.backprop.push(backward);
    }
    return out;
  },
  dot: function(G, m1, m2) {
    // m1 m2 are both column vectors
    assert(m1.w.length === m2.w.length);
    var out = Mat(1,1);
    var dot = 0.0;
    for(var i=0,n=m1.w.length;i<n;i++) {
      dot += m1.w[i] * m2.w[i];
    }
    out.w[0] = dot;
    if(G.needs_backprop) {
      var backward = function() {
        for(var i=0,n=m1.w.length;i<n;i++) {
          m1.dw[i] += m2.w[i] * out.dw[0];
          m2.dw[i] += m1.w[i] * out.dw[0];
        }
      }
      G.backprop.push(backward);
    }
    return out;
  },
  eltmul: function(G, m1, m2) {
    assert(m1.w.length === m2.w.length);

    var out = Mat(m1.n, m1.d);
    for(var i=0,n=m1.w.length;i<n;i++) {
      out.w[i] = m1.w[i] * m2.w[i];
    }
    if(G.needs_backprop) {
      var backward = function() {
        for(var i=0,n=m1.w.length;i<n;i++) {
          m1.dw[i] += m2.w[i] * out.dw[i];
          m2.dw[i] += m1.w[i] * out.dw[i];
        }
      }
      G.backprop.push(backward);
    }
    return out;
  },
}


var softmax = function(m) {
    var out = Mat(m.n, m.d); // probability volume
    var maxval = -999999;
    for(var i=0,n=m.w.length;i<n;i++) { if(m.w[i] > maxval) maxval = m.w[i]; }

    var s = 0.0;
    for(var i=0,n=m.w.length;i<n;i++) { 
      out.w[i] = Math.exp(m.w[i] - maxval);
      s += out.w[i];
    }
    for(var i=0,n=m.w.length;i<n;i++) { out.w[i] /= s; }

    // no backward pass here needed
    // since we will use the computed probabilities outside
    // to set gradients directly on m
    return out;
  }


var Solver = function() {
  var S = {}
  S.decay_rate = 0.999;
  S.smooth_eps = 1e-8;
  S.step_cache = {};
  return S
}
Solver.code = {
  step: function(S, model, step_size, regc, clipval) {
    // perform parameter update
    var solver_stats = {};
    var num_clipped = 0;
    var num_tot = 0;
    for(var k in model) {
      if(model.hasOwnProperty(k)) {
        var m = model[k]; // mat ref
        if(!(k in S.step_cache)) { S.step_cache[k] = Mat(m.n, m.d); }
        var s = S.step_cache[k];
        for(var i=0,n=m.w.length;i<n;i++) {

          // rmsprop adaptive learning rate
          var mdwi = m.dw[i];
          s.w[i] = s.w[i] * S.decay_rate + (1.0 - S.decay_rate) * mdwi * mdwi;

          // gradient clip
          if(mdwi > clipval) {
            mdwi = clipval;
            num_clipped++;
          }
          if(mdwi < -clipval) {
            mdwi = -clipval;
            num_clipped++;
          }
          num_tot++;

          // update (and regularize)
          m.w[i] += - step_size * mdwi / Math.sqrt(s.w[i] + S.smooth_eps) - regc * m.w[i];
          m.dw[i] = 0; // reset gradients for next iteration
        }
      }
    }
    solver_stats['ratio_clipped'] = num_clipped*1.0/num_tot;
    return solver_stats;
  }
}

var initLSTM = function(input_size, hidden_sizes, output_size) {
  // hidden size should be a list

  var model = {};
  for(var d=0;d<hidden_sizes.length;d++) { // loop over depths
    var prev_size = d === 0 ? input_size : hidden_sizes[d - 1];
    var hidden_size = hidden_sizes[d];

    // gates parameters
    model['Wix'+d]  = RandMat(hidden_size, prev_size , 0, 0.08);  
    model['Wih'+d]  = RandMat(hidden_size, hidden_size , 0, 0.08);
    model['bi'+d]   = Mat(hidden_size, 1);
    model['Wfx'+d]  = RandMat(hidden_size, prev_size , 0, 0.08);  
    model['Wfh'+d]  = RandMat(hidden_size, hidden_size , 0, 0.08);
    model['bf'+d]   = Mat(hidden_size, 1);
    model['Wox'+d]  = RandMat(hidden_size, prev_size , 0, 0.08);  
    model['Woh'+d]  = RandMat(hidden_size, hidden_size , 0, 0.08);
    model['bo'+d]   = Mat(hidden_size, 1);
    // cell write params
    model['Wcx'+d]  = RandMat(hidden_size, prev_size , 0, 0.08);  
    model['Wch'+d]  = RandMat(hidden_size, hidden_size , 0, 0.08);
    model['bc'+d]   = Mat(hidden_size, 1);
  }
  // decoder params
  model['Whd']  = RandMat(output_size, hidden_size, 0, 0.08);
  model['bd']   = Mat(output_size, 1);
  return model;
}

var forwardLSTM = function(G, model, hidden_sizes, x, prev) {
  // forward prop for a single tick of LSTM
  // G is graph to append ops to
  // model contains LSTM parameters
  // x is 1D column vector with observation
  // prev is a struct containing hidden and cell
  // from previous iteration

  if(prev == null || typeof prev.h === 'undefined') {
    var hidden_prevs = [];
    var cell_prevs = [];
    for(var d=0;d<hidden_sizes.length;d++) {
      hidden_prevs.push(R.Mat(hidden_sizes[d],1)); 
      cell_prevs.push(R.Mat(hidden_sizes[d],1)); 
    }
  } else {
    var hidden_prevs = prev.h;
    var cell_prevs = prev.c;
  }

  var hidden = [];
  var cell = [];
  for(var d=0;d<hidden_sizes.length;d++) {

    var input_vector = d === 0 ? x : hidden[d-1];
    var hidden_prev = hidden_prevs[d];
    var cell_prev = cell_prevs[d];

    // input gate
    var h0 = Graph.code.mul(G,model['Wix'+d], input_vector);
    var h1 = Graph.code.mul(G,model['Wih'+d], hidden_prev);
    var input_gate = Graph.code.sigmoid(G,Graph.code.add(G,Graph.code.add(G,h0,h1),
                                        model['bi'+d]));

    // forget gate
    var h2 = Graph.code.mul(G,model['Wfx'+d], input_vector);
    var h3 = Graph.code.mul(G,model['Wfh'+d], hidden_prev);
    var forget_gate = Graph.code.sigmoid(
                        G,Graph.code.add(G,Graph.code.add(G,h2, h3),
                        model['bf'+d]));

    // output gate
    var h4 = Graph.code.mul(G,model['Wox'+d], input_vector);
    var h5 = Graph.code.mul(G,model['Woh'+d], hidden_prev);
    var output_gate = Graph.code.sigmoid(G,Graph.code.add(G,Graph.code.add(G,h4, h5),
                                                          model['bo'+d]));

    // write operation on cells
    var h6 = Graph.code.mul(G,model['Wcx'+d], input_vector);
    var h7 = Graph.code.mul(G,model['Wch'+d], hidden_prev);
    var cell_write = Graph.code.tanh(G,Graph.code.add(
                                         G,Graph.code.add(G,h6, h7),
                                         model['bc'+d]));

    // compute new cell activation
    var retain_cell = Graph.code.eltmul(G,forget_gate, cell_prev); // what do we keep from cell
    var write_cell = Graph.code.eltmul(G,input_gate, cell_write); // what do we write to cell
    var cell_d = Graph.code.add(G,retain_cell, write_cell); // new cell contents

    // compute hidden state as gated, saturated cell activations
    var hidden_d = Graph.code.eltmul(G, output_gate, Graph.code.tanh(G,cell_d));

    hidden.push(hidden_d);
    cell.push(cell_d);
  }

  // one decoder to outputs at end
  var output = Graph.code.add(G,Graph.code.mul(G,model['Whd'], hidden[hidden.length - 1]),model['bd']);

  // return cell memory, hidden representation and output
  return {'h':hidden, 'c':cell, 'o' : output};
}

var sig = function(x) {
  // helper function for computing sigmoid
  return 1.0/(1+Math.exp(-x));
}

var maxi = function(w) {
  // argmax of array w
  var maxv = w[0];
  var maxix = 0;
  for(var i=1,n=w.length;i<n;i++) {
    var v = w[i];
    if(v > maxv) {
      maxix = i;
      maxv = v;
    }
  }
  return maxix;
}

var samplei = function(w) {
  // sample argmax from w, assuming w are 
  // probabilities that sum to one
  var r = randf(0,1);
  var x = 0.0;
  var i = 0;
  while(true) {
    x += w[i];
    if(x > r) { return i; }
    i++;
  }
  return w.length - 1; // pretty sure we should never get here?
}

// various utils
module.exports.assert = assert;
module.exports.zeros = zeros;
module.exports.maxi = maxi;
module.exports.samplei = samplei;
module.exports.randi = randi;
module.exports.randn = randn;
module.exports.softmax = softmax;
// classes
module.exports.Mat = Mat;
module.exports.RandMat = RandMat;
module.exports.forwardLSTM = forwardLSTM;
module.exports.initLSTM = initLSTM;
// more utils
module.exports.updateMat = updateMat;
module.exports.updateNet = updateNet;
module.exports.copyMat = copyMat;
module.exports.copyNet = copyNet;
module.exports.netToJSON = netToJSON;
module.exports.netFromJSON = netFromJSON;
module.exports.netZeroGrads = netZeroGrads;
module.exports.netFlattenGrads = netFlattenGrads;
// optimization
module.exports.Solver = Solver;
module.exports.Graph = Graph;

// END OF RECURRENTJS

var RL = module.exports;

// syntactic sugar function for getting default parameter values
var getopt = function(opt, field_name, default_value) {
  if(typeof opt === 'undefined') { return default_value; }
  return (typeof opt[field_name] !== 'undefined') ? opt[field_name] : default_value;
}

var zeros = R.zeros; // inherit these
var assert = R.assert;
var randi = R.randi;
var randf = R.randf;

var setConst = function(arr, c) {
  for(var i=0,n=arr.length;i<n;i++) {
    arr[i] = c;
  }
}

var sampleWeighted = function(p) {
  var r = Math.random();
  var c = 0.0;
  for(var i=0,n=p.length;i<n;i++) {
    c += p[i];
    if(c >= r) { return i; }
  }
  // assert(false, 'sampleWeighted: Invalid samples '+Io.inspect(p));
  return 0
}

// ------
// AGENTS
// ------

// DPAgent performs Value Iteration
// - can also be used for Policy Iteration if you really wanted to
// - requires model of the environment :(
// - does not learn from experience :(
// - assumes finite MDP :(
var DPAgent = function(env, opt) {
  var L={};
  L.V = null; // state value function
  L.P = null; // policy distribution \pi(s,a)
  L.env = env; // store pointer to environment
  L.gamma = getopt(opt, 'gamma', 0.75); // future reward discount factor
  DPAgent.code.reset(L);
  return L;
}
DPAgent.code = {
  reset: function(L) {
    // reset the agent's policy and value function
    L.ns = L.env.getNumStates();
    L.na = L.env.getMaxNumActions();
    L.V = zeros(L.ns);
    L.P = zeros(L.ns * L.na);
    // initialize uniform random policy
    for(var s=0;s<L.ns;s++) {
      var poss = L.env.allowedActions(s);
      for(var i=0,n=poss.length;i<n;i++) {
        L.P[poss[i]*L.ns+s] = 1.0 / poss.length;
      }
    }
  },
  act: function(L,s) {
    // behave according to the learned policy
    var poss = L.env.allowedActions(s);
    var ps = [];
    for(var i=0,n=poss.length;i<n;i++) {
      var a = poss[i];
      var prob = L.P[a*L.ns+s];
      ps.push(prob);
    }
    var maxi = sampleWeighted(ps);
    return poss[maxi];
  },
  learn: function(L) {
    // perform a single round of value iteration
    DPAgent.code.evaluatePolicy(L); // writes this.V
    DPAgent.code.updatePolicy(L); // writes this.P
  },
  evaluatePolicy: function(L) {
    // perform a synchronous update of the value function
    var Vnew = zeros(L.ns);
    for(var s=0;s<L.ns;s++) {
      // integrate over actions in a stochastic policy
      // note that we assume that policy probability mass over allowed actions sums to one
      var v = 0.0;
      var poss = L.env.allowedActions(s);
      for(var i=0,n=poss.length;i<n;i++) {
        var a = poss[i];
        var prob = L.P[a*L.ns+s]; // probability of taking action under policy
        if(prob === 0) { continue; } // no contribution, skip for speed
        var ns = L.env.nextState(s,a);
        var rs = L.env.reward(s,a,ns); // reward for s->a->ns transition
        v += prob * (rs + L.gamma * L.V[ns]);
      }
      Vnew[s] = v;
    }
    L.V = Vnew; // swap
  },
  updatePolicy: function(L) {
    // update policy to be greedy w.r.t. learned Value function
    for(var s=0;s<L.ns;s++) {
      var poss = L.env.allowedActions(s);
      // compute value of taking each allowed action
      var vmax, nmax;
      var vs = [];
      for(var i=0,n=poss.length;i<n;i++) {
        var a = poss[i];
        var ns = L.env.nextState(s,a);
        var rs = L.env.reward(s,a,ns);
        var v = rs + L.gamma * L.V[ns];
        vs.push(v);
        if(i === 0 || v > vmax) { vmax = v; nmax = 1; }
        else if(v === vmax) { nmax += 1; }
      }
      // update policy smoothly across all argmaxy actions
      for(var i=0,n=poss.length;i<n;i++) {
        var a = poss[i];
        L.P[a*L.ns+s] = (vs[i] === vmax) ? 1.0/nmax : 0.0;
      }
    }
  },
}

// QAgent uses TD (Q-Learning, SARSA)
// - does not require environment model :)
// - learns from experience :)
var TDAgent = function(env, opt) {
  var L={}
  L.update = getopt(opt, 'update', 'qlearn'); // qlearn | sarsa
  L.gamma = getopt(opt, 'gamma', 0.75); // future reward discount factor
  L.epsilon = getopt(opt, 'epsilon', 0.1); // for epsilon-greedy policy
  L.alpha = getopt(opt, 'alpha', 0.01); // value function learning rate

  // class allows non-deterministic policy, and smoothly regressing towards the optimal policy based on Q
  L.smooth_policy_update = getopt(opt, 'smooth_policy_update', false);
  L.beta = getopt(opt, 'beta', 0.01); // learning rate for policy, if smooth updates are on

  // eligibility traces
  L.lambda = getopt(opt, 'lambda', 0); // eligibility trace decay. 0 = no eligibility traces used
  L.replacing_traces = getopt(opt, 'replacing_traces', true);

  // optional optimistic initial values
  L.q_init_val = getopt(opt, 'q_init_val', 0);

  L.planN = getopt(opt, 'planN', 0); // number of planning steps per learning iteration (0 = no planning)

  L.Q = null; // state action value function
  L.P = null; // policy distribution \pi(s,a)
  L.e = null; // eligibility trace
  L.env_model_s = null;; // environment model (s,a) -> (s',r)
  L.env_model_r = null;; // environment model (s,a) -> (s',r)
  L.env = env; // store pointer to environment
  TDAgent.code.reset(L);
  return L;
}
TDAgent.code = {
  reset: function(L){
    // reset the agent's policy and value function
    L.ns = L.env.getNumStates();
    L.na = L.env.getMaxNumActions();
    L.Q = zeros(L.ns * L.na);
    if(L.q_init_val !== 0) { setConst(L.Q, L.q_init_val); }
    L.P = zeros(L.ns * L.na);
    L.e = zeros(L.ns * L.na);

    // model/planning vars
    L.env_model_s = zeros(L.ns * L.na);
    setConst(L.env_model_s, -1); // init to -1 so we can test if we saw the state before
    L.env_model_r = zeros(L.ns * L.na);
    L.sa_seen = [];
    L.pq = zeros(L.ns * L.na);

    // initialize uniform random policy
    for(var s=0;s<L.ns;s++) {
      var poss = L.env.allowedActions(s);
      for(var i=0,n=poss.length;i<n;i++) {
        L.P[poss[i]*L.ns+s] = 1.0 / poss.length;
      }
    }
    // agent memory, needed for streaming updates
    // (s0,a0,r0,s1,a1,r1,...)
    L.r0 = null;
    L.s0 = null;
    L.s1 = null;
    L.a0 = null;
    L.a1 = null;
  },
  resetEpisode: function(L) {
    // an episode finished
  },
  act: function(L,s){
    // act according to epsilon greedy policy
    var poss = L.env.allowedActions(s);
    var probs = [];
    for(var i=0,n=poss.length;i<n;i++) {
      probs.push(L.P[poss[i]*L.ns+s]);
    }
    // epsilon greedy policy
    if(Math.random() < L.epsilon) {
      var a = poss[randi(0,poss.length)]; // random available action
      L.explored = true;
    } else {
      var a = poss[sampleWeighted(probs)];
      L.explored = false;
    }
    // shift state memory
    L.s0 = L.s1;
    L.a0 = L.a1;
    L.s1 = s;
    L.a1 = a;
    return a;
  },
  learn: function(L,r1){
    // takes reward for previous action, which came from a call to act()
    if(!(L.r0 == null)) {
      TDAgent.code.learnFromTuple(L, L.s0, L.a0, L.r0, L.s1, L.a1, L.lambda);
      if(L.planN > 0) {
        TDAgent.code.updateModel(L, L.s0, L.a0, L.r0, L.s1);
        TDAgent.code.plan(L);
      }
    }
    L.r0 = r1; // store this for next update
  },
  updateModel: function(L, s0, a0, r0, s1) {
    // transition (s0,a0) -> (r0,s1) was observed. Update environment model
    var sa = a0 * L.ns + s0;
    if(L.env_model_s[sa] === -1) {
      // first time we see this state action
      L.sa_seen.push(a0 * L.ns + s0); // add as seen state
    }
    L.env_model_s[sa] = s1;
    L.env_model_r[sa] = r0;
  },
  plan: function(L) {

    // order the states based on current priority queue information
    var spq = [];
    for(var i=0,n=L.sa_seen.length;i<n;i++) {
      var sa = L.sa_seen[i];
      var sap = L.pq[sa];
      if(sap > 1e-5) { // gain a bit of efficiency
        spq.push({sa:sa, p:sap});
      }
    }
    spq.sort(function(a,b){ return a.p < b.p ? 1 : -1});

    // perform the updates
    var nsteps = Math.min(L.planN, spq.length);
    for(var k=0;k<nsteps;k++) {
      // random exploration
      //var i = randi(0, this.sa_seen.length); // pick random prev seen state action
      //var s0a0 = this.sa_seen[i];
      var s0a0 = spq[k].sa;
      L.pq[s0a0] = 0; // erase priority, since we're backing up this state
      var s0 = s0a0 % L.ns;
      var a0 = Math.floor(s0a0 / L.ns);
      var r0 = L.env_model_r[s0a0];
      var s1 = L.env_model_s[s0a0];
      var a1 = -1; // not used for Q learning
      if(L.update === 'sarsa') {
        // generate random action?...
        var poss = L.env.allowedActions(s1);
        var a1 = poss[randi(0,poss.length)];
      }
      TDAgent.code.learnFromTuple(L, s0, a0, r0, s1, a1, 0); // note lambda = 0 - shouldnt use eligibility trace here
    }
  },
  learnFromTuple: function(L, s0, a0, r0, s1, a1, lambda) {
    var sa = a0 * L.ns + s0;

    // calculate the target for Q(s,a)
    if(L.update === 'qlearn') {
      // Q learning target is Q(s0,a0) = r0 + gamma * max_a Q[s1,a]
      var poss = L.env.allowedActions(s1);
      var qmax = 0;
      for(var i=0,n=poss.length;i<n;i++) {
        var s1a = poss[i] * L.ns + s1;
        var qval = L.Q[s1a];
        if(i === 0 || qval > qmax) { qmax = qval; }
      }
      var target = r0 + L.gamma * qmax;
    } else if(L.update === 'sarsa') {
      // SARSA target is Q(s0,a0) = r0 + gamma * Q[s1,a1]
      var s1a1 = a1 * L.ns + s1;
      var target = r0 + L.gamma * L.Q[s1a1];
    }

    if(lambda > 0) {
      // perform an eligibility trace update
      if(L.replacing_traces) {
        L.e[sa] = 1;
      } else {
        L.e[sa] += 1;
      }
      var edecay = lambda * L.gamma;
      var state_update = zeros(L.ns);
      for(var s=0;s<L.ns;s++) {
        var poss = L.env.allowedActions(s);
        for(var i=0;i<poss.length;i++) {
          var a = poss[i];
          var saloop = a * L.ns + s;
          var esa = L.e[saloop];
          var update = L.alpha * esa * (target - L.Q[saloop]);
          L.Q[saloop] += update;
          L.updatePriority(s, a, update);
          L.e[saloop] *= edecay;
          var u = Math.abs(update);
          if(u > state_update[s]) { state_update[s] = u; }
        }
      }
      for(var s=0;s<L.ns;s++) {
        if(state_update[s] > 1e-5) { // save efficiency here
          TDAgent.code.updatePolicy(L,s);
        }
      }
      if(L.explored && L.update === 'qlearn') {
        // have to wipe the trace since q learning is off-policy :(
        L.e = zeros(L.ns * L.na);
      }
    } else {
      // simpler and faster update without eligibility trace
      // update Q[sa] towards it with some step size
      var update = L.alpha * (target - L.Q[sa]);
      L.Q[sa] += update;
      TDAgent.code.updatePriority(L,s0, a0, update);
      // update the policy to reflect the change (if appropriate)
      TDAgent.code.updatePolicy(L,s0);
    }
  },
  updatePriority: function(L,s,a,u) {
    // used in planning. Invoked when Q[sa] += update
    // we should find all states that lead to (s,a) and upgrade their priority
    // of being update in the next planning step
    u = Math.abs(u);
    if(u < 1e-5) { return; } // for efficiency skip small updates
    if(L.planN === 0) { return; } // there is no planning to be done, skip.
    for(var si=0;si<L.ns;si++) {
      // note we are also iterating over impossible actions at all states,
      // but this should be okay because their env_model_s should simply be -1
      // as initialized, so they will never be predicted to point to any state
      // because they will never be observed, and hence never be added to the model
      for(var ai=0;ai<L.na;ai++) {
        var siai = ai * L.ns + si;
        if(L.env_model_s[siai] === s) {
          // this state leads to s, add it to priority queue
          L.pq[siai] += u;
        }
      }
    }
  },
  updatePolicy: function(L,s) {
    var poss = L.env.allowedActions(s);
    // set policy at s to be the action that achieves max_a Q(s,a)
    // first find the maxy Q values
    var qmax, nmax;
    var qs = [];
    for(var i=0,n=poss.length;i<n;i++) {
      var a = poss[i];
      var qval = L.Q[a*L.ns+s];
      qs.push(qval);
      if(i === 0 || qval > qmax) { qmax = qval; nmax = 1; }
      else if(qval === qmax) { nmax += 1; }
    }
    // now update the policy smoothly towards the argmaxy actions
    var psum = 0.0;
    for(var i=0,n=poss.length;i<n;i++) {
      var a = poss[i];
      var target = (qs[i] === qmax) ? 1.0/nmax : 0.0;
      var ix = a*L.ns+s;
      if(L.smooth_policy_update) {
        // slightly hacky :p
        L.P[ix] += L.beta * (target - L.P[ix]);
        psum += L.P[ix];
      } else {
        // set hard target
        L.P[ix] = target;
      }
    }
    if(L.smooth_policy_update) {
      // renomalize P if we're using smooth policy updates
      for(var i=0,n=poss.length;i<n;i++) {
        var a = poss[i];
        L.P[a*L.ns+s] /= psum;
      }
    }
  }
}


var DQNAgent = function(env, opt) {
  var L = {}
  L.gamma = getopt(opt, 'gamma', 0.75); // future reward discount factor
  L.epsilon = getopt(opt, 'epsilon', 0.1); // for epsilon-greedy policy
  L.alpha = getopt(opt, 'alpha', 0.01); // value function learning rate

  L.experience_add_every = getopt(opt, 'experience_add_every', 25); // number of time steps before we add another experience to replay memory
  L.experience_size = getopt(opt, 'experience_size', 5000); // size of experience replay
  L.learning_steps_per_iteration = getopt(opt, 'learning_steps_per_iteration', 10);
  L.tderror_clamp = getopt(opt, 'tderror_clamp', 1.0); 

  L.num_hidden_units =  getopt(opt, 'num_hidden_units', 100); 

  L.env = env;
  DQNAgent.code.reset(L);
  return L
}
DQNAgent.code = {
  reset: function(L) {
    L.nh = L.num_hidden_units; // number of hidden units
    L.ns = L.env.getNumStates();
    L.na = L.env.getMaxNumActions();

    // nets are hardcoded for now as key (str) -> Mat
    // not proud of this. better solution is to have a whole Net object
    // on top of Mats, but for now sticking with this
    L.net = {};
    L.net.W1 = R.RandMat(L.nh, L.ns, 0, 0.01);
    L.net.b1 = R.Mat(L.nh, 1, 0, 0.01);
    L.net.W2 = R.RandMat(L.na, L.nh, 0, 0.01);
    L.net.b2 = R.Mat(L.na, 1, 0, 0.01);

    L.exp = []; // experience
    L.expi = 0; // where to insert

    L.t = 0;

    L.r0 = null;
    L.s0 = null;
    L.s1 = null;
    L.a0 = null;
    L.a1 = null;

    L.tderror = 0; // for visualization only...
  },
  toJSON: function(L) {
    // save function
    var j = {};
    j.nh = L.nh;
    j.ns = L.ns;
    j.na = L.na;
    j.net = R.netToJSON(L.net);
    return j;
  },
  fromJSON: function(L,j) {
    // load function
    L.nh = j.nh;
    L.ns = j.ns;
    L.na = j.na;
    L.net = R.netFromJSON(j.net);
  },
  forwardQ: function(L, net, s, needs_backprop) {
    var G = R.Graph(needs_backprop);
    var a1mat = Graph.code.add(G,Graph.code.mul(G,net.W1, s), net.b1);
    var h1mat = Graph.code.tanh(G,a1mat);
    var a2mat = Graph.code.add(G,Graph.code.mul(G,net.W2, h1mat), net.b2);
    L.lastG = G; // back this up. Kind of hacky isn't it
    return a2mat;
  },
  act: function(L,slist) {
    // convert to a Mat column vector
    var s = R.Mat(L.ns, 1);
    Mat.code.setFrom(s,slist);

    // epsilon greedy policy
    if(Math.random() < L.epsilon) {
      var a = randi(0, L.na);
    } else {
      // greedy wrt Q function
      var amat = DQNAgent.code.forwardQ(L,L.net, s, false);
      var a = R.maxi(amat.w); // returns index of argmax action
    }

    // shift state memory
    L.s0 = L.s1;
    L.a0 = L.a1;
    L.s1 = s;
    L.a1 = a;

    return a;
  },
  learn: function(L,r1) {
    // perform an update on Q function
    if(!(L.r0 == null) && L.alpha > 0) {

      // learn from this tuple to get a sense of how "surprising" it is to the agent
      var tderror = DQNAgent.code.learnFromTuple(L, L.s0, L.a0, L.r0, L.s1, L.a1);
      L.tderror = tderror; // a measure of surprise
      // decide if we should keep this experience in the replay
      if(L.t % L.experience_add_every === 0) {
        L.exp[L.expi] = [L.s0, L.a0, L.r0, L.s1, L.a1];
        L.expi += 1;
        if(L.expi > L.experience_size) { L.expi = 0; } // roll over when we run out
      }
      L.t += 1;

      // sample some additional experience from replay memory and learn from it
      for(var k=0;k<L.learning_steps_per_iteration;k++) {
        var ri = randi(0, L.exp.length); // todo: priority sweeps?
        var e = L.exp[ri];
        DQNAgent.code.learnFromTuple(L, e[0], e[1], e[2], e[3], e[4])
      }
    }
    L.r0 = r1; // store for next update
  },
  learnFromTuple: function(L, s0, a0, r0, s1, a1) {
    // want: Q(s,a) = r + gamma * max_a' Q(s',a')

    // compute the target Q value
    var tmat = DQNAgent.code.forwardQ(L, L.net, s1, false);
    var qmax = r0 + L.gamma * tmat.w[R.maxi(tmat.w)];

    // now predict
    var pred = DQNAgent.code.forwardQ(L, L.net, s0, true);

    var tderror = pred.w[a0] - qmax;
    var clamp = L.tderror_clamp;
    if(Math.abs(tderror) > clamp) {  // huber loss to robustify
      if(tderror > clamp) tderror = clamp;
      if(tderror < -clamp) tderror = -clamp;
    }
    pred.dw[a0] = tderror;

    Graph.code.backward( L.lastG); // compute gradients on net params

    // update net
    R.updateNet(L.net, L.alpha);
    return tderror;
  }
}



// exports
module.exports.DPAgent = DPAgent;
module.exports.TDAgent = TDAgent;
module.exports.DQNAgent = DQNAgent;
//module.exports.SimpleReinforceAgent = SimpleReinforceAgent;
//module.exports.RecurrentReinforceAgent = RecurrentReinforceAgent;
//module.exports.DeterministPG = DeterministPG;


};
BundleModuleCode['ml/stats']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $CREATED:     (C) 2006-2020 bLAB by sbosse
 **    $VERSION:     1.1.8
 **
 **    $INFO:
 **
 **  ML Data Statistics and Utils 
 **
 **  New:
 **    type eps = number | number []
 **
 **    $ENDOFINFO
 */
var Io = Require('com/io');
var Comp = Require('com/compat');

///////// UTILS ////////////
var stat = {
	max: function(array) {
		return Math.max.apply(null, array);
	},
	
	min: function(array) {
		return Math.min.apply(null, array);
	},
	
	range: function(array) {
		return stat.max(array) - stat.min(array);
	},
	
	midrange: function(array) {
		return stat.range(array) / 2;
	},

	sum: function(array) {
		var num = 0;
		for (var i = 0, l = array.length; i < l; i++) num += array[i];
		return num;
	},
	
	mean: function(array) {
		return stat.sum(array) / array.length;
	},
	
	median: function(array) {
		array.sort(function(a, b) {
			return a - b;
		});
		var mid = array.length / 2;
		return mid % 1 ? array[mid - 0.5] : (array[mid - 1] + array[mid]) / 2;
	},
	
	modes: function(array) {
		if (!array.length) return [];
		var modeMap = {},
			maxCount = 0,
			modes = [];

		array.forEach(function(val) {
			if (!modeMap[val]) modeMap[val] = 1;
			else modeMap[val]++;

			if (modeMap[val] > maxCount) {
				modes = [val];
				maxCount = modeMap[val];
			}
			else if (modeMap[val] === maxCount) {
				modes.push(val);
				maxCount = modeMap[val];
			}
		});
		return modes;
	},
	
	variance: function(array) {
		var mean = stat.mean(array);
		return stat.mean(array.map(function(num) {
			return Math.pow(num - mean, 2);
		}));
	},
	
	standardDeviation: function(array) {
		return Math.sqrt(stat.variance(array));
	},
	
	meanAbsoluteDeviation: function(array) {
		var mean = stat.mean(array);
		return stat.mean(array.map(function(num) {
			return Math.abs(num - mean);
		}));
	},
	
	zScores: function(array) {
		var mean = stat.mean(array);
		var standardDeviation = stat.standardDeviation(array);
		return array.map(function(num) {
			return (num - mean) / standardDeviation;
		});
	}
};

// Function aliases:
stat.average = stat.mean;

// function ({$x:number}|{value:*,prob;number}[]|number [],boolean) 
// -> {value:*,prob:number}|{index:number, prob:number}
// normalize=1: scale output max=[0,1]
// normalize=2: scale and weight output max*[0,1]

function best(o,normalize) {
  var p,max,pos=0,sum=0,res;
  if (Comp.obj.isArray(o) && typeof o[0]=='number')  {
    max=-Infinity;
    for(p in o) {
      sum += o[p];       
      if (o[p] > max) max=o[p],pos=p;
    }  
    res = {index:pos,prob:max}   
  } else if (Comp.obj.isArray(o) && typeof o[0]=='object')  {
    for(p in o) {
      sum += o[p].prob; 
      if (!max || o[p].prob>max.prob) max=o[p];
    }
    res = {value:max.value,prob:max.prob}
  } else if (Comp.obj.isObj(o)) {
    max=-Infinity;
    for(p in o) {
      sum += o[p];
      if (o[p]>max) max=o[p],pos=p;
    }
    res = {value:pos,prob:max}      
  }
  if (!res) return;
  switch (normalize) {
    case 1: res.prob=res.prob/sum; break;
    case 2: res.prob=res.prob*(res.prob/sum); break;
    default: 
  }
  return res;
}
function bestNormalize(o) { return best(o,1) }


function log2(n) {
  return Math.log(n) / Math.log(2);
}

// Select maximal value of an array by values 
// retuned by optional function applied to array values
function max(array,fun) {        
    var res,max,num;
    for(var i in array) {
        if (fun) num=fun(array[i],i); else num=array[i];
        if (max==undefined) { max=num; res=array[i] } 
        else if (num > max) { max=num; res=array[i] }
    }
    return res;
}

/**
 * Finds element with highest occurrence in a list
 * @private
 */
function mostCommon(list) {
  var elementFrequencyMap = {};
  var largestFrequency = -1;
  var mostCommonElement = null;
  list.forEach(function(element) {
    var elementFrequency = (elementFrequencyMap[element] || 0) + 1;
    elementFrequencyMap[element] = elementFrequency;

    if (largestFrequency < elementFrequency) {
      mostCommonElement = element;
      largestFrequency = elementFrequency;
    }
  });

  return mostCommonElement;
}


function pluck(collection, key) {
  return collection.map(function(object) {
    return object == null ? undefined : object[key];
  });
}

function prob(value, list) {
  var occurrences = list.filter(function(element) {
    return element === value
  });

  var numOccurrences = occurrences.length;
  var numElements = list.length;
  return numOccurrences / numElements;
}


function sort(array) {
  return array.sort(function (a,b) { return a<b?-1:1 });
}

function sum (a,b) { return a+b }

function unique(array) {
  var length = array ? array.length : 0;
  function baseUniq(array) {
    var index = -1,
        length = array.length,
        seen,
        result = [];

    seen = result;
    outer:
    while (++index < length) {
      var value = array[index];
      var seenIndex = seen.length;
      while (seenIndex--) {
        if (seen[seenIndex] === value) {
          continue outer;
        }
      }
      result.push(value);
    }
    return result;
  }
  if (!length) {
    return [];
  }
  return baseUniq(array);
}

function without () {
  var array,
      values=[];
  for(var i in arguments) {
    if (i==0) array=arguments[0];
    else values.push(arguments[i]);
  }
  return array.filter(function (e) {
    return values.indexOf(e) == -1;
  });
}


////////////////////////////////////////

// Entropy of data vectors
function entropy(vals) {
  var uniqueVals = unique(vals);
  var probs = uniqueVals.map(function(x) {
    return prob(x, vals)
  });

  var logVals = probs.map(function(p) {
    return -p * log2(p)
  });

  return logVals.reduce(sum,0);
}

function entropyN(dist,N) {
  var p, probs=[];
  for(p in dist) probs.push(dist[p]/N);
  var logVals = probs.map(function(p) {
    return p==0?0:-p * log2(p)
  });
  return logVals.reduce(sum, 0);
  
}

function entropyEps(vals,eps) {
  var uniqueVals = uniqueEps(vals,eps);
  var probs = uniqueVals.map(function(x) {
    return probEps(x, vals, eps)
  });

  var logVals = probs.map(function(p) {
    return -p * log2(p)
  });

  return logVals.reduce(sum, 0);
}

// Entropy of target variable partitioned feature vector
function entropyT(data,featureIndex,targetIndex,targets) {
  var en = 0;
  var col =  pluck(data,featureIndex);
  var uniqueVals = unique(col);
  uniqueVals.forEach(function (v) {
    var frac = targets.map(function () { return 0 }),
        cn=0;
    col.forEach (function (v2,row) {
      if (v2==v) cn++,frac[targets.indexOf(data[row][targetIndex])]++;
    })
    var p = cn/data.length;
    en += (p*entropyN(frac,frac.reduce(sum)))
    // print(frac,p,frac.reduce(sum))
  })
  return en;
}

function entropyTEps(data,feature,target,targets,eps) {
  var en = 0;
  var col =  pluck(data,feature);
  var uniqueVals = uniqueEps(col,eps);
  uniqueVals.forEach(function (v) {
    var frac = targets.map(function () { return 0 }),
        cn=0;
    col.forEach (function (v2,row) {
      if (v2>=v-eps && v2<=v+eps) cn++,frac[targets.indexOf(data[row][target])]++;
    })
    var p = cn/data.length;
    en += (p*entropyN(frac,frac.reduce(sum)))
    // print(frac,p,frac.reduce(sum))
  })
  return en;
}

function features (data,target) {
  var f;
  if (Comp.obj.isObj(data[0])) 
    f=Object.keys(data[0]);
  else if (Comp.obj.isArray(data[0]))
    f=data[0].map(function (x,i) { return String(i) });
  if (f && target) delete f[target];
  return f;
}

function gainEps(data,feature,target,targets,eps) {
  var et = entropy(pluck(data,target));
  return et/entropyTEps(data,feature,target,targets,eps)
}


function maxGainEps(data,features,target,targets,eps) {
  var maxgain=max(features, function(feature,index) {
    var g = gainEps(data,feature,target,targets,selectEps(eps,index));
    return g;
  });
  return maxgain;
}

function partition(data,feature,target,targets) {
  var parts={};
  targets.forEach(function (t) {parts[t]=[]});
  data.forEach(function (row) {
    parts[row[target]].push(row[feature]);
  })
  return parts
}

function partitionEps(data,feature,target,targets,eps) {
  var p,parts={}
  targets.forEach(function (t) {parts[t]={range:[Number.MAX_VALUE,-Number.MAX_VALUE],values:[]}});
  data.forEach(function (row) {
    parts[row[target]].values.push(row[feature]);
    parts[row[target]].range[0]=Math.min(parts[row[target]].range[0],row[feature]);
    parts[row[target]].range[1]=Math.max(parts[row[target]].range[1],row[feature]);
  })
  for(p in parts) {
    parts[p].unique=uniqueEps(parts[p].values,eps)
    parts[p].noise=2*stat.standardDeviation(parts[p].values);
  }
  return parts
}

// Return only eps-not-overlapping parititions - the most significant are selected 
// (with the lowest unique column values) 
function partitionUniqueEps(data,feature,target,targets,eps) {
  var p, q, parts={}
  // 1. Create all partitions 
  targets.forEach(function (t) {parts[t]={range:[Number.MAX_VALUE,-Number.MAX_VALUE],values:[]}});
  data.forEach(function (row) {
    parts[row[target]].values.push(row[feature]);
    parts[row[target]].range[0]=Math.min(parts[row[target]].range[0],row[feature]);
    parts[row[target]].range[1]=Math.max(parts[row[target]].range[1],row[feature]);
  })
  for(p in parts) {
    parts[p].unique=uniqueEps(parts[p].values,eps)
  }
  // 2. Remove overlapping partitions
  for(p in parts) {
    if (!parts[p]) continue;
    for (q in parts) {
      if (!parts[p]) break;
      if (p==q || !parts[q]) continue;
      if ((parts[p].range[0]-eps)<parts[q].range[1] ||
          (parts[p].range[1]+eps)>parts[q].range[0]) {
        // overlapping, select the part with best unique column values
        if ((parts[p].unique.length/parts[p].values.length)<
            (parts[q].unique.length/parts[q].values.length)) {
          //print('delete '+q)
          delete parts[q];
        } else {
          //print('delete '+p)
          delete parts[p];
        }
      }
    }
  }  
  return parts
}

function select (data,what) {
  if (Comp.obj.isArray(what) && what.length==2) {
    var c0=what[0],c1=what[1];
    return data.map(function (row) {
      return row.slice(c0,c1+1);
    })
  } 
}

function selectEps (eps,index) {
  if (typeof eps == 'number') return eps;
  else return eps[index]
}

/** Split a data set by finding the best feature (column) 
 *  based on maximal gain/entropy calculation of columns. 
 *  type eps = number | number []
 */

function splitEps (data,features,target,targets,eps) {
  var bestFeature = maxGainEps(data,features,target,targets,eps);
  var index = features.indexOf(bestFeature);
  eps = selectEps(eps,index);
  var remainingFeatures = without(features, bestFeature);
  var possibleValues = sort(uniqueEps(pluck(data, bestFeature),eps));
  var choices = possibleValues.map( function(v) {
    var dataS = data.filter(function(x) {
      return Math.abs(x[bestFeature] - v) <= eps
    });
    return {
      val:v,
      data:dataS,
    }
  });
  return {
    feature:bestFeature,
    choices:choices,
    possibleValues:possibleValues,
    remainingFeatures:remainingFeatures
  };
}

function uniqueEps(array,eps) {
  var result=[];
  array.forEach(function (x) {
    var found;
    if (!result.length) result.push(x);
    else {
      result.forEach(function (y) {
        if (found) return;
        found = Math.abs(x-y)<=eps;
      }); 
      if (!found) result.push(x);
    }
  });
  return result;
}



module.exports =  {
  analyze : function (data,features,target,eps) {
    var noise=[];
    if (!eps) eps=0;
    var targets = unique(pluck(data,target));
    var parts = {}, partsUnique = {},diversity={}
    features.forEach(function (feature) {
      partsUnique[feature]=partitionUniqueEps(data,feature,target,targets,eps);
      parts[feature]=partitionEps(data,feature,target,targets,eps);
      for(var p in parts[feature]) noise.push(parts[feature][p].noise);
    })
    features.forEach(function (feature) {
      diversity[feature]=Object.keys(partsUnique[feature]).length;
    })
   
    return {
      features:features,
      partitions:parts, // for each data column
      diversity:diversity,
      noise:stat.mean(noise)
    }
  },
  entropy:entropy,
  entropyN:entropyN,
  entropyEps:entropyEps,
  entropyTEps:entropyTEps,
  entropyT:entropyT,
  features:features,
  gainEps:gainEps,
  maxGainEps:maxGainEps,
  mostCommon:mostCommon,
  partition:partition,
  partitionEps:partitionEps,
  partitionUniqueEps:partitionUniqueEps,
  splitEps:splitEps,
  unique:unique,
  uniqueEps:uniqueEps,
  utils : {
    // return column by key of a matrix (array array|record array) 
    best:best,
    bestNormalize:bestNormalize,
    column:pluck,
    log2:log2,
    prob:prob,
    // transform [v][] -> v[]
    relax: function (mat) {
      if (Comp.obj.isMatrix(mat) && mat[0].length==1) return mat.map(function (row) { return row[0]})
      else return mat;
    },
    select:select,
    selectEps:selectEps,
    sort:sort,
    stat:stat,
    without:without,
    // transform v[] -> [v][]
    wrap: function (mat) {
      if (!Comp.obj.isMatrix(mat)) return mat.map(function (v) { return [v]})
      else return mat
    },
  },
};

};
BundleModuleCode['ml/cnn']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $CREATED:     (C) 2006-2019 bLAB by sbosse
 **    $VERSION:     1.1.1
 **
 **    $INFO:
 **
 ** Convolutional neural network ML Algorithm
 **
 ** Incremental learner using ml.update! Initial training data via ml.learn (or empty data set) 
 ** 
 **    $ENDOFINFO
 */
'use strict';
var Io = Require('com/io');
var Comp = Require('com/compat');
var current=none;
var Aios=none;

var convnetjs = Require('ml/convnet')
var that;

that = module.exports =  {
  // typeof options = {x:[][],y:[],width,height,depth,normalize?:[a,b],layers:{}[]..}
  // format x = [ [row1=[col1=[z1,z2,..],col2,..],row2,..] ]
  create : function (options) {
    var net = new convnetjs.Net();
    if (options.layers)
      net.makeLayers(options.layers);
    if (!options.iterations) options.iterations=10;
    if (!options.depth) options.depth=1;
    if (!options.width) options.width=options.x[0].length,options.height=1;
    var trainer = new convnetjs.SGDTrainer(net, options.trainer||
                                          {method: 'adadelta', 
                                          l2_decay: 0.001, 
                                          batch_size: 10});
    // convert matrix (2dim/3dim) to volume elements
    var x = options.x;
    if (options.normalize) {
      var a,b,
          c=options.normalize[0],
          d=options.normalize[1];
      x.forEach(function (row) {
        var min=Math.min.apply(null,row),
            max=Math.max.apply(null,row);
        if (a==undefined) a=min; else a=Math.min(a,min);
        if (b==undefined) b=max; else b=Math.max(b,max);        
      })
      x=x.map(function (row) {
        return row.map(function (col) { return (((col-a)/(b-a))*(d-c))+c })  // scale [0,1] -> [c,d]
      })
    }
    x=x.map(function (row) {
      var vol = new convnetjs.Vol(options.width, options.height, options.depth, 0.0); //input volume (image)
      vol.w = row;
      return vol;
    });
    x.forEach (function (row) {
      //net.forward(row);
    })
    var y = options.y;
    if (!options.targets) {
      options.targets=that.ml.stats.unique(y);
    }
    for(var iters=0;iters<options.iterations;iters++) {
      y.forEach(function (v,i) {
        trainer.train(x[i],options.targets.indexOf(v));
      })
    }
    trainer.options= {width:options.width,height:options.height,depth:options.depth,targets:options.targets};
    return trainer;
  },
  ml:{},
  predict: function (model,sample) {
    var options = model.options;
    var vol = new convnetjs.Vol(options.width, options.height, options.depth, 0.0); //input volume (image)
    vol.w = sample;
    return model.net.forward(vol);
  },
  print: function () {
  },
  update: function (data) {
  },
  current:function (module) { current=module.current; Aios=module;}
};
};
BundleModuleCode['ml/convnet']=function (module,exports){

/*** https://github.com/karpathy/convnetjs ***/

var convnet={REVISION: 'ALPHA'}
module.exports=convnet;
"use strict";

/*** convnet_util ***/
  // Random number utilities
  var return_v = false;
  var v_val = 0.0;
  var gaussRandom = function() {
    if(return_v) { 
      return_v = false;
      return v_val; 
    }
    var u = 2*Math.random()-1;
    var v = 2*Math.random()-1;
    var r = u*u + v*v;
    if(r == 0 || r > 1) return gaussRandom();
    var c = Math.sqrt(-2*Math.log(r)/r);
    v_val = v*c; // cache this
    return_v = true;
    return u*c;
  }
  var randf = function(a, b) { return Math.random()*(b-a)+a; }
  var randi = function(a, b) { return Math.floor(Math.random()*(b-a)+a); }
  var randn = function(mu, std){ return mu+gaussRandom()*std; }

  // Array utilities
  var zeros = function(n) {
    if(typeof(n)==='undefined' || isNaN(n)) { return []; }
    if(typeof ArrayBuffer === 'undefined') {
      // lacking browser support
      var arr = new Array(n);
      for(var i=0;i<n;i++) { arr[i]= 0; }
      return arr;
    } else {
      return new Float64Array(n);
    }
  }

  var arrContains = function(arr, elt) {
    for(var i=0,n=arr.length;i<n;i++) {
      if(arr[i]===elt) return true;
    }
    return false;
  }

  var arrUnique = function(arr) {
    var b = [];
    for(var i=0,n=arr.length;i<n;i++) {
      if(!arrContains(b, arr[i])) {
        b.push(arr[i]);
      }
    }
    return b;
  }

  // return max and min of a given non-empty array.
  var maxmin = function(w) {
    if(w.length === 0) { return {}; } // ... ;s
    var maxv = w[0];
    var minv = w[0];
    var maxi = 0;
    var mini = 0;
    var n = w.length;
    for(var i=1;i<n;i++) {
      if(w[i] > maxv) { maxv = w[i]; maxi = i; } 
      if(w[i] < minv) { minv = w[i]; mini = i; } 
    }
    return {maxi: maxi, maxv: maxv, mini: mini, minv: minv, dv:maxv-minv};
  }

  // create random permutation of numbers, in range [0...n-1]
  var randperm = function(n) {
    var i = n,
        j = 0,
        temp;
    var array = [];
    for(var q=0;q<n;q++)array[q]=q;
    while (i--) {
        j = Math.floor(Math.random() * (i+1));
        temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }
    return array;
  }

  // sample from list lst according to probabilities in list probs
  // the two lists are of same size, and probs adds up to 1
  var weightedSample = function(lst, probs) {
    var p = randf(0, 1.0);
    var cumprob = 0.0;
    for(var k=0,n=lst.length;k<n;k++) {
      cumprob += probs[k];
      if(p < cumprob) { return lst[k]; }
    }
  }

  // syntactic sugar function for getting default parameter values
  var getopt = function(opt, field_name, default_value) {
    if(typeof field_name === 'string') {
      // case of single string
      return (typeof opt[field_name] !== 'undefined') ? opt[field_name] : default_value;
    } else {
      // assume we are given a list of string instead
      var ret = default_value;
      for(var i=0;i<field_name.length;i++) {
        var f = field_name[i];
        if (typeof opt[f] !== 'undefined') {
          ret = opt[f]; // overwrite return value
        }
      }
      return ret;
    }
  }

  function assert(condition, message) {
    if (!condition) {
      message = message || "Assertion failed";
      if (typeof Error !== "undefined") {
        throw new Error(message);
      }
      throw message; // Fallback
    }
  }

  convnet.randf = randf;
  convnet.randi = randi;
  convnet.randn = randn;
  convnet.zeros = zeros;
  convnet.maxmin = maxmin;
  convnet.randperm = randperm;
  convnet.weightedSample = weightedSample;
  convnet.arrUnique = arrUnique;
  convnet.arrContains = arrContains;
  convnet.getopt = getopt;
  convnet.assert = assert;

/*** convnet_vol ***/
 // Vol is the basic building block of all data in a net.
  // it is essentially just a 3D volume of numbers, with a
  // width (sx), height (sy), and depth (depth).
  // it is used to hold data for all filters, all volumes,
  // all weights, and also stores all gradients w.r.t. 
  // the data. c is optionally a value to initialize the volume
  // with. If c is missing, fills the Vol with random numbers.
  var Vol = function(sx, sy, depth, c) {
    // this is how you check if a variable is an array. Oh, Javascript :)
    if(Object.prototype.toString.call(sx) === '[object Array]') {
      // we were given a list in sx, assume 1D volume and fill it up
      this.sx = 1;
      this.sy = 1;
      this.depth = sx.length;
      // we have to do the following copy because we want to use
      // fast typed arrays, not an ordinary javascript array
      this.w = convnet.zeros(this.depth);
      this.dw = convnet.zeros(this.depth);
      for(var i=0;i<this.depth;i++) {
        this.w[i] = sx[i];
      }
    } else {
      // we were given dimensions of the vol
      this.sx = sx;
      this.sy = sy;
      this.depth = depth;
      var n = sx*sy*depth;
      this.w = convnet.zeros(n);
      this.dw = convnet.zeros(n);
      if(typeof c === 'undefined') {
        // weight normalization is done to equalize the output
        // variance of every neuron, otherwise neurons with a lot
        // of incoming connections have outputs of larger variance
        var scale = Math.sqrt(1.0/(sx*sy*depth));
        for(var i=0;i<n;i++) { 
          this.w[i] = convnet.randn(0.0, scale);
        }
      } else {
        for(var i=0;i<n;i++) { 
          this.w[i] = c;
        }
      }
    }
  }

  Vol.prototype = {
    get: function(x, y, d) { 
      var ix=((this.sx * y)+x)*this.depth+d;
      return this.w[ix];
    },
    set: function(x, y, d, v) { 
      var ix=((this.sx * y)+x)*this.depth+d;
      this.w[ix] = v; 
    },
    add: function(x, y, d, v) { 
      var ix=((this.sx * y)+x)*this.depth+d;
      this.w[ix] += v; 
    },
    get_grad: function(x, y, d) { 
      var ix = ((this.sx * y)+x)*this.depth+d;
      return this.dw[ix]; 
    },
    set_grad: function(x, y, d, v) { 
      var ix = ((this.sx * y)+x)*this.depth+d;
      this.dw[ix] = v; 
    },
    add_grad: function(x, y, d, v) { 
      var ix = ((this.sx * y)+x)*this.depth+d;
      this.dw[ix] += v; 
    },
    cloneAndZero: function() { return new Vol(this.sx, this.sy, this.depth, 0.0)},
    clone: function() {
      var V = new Vol(this.sx, this.sy, this.depth, 0.0);
      var n = this.w.length;
      for(var i=0;i<n;i++) { V.w[i] = this.w[i]; }
      return V;
    },
    addFrom: function(V) { for(var k=0;k<this.w.length;k++) { this.w[k] += V.w[k]; }},
    addFromScaled: function(V, a) { for(var k=0;k<this.w.length;k++) { this.w[k] += a*V.w[k]; }},
    setConst: function(a) { for(var k=0;k<this.w.length;k++) { this.w[k] = a; }},

    toJSON: function() {
      // todo: we may want to only save d most significant digits to save space
      var json = {}
      json.sx = this.sx; 
      json.sy = this.sy;
      json.depth = this.depth;
      json.w = this.w;
      return json;
      // we wont back up gradients to save space
    },
    fromJSON: function(json) {
      this.sx = json.sx;
      this.sy = json.sy;
      this.depth = json.depth;

      var n = this.sx*this.sy*this.depth;
      this.w = convnet.zeros(n);
      this.dw = convnet.zeros(n);
      // copy over the elements.
      for(var i=0;i<n;i++) {
        this.w[i] = json.w[i];
      }
    }
  }

  convnet.Vol = Vol;

/*** convnet_vol_util ***/
  var Vol = convnet.Vol; // convenience

  // Volume utilities
  // intended for use with data augmentation
  // crop is the size of output
  // dx,dy are offset wrt incoming volume, of the shift
  // fliplr is boolean on whether we also want to flip left<->right
  var augment = function(V, crop, dx, dy, fliplr) {
    // note assumes square outputs of size crop x crop
    if(typeof(fliplr)==='undefined') var fliplr = false;
    if(typeof(dx)==='undefined') var dx = convnet.randi(0, V.sx - crop);
    if(typeof(dy)==='undefined') var dy = convnet.randi(0, V.sy - crop);
    
    // randomly sample a crop in the input volume
    var W;
    if(crop !== V.sx || dx!==0 || dy!==0) {
      W = new Vol(crop, crop, V.depth, 0.0);
      for(var x=0;x<crop;x++) {
        for(var y=0;y<crop;y++) {
          if(x+dx<0 || x+dx>=V.sx || y+dy<0 || y+dy>=V.sy) continue; // oob
          for(var d=0;d<V.depth;d++) {
           W.set(x,y,d,V.get(x+dx,y+dy,d)); // copy data over
          }
        }
      }
    } else {
      W = V;
    }

    if(fliplr) {
      // flip volume horziontally
      var W2 = W.cloneAndZero();
      for(var x=0;x<W.sx;x++) {
        for(var y=0;y<W.sy;y++) {
          for(var d=0;d<W.depth;d++) {
           W2.set(x,y,d,W.get(W.sx - x - 1,y,d)); // copy data over
          }
        }
      }
      W = W2; //swap
    }
    return W;
  }

  // img is a DOM element that contains a loaded image
  // returns a Vol of size (W, H, 4). 4 is for RGBA
  var img_to_vol = function(img, convert_grayscale) {

    if(typeof(convert_grayscale)==='undefined') var convert_grayscale = false;

    var canvas = document.createElement('canvas');
    canvas.width = img.width;
    canvas.height = img.height;
    var ctx = canvas.getContext("2d");

    // due to a Firefox bug
    try {
      ctx.drawImage(img, 0, 0);
    } catch (e) {
      if (e.name === "NS_ERROR_NOT_AVAILABLE") {
        // sometimes happens, lets just abort
        return false;
      } else {
        throw e;
      }
    }

    try {
      var img_data = ctx.getImageData(0, 0, canvas.width, canvas.height);
    } catch (e) {
      if(e.name === 'IndexSizeError') {
        return false; // not sure what causes this sometimes but okay abort
      } else {
        throw e;
      }
    }

    // prepare the input: get pixels and normalize them
    var p = img_data.data;
    var W = img.width;
    var H = img.height;
    var pv = []
    for(var i=0;i<p.length;i++) {
      pv.push(p[i]/255.0-0.5); // normalize image pixels to [-0.5, 0.5]
    }
    var x = new Vol(W, H, 4, 0.0); //input volume (image)
    x.w = pv;

    if(convert_grayscale) {
      // flatten into depth=1 array
      var x1 = new Vol(W, H, 1, 0.0);
      for(var i=0;i<W;i++) {
        for(var j=0;j<H;j++) {
          x1.set(i,j,0,x.get(i,j,0));
        }
      }
      x = x1;
    }

    return x;
  }
  
  convnet.augment = augment;
  convnet.img_to_vol = img_to_vol;


/*** convnet_layers_dotproducts ***/
  // This file contains all layers that do dot products with input,
  // but usually in a different connectivity pattern and weight sharing
  // schemes: 
  // - FullyConn is fully connected dot products 
  // - ConvLayer does convolutions (so weight sharing spatially)
  // putting them together in one file because they are very similar
  var ConvLayer = function(opt) {
    var opt = opt || {};

    // required
    this.out_depth = opt.filters;
    this.sx = opt.sx; // filter size. Should be odd if possible, it's cleaner.
    this.in_depth = opt.in_depth;
    this.in_sx = opt.in_sx;
    this.in_sy = opt.in_sy;
    
    // optional
    this.sy = typeof opt.sy !== 'undefined' ? opt.sy : this.sx;
    this.stride = typeof opt.stride !== 'undefined' ? opt.stride : 1; // stride at which we apply filters to input volume
    this.pad = typeof opt.pad !== 'undefined' ? opt.pad : 0; // amount of 0 padding to add around borders of input volume
    this.l1_decay_mul = typeof opt.l1_decay_mul !== 'undefined' ? opt.l1_decay_mul : 0.0;
    this.l2_decay_mul = typeof opt.l2_decay_mul !== 'undefined' ? opt.l2_decay_mul : 1.0;

    // computed
    // note we are doing floor, so if the strided convolution of the filter doesnt fit into the input
    // volume exactly, the output volume will be trimmed and not contain the (incomplete) computed
    // final application.
    this.out_sx = Math.floor((this.in_sx + this.pad * 2 - this.sx) / this.stride + 1);
    this.out_sy = Math.floor((this.in_sy + this.pad * 2 - this.sy) / this.stride + 1);
    this.layer_type = 'conv';

    // initializations
    var bias = typeof opt.bias_pref !== 'undefined' ? opt.bias_pref : 0.0;
    this.filters = [];
    for(var i=0;i<this.out_depth;i++) { this.filters.push(new Vol(this.sx, this.sy, this.in_depth)); }
    this.biases = new Vol(1, 1, this.out_depth, bias);
  }
  ConvLayer.prototype = {
    forward: function(V, is_training) {
      // optimized code by @mdda that achieves 2x speedup over previous version

      this.in_act = V;
      var A = new Vol(this.out_sx |0, this.out_sy |0, this.out_depth |0, 0.0);
      
      var V_sx = V.sx |0;
      var V_sy = V.sy |0;
      var xy_stride = this.stride |0;

      for(var d=0;d<this.out_depth;d++) {
        var f = this.filters[d];
        var x = -this.pad |0;
        var y = -this.pad |0;
        for(var ay=0; ay<this.out_sy; y+=xy_stride,ay++) {  // xy_stride
          x = -this.pad |0;
          for(var ax=0; ax<this.out_sx; x+=xy_stride,ax++) {  // xy_stride

            // convolve centered at this particular location
            var a = 0.0;
            for(var fy=0;fy<f.sy;fy++) {
              var oy = y+fy; // coordinates in the original input array coordinates
              for(var fx=0;fx<f.sx;fx++) {
                var ox = x+fx;
                if(oy>=0 && oy<V_sy && ox>=0 && ox<V_sx) {
                  for(var fd=0;fd<f.depth;fd++) {
                    // avoid function call overhead (x2) for efficiency, compromise modularity :(
                    a += f.w[((f.sx * fy)+fx)*f.depth+fd] * V.w[((V_sx * oy)+ox)*V.depth+fd];
                  }
                }
              }
            }
            a += this.biases.w[d];
            A.set(ax, ay, d, a);
          }
        }
      }
      this.out_act = A;
      return this.out_act;
    },
    backward: function() {

      var V = this.in_act;
      V.dw = convnet.zeros(V.w.length); // zero out gradient wrt bottom data, we're about to fill it

      var V_sx = V.sx |0;
      var V_sy = V.sy |0;
      var xy_stride = this.stride |0;

      for(var d=0;d<this.out_depth;d++) {
        var f = this.filters[d];
        var x = -this.pad |0;
        var y = -this.pad |0;
        for(var ay=0; ay<this.out_sy; y+=xy_stride,ay++) {  // xy_stride
          x = -this.pad |0;
          for(var ax=0; ax<this.out_sx; x+=xy_stride,ax++) {  // xy_stride

            // convolve centered at this particular location
            var chain_grad = this.out_act.get_grad(ax,ay,d); // gradient from above, from chain rule
            for(var fy=0;fy<f.sy;fy++) {
              var oy = y+fy; // coordinates in the original input array coordinates
              for(var fx=0;fx<f.sx;fx++) {
                var ox = x+fx;
                if(oy>=0 && oy<V_sy && ox>=0 && ox<V_sx) {
                  for(var fd=0;fd<f.depth;fd++) {
                    // avoid function call overhead (x2) for efficiency, compromise modularity :(
                    var ix1 = ((V_sx * oy)+ox)*V.depth+fd;
                    var ix2 = ((f.sx * fy)+fx)*f.depth+fd;
                    f.dw[ix2] += V.w[ix1]*chain_grad;
                    V.dw[ix1] += f.w[ix2]*chain_grad;
                  }
                }
              }
            }
            this.biases.dw[d] += chain_grad;
          }
        }
      }
    },
    getParamsAndGrads: function() {
      var response = [];
      for(var i=0;i<this.out_depth;i++) {
        response.push({params: this.filters[i].w, grads: this.filters[i].dw, l2_decay_mul: this.l2_decay_mul, l1_decay_mul: this.l1_decay_mul});
      }
      response.push({params: this.biases.w, grads: this.biases.dw, l1_decay_mul: 0.0, l2_decay_mul: 0.0});
      return response;
    },
    toJSON: function() {
      var json = {};
      json.sx = this.sx; // filter size in x, y dims
      json.sy = this.sy;
      json.stride = this.stride;
      json.in_depth = this.in_depth;
      json.out_depth = this.out_depth;
      json.out_sx = this.out_sx;
      json.out_sy = this.out_sy;
      json.layer_type = this.layer_type;
      json.l1_decay_mul = this.l1_decay_mul;
      json.l2_decay_mul = this.l2_decay_mul;
      json.pad = this.pad;
      json.filters = [];
      for(var i=0;i<this.filters.length;i++) {
        json.filters.push(this.filters[i].toJSON());
      }
      json.biases = this.biases.toJSON();
      return json;
    },
    fromJSON: function(json) {
      this.out_depth = json.out_depth;
      this.out_sx = json.out_sx;
      this.out_sy = json.out_sy;
      this.layer_type = json.layer_type;
      this.sx = json.sx; // filter size in x, y dims
      this.sy = json.sy;
      this.stride = json.stride;
      this.in_depth = json.in_depth; // depth of input volume
      this.filters = [];
      this.l1_decay_mul = typeof json.l1_decay_mul !== 'undefined' ? json.l1_decay_mul : 1.0;
      this.l2_decay_mul = typeof json.l2_decay_mul !== 'undefined' ? json.l2_decay_mul : 1.0;
      this.pad = typeof json.pad !== 'undefined' ? json.pad : 0;
      for(var i=0;i<json.filters.length;i++) {
        var v = new Vol(0,0,0,0);
        v.fromJSON(json.filters[i]);
        this.filters.push(v);
      }
      this.biases = new Vol(0,0,0,0);
      this.biases.fromJSON(json.biases);
    }
  }

  var FullyConnLayer = function(opt) {
    var opt = opt || {};

    // required
    // ok fine we will allow 'filters' as the word as well
    this.out_depth = typeof opt.num_neurons !== 'undefined' ? opt.num_neurons : opt.filters;

    // optional 
    this.l1_decay_mul = typeof opt.l1_decay_mul !== 'undefined' ? opt.l1_decay_mul : 0.0;
    this.l2_decay_mul = typeof opt.l2_decay_mul !== 'undefined' ? opt.l2_decay_mul : 1.0;

    // computed
    this.num_inputs = opt.in_sx * opt.in_sy * opt.in_depth;
    this.out_sx = 1;
    this.out_sy = 1;
    this.layer_type = 'fc';

    // initializations
    var bias = typeof opt.bias_pref !== 'undefined' ? opt.bias_pref : 0.0;
    this.filters = [];
    for(var i=0;i<this.out_depth ;i++) { this.filters.push(new Vol(1, 1, this.num_inputs)); }
    this.biases = new Vol(1, 1, this.out_depth, bias);
  }

  FullyConnLayer.prototype = {
    forward: function(V, is_training) {
      this.in_act = V;
      var A = new Vol(1, 1, this.out_depth, 0.0);
      var Vw = V.w;
      for(var i=0;i<this.out_depth;i++) {
        var a = 0.0;
        var wi = this.filters[i].w;
        for(var d=0;d<this.num_inputs;d++) {
          a += Vw[d] * wi[d]; // for efficiency use Vols directly for now
        }
        a += this.biases.w[i];
        A.w[i] = a;
      }
      this.out_act = A;
      return this.out_act;
    },
    backward: function() {
      var V = this.in_act;
      V.dw = convnet.zeros(V.w.length); // zero out the gradient in input Vol
      
      // compute gradient wrt weights and data
      for(var i=0;i<this.out_depth;i++) {
        var tfi = this.filters[i];
        var chain_grad = this.out_act.dw[i];
        for(var d=0;d<this.num_inputs;d++) {
          V.dw[d] += tfi.w[d]*chain_grad; // grad wrt input data
          tfi.dw[d] += V.w[d]*chain_grad; // grad wrt params
        }
        this.biases.dw[i] += chain_grad;
      }
    },
    getParamsAndGrads: function() {
      var response = [];
      for(var i=0;i<this.out_depth;i++) {
        response.push({params: this.filters[i].w, grads: this.filters[i].dw, l1_decay_mul: this.l1_decay_mul, l2_decay_mul: this.l2_decay_mul});
      }
      response.push({params: this.biases.w, grads: this.biases.dw, l1_decay_mul: 0.0, l2_decay_mul: 0.0});
      return response;
    },
    toJSON: function() {
      var json = {};
      json.out_depth = this.out_depth;
      json.out_sx = this.out_sx;
      json.out_sy = this.out_sy;
      json.layer_type = this.layer_type;
      json.num_inputs = this.num_inputs;
      json.l1_decay_mul = this.l1_decay_mul;
      json.l2_decay_mul = this.l2_decay_mul;
      json.filters = [];
      for(var i=0;i<this.filters.length;i++) {
        json.filters.push(this.filters[i].toJSON());
      }
      json.biases = this.biases.toJSON();
      return json;
    },
    fromJSON: function(json) {
      this.out_depth = json.out_depth;
      this.out_sx = json.out_sx;
      this.out_sy = json.out_sy;
      this.layer_type = json.layer_type;
      this.num_inputs = json.num_inputs;
      this.l1_decay_mul = typeof json.l1_decay_mul !== 'undefined' ? json.l1_decay_mul : 1.0;
      this.l2_decay_mul = typeof json.l2_decay_mul !== 'undefined' ? json.l2_decay_mul : 1.0;
      this.filters = [];
      for(var i=0;i<json.filters.length;i++) {
        var v = new Vol(0,0,0,0);
        v.fromJSON(json.filters[i]);
        this.filters.push(v);
      }
      this.biases = new Vol(0,0,0,0);
      this.biases.fromJSON(json.biases);
    }
  }

  convnet.ConvLayer = ConvLayer;
  convnet.FullyConnLayer = FullyConnLayer;


/*** convnet_layers_pool ***/
  var PoolLayer = function(opt) {

    var opt = opt || {};

    // required
    this.sx = opt.sx; // filter size
    this.in_depth = opt.in_depth;
    this.in_sx = opt.in_sx;
    this.in_sy = opt.in_sy;

    // optional
    this.sy = typeof opt.sy !== 'undefined' ? opt.sy : this.sx;
    this.stride = typeof opt.stride !== 'undefined' ? opt.stride : 2;
    this.pad = typeof opt.pad !== 'undefined' ? opt.pad : 0; // amount of 0 padding to add around borders of input volume

    // computed
    this.out_depth = this.in_depth;
    this.out_sx = Math.floor((this.in_sx + this.pad * 2 - this.sx) / this.stride + 1);
    this.out_sy = Math.floor((this.in_sy + this.pad * 2 - this.sy) / this.stride + 1);
    this.layer_type = 'pool';
    // store switches for x,y coordinates for where the max comes from, for each output neuron
    this.switchx = convnet.zeros(this.out_sx*this.out_sy*this.out_depth);
    this.switchy = convnet.zeros(this.out_sx*this.out_sy*this.out_depth);
  }

  PoolLayer.prototype = {
    forward: function(V, is_training) {
      this.in_act = V;

      var A = new Vol(this.out_sx, this.out_sy, this.out_depth, 0.0);
      
      var n=0; // a counter for switches
      for(var d=0;d<this.out_depth;d++) {
        var x = -this.pad;
        var y = -this.pad;
        for(var ax=0; ax<this.out_sx; x+=this.stride,ax++) {
          y = -this.pad;
          for(var ay=0; ay<this.out_sy; y+=this.stride,ay++) {

            // convolve centered at this particular location
            var a = -99999; // hopefully small enough ;\
            var winx=-1,winy=-1;
            for(var fx=0;fx<this.sx;fx++) {
              for(var fy=0;fy<this.sy;fy++) {
                var oy = y+fy;
                var ox = x+fx;
                if(oy>=0 && oy<V.sy && ox>=0 && ox<V.sx) {
                  var v = V.get(ox, oy, d);
                  // perform max pooling and store pointers to where
                  // the max came from. This will speed up backprop 
                  // and can help make nice visualizations in future
                  if(v > a) { a = v; winx=ox; winy=oy;}
                }
              }
            }
            this.switchx[n] = winx;
            this.switchy[n] = winy;
            n++;
            A.set(ax, ay, d, a);
          }
        }
      }
      this.out_act = A;
      return this.out_act;
    },
    backward: function() { 
      // pooling layers have no parameters, so simply compute 
      // gradient wrt data here
      var V = this.in_act;
      V.dw = convnet.zeros(V.w.length); // zero out gradient wrt data
      var A = this.out_act; // computed in forward pass 

      var n = 0;
      for(var d=0;d<this.out_depth;d++) {
        var x = -this.pad;
        var y = -this.pad;
        for(var ax=0; ax<this.out_sx; x+=this.stride,ax++) {
          y = -this.pad;
          for(var ay=0; ay<this.out_sy; y+=this.stride,ay++) {

            var chain_grad = this.out_act.get_grad(ax,ay,d);
            V.add_grad(this.switchx[n], this.switchy[n], d, chain_grad);
            n++;

          }
        }
      }
    },
    getParamsAndGrads: function() {
      return [];
    },
    toJSON: function() {
      var json = {};
      json.sx = this.sx;
      json.sy = this.sy;
      json.stride = this.stride;
      json.in_depth = this.in_depth;
      json.out_depth = this.out_depth;
      json.out_sx = this.out_sx;
      json.out_sy = this.out_sy;
      json.layer_type = this.layer_type;
      json.pad = this.pad;
      return json;
    },
    fromJSON: function(json) {
      this.out_depth = json.out_depth;
      this.out_sx = json.out_sx;
      this.out_sy = json.out_sy;
      this.layer_type = json.layer_type;
      this.sx = json.sx;
      this.sy = json.sy;
      this.stride = json.stride;
      this.in_depth = json.in_depth;
      this.pad = typeof json.pad !== 'undefined' ? json.pad : 0; // backwards compatibility
      this.switchx = convnet.zeros(this.out_sx*this.out_sy*this.out_depth); // need to re-init these appropriately
      this.switchy = convnet.zeros(this.out_sx*this.out_sy*this.out_depth);
    }
  }

  convnet.PoolLayer = PoolLayer;


/*** convnet_layers_input ***/
  var getopt = convnet.getopt;

  var InputLayer = function(opt) {
    var opt = opt || {};

    // required: depth
    this.out_depth = getopt(opt, ['out_depth', 'depth'], 0);

    // optional: default these dimensions to 1
    this.out_sx = getopt(opt, ['out_sx', 'sx', 'width'], 1);
    this.out_sy = getopt(opt, ['out_sy', 'sy', 'height'], 1);
    
    // computed
    this.layer_type = 'input';
  }
  InputLayer.prototype = {
    forward: function(V, is_training) {
      this.in_act = V;
      this.out_act = V;
      return this.out_act; // simply identity function for now
    },
    backward: function() { },
    getParamsAndGrads: function() {
      return [];
    },
    toJSON: function() {
      var json = {};
      json.out_depth = this.out_depth;
      json.out_sx = this.out_sx;
      json.out_sy = this.out_sy;
      json.layer_type = this.layer_type;
      return json;
    },
    fromJSON: function(json) {
      this.out_depth = json.out_depth;
      this.out_sx = json.out_sx;
      this.out_sy = json.out_sy;
      this.layer_type = json.layer_type; 
    }
  }

  convnet.InputLayer = InputLayer;


/*** convnet_layers_loss ***/
  // Layers that implement a loss. Currently these are the layers that 
  // can initiate a backward() pass. In future we probably want a more 
  // flexible system that can accomodate multiple losses to do multi-task
  // learning, and stuff like that. But for now, one of the layers in this
  // file must be the final layer in a Net.

  // This is a classifier, with N discrete classes from 0 to N-1
  // it gets a stream of N incoming numbers and computes the softmax
  // function (exponentiate and normalize to sum to 1 as probabilities should)
  var SoftmaxLayer = function(opt) {
    var opt = opt || {};

    // computed
    this.num_inputs = opt.in_sx * opt.in_sy * opt.in_depth;
    this.out_depth = this.num_inputs;
    this.out_sx = 1;
    this.out_sy = 1;
    this.layer_type = 'softmax';
  }

  SoftmaxLayer.prototype = {
    forward: function(V, is_training) {
      this.in_act = V;

      var A = new Vol(1, 1, this.out_depth, 0.0);

      // compute max activation
      var as = V.w;
      var amax = V.w[0];
      for(var i=1;i<this.out_depth;i++) {
        if(as[i] > amax) amax = as[i];
      }

      // compute exponentials (carefully to not blow up)
      var es = convnet.zeros(this.out_depth);
      var esum = 0.0;
      for(var i=0;i<this.out_depth;i++) {
        var e = Math.exp(as[i] - amax);
        esum += e;
        es[i] = e;
      }

      // normalize and output to sum to one
      for(var i=0;i<this.out_depth;i++) {
        es[i] /= esum;
        A.w[i] = es[i];
      }

      this.es = es; // save these for backprop
      this.out_act = A;
      return this.out_act;
    },
    backward: function(y) {

      // compute and accumulate gradient wrt weights and bias of this layer
      var x = this.in_act;
      x.dw = convnet.zeros(x.w.length); // zero out the gradient of input Vol

      for(var i=0;i<this.out_depth;i++) {
        var indicator = i === y ? 1.0 : 0.0;
        var mul = -(indicator - this.es[i]);
        x.dw[i] = mul;
      }

      // loss is the class negative log likelihood
      return -Math.log(this.es[y]);
    },
    getParamsAndGrads: function() { 
      return [];
    },
    toJSON: function() {
      var json = {};
      json.out_depth = this.out_depth;
      json.out_sx = this.out_sx;
      json.out_sy = this.out_sy;
      json.layer_type = this.layer_type;
      json.num_inputs = this.num_inputs;
      return json;
    },
    fromJSON: function(json) {
      this.out_depth = json.out_depth;
      this.out_sx = json.out_sx;
      this.out_sy = json.out_sy;
      this.layer_type = json.layer_type;
      this.num_inputs = json.num_inputs;
    }
  }

  // implements an L2 regression cost layer,
  // so penalizes \sum_i(||x_i - y_i||^2), where x is its input
  // and y is the user-provided array of "correct" values.
  var RegressionLayer = function(opt) {
    var opt = opt || {};

    // computed
    this.num_inputs = opt.in_sx * opt.in_sy * opt.in_depth;
    this.out_depth = this.num_inputs;
    this.out_sx = 1;
    this.out_sy = 1;
    this.layer_type = 'regression';
  }

  RegressionLayer.prototype = {
    forward: function(V, is_training) {
      this.in_act = V;
      this.out_act = V;
      return V; // identity function
    },
    // y is a list here of size num_inputs
    // or it can be a number if only one value is regressed
    // or it can be a struct {dim: i, val: x} where we only want to 
    // regress on dimension i and asking it to have value x
    backward: function(y) { 

      // compute and accumulate gradient wrt weights and bias of this layer
      var x = this.in_act;
      x.dw = convnet.zeros(x.w.length); // zero out the gradient of input Vol
      var loss = 0.0;
      if(y instanceof Array || y instanceof Float64Array) {
        for(var i=0;i<this.out_depth;i++) {
          var dy = x.w[i] - y[i];
          x.dw[i] = dy;
          loss += 0.5*dy*dy;
        }
      } else if(typeof y === 'number') {
        // lets hope that only one number is being regressed
        var dy = x.w[0] - y;
        x.dw[0] = dy;
        loss += 0.5*dy*dy;
      } else {
        // assume it is a struct with entries .dim and .val
        // and we pass gradient only along dimension dim to be equal to val
        var i = y.dim;
        var yi = y.val;
        var dy = x.w[i] - yi;
        x.dw[i] = dy;
        loss += 0.5*dy*dy;
      }
      return loss;
    },
    getParamsAndGrads: function() { 
      return [];
    },
    toJSON: function() {
      var json = {};
      json.out_depth = this.out_depth;
      json.out_sx = this.out_sx;
      json.out_sy = this.out_sy;
      json.layer_type = this.layer_type;
      json.num_inputs = this.num_inputs;
      return json;
    },
    fromJSON: function(json) {
      this.out_depth = json.out_depth;
      this.out_sx = json.out_sx;
      this.out_sy = json.out_sy;
      this.layer_type = json.layer_type;
      this.num_inputs = json.num_inputs;
    }
  }

  var SVMLayer = function(opt) {
    var opt = opt || {};

    // computed
    this.num_inputs = opt.in_sx * opt.in_sy * opt.in_depth;
    this.out_depth = this.num_inputs;
    this.out_sx = 1;
    this.out_sy = 1;
    this.layer_type = 'svm';
  }

  SVMLayer.prototype = {
    forward: function(V, is_training) {
      this.in_act = V;
      this.out_act = V; // nothing to do, output raw scores
      return V;
    },
    backward: function(y) {

      // compute and accumulate gradient wrt weights and bias of this layer
      var x = this.in_act;
      x.dw = convnet.zeros(x.w.length); // zero out the gradient of input Vol

      // we're using structured loss here, which means that the score
      // of the ground truth should be higher than the score of any other 
      // class, by a margin
      var yscore = x.w[y]; // score of ground truth
      var margin = 1.0;
      var loss = 0.0;
      for(var i=0;i<this.out_depth;i++) {
        if(y === i) { continue; }
        var ydiff = -yscore + x.w[i] + margin;
        if(ydiff > 0) {
          // violating dimension, apply loss
          x.dw[i] += 1;
          x.dw[y] -= 1;
          loss += ydiff;
        }
      }

      return loss;
    },
    getParamsAndGrads: function() { 
      return [];
    },
    toJSON: function() {
      var json = {};
      json.out_depth = this.out_depth;
      json.out_sx = this.out_sx;
      json.out_sy = this.out_sy;
      json.layer_type = this.layer_type;
      json.num_inputs = this.num_inputs;
      return json;
    },
    fromJSON: function(json) {
      this.out_depth = json.out_depth;
      this.out_sx = json.out_sx;
      this.out_sy = json.out_sy;
      this.layer_type = json.layer_type;
      this.num_inputs = json.num_inputs;
    }
  }
  
  convnet.RegressionLayer = RegressionLayer;
  convnet.SoftmaxLayer = SoftmaxLayer;
  convnet.SVMLayer = SVMLayer;


/*** convnet_layers_nonlinearities ***/
  // Implements ReLU nonlinearity elementwise
  // x -> max(0, x)
  // the output is in [0, inf)
  var ReluLayer = function(opt) {
    var opt = opt || {};

    // computed
    this.out_sx = opt.in_sx;
    this.out_sy = opt.in_sy;
    this.out_depth = opt.in_depth;
    this.layer_type = 'relu';
  }
  ReluLayer.prototype = {
    forward: function(V, is_training) {
      this.in_act = V;
      var V2 = V.clone();
      var N = V.w.length;
      var V2w = V2.w;
      for(var i=0;i<N;i++) { 
        if(V2w[i] < 0) V2w[i] = 0; // threshold at 0
      }
      this.out_act = V2;
      return this.out_act;
    },
    backward: function() {
      var V = this.in_act; // we need to set dw of this
      var V2 = this.out_act;
      var N = V.w.length;
      V.dw = convnet.zeros(N); // zero out gradient wrt data
      for(var i=0;i<N;i++) {
        if(V2.w[i] <= 0) V.dw[i] = 0; // threshold
        else V.dw[i] = V2.dw[i];
      }
    },
    getParamsAndGrads: function() {
      return [];
    },
    toJSON: function() {
      var json = {};
      json.out_depth = this.out_depth;
      json.out_sx = this.out_sx;
      json.out_sy = this.out_sy;
      json.layer_type = this.layer_type;
      return json;
    },
    fromJSON: function(json) {
      this.out_depth = json.out_depth;
      this.out_sx = json.out_sx;
      this.out_sy = json.out_sy;
      this.layer_type = json.layer_type; 
    }
  }

  // Implements Sigmoid nnonlinearity elementwise
  // x -> 1/(1+e^(-x))
  // so the output is between 0 and 1.
  var SigmoidLayer = function(opt) {
    var opt = opt || {};

    // computed
    this.out_sx = opt.in_sx;
    this.out_sy = opt.in_sy;
    this.out_depth = opt.in_depth;
    this.layer_type = 'sigmoid';
  }
  SigmoidLayer.prototype = {
    forward: function(V, is_training) {
      this.in_act = V;
      var V2 = V.cloneAndZero();
      var N = V.w.length;
      var V2w = V2.w;
      var Vw = V.w;
      for(var i=0;i<N;i++) { 
        V2w[i] = 1.0/(1.0+Math.exp(-Vw[i]));
      }
      this.out_act = V2;
      return this.out_act;
    },
    backward: function() {
      var V = this.in_act; // we need to set dw of this
      var V2 = this.out_act;
      var N = V.w.length;
      V.dw = convnet.zeros(N); // zero out gradient wrt data
      for(var i=0;i<N;i++) {
        var v2wi = V2.w[i];
        V.dw[i] =  v2wi * (1.0 - v2wi) * V2.dw[i];
      }
    },
    getParamsAndGrads: function() {
      return [];
    },
    toJSON: function() {
      var json = {};
      json.out_depth = this.out_depth;
      json.out_sx = this.out_sx;
      json.out_sy = this.out_sy;
      json.layer_type = this.layer_type;
      return json;
    },
    fromJSON: function(json) {
      this.out_depth = json.out_depth;
      this.out_sx = json.out_sx;
      this.out_sy = json.out_sy;
      this.layer_type = json.layer_type; 
    }
  }

  // Implements Maxout nnonlinearity that computes
  // x -> max(x)
  // where x is a vector of size group_size. Ideally of course,
  // the input size should be exactly divisible by group_size
  var MaxoutLayer = function(opt) {
    var opt = opt || {};

    // required
    this.group_size = typeof opt.group_size !== 'undefined' ? opt.group_size : 2;

    // computed
    this.out_sx = opt.in_sx;
    this.out_sy = opt.in_sy;
    this.out_depth = Math.floor(opt.in_depth / this.group_size);
    this.layer_type = 'maxout';

    this.switches = convnet.zeros(this.out_sx*this.out_sy*this.out_depth); // useful for backprop
  }
  MaxoutLayer.prototype = {
    forward: function(V, is_training) {
      this.in_act = V;
      var N = this.out_depth; 
      var V2 = new Vol(this.out_sx, this.out_sy, this.out_depth, 0.0);

      // optimization branch. If we're operating on 1D arrays we dont have
      // to worry about keeping track of x,y,d coordinates inside
      // input volumes. In convnets we do :(
      if(this.out_sx === 1 && this.out_sy === 1) {
        for(var i=0;i<N;i++) {
          var ix = i * this.group_size; // base index offset
          var a = V.w[ix];
          var ai = 0;
          for(var j=1;j<this.group_size;j++) {
            var a2 = V.w[ix+j];
            if(a2 > a) {
              a = a2;
              ai = j;
            }
          }
          V2.w[i] = a;
          this.switches[i] = ix + ai;
        }
      } else {
        var n=0; // counter for switches
        for(var x=0;x<V.sx;x++) {
          for(var y=0;y<V.sy;y++) {
            for(var i=0;i<N;i++) {
              var ix = i * this.group_size;
              var a = V.get(x, y, ix);
              var ai = 0;
              for(var j=1;j<this.group_size;j++) {
                var a2 = V.get(x, y, ix+j);
                if(a2 > a) {
                  a = a2;
                  ai = j;
                }
              }
              V2.set(x,y,i,a);
              this.switches[n] = ix + ai;
              n++;
            }
          }
        }

      }
      this.out_act = V2;
      return this.out_act;
    },
    backward: function() {
      var V = this.in_act; // we need to set dw of this
      var V2 = this.out_act;
      var N = this.out_depth;
      V.dw = convnet.zeros(V.w.length); // zero out gradient wrt data

      // pass the gradient through the appropriate switch
      if(this.out_sx === 1 && this.out_sy === 1) {
        for(var i=0;i<N;i++) {
          var chain_grad = V2.dw[i];
          V.dw[this.switches[i]] = chain_grad;
        }
      } else {
        // bleh okay, lets do this the hard way
        var n=0; // counter for switches
        for(var x=0;x<V2.sx;x++) {
          for(var y=0;y<V2.sy;y++) {
            for(var i=0;i<N;i++) {
              var chain_grad = V2.get_grad(x,y,i);
              V.set_grad(x,y,this.switches[n],chain_grad);
              n++;
            }
          }
        }
      }
    },
    getParamsAndGrads: function() {
      return [];
    },
    toJSON: function() {
      var json = {};
      json.out_depth = this.out_depth;
      json.out_sx = this.out_sx;
      json.out_sy = this.out_sy;
      json.layer_type = this.layer_type;
      json.group_size = this.group_size;
      return json;
    },
    fromJSON: function(json) {
      this.out_depth = json.out_depth;
      this.out_sx = json.out_sx;
      this.out_sy = json.out_sy;
      this.layer_type = json.layer_type; 
      this.group_size = json.group_size;
      this.switches = convnet.zeros(this.group_size);
    }
  }

  // a helper function, since tanh is not yet part of ECMAScript. Will be in v6.
  function tanh(x) {
    var y = Math.exp(2 * x);
    return (y - 1) / (y + 1);
  }
  // Implements Tanh nnonlinearity elementwise
  // x -> tanh(x) 
  // so the output is between -1 and 1.
  var TanhLayer = function(opt) {
    var opt = opt || {};

    // computed
    this.out_sx = opt.in_sx;
    this.out_sy = opt.in_sy;
    this.out_depth = opt.in_depth;
    this.layer_type = 'tanh';
  }
  TanhLayer.prototype = {
    forward: function(V, is_training) {
      this.in_act = V;
      var V2 = V.cloneAndZero();
      var N = V.w.length;
      for(var i=0;i<N;i++) { 
        V2.w[i] = tanh(V.w[i]);
      }
      this.out_act = V2;
      return this.out_act;
    },
    backward: function() {
      var V = this.in_act; // we need to set dw of this
      var V2 = this.out_act;
      var N = V.w.length;
      V.dw = convnet.zeros(N); // zero out gradient wrt data
      for(var i=0;i<N;i++) {
        var v2wi = V2.w[i];
        V.dw[i] = (1.0 - v2wi * v2wi) * V2.dw[i];
      }
    },
    getParamsAndGrads: function() {
      return [];
    },
    toJSON: function() {
      var json = {};
      json.out_depth = this.out_depth;
      json.out_sx = this.out_sx;
      json.out_sy = this.out_sy;
      json.layer_type = this.layer_type;
      return json;
    },
    fromJSON: function(json) {
      this.out_depth = json.out_depth;
      this.out_sx = json.out_sx;
      this.out_sy = json.out_sy;
      this.layer_type = json.layer_type; 
    }
  }
  
  convnet.TanhLayer = TanhLayer;
  convnet.MaxoutLayer = MaxoutLayer;
  convnet.ReluLayer = ReluLayer;
  convnet.SigmoidLayer = SigmoidLayer;




/*** convnet_layers_dropout ***/
  // An inefficient dropout layer
  // Note this is not most efficient implementation since the layer before
  // computed all these activations and now we're just going to drop them :(
  // same goes for backward pass. Also, if we wanted to be efficient at test time
  // we could equivalently be clever and upscale during train and copy pointers during test
  // todo: make more efficient.
  var DropoutLayer = function(opt) {
    var opt = opt || {};

    // computed
    this.out_sx = opt.in_sx;
    this.out_sy = opt.in_sy;
    this.out_depth = opt.in_depth;
    this.layer_type = 'dropout';
    this.drop_prob = typeof opt.drop_prob !== 'undefined' ? opt.drop_prob : 0.5;
    this.dropped = convnet.zeros(this.out_sx*this.out_sy*this.out_depth);
  }
  DropoutLayer.prototype = {
    forward: function(V, is_training) {
      this.in_act = V;
      if(typeof(is_training)==='undefined') { is_training = false; } // default is prediction mode
      var V2 = V.clone();
      var N = V.w.length;
      if(is_training) {
        // do dropout
        for(var i=0;i<N;i++) {
          if(Math.random()<this.drop_prob) { V2.w[i]=0; this.dropped[i] = true; } // drop!
          else {this.dropped[i] = false;}
        }
      } else {
        // scale the activations during prediction
        for(var i=0;i<N;i++) { V2.w[i]*=this.drop_prob; }
      }
      this.out_act = V2;
      return this.out_act; // dummy identity function for now
    },
    backward: function() {
      var V = this.in_act; // we need to set dw of this
      var chain_grad = this.out_act;
      var N = V.w.length;
      V.dw = convnet.zeros(N); // zero out gradient wrt data
      for(var i=0;i<N;i++) {
        if(!(this.dropped[i])) { 
          V.dw[i] = chain_grad.dw[i]; // copy over the gradient
        }
      }
    },
    getParamsAndGrads: function() {
      return [];
    },
    toJSON: function() {
      var json = {};
      json.out_depth = this.out_depth;
      json.out_sx = this.out_sx;
      json.out_sy = this.out_sy;
      json.layer_type = this.layer_type;
      json.drop_prob = this.drop_prob;
      return json;
    },
    fromJSON: function(json) {
      this.out_depth = json.out_depth;
      this.out_sx = json.out_sx;
      this.out_sy = json.out_sy;
      this.layer_type = json.layer_type; 
      this.drop_prob = json.drop_prob;
    }
  }
  
  convnet.DropoutLayer = DropoutLayer;

/*** convnet_layers_normailzation ***/
  // a bit experimental layer for now. I think it works but I'm not 100%
  // the gradient check is a bit funky. I'll look into this a bit later.
  // Local Response Normalization in window, along depths of volumes
  var LocalResponseNormalizationLayer = function(opt) {
    var opt = opt || {};

    // required
    this.k = opt.k;
    this.n = opt.n;
    this.alpha = opt.alpha;
    this.beta = opt.beta;

    // computed
    this.out_sx = opt.in_sx;
    this.out_sy = opt.in_sy;
    this.out_depth = opt.in_depth;
    this.layer_type = 'lrn';

    // checks
    if(this.n%2 === 0) { console.log('WARNING n should be odd for LRN layer'); }
  }
  LocalResponseNormalizationLayer.prototype = {
    forward: function(V, is_training) {
      this.in_act = V;

      var A = V.cloneAndZero();
      this.S_cache_ = V.cloneAndZero();
      var n2 = Math.floor(this.n/2);
      for(var x=0;x<V.sx;x++) {
        for(var y=0;y<V.sy;y++) {
          for(var i=0;i<V.depth;i++) {

            var ai = V.get(x,y,i);

            // normalize in a window of size n
            var den = 0.0;
            for(var j=Math.max(0,i-n2);j<=Math.min(i+n2,V.depth-1);j++) {
              var aa = V.get(x,y,j);
              den += aa*aa;
            }
            den *= this.alpha / this.n;
            den += this.k;
            this.S_cache_.set(x,y,i,den); // will be useful for backprop
            den = Math.pow(den, this.beta);
            A.set(x,y,i,ai/den);
          }
        }
      }

      this.out_act = A;
      return this.out_act; // dummy identity function for now
    },
    backward: function() { 
      // evaluate gradient wrt data
      var V = this.in_act; // we need to set dw of this
      V.dw = convnet.zeros(V.w.length); // zero out gradient wrt data
      var A = this.out_act; // computed in forward pass 

      var n2 = Math.floor(this.n/2);
      for(var x=0;x<V.sx;x++) {
        for(var y=0;y<V.sy;y++) {
          for(var i=0;i<V.depth;i++) {

            var chain_grad = this.out_act.get_grad(x,y,i);
            var S = this.S_cache_.get(x,y,i);
            var SB = Math.pow(S, this.beta);
            var SB2 = SB*SB;

            // normalize in a window of size n
            for(var j=Math.max(0,i-n2);j<=Math.min(i+n2,V.depth-1);j++) {              
              var aj = V.get(x,y,j); 
              var g = -aj*this.beta*Math.pow(S,this.beta-1)*this.alpha/this.n*2*aj;
              if(j===i) g+= SB;
              g /= SB2;
              g *= chain_grad;
              V.add_grad(x,y,j,g);
            }

          }
        }
      }
    },
    getParamsAndGrads: function() { return []; },
    toJSON: function() {
      var json = {};
      json.k = this.k;
      json.n = this.n;
      json.alpha = this.alpha; // normalize by size
      json.beta = this.beta;
      json.out_sx = this.out_sx; 
      json.out_sy = this.out_sy;
      json.out_depth = this.out_depth;
      json.layer_type = this.layer_type;
      return json;
    },
    fromJSON: function(json) {
      this.k = json.k;
      this.n = json.n;
      this.alpha = json.alpha; // normalize by size
      this.beta = json.beta;
      this.out_sx = json.out_sx; 
      this.out_sy = json.out_sy;
      this.out_depth = json.out_depth;
      this.layer_type = json.layer_type;
    }
  }
  
  convnet.LocalResponseNormalizationLayer = LocalResponseNormalizationLayer;



/*** convnet_net ***/
  var assert = convnet.assert;

  // Net manages a set of layers
  // For now constraints: Simple linear order of layers, first layer input last layer a cost layer
  var Net = function(options) {
    this.layers = [];
  }

  Net.prototype = {
    
    // takes a list of layer definitions and creates the network layer objects
    makeLayers: function(defs) {

      // few checks
      assert(defs.length >= 2, 'Error! At least one input layer and one loss layer are required.');
      assert(defs[0].type === 'input', 'Error! First layer must be the input layer, to declare size of inputs');

      // desugar layer_defs for adding activation, dropout layers etc
      var desugar = function() {
        var new_defs = [];
        for(var i=0;i<defs.length;i++) {
          var def = defs[i];
          
          if(def.type==='softmax' || def.type==='svm') {
            // add an fc layer here, there is no reason the user should
            // have to worry about this and we almost always want to
            new_defs.push({type:'fc', num_neurons: def.num_classes});
          }

          if(def.type==='regression') {
            // add an fc layer here, there is no reason the user should
            // have to worry about this and we almost always want to
            new_defs.push({type:'fc', num_neurons: def.num_neurons});
          }

          if((def.type==='fc' || def.type==='conv') 
              && typeof(def.bias_pref) === 'undefined'){
            def.bias_pref = 0.0;
            if(typeof def.activation !== 'undefined' && def.activation === 'relu') {
              def.bias_pref = 0.1; // relus like a bit of positive bias to get gradients early
              // otherwise it's technically possible that a relu unit will never turn on (by chance)
              // and will never get any gradient and never contribute any computation. Dead relu.
            }
          }

          new_defs.push(def);

          if(typeof def.activation !== 'undefined') {
            if(def.activation==='relu') { new_defs.push({type:'relu'}); }
            else if (def.activation==='sigmoid') { new_defs.push({type:'sigmoid'}); }
            else if (def.activation==='tanh') { new_defs.push({type:'tanh'}); }
            else if (def.activation==='maxout') {
              // create maxout activation, and pass along group size, if provided
              var gs = def.group_size !== 'undefined' ? def.group_size : 2;
              new_defs.push({type:'maxout', group_size:gs});
            }
            else { console.log('ERROR unsupported activation ' + def.activation); }
          }
          if(typeof def.drop_prob !== 'undefined' && def.type !== 'dropout') {
            new_defs.push({type:'dropout', drop_prob: def.drop_prob});
          }

        }
        return new_defs;
      }
      defs = desugar(defs);

      // create the layers
      this.layers = [];
      for(var i=0;i<defs.length;i++) {
        var def = defs[i];
        if(i>0) {
          var prev = this.layers[i-1];
          def.in_sx = prev.out_sx;
          def.in_sy = prev.out_sy;
          def.in_depth = prev.out_depth;
        }

        switch(def.type) {
          case 'fc': this.layers.push(new convnet.FullyConnLayer(def)); break;
          case 'lrn': this.layers.push(new convnet.LocalResponseNormalizationLayer(def)); break;
          case 'dropout': this.layers.push(new convnet.DropoutLayer(def)); break;
          case 'input': this.layers.push(new convnet.InputLayer(def)); break;
          case 'softmax': this.layers.push(new convnet.SoftmaxLayer(def)); break;
          case 'regression': this.layers.push(new convnet.RegressionLayer(def)); break;
          case 'conv': this.layers.push(new convnet.ConvLayer(def)); break;
          case 'pool': this.layers.push(new convnet.PoolLayer(def)); break;
          case 'relu': this.layers.push(new convnet.ReluLayer(def)); break;
          case 'sigmoid': this.layers.push(new convnet.SigmoidLayer(def)); break;
          case 'tanh': this.layers.push(new convnet.TanhLayer(def)); break;
          case 'maxout': this.layers.push(new convnet.MaxoutLayer(def)); break;
          case 'svm': this.layers.push(new convnet.SVMLayer(def)); break;
          default: console.log('ERROR: UNRECOGNIZED LAYER TYPE: ' + def.type);
        }
      }
    },

    // forward prop the network. 
    // The trainer class passes is_training = true, but when this function is
    // called from outside (not from the trainer), it defaults to prediction mode
    forward: function(V, is_training) {
      if(typeof(is_training) === 'undefined') is_training = false;
      var act = this.layers[0].forward(V, is_training);
      for(var i=1;i<this.layers.length;i++) {
        act = this.layers[i].forward(act, is_training);
      }
      return act;
    },

    getCostLoss: function(V, y) {
      this.forward(V, false);
      var N = this.layers.length;
      var loss = this.layers[N-1].backward(y);
      return loss;
    },
    
    // backprop: compute gradients wrt all parameters
    backward: function(y) {
      var N = this.layers.length;
      var loss = this.layers[N-1].backward(y); // last layer assumed to be loss layer
      for(var i=N-2;i>=0;i--) { // first layer assumed input
        this.layers[i].backward();
      }
      return loss;
    },
    getParamsAndGrads: function() {
      // accumulate parameters and gradients for the entire network
      var response = [];
      for(var i=0;i<this.layers.length;i++) {
        var layer_reponse = this.layers[i].getParamsAndGrads();
        for(var j=0;j<layer_reponse.length;j++) {
          response.push(layer_reponse[j]);
        }
      }
      return response;
    },
    getPrediction: function() {
      // this is a convenience function for returning the argmax
      // prediction, assuming the last layer of the net is a softmax
      var S = this.layers[this.layers.length-1];
      assert(S.layer_type === 'softmax', 'getPrediction function assumes softmax as last layer of the net!');

      var p = S.out_act.w;
      var maxv = p[0];
      var maxi = 0;
      for(var i=1;i<p.length;i++) {
        if(p[i] > maxv) { maxv = p[i]; maxi = i;}
      }
      return maxi; // return index of the class with highest class probability
    },
    toJSON: function() {
      var json = {};
      json.layers = [];
      for(var i=0;i<this.layers.length;i++) {
        json.layers.push(this.layers[i].toJSON());
      }
      return json;
    },
    fromJSON: function(json) {
      this.layers = [];
      for(var i=0;i<json.layers.length;i++) {
        var Lj = json.layers[i]
        var t = Lj.layer_type;
        var L;
        if(t==='input') { L = new convnet.InputLayer(); }
        if(t==='relu') { L = new convnet.ReluLayer(); }
        if(t==='sigmoid') { L = new convnet.SigmoidLayer(); }
        if(t==='tanh') { L = new convnet.TanhLayer(); }
        if(t==='dropout') { L = new convnet.DropoutLayer(); }
        if(t==='conv') { L = new convnet.ConvLayer(); }
        if(t==='pool') { L = new convnet.PoolLayer(); }
        if(t==='lrn') { L = new convnet.LocalResponseNormalizationLayer(); }
        if(t==='softmax') { L = new convnet.SoftmaxLayer(); }
        if(t==='regression') { L = new convnet.RegressionLayer(); }
        if(t==='fc') { L = new convnet.FullyConnLayer(); }
        if(t==='maxout') { L = new convnet.MaxoutLayer(); }
        if(t==='svm') { L = new convnet.SVMLayer(); }
        L.fromJSON(Lj);
        this.layers.push(L);
      }
    }
  }
  
  convnet.Net = Net;


/*** convnet_trainers ***/
  var Trainer = function(net, options) {

    this.net = net;

    var options = options || {};
    this.learning_rate = typeof options.learning_rate !== 'undefined' ? options.learning_rate : 0.01;
    this.l1_decay = typeof options.l1_decay !== 'undefined' ? options.l1_decay : 0.0;
    this.l2_decay = typeof options.l2_decay !== 'undefined' ? options.l2_decay : 0.0;
    this.batch_size = typeof options.batch_size !== 'undefined' ? options.batch_size : 1;
    this.method = typeof options.method !== 'undefined' ? options.method : 'sgd'; // sgd/adam/adagrad/adadelta/windowgrad/netsterov

    this.momentum = typeof options.momentum !== 'undefined' ? options.momentum : 0.9;
    this.ro = typeof options.ro !== 'undefined' ? options.ro : 0.95; // used in adadelta
    this.eps = typeof options.eps !== 'undefined' ? options.eps : 1e-8; // used in adam or adadelta
    this.beta1 = typeof options.beta1 !== 'undefined' ? options.beta1 : 0.9; // used in adam
    this.beta2 = typeof options.beta2 !== 'undefined' ? options.beta2 : 0.999; // used in adam

    this.k = 0; // iteration counter
    this.gsum = []; // last iteration gradients (used for momentum calculations)
    this.xsum = []; // used in adam or adadelta

    // check if regression is expected 
    if(this.net.layers[this.net.layers.length - 1].layer_type === "regression")
      this.regression = true;
    else
      this.regression = false;
  }

  Trainer.prototype = {
    train: function(x, y) {

      var start = new Date().getTime();
      this.net.forward(x, true); // also set the flag that lets the net know we're just training
      var end = new Date().getTime();
      var fwd_time = end - start;

      var start = new Date().getTime();
      var cost_loss = this.net.backward(y);
      var l2_decay_loss = 0.0;
      var l1_decay_loss = 0.0;
      var end = new Date().getTime();
      var bwd_time = end - start;

      if(this.regression && y.constructor !== Array)
        console.log("Warning: a regression net requires an array as training output vector.");
      
      this.k++;
      if(this.k % this.batch_size === 0) {

        var pglist = this.net.getParamsAndGrads();

        // initialize lists for accumulators. Will only be done once on first iteration
        if(this.gsum.length === 0 && (this.method !== 'sgd' || this.momentum > 0.0)) {
          // only vanilla sgd doesnt need either lists
          // momentum needs gsum
          // adagrad needs gsum
          // adam and adadelta needs gsum and xsum
          for(var i=0;i<pglist.length;i++) {
            this.gsum.push(convnet.zeros(pglist[i].params.length));
            if(this.method === 'adam' || this.method === 'adadelta') {
              this.xsum.push(convnet.zeros(pglist[i].params.length));
            } else {
              this.xsum.push([]); // conserve memory
            }
          }
        }

        // perform an update for all sets of weights
        for(var i=0;i<pglist.length;i++) {
          var pg = pglist[i]; // param, gradient, other options in future (custom learning rate etc)
          var p = pg.params;
          var g = pg.grads;

          // learning rate for some parameters.
          var l2_decay_mul = typeof pg.l2_decay_mul !== 'undefined' ? pg.l2_decay_mul : 1.0;
          var l1_decay_mul = typeof pg.l1_decay_mul !== 'undefined' ? pg.l1_decay_mul : 1.0;
          var l2_decay = this.l2_decay * l2_decay_mul;
          var l1_decay = this.l1_decay * l1_decay_mul;

          var plen = p.length;
          for(var j=0;j<plen;j++) {
            l2_decay_loss += l2_decay*p[j]*p[j]/2; // accumulate weight decay loss
            l1_decay_loss += l1_decay*Math.abs(p[j]);
            var l1grad = l1_decay * (p[j] > 0 ? 1 : -1);
            var l2grad = l2_decay * (p[j]);

            var gij = (l2grad + l1grad + g[j]) / this.batch_size; // raw batch gradient

            var gsumi = this.gsum[i];
            var xsumi = this.xsum[i];
            if(this.method === 'adam') {
              // adam update
              gsumi[j] = gsumi[j] * this.beta1 + (1- this.beta1) * gij; // update biased first moment estimate
              xsumi[j] = xsumi[j] * this.beta2 + (1-this.beta2) * gij * gij; // update biased second moment estimate
              var biasCorr1 = gsumi[j] * (1 - Math.pow(this.beta1, this.k)); // correct bias first moment estimate
              var biasCorr2 = xsumi[j] * (1 - Math.pow(this.beta2, this.k)); // correct bias second moment estimate
              var dx =  - this.learning_rate * biasCorr1 / (Math.sqrt(biasCorr2) + this.eps);
              p[j] += dx;
            } else if(this.method === 'adagrad') {
              // adagrad update
              gsumi[j] = gsumi[j] + gij * gij;
              var dx = - this.learning_rate / Math.sqrt(gsumi[j] + this.eps) * gij;
              p[j] += dx;
            } else if(this.method === 'windowgrad') {
              // this is adagrad but with a moving window weighted average
              // so the gradient is not accumulated over the entire history of the run. 
              // it's also referred to as Idea #1 in Zeiler paper on Adadelta. Seems reasonable to me!
              gsumi[j] = this.ro * gsumi[j] + (1-this.ro) * gij * gij;
              var dx = - this.learning_rate / Math.sqrt(gsumi[j] + this.eps) * gij; // eps added for better conditioning
              p[j] += dx;
            } else if(this.method === 'adadelta') {
              gsumi[j] = this.ro * gsumi[j] + (1-this.ro) * gij * gij;
              var dx = - Math.sqrt((xsumi[j] + this.eps)/(gsumi[j] + this.eps)) * gij;
              xsumi[j] = this.ro * xsumi[j] + (1-this.ro) * dx * dx; // yes, xsum lags behind gsum by 1.
              p[j] += dx;
            } else if(this.method === 'nesterov') {
            	var dx = gsumi[j];
            	gsumi[j] = gsumi[j] * this.momentum + this.learning_rate * gij;
                dx = this.momentum * dx - (1.0 + this.momentum) * gsumi[j];
                p[j] += dx;
            } else {
              // assume SGD
              if(this.momentum > 0.0) {
                // momentum update
                var dx = this.momentum * gsumi[j] - this.learning_rate * gij; // step
                gsumi[j] = dx; // back this up for next iteration of momentum
                p[j] += dx; // apply corrected gradient
              } else {
                // vanilla sgd
                p[j] +=  - this.learning_rate * gij;
              }
            }
            g[j] = 0.0; // zero out gradient so that we can begin accumulating anew
          }
        }
      }

      // appending softmax_loss for backwards compatibility, but from now on we will always use cost_loss
      // in future, TODO: have to completely redo the way loss is done around the network as currently 
      // loss is a bit of a hack. Ideally, user should specify arbitrary number of loss functions on any layer
      // and it should all be computed correctly and automatically. 
      return {fwd_time: fwd_time, bwd_time: bwd_time, 
              l2_decay_loss: l2_decay_loss, l1_decay_loss: l1_decay_loss,
              cost_loss: cost_loss, softmax_loss: cost_loss, 
              loss: cost_loss + l1_decay_loss + l2_decay_loss}
    }
  }
  
  convnet.Trainer = Trainer;
  convnet.SGDTrainer = Trainer; // backwards compatibility


/*** convnet_magicnets ***/
  // used utilities, make explicit local references
  var randf = convnet.randf;
  var randi = convnet.randi;
  var Net = convnet.Net;
  var Trainer = convnet.Trainer;
  var maxmin = convnet.maxmin;
  var randperm = convnet.randperm;
  var weightedSample = convnet.weightedSample;
  var getopt = convnet.getopt;
  var arrUnique = convnet.arrUnique;

  /*
  A MagicNet takes data: a list of convnetjs.Vol(), and labels
  which for now are assumed to be class indeces 0..K. MagicNet then:
  - creates data folds for cross-validation
  - samples candidate networks
  - evaluates candidate networks on all data folds
  - produces predictions by model-averaging the best networks
  */
  var MagicNet = function(data, labels, opt) {
    var opt = opt || {};
    if(typeof data === 'undefined') { data = []; }
    if(typeof labels === 'undefined') { labels = []; }

    // required inputs
    this.data = data; // store these pointers to data
    this.labels = labels;

    // optional inputs
    this.train_ratio = getopt(opt, 'train_ratio', 0.7);
    this.num_folds = getopt(opt, 'num_folds', 10);
    this.num_candidates = getopt(opt, 'num_candidates', 50); // we evaluate several in parallel
    // how many epochs of data to train every network? for every fold?
    // higher values mean higher accuracy in final results, but more expensive
    this.num_epochs = getopt(opt, 'num_epochs', 50); 
    // number of best models to average during prediction. Usually higher = better
    this.ensemble_size = getopt(opt, 'ensemble_size', 10);

    // candidate parameters
    this.batch_size_min = getopt(opt, 'batch_size_min', 10);
    this.batch_size_max = getopt(opt, 'batch_size_max', 300);
    this.l2_decay_min = getopt(opt, 'l2_decay_min', -4);
    this.l2_decay_max = getopt(opt, 'l2_decay_max', 2);
    this.learning_rate_min = getopt(opt, 'learning_rate_min', -4);
    this.learning_rate_max = getopt(opt, 'learning_rate_max', 0);
    this.momentum_min = getopt(opt, 'momentum_min', 0.9);
    this.momentum_max = getopt(opt, 'momentum_max', 0.9);
    this.neurons_min = getopt(opt, 'neurons_min', 5);
    this.neurons_max = getopt(opt, 'neurons_max', 30);

    // computed
    this.folds = []; // data fold indices, gets filled by sampleFolds()
    this.candidates = []; // candidate networks that are being currently evaluated
    this.evaluated_candidates = []; // history of all candidates that were fully evaluated on all folds
    this.unique_labels = arrUnique(labels);
    this.iter = 0; // iteration counter, goes from 0 -> num_epochs * num_training_data
    this.foldix = 0; // index of active fold

    // callbacks
    this.finish_fold_callback = null;
    this.finish_batch_callback = null;

    // initializations
    if(this.data.length > 0) {
      this.sampleFolds();
      this.sampleCandidates();
    }
  };

  MagicNet.prototype = {

    // sets this.folds to a sampling of this.num_folds folds
    sampleFolds: function() {
      var N = this.data.length;
      var num_train = Math.floor(this.train_ratio * N);
      this.folds = []; // flush folds, if any
      for(var i=0;i<this.num_folds;i++) {
        var p = randperm(N);
        this.folds.push({train_ix: p.slice(0, num_train), test_ix: p.slice(num_train, N)});
      }
    },

    // returns a random candidate network
    sampleCandidate: function() {
      var input_depth = this.data[0].w.length;
      var num_classes = this.unique_labels.length;

      // sample network topology and hyperparameters
      var layer_defs = [];
      layer_defs.push({type:'input', out_sx:1, out_sy:1, out_depth: input_depth});
      var nwl = weightedSample([0,1,2,3], [0.2, 0.3, 0.3, 0.2]); // prefer nets with 1,2 hidden layers
      for(var q=0;q<nwl;q++) {
        var ni = randi(this.neurons_min, this.neurons_max);
        var act = ['tanh','maxout','relu'][randi(0,3)];
        if(randf(0,1)<0.5) {
          var dp = Math.random();
          layer_defs.push({type:'fc', num_neurons: ni, activation: act, drop_prob: dp});
        } else {
          layer_defs.push({type:'fc', num_neurons: ni, activation: act});
        }
      }
      layer_defs.push({type:'softmax', num_classes: num_classes});
      var net = new Net();
      net.makeLayers(layer_defs);

      // sample training hyperparameters
      var bs = randi(this.batch_size_min, this.batch_size_max); // batch size
      var l2 = Math.pow(10, randf(this.l2_decay_min, this.l2_decay_max)); // l2 weight decay
      var lr = Math.pow(10, randf(this.learning_rate_min, this.learning_rate_max)); // learning rate
      var mom = randf(this.momentum_min, this.momentum_max); // momentum. Lets just use 0.9, works okay usually ;p
      var tp = randf(0,1); // trainer type
      var trainer_def;
      if(tp<0.33) {
        trainer_def = {method:'adadelta', batch_size:bs, l2_decay:l2};
      } else if(tp<0.66) {
        trainer_def = {method:'adagrad', learning_rate: lr, batch_size:bs, l2_decay:l2};
      } else {
        trainer_def = {method:'sgd', learning_rate: lr, momentum: mom, batch_size:bs, l2_decay:l2};
      }
      
      var trainer = new Trainer(net, trainer_def);

      var cand = {};
      cand.acc = [];
      cand.accv = 0; // this will maintained as sum(acc) for convenience
      cand.layer_defs = layer_defs;
      cand.trainer_def = trainer_def;
      cand.net = net;
      cand.trainer = trainer;
      return cand;
    },

    // sets this.candidates with this.num_candidates candidate nets
    sampleCandidates: function() {
      this.candidates = []; // flush, if any
      for(var i=0;i<this.num_candidates;i++) {
        var cand = this.sampleCandidate();
        this.candidates.push(cand);
      }
    },

    step: function() {
      
      // run an example through current candidate
      this.iter++;

      // step all candidates on a random data point
      var fold = this.folds[this.foldix]; // active fold
      var dataix = fold.train_ix[randi(0, fold.train_ix.length)];
      for(var k=0;k<this.candidates.length;k++) {
        var x = this.data[dataix];
        var l = this.labels[dataix];
        this.candidates[k].trainer.train(x, l);
      }

      // process consequences: sample new folds, or candidates
      var lastiter = this.num_epochs * fold.train_ix.length;
      if(this.iter >= lastiter) {
        // finished evaluation of this fold. Get final validation
        // accuracies, record them, and go on to next fold.
        var val_acc = this.evalValErrors();
        for(var k=0;k<this.candidates.length;k++) {
          var c = this.candidates[k];
          c.acc.push(val_acc[k]);
          c.accv += val_acc[k];
        }
        this.iter = 0; // reset step number
        this.foldix++; // increment fold

        if(this.finish_fold_callback !== null) {
          this.finish_fold_callback();
        }

        if(this.foldix >= this.folds.length) {
          // we finished all folds as well! Record these candidates
          // and sample new ones to evaluate.
          for(var k=0;k<this.candidates.length;k++) {
            this.evaluated_candidates.push(this.candidates[k]);
          }
          // sort evaluated candidates according to accuracy achieved
          this.evaluated_candidates.sort(function(a, b) { 
            return (a.accv / a.acc.length) 
                 > (b.accv / b.acc.length) 
                 ? -1 : 1;
          });
          // and clip only to the top few ones (lets place limit at 3*ensemble_size)
          // otherwise there are concerns with keeping these all in memory 
          // if MagicNet is being evaluated for a very long time
          if(this.evaluated_candidates.length > 3 * this.ensemble_size) {
            this.evaluated_candidates = this.evaluated_candidates.slice(0, 3 * this.ensemble_size);
          }
          if(this.finish_batch_callback !== null) {
            this.finish_batch_callback();
          }
          this.sampleCandidates(); // begin with new candidates
          this.foldix = 0; // reset this
        } else {
          // we will go on to another fold. reset all candidates nets
          for(var k=0;k<this.candidates.length;k++) {
            var c = this.candidates[k];
            var net = new Net();
            net.makeLayers(c.layer_defs);
            var trainer = new Trainer(net, c.trainer_def);
            c.net = net;
            c.trainer = trainer;
          }
        }
      }
    },

    evalValErrors: function() {
      // evaluate candidates on validation data and return performance of current networks
      // as simple list
      var vals = [];
      var fold = this.folds[this.foldix]; // active fold
      for(var k=0;k<this.candidates.length;k++) {
        var net = this.candidates[k].net;
        var v = 0.0;
        for(var q=0;q<fold.test_ix.length;q++) {
          var x = this.data[fold.test_ix[q]];
          var l = this.labels[fold.test_ix[q]];
          net.forward(x);
          var yhat = net.getPrediction();
          v += (yhat === l ? 1.0 : 0.0); // 0 1 loss
        }
        v /= fold.test_ix.length; // normalize
        vals.push(v);
      }
      return vals;
    },

    // returns prediction scores for given test data point, as Vol
    // uses an averaged prediction from the best ensemble_size models
    // x is a Vol.
    predict_soft: function(data) {
      // forward prop the best networks
      // and accumulate probabilities at last layer into a an output Vol

      var eval_candidates = [];
      var nv = 0;
      if(this.evaluated_candidates.length === 0) {
        // not sure what to do here, first batch of nets hasnt evaluated yet
        // lets just predict with current candidates.
        nv = this.candidates.length;
        eval_candidates = this.candidates;
      } else {
        // forward prop the best networks from evaluated_candidates
        nv = Math.min(this.ensemble_size, this.evaluated_candidates.length);
        eval_candidates = this.evaluated_candidates
      }

      // forward nets of all candidates and average the predictions
      var xout, n;
      for(var j=0;j<nv;j++) {
        var net = eval_candidates[j].net;
        var x = net.forward(data);
        if(j===0) { 
          xout = x; 
          n = x.w.length; 
        } else {
          // add it on
          for(var d=0;d<n;d++) {
            xout.w[d] += x.w[d];
          }
        }
      }
      // produce average
      for(var d=0;d<n;d++) {
        xout.w[d] /= nv;
      }
      return xout;
    },

    predict: function(data) {
      var xout = this.predict_soft(data);
      if(xout.w.length !== 0) {
        var stats = maxmin(xout.w);
        var predicted_label = stats.maxi; 
      } else {
        var predicted_label = -1; // error out
      }
      return predicted_label;

    },

    toJSON: function() {
      // dump the top ensemble_size networks as a list
      var nv = Math.min(this.ensemble_size, this.evaluated_candidates.length);
      var json = {};
      json.nets = [];
      for(var i=0;i<nv;i++) {
        json.nets.push(this.evaluated_candidates[i].net.toJSON());
      }
      return json;
    },

    fromJSON: function(json) {
      this.ensemble_size = json.nets.length;
      this.evaluated_candidates = [];
      for(var i=0;i<this.ensemble_size;i++) {
        var net = new Net();
        net.fromJSON(json.nets[i]);
        var dummy_candidate = {};
        dummy_candidate.net = net;
        this.evaluated_candidates.push(dummy_candidate);
      }
    },

    // callback functions
    // called when a fold is finished, while evaluating a batch
    onFinishFold: function(f) { this.finish_fold_callback = f; },
    // called when a batch of candidates has finished evaluating
    onFinishBatch: function(f) { this.finish_batch_callback = f; }
    
  };

  convnet.MagicNet = MagicNet;


};
BundleModuleCode['ml/ann']=function (module,exports){
/*******************************************************************************
                                      CONFIG
*******************************************************************************/

// Config
var config = {
  warnings: false
};

/*******************************************************************************
                                  ACTIVATION FUNCTIONS
*******************************************************************************/

// https://en.wikipedia.org/wiki/Activation_function
// https://stats.stackexchange.com/questions/115258/comprehensive-list-of-activation-functions-in-neural-networks-with-pros-cons
var activation = {
  LOGISTIC: function LOGISTIC (x, derivate) {
    var fx = 1 / (1 + Math.exp(-x));
    if (!derivate) return fx;
    return fx * (1 - fx);
  },
  TANH: function TANH (x, derivate) {
    if (derivate) return 1 - Math.pow(Math.tanh(x), 2);
    return Math.tanh(x);
  },
  IDENTITY: function IDENTITY (x, derivate) {
    return derivate ? 1 : x;
  },
  STEP: function STEP (x, derivate) {
    return derivate ? 0 : x > 0 ? 1 : 0;
  },
  RELU: function RELU (x, derivate) {
    if (derivate) return x > 0 ? 1 : 0;
    return x > 0 ? x : 0;
  },
  SOFTSIGN: function SOFTSIGN (x, derivate) {
    var d = 1 + Math.abs(x);
    if (derivate) return x / Math.pow(d, 2);
    return x / d;
  },
  SINUSOID: function SINUSOID (x, derivate) {
    if (derivate) return Math.cos(x);
    return Math.sin(x);
  },
  GAUSSIAN: function GAUSSIAN (x, derivate) {
    var d = Math.exp(-Math.pow(x, 2));
    if (derivate) return -2 * x * d;
    return d;
  },
  BENT_IDENTITY: function BENT_IDENTITY (x, derivate) {
    var d = Math.sqrt(Math.pow(x, 2) + 1);
    if (derivate) return x / (2 * d) + 1;
    return (d - 1) / 2 + x;
  },
  BIPOLAR: function BIPOLAR (x, derivate) {
    return derivate ? 0 : x > 0 ? 1 : -1;
  },
  BIPOLAR_SIGMOID: function BIPOLAR_SIGMOID (x, derivate) {
    var d = 2 / (1 + Math.exp(-x)) - 1;
    if (derivate) return 1 / 2 * (1 + d) * (1 - d);
    return d;
  },
  HARD_TANH: function HARD_TANH (x, derivate) {
    if (derivate) return x > -1 && x < 1 ? 1 : 0;
    return Math.max(-1, Math.min(1, x));
  },
  ABSOLUTE: function ABSOLUTE (x, derivate) {
    if (derivate) return x < 0 ? -1 : 1;
    return Math.abs(x);
  },
  INVERSE: function INVERSE (x, derivate) {
    if (derivate) return -1;
    return 1 - x;
  },
  // https://arxiv.org/pdf/1706.02515.pdf
  SELU: function SELU (x, derivate) {
    var alpha = 1.6732632423543772848170429916717;
    var scale = 1.0507009873554804934193349852946;
    var fx = x > 0 ? x : alpha * Math.exp(x) - alpha;
    if (derivate) { return x > 0 ? scale : (fx + alpha) * scale; }
    return fx * scale;
  }
};

/*******************************************************************************
                                      MUTATION
*******************************************************************************/

// https://en.wikipedia.org/wiki/mutation_(genetic_algorithm)
var mutation = {
  ADD_NODE: {
    name: 'ADD_NODE'
  },
  SUB_NODE: {
    name: 'SUB_NODE',
    keep_gates: true
  },
  ADD_CONN: {
    name: 'ADD_CONN'
  },
  SUB_CONN: {
    name: 'REMOVE_CONN'
  },
  MOD_WEIGHT: {
    name: 'MOD_WEIGHT',
    min: -1,
    max: 1
  },
  MOD_BIAS: {
    name: 'MOD_BIAS',
    min: -1,
    max: 1
  },
  MOD_ACTIVATION: {
    name: 'MOD_ACTIVATION',
    mutateOutput: true,
    allowed: [
      activation.LOGISTIC,
      activation.TANH,
      activation.RELU,
      activation.IDENTITY,
      activation.STEP,
      activation.SOFTSIGN,
      activation.SINUSOID,
      activation.GAUSSIAN,
      activation.BENT_IDENTITY,
      activation.BIPOLAR,
      activation.BIPOLAR_SIGMOID,
      activation.HARD_TANH,
      activation.ABSOLUTE,
      activation.INVERSE,
      activation.SELU
    ]
  },
  ADD_SELF_CONN: {
    name: 'ADD_SELF_CONN'
  },
  SUB_SELF_CONN: {
    name: 'SUB_SELF_CONN'
  },
  ADD_GATE: {
    name: 'ADD_GATE'
  },
  SUB_GATE: {
    name: 'SUB_GATE'
  },
  ADD_BACK_CONN: {
    name: 'ADD_BACK_CONN'
  },
  SUB_BACK_CONN: {
    name: 'SUB_BACK_CONN'
  },
  SWAP_NODES: {
    name: 'SWAP_NODES',
    mutateOutput: true
  }
};

mutation.ALL = [
  mutation.ADD_NODE,
  mutation.SUB_NODE,
  mutation.ADD_CONN,
  mutation.SUB_CONN,
  mutation.MOD_WEIGHT,
  mutation.MOD_BIAS,
  mutation.MOD_ACTIVATION,
  mutation.ADD_GATE,
  mutation.SUB_GATE,
  mutation.ADD_SELF_CONN,
  mutation.SUB_SELF_CONN,
  mutation.ADD_BACK_CONN,
  mutation.SUB_BACK_CONN,
  mutation.SWAP_NODES
];

mutation.FFW = [
  mutation.ADD_NODE,
  mutation.SUB_NODE,
  mutation.ADD_CONN,
  mutation.SUB_CONN,
  mutation.MOD_WEIGHT,
  mutation.MOD_BIAS,
  mutation.MOD_ACTIVATION,
  mutation.SWAP_NODES
];

/*******************************************************************************
                                      SELECTION
*******************************************************************************/

// https://en.wikipedia.org/wiki/Selection_(genetic_algorithm)

var selection = {
  FITNESS_PROPORTIONATE: {
    name: 'FITNESS_PROPORTIONATE'
  },
  POWER: {
    name: 'POWER',
    power: 4
  },
  TOURNAMENT: {
    name: 'TOURNAMENT',
    size: 5,
    probability: 0.5
  }
};

/*******************************************************************************
                                      CROSSOVER
*******************************************************************************/

// https://en.wikipedia.org/wiki/Crossover_(genetic_algorithm)
var crossover = {
  SINGLE_POINT: {
    name: 'SINGLE_POINT',
    config: [0.4]
  },
  TWO_POINT: {
    name: 'TWO_POINT',
    config: [0.4, 0.9]
  },
  UNIFORM: {
    name: 'UNIFORM'
  },
  AVERAGE: {
    name: 'AVERAGE'
  }
};

/*******************************************************************************
                                    COST FUNCTIONS
*******************************************************************************/

// https://en.wikipedia.org/wiki/Loss_function
var cost = {
  // Cross entropy error
  CROSS_ENTROPY: function (target, output) {
    var error = 0;
    for (var i = 0; i < output.length; i++) {
      // Avoid negative and zero numbers, use 1e-15 http://bit.ly/2p5W29A
      error -= target[i] * Math.log(Math.max(output[i], 1e-15)) + (1 - target[i]) * Math.log(1 - Math.max(output[i], 1e-15));
    }
    return error / output.length;
  },
  // Mean Squared Error
  MSE: function (target, output) {
    var error = 0;
    for (var i = 0; i < output.length; i++) {
      error += Math.pow(target[i] - output[i], 2);
    }

    return error / output.length;
  },
  // Binary error
  BINARY: function (target, output) {
    var misses = 0;
    for (var i = 0; i < output.length; i++) {
      misses += Math.round(target[i] * 2) !== Math.round(output[i] * 2);
    }

    return misses;
  },
  // Mean Absolute Error
  MAE: function (target, output) {
    var error = 0;
    for (var i = 0; i < output.length; i++) {
      error += Math.abs(target[i] - output[i]);
    }

    return error / output.length;
  },
  // Mean Absolute Percentage Error
  MAPE: function (target, output) {
    var error = 0;
    for (var i = 0; i < output.length; i++) {
      error += Math.abs((output[i] - target[i]) / Math.max(target[i], 1e-15));
    }

    return error / output.length;
  },
  // Mean Squared Logarithmic Error
  MSLE: function (target, output) {
    var error = 0;
    for (var i = 0; i < output.length; i++) {
      error += Math.log(Math.max(target[i], 1e-15)) - Math.log(Math.max(output[i], 1e-15));
    }

    return error;
  },
  // Hinge loss, for classifiers
  HINGE: function (target, output) {
    var error = 0;
    for (var i = 0; i < output.length; i++) {
      error += Math.max(0, 1 - target[i] * output[i]);
    }

    return error;
  }
};


/*******************************************************************************
                                    GATING
*******************************************************************************/

// Specifies how to gate a connection between two groups of multiple neurons
var gating = {
  OUTPUT: {
    name: 'OUTPUT'
  },
  INPUT: {
    name: 'INPUT'
  },
  SELF: {
    name: 'SELF'
  }
};


/*******************************************************************************
                                    CONNECTION
*******************************************************************************/

// Specifies in what manner two groups are connected
var connection = {
  ALL_TO_ALL: {
    name: 'OUTPUT'
  },
  ALL_TO_ELSE: {
    name: 'INPUT'
  },
  ONE_TO_ONE: {
    name: 'SELF'
  }
};


/*******************************************************************************
                                      RATE
*******************************************************************************/

// https://stackoverflow.com/questions/30033096/what-is-lr-policy-in-caffe/30045244
var rate = {
  FIXED: function () {
    var func = function (baseRate, iteration) { return baseRate; };
    return func;
  },
  STEP: function (gamma, stepSize) {
    gamma = gamma || 0.9;
    stepSize = stepSize || 100;

    var func = function (baseRate, iteration) {
      return baseRate * Math.pow(gamma, Math.floor(iteration / stepSize));
    };

    return func;
  },
  EXP: function (gamma) {
    gamma = gamma || 0.999;

    var func = function (baseRate, iteration) {
      return baseRate * Math.pow(gamma, iteration);
    };

    return func;
  },
  INV: function (gamma, power) {
    gamma = gamma || 0.001;
    power = power || 2;

    var func = function (baseRate, iteration) {
      return baseRate * Math.pow(1 + gamma * iteration, -power);
    };

    return func;
  }
};

/*******************************************************************************
                                  METHODS
*******************************************************************************/

var methods = {
  activation: activation,
  mutation: mutation,
  selection: selection,
  crossover: crossover,
  cost: cost,
  gating: gating,
  connection: connection,
  rate: rate
};

/*******************************************************************************
                                      CONNECTION
*******************************************************************************/

function Connection (from, to, weight) {
  this.from = from;
  this.to = to;
  this.gain = 1;

  this.weight = (typeof weight === 'undefined') ? Math.random() * 0.2 - 0.1 : weight;

  this.gater = null;
  this.elegibility = 0;

  // For tracking momentum
  this.previousDeltaWeight = 0;

  // Batch training
  this.totalDeltaWeight = 0;

  this.xtrace = {
    nodes: [],
    values: []
  };
}

Connection.prototype = {
  /**
   * Converts the connection to a json object
   */
  toJSON: function () {
    var json = {
      weight: this.weight
    };

    return json;
  }
};

/**
 * Returns an innovation ID
 * https://en.wikipedia.org/wiki/Pairing_function (Cantor pairing function)
 */
Connection.innovationID = function (a, b) {
  return 1 / 2 * (a + b) * (a + b + 1) + b;
};

/*******************************************************************************
                                 NETWORK
*******************************************************************************/


/* Easier variable naming */
var mutation = methods.mutation;

function Network (input, output) {
  if (typeof input === 'undefined' || typeof output === 'undefined') {
    throw new Error('No input or output size given');
  }

  this.input = input;
  this.output = output;

  // Store all the node and connection genes
  this.nodes = []; // Stored in activation order
  this.connections = [];
  this.gates = [];
  this.selfconns = [];

  // Regularization
  this.dropout = 0;

  // Create input and output nodes
  var i;
  for (i = 0; i < this.input + this.output; i++) {
    var type = i < this.input ? 'input' : 'output';
    this.nodes.push(new Node(type));
  }

  // Connect input nodes with output nodes directly
  for (i = 0; i < this.input; i++) {
    for (var j = this.input; j < this.output + this.input; j++) {
      // https://stats.stackexchange.com/a/248040/147931
      var weight = Math.random() * this.input * Math.sqrt(2 / this.input);
      this.connect(this.nodes[i], this.nodes[j], weight);
    }
  }
}

Network.prototype = {
  /**
   * Activates the network
   */
  activate: function (input, training) {
    var output = [];

    // Activate nodes chronologically
    for (var i = 0; i < this.nodes.length; i++) {
      if (this.nodes[i].type === 'input') {
        this.nodes[i].activate(input[i]);
      } else if (this.nodes[i].type === 'output') {
        var activation = this.nodes[i].activate();
        output.push(activation);
      } else {
        if (training) this.nodes[i].mask = Math.random() < this.dropout ? 0 : 1;
        this.nodes[i].activate();
      }
    }

    return output;
  },

  /**
   * Activates the network without calculating elegibility traces and such
   */
  noTraceActivate: function (input) {
    var output = [];

    // Activate nodes chronologically
    for (var i = 0; i < this.nodes.length; i++) {
      if (this.nodes[i].type === 'input') {
        this.nodes[i].noTraceActivate(input[i]);
      } else if (this.nodes[i].type === 'output') {
        var activation = this.nodes[i].noTraceActivate();
        output.push(activation);
      } else {
        this.nodes[i].noTraceActivate();
      }
    }

    return output;
  },

  /**
   * Backpropagate the network
   */
  propagate: function (rate, momentum, update, target) {
    if (typeof target === 'undefined' || target.length !== this.output) {
      throw new Error('Output target length should match network output length');
    }

    var targetIndex = target.length;

    // Propagate output nodes
    var i;
    for (i = this.nodes.length - 1; i >= this.nodes.length - this.output; i--) {
      this.nodes[i].propagate(rate, momentum, update, target[--targetIndex]);
    }

    // Propagate hidden and input nodes
    for (i = this.nodes.length - this.output - 1; i >= this.input; i--) {
      this.nodes[i].propagate(rate, momentum, update);
    }
  },

  /**
   * Clear the context of the network
   */
  clear: function () {
    for (var i = 0; i < this.nodes.length; i++) {
      this.nodes[i].clear();
    }
  },

  /**
   * Connects the from node to the to node
   */
  connect: function (from, to, weight) {
    var connections = from.connect(to, weight);

    for (var i = 0; i < connections.length; i++) {
      var connection = connections[i];
      if (from !== to) {
        this.connections.push(connection);
      } else {
        this.selfconns.push(connection);
      }
    }

    return connections;
  },

  /**
   * Disconnects the from node from the to node
   */
  disconnect: function (from, to) {
    // Delete the connection in the network's connection array
    var connections = from === to ? this.selfconns : this.connections;

    for (var i = 0; i < connections.length; i++) {
      var connection = connections[i];
      if (connection.from === from && connection.to === to) {
        if (connection.gater !== null) this.ungate(connection);
        connections.splice(i, 1);
        break;
      }
    }

    // Delete the connection at the sending and receiving neuron
    from.disconnect(to);
  },

  /**
   * Gate a connection with a node
   */
  gate: function (node, connection) {
    if (this.nodes.indexOf(node) === -1) {
      throw new Error('This node is not part of the network!');
    } else if (connection.gater != null) {
      if (config.warnings) console.warn('This connection is already gated!');
      return;
    }
    node.gate(connection);
    this.gates.push(connection);
  },

  /**
   *  Remove the gate of a connection
   */
  ungate: function (connection) {
    var index = this.gates.indexOf(connection);
    if (index === -1) {
      throw new Error('This connection is not gated!');
    }

    this.gates.splice(index, 1);
    connection.gater.ungate(connection);
  },

  /**
   *  Removes a node from the network
   */
  remove: function (node) {
    var index = this.nodes.indexOf(node);

    if (index === -1) {
      throw new Error('This node does not exist in the network!');
    }

    // Keep track of gaters
    var gaters = [];

    // Remove selfconnections from this.selfconns
    this.disconnect(node, node);

    // Get all its inputting nodes
    var inputs = [];
    for (var i = node.connections.in.length - 1; i >= 0; i--) {
      var connection = node.connections.in[i];
      if (mutation.SUB_NODE.keep_gates && connection.gater !== null && connection.gater !== node) {
        gaters.push(connection.gater);
      }
      inputs.push(connection.from);
      this.disconnect(connection.from, node);
    }

    // Get all its outputing nodes
    var outputs = [];
    for (i = node.connections.out.length - 1; i >= 0; i--) {
      var connection = node.connections.out[i];
      if (mutation.SUB_NODE.keep_gates && connection.gater !== null && connection.gater !== node) {
        gaters.push(connection.gater);
      }
      outputs.push(connection.to);
      this.disconnect(node, connection.to);
    }

    // Connect the input nodes to the output nodes (if not already connected)
    var connections = [];
    for (i = 0; i < inputs.length; i++) {
      var input = inputs[i];
      for (var j = 0; j < outputs.length; j++) {
        var output = outputs[j];
        if (!input.isProjectingTo(output)) {
          var conn = this.connect(input, output);
          connections.push(conn[0]);
        }
      }
    }

    // Gate random connections with gaters
    for (i = 0; i < gaters.length; i++) {
      if (connections.length === 0) break;

      var gater = gaters[i];
      var connIndex = Math.floor(Math.random() * connections.length);

      this.gate(gater, connections[connIndex]);
      connections.splice(connIndex, 1);
    }

    // Remove gated connections gated by this node
    for (i = node.connections.gated.length - 1; i >= 0; i--) {
      var conn = node.connections.gated[i];
      this.ungate(conn);
    }

    // Remove selfconnection
    this.disconnect(node, node);

    // Remove the node from this.nodes
    this.nodes.splice(index, 1);
  },

  /**
   * Mutates the network with the given method
   */
  mutate: function (method) {
    if (typeof method === 'undefined') {
      throw new Error('No (correct) mutate method given!');
    }

    var i, j;
    switch (method) {
      case mutation.ADD_NODE:
        // Look for an existing connection and place a node in between
        var connection = this.connections[Math.floor(Math.random() * this.connections.length)];
        var gater = connection.gater;
        this.disconnect(connection.from, connection.to);

        // Insert the new node right before the old connection.to
        var toIndex = this.nodes.indexOf(connection.to);
        var node = new Node('hidden');

        // Random squash function
        node.mutate(mutation.MOD_ACTIVATION);

        // Place it in this.nodes
        var minBound = Math.min(toIndex, this.nodes.length - this.output);
        this.nodes.splice(minBound, 0, node);

        // Now create two new connections
        var newConn1 = this.connect(connection.from, node)[0];
        var newConn2 = this.connect(node, connection.to)[0];

        // Check if the original connection was gated
        if (gater != null) {
          this.gate(gater, Math.random() >= 0.5 ? newConn1 : newConn2);
        }
        break;
      case mutation.SUB_NODE:
        // Check if there are nodes left to remove
        if (this.nodes.length === this.input + this.output) {
          if (config.warnings) console.warn('No more nodes left to remove!');
          break;
        }

        // Select a node which isn't an input or output node
        var index = Math.floor(Math.random() * (this.nodes.length - this.output - this.input) + this.input);
        this.remove(this.nodes[index]);
        break;
      case mutation.ADD_CONN:
        // Create an array of all uncreated (feedforward) connections
        var available = [];
        for (i = 0; i < this.nodes.length - this.output; i++) {
          var node1 = this.nodes[i];
          for (j = Math.max(i + 1, this.input); j < this.nodes.length; j++) {
            var node2 = this.nodes[j];
            if (!node1.isProjectingTo(node2)) available.push([node1, node2]);
          }
        }

        if (available.length === 0) {
          if (config.warnings) console.warn('No more connections to be made!');
          break;
        }

        var pair = available[Math.floor(Math.random() * available.length)];
        this.connect(pair[0], pair[1]);
        break;
      case mutation.SUB_CONN:
        // List of possible connections that can be removed
        var possible = [];

        for (i = 0; i < this.connections.length; i++) {
          var conn = this.connections[i];
          // Check if it is not disabling a node
          if (conn.from.connections.out.length > 1 && conn.to.connections.in.length > 1 && this.nodes.indexOf(conn.to) > this.nodes.indexOf(conn.from)) {
            possible.push(conn);
          }
        }

        if (possible.length === 0) {
          if (config.warnings) console.warn('No connections to remove!');
          break;
        }

        var randomConn = possible[Math.floor(Math.random() * possible.length)];
        this.disconnect(randomConn.from, randomConn.to);
        break;
      case mutation.MOD_WEIGHT:
        var allconnections = this.connections.concat(this.selfconns);

        var connection = allconnections[Math.floor(Math.random() * allconnections.length)];
        var modification = Math.random() * (method.max - method.min) + method.min;
        connection.weight += modification;
        break;
      case mutation.MOD_BIAS:
        // Has no effect on input node, so they are excluded
        var index = Math.floor(Math.random() * (this.nodes.length - this.input) + this.input);
        var node = this.nodes[index];
        node.mutate(method);
        break;
      case mutation.MOD_ACTIVATION:
        // Has no effect on input node, so they are excluded
        if (!method.mutateOutput && this.input + this.output === this.nodes.length) {
          if (config.warnings) console.warn('No nodes that allow mutation of activation function');
          break;
        }

        var index = Math.floor(Math.random() * (this.nodes.length - (method.mutateOutput ? 0 : this.output) - this.input) + this.input);
        var node = this.nodes[index];

        node.mutate(method);
        break;
      case mutation.ADD_SELF_CONN:
        // Check which nodes aren't selfconnected yet
        var possible = [];
        for (i = this.input; i < this.nodes.length; i++) {
          var node = this.nodes[i];
          if (node.connections.self.weight === 0) {
            possible.push(node);
          }
        }

        if (possible.length === 0) {
          if (config.warnings) console.warn('No more self-connections to add!');
          break;
        }

        // Select a random node
        var node = possible[Math.floor(Math.random() * possible.length)];

        // Connect it to himself
        this.connect(node, node);
        break;
      case mutation.SUB_SELF_CONN:
        if (this.selfconns.length === 0) {
          if (config.warnings) console.warn('No more self-connections to remove!');
          break;
        }
        var conn = this.selfconns[Math.floor(Math.random() * this.selfconns.length)];
        this.disconnect(conn.from, conn.to);
        break;
      case mutation.ADD_GATE:
        var allconnections = this.connections.concat(this.selfconns);

        // Create a list of all non-gated connections
        var possible = [];
        for (i = 0; i < allconnections.length; i++) {
          var conn = allconnections[i];
          if (conn.gater === null) {
            possible.push(conn);
          }
        }

        if (possible.length === 0) {
          if (config.warnings) console.warn('No more connections to gate!');
          break;
        }

        // Select a random gater node and connection, can't be gated by input
        var index = Math.floor(Math.random() * (this.nodes.length - this.input) + this.input);
        var node = this.nodes[index];
        var conn = possible[Math.floor(Math.random() * possible.length)];

        // Gate the connection with the node
        this.gate(node, conn);
        break;
      case mutation.SUB_GATE:
        // Select a random gated connection
        if (this.gates.length === 0) {
          if (config.warnings) console.warn('No more connections to ungate!');
          break;
        }

        var index = Math.floor(Math.random() * this.gates.length);
        var gatedconn = this.gates[index];

        this.ungate(gatedconn);
        break;
      case mutation.ADD_BACK_CONN:
        // Create an array of all uncreated (backfed) connections
        var available = [];
        for (i = this.input; i < this.nodes.length; i++) {
          var node1 = this.nodes[i];
          for (j = this.input; j < i; j++) {
            var node2 = this.nodes[j];
            if (!node1.isProjectingTo(node2)) available.push([node1, node2]);
          }
        }

        if (available.length === 0) {
          if (config.warnings) console.warn('No more connections to be made!');
          break;
        }

        var pair = available[Math.floor(Math.random() * available.length)];
        this.connect(pair[0], pair[1]);
        break;
      case mutation.SUB_BACK_CONN:
        // List of possible connections that can be removed
        var possible = [];

        for (i = 0; i < this.connections.length; i++) {
          var conn = this.connections[i];
          // Check if it is not disabling a node
          if (conn.from.connections.out.length > 1 && conn.to.connections.in.length > 1 && this.nodes.indexOf(conn.from) > this.nodes.indexOf(conn.to)) {
            possible.push(conn);
          }
        }

        if (possible.length === 0) {
          if (config.warnings) console.warn('No connections to remove!');
          break;
        }

        var randomConn = possible[Math.floor(Math.random() * possible.length)];
        this.disconnect(randomConn.from, randomConn.to);
        break;
      case mutation.SWAP_NODES:
        // Has no effect on input node, so they are excluded
        if ((method.mutateOutput && this.nodes.length - this.input < 2) ||
          (!method.mutateOutput && this.nodes.length - this.input - this.output < 2)) {
          if (config.warnings) console.warn('No nodes that allow swapping of bias and activation function');
          break;
        }

        var index = Math.floor(Math.random() * (this.nodes.length - (method.mutateOutput ? 0 : this.output) - this.input) + this.input);
        var node1 = this.nodes[index];
        index = Math.floor(Math.random() * (this.nodes.length - (method.mutateOutput ? 0 : this.output) - this.input) + this.input);
        var node2 = this.nodes[index];

        var biasTemp = node1.bias;
        var squashTemp = node1.squash;

        node1.bias = node2.bias;
        node1.squash = node2.squash;
        node2.bias = biasTemp;
        node2.squash = squashTemp;
        break;
    }
  },

  /**
   * Train the given set to this network
   */
  train: function (set, options) {
    if (set[0].input.length !== this.input || set[0].output.length !== this.output) {
      throw new Error('Dataset input/output size should be same as network input/output size!');
    }

    options = options || {};

    // Warning messages
    if (typeof options.rate === 'undefined') {
      if (config.warnings) console.warn('Using default learning rate, please define a rate!');
    }
    if (typeof options.iterations === 'undefined') {
      if (config.warnings) console.warn('No target iterations given, running until error is reached!');
    }

    // Read the options
    var targetError = options.error || 0.05;
    var cost = options.cost || methods.cost.MSE;
    var baseRate = options.rate || 0.3;
    var dropout = options.dropout || 0;
    var momentum = options.momentum || 0;
    var batchSize = options.batchSize || 1; // online learning
    var ratePolicy = options.ratePolicy || methods.rate.FIXED();

    var start = Date.now();

    if (batchSize > set.length) {
      throw new Error('Batch size must be smaller or equal to dataset length!');
    } else if (typeof options.iterations === 'undefined' && typeof options.error === 'undefined') {
      throw new Error('At least one of the following options must be specified: error, iterations');
    } else if (typeof options.error === 'undefined') {
      targetError = -1; // run until iterations
    } else if (typeof options.iterations === 'undefined') {
      options.iterations = 0; // run until target error
    }

    // Save to network
    this.dropout = dropout;

    if (options.crossValidate) {
      var numTrain = Math.ceil((1 - options.crossValidate.testSize) * set.length);
      var trainSet = set.slice(0, numTrain);
      var testSet = set.slice(numTrain);
    }

    // Loops the training process
    var currentRate = baseRate;
    var iteration = 0;
    var error = 1;

    var i, j, x;
    while (error > targetError && (options.iterations === 0 || iteration < options.iterations)) {
      if (options.crossValidate && error <= options.crossValidate.testError) break;

      iteration++;

      // Update the rate
      currentRate = ratePolicy(baseRate, iteration);

      // Checks if cross validation is enabled
      if (options.crossValidate) {
        this._trainSet(trainSet, batchSize, currentRate, momentum, cost);
        if (options.clear) this.clear();
        error = this.test(testSet, cost).error;
        if (options.clear) this.clear();
      } else {
        error = this._trainSet(set, batchSize, currentRate, momentum, cost);
        if (options.clear) this.clear();
      }

      // Checks for options such as scheduled logs and shuffling
      if (options.shuffle) {
        for (j, x, i = set.length; i; j = Math.floor(Math.random() * i), x = set[--i], set[i] = set[j], set[j] = x);
      }

      if (options.log && iteration % options.log === 0) {
        console.log('iteration', iteration, 'error', error, 'rate', currentRate);
      }

      if (options.schedule && iteration % options.schedule.iterations === 0) {
        options.schedule.function({ error: error, iteration: iteration });
      }
    }

    if (options.clear) this.clear();

    if (dropout) {
      for (i = 0; i < this.nodes.length; i++) {
        if (this.nodes[i].type === 'hidden' || this.nodes[i].type === 'constant') {
          this.nodes[i].mask = 1 - this.dropout;
        }
      }
    }

    return {
      error: error,
      iterations: iteration,
      time: Date.now() - start
    };
  },

  /**
   * Performs one training epoch and returns the error
   * private function used in this.train
   */
  _trainSet: function (set, batchSize, currentRate, momentum, costFunction) {
    var errorSum = 0;
    for (var i = 0; i < set.length; i++) {
      var input = set[i].input;
      var target = set[i].output;

      var update = !!((i + 1) % batchSize === 0 || (i + 1) === set.length);

      var output = this.activate(input, true);
      this.propagate(currentRate, momentum, update, target);

      errorSum += costFunction(target, output);
    }
    return errorSum / set.length;
  },

  /**
   * Tests a set and returns the error and elapsed time
   */
  test: function (set, cost) {
    if (cost == undefined) cost = methods.cost.MSE;
    // Check if dropout is enabled, set correct mask
    var i;
    if (this.dropout) {
      for (i = 0; i < this.nodes.length; i++) {
        if (this.nodes[i].type === 'hidden' || this.nodes[i].type === 'constant') {
          this.nodes[i].mask = 1 - this.dropout;
        }
      }
    }

    var error = 0;
    var start = Date.now();

    for (i = 0; i < set.length; i++) {
      var input = set[i].input;
      var target = set[i].output;
      var output = this.noTraceActivate(input);
      error += cost(target, output);
    }

    error /= set.length;

    var results = {
      error: error,
      time: Date.now() - start
    };

    return results;
  },

  /**
   * Creates a json that can be used to create a graph with d3 and webcola
   */
  graph: function (width, height) {
    var input = 0;
    var output = 0;

    var json = {
      nodes: [],
      links: [],
      constraints: [{
        type: 'alignment',
        axis: 'x',
        offsets: []
      }, {
        type: 'alignment',
        axis: 'y',
        offsets: []
      }]
    };

    var i;
    for (i = 0; i < this.nodes.length; i++) {
      var node = this.nodes[i];

      if (node.type === 'input') {
        if (this.input === 1) {
          json.constraints[0].offsets.push({
            node: i,
            offset: 0
          });
        } else {
          json.constraints[0].offsets.push({
            node: i,
            offset: 0.8 * width / (this.input - 1) * input++
          });
        }
        json.constraints[1].offsets.push({
          node: i,
          offset: 0
        });
      } else if (node.type === 'output') {
        if (this.output === 1) {
          json.constraints[0].offsets.push({
            node: i,
            offset: 0
          });
        } else {
          json.constraints[0].offsets.push({
            node: i,
            offset: 0.8 * width / (this.output - 1) * output++
          });
        }
        json.constraints[1].offsets.push({
          node: i,
          offset: -0.8 * height
        });
      }

      json.nodes.push({
        id: i,
        name: node.type === 'hidden' ? node.squash.name : node.type.toUpperCase(),
        activation: node.activation,
        bias: node.bias
      });
    }

    var connections = this.connections.concat(this.selfconns);
    for (i = 0; i < connections.length; i++) {
      var connection = connections[i];
      if (connection.gater == null) {
        json.links.push({
          source: this.nodes.indexOf(connection.from),
          target: this.nodes.indexOf(connection.to),
          weight: connection.weight
        });
      } else {
        // Add a gater 'node'
        var index = json.nodes.length;
        json.nodes.push({
          id: index,
          activation: connection.gater.activation,
          name: 'GATE'
        });
        json.links.push({
          source: this.nodes.indexOf(connection.from),
          target: index,
          weight: 1 / 2 * connection.weight
        });
        json.links.push({
          source: index,
          target: this.nodes.indexOf(connection.to),
          weight: 1 / 2 * connection.weight
        });
        json.links.push({
          source: this.nodes.indexOf(connection.gater),
          target: index,
          weight: connection.gater.activation,
          gate: true
        });
      }
    }

    return json;
  },

  /**
   * Convert the network to a json object
   */
  toJSON: function () {
    var json = {
      nodes: [],
      connections: [],
      input: this.input,
      output: this.output,
      dropout: this.dropout
    };

    // So we don't have to use expensive .indexOf()
    var i;
    for (i = 0; i < this.nodes.length; i++) {
      this.nodes[i].index = i;
    }

    for (i = 0; i < this.nodes.length; i++) {
      var node = this.nodes[i];
      var tojson = node.toJSON();
      tojson.index = i;
      json.nodes.push(tojson);

      if (node.connections.self.weight !== 0) {
        var tojson = node.connections.self.toJSON();
        tojson.from = i;
        tojson.to = i;

        tojson.gater = node.connections.self.gater != null ? node.connections.self.gater.index : null;
        json.connections.push(tojson);
      }
    }

    for (i = 0; i < this.connections.length; i++) {
      var conn = this.connections[i];
      var tojson = conn.toJSON();
      tojson.from = conn.from.index;
      tojson.to = conn.to.index;

      tojson.gater = conn.gater != null ? conn.gater.index : null;

      json.connections.push(tojson);
    }

    return json;
  },

  /**
   * Sets the value of a property for every node in this network
   */
  set: function (values) {
    for (var i = 0; i < this.nodes.length; i++) {
      this.nodes[i].bias = values.bias || this.nodes[i].bias;
      this.nodes[i].squash = values.squash || this.nodes[i].squash;
    }
  },

  /**
   * Evolves the network to reach a lower error on a dataset
   */
  evolve: function (set, options) {
    if (set[0].input.length !== this.input || set[0].output.length !== this.output) {
      throw new Error('Dataset input/output size should be same as network input/output size!');
    }

    // Read the options
    options = options || {};
    var targetError = typeof options.error !== 'undefined' ? options.error : 0.05;
    var growth = typeof options.growth !== 'undefined' ? options.growth : 0.0001;
    var cost = options.cost || methods.cost.MSE;
    var amount = options.amount || 1;


    var start = Date.now();

    if (typeof options.iterations === 'undefined' && typeof options.error === 'undefined') {
      throw new Error('At least one of the following options must be specified: error, iterations');
    } else if (typeof options.error === 'undefined') {
      targetError = -1; // run until iterations
    } else if (typeof options.iterations === 'undefined') {
      options.iterations = 0; // run until target error
    }

    var fitnessFunction;
    {
      // Create the fitness function
      fitnessFunction = function (genome) {
        var score = 0;
        for (var i = 0; i < amount; i++) {
          score -= genome.test(set, cost).error;
        }

        score -= (genome.nodes.length - genome.input - genome.output + genome.connections.length + genome.gates.length) * growth;
        score = isNaN(score) ? -Infinity : score; // this can cause problems with fitness proportionate selection

        return score / amount;
      };
    } 

    // Intialise the NEAT instance
    options.network = this;
    var neat = new Neat(this.input, this.output, fitnessFunction, options);

    var error = -Infinity;
    var bestFitness = -Infinity;
    var bestGenome;

    while (error < -targetError && (options.iterations === 0 || neat.generation < options.iterations)) {
      var fittest = neat.evolve();
      var fitness = fittest.score;
      error = fitness + (fittest.nodes.length - fittest.input - fittest.output + fittest.connections.length + fittest.gates.length) * growth;

      if (fitness > bestFitness) {
        bestFitness = fitness;
        bestGenome = fittest;
      }

      if (options.log && neat.generation % options.log === 0) {
        console.log('iteration', neat.generation, 'fitness', fitness, 'error', -error);
      }

      if (options.schedule && neat.generation % options.schedule.iterations === 0) {
        options.schedule.function({ fitness: fitness, error: -error, iteration: neat.generation });
      }
    }


    if (typeof bestGenome !== 'undefined') {
      this.nodes = bestGenome.nodes;
      this.connections = bestGenome.connections;
      this.selfconns = bestGenome.selfconns;
      this.gates = bestGenome.gates;

      if (options.clear) this.clear();
    }

    return {
      error: -error,
      iterations: neat.generation,
      time: Date.now() - start
    };
  },

  /**
   * Creates a standalone function of the network which can be run without the
   * need of a library
   */
  standalone: function () {
    var present = [];
    var activations = [];
    var states = [];
    var lines = [];
    var functions = [];

    var i;
    for (i = 0; i < this.input; i++) {
      var node = this.nodes[i];
      activations.push(node.activation);
      states.push(node.state);
    }

    lines.push('for(var i = 0; i < input.length; i++) A[i] = input[i];');

    // So we don't have to use expensive .indexOf()
    for (i = 0; i < this.nodes.length; i++) {
      this.nodes[i].index = i;
    }

    for (i = this.input; i < this.nodes.length; i++) {
      var node = this.nodes[i];
      activations.push(node.activation);
      states.push(node.state);

      var functionIndex = present.indexOf(node.squash.name);

      if (functionIndex === -1) {
        functionIndex = present.length;
        present.push(node.squash.name);
        functions.push(node.squash.toString());
      }

      var incoming = [];
      for (var j = 0; j < node.connections.in.length; j++) {
        var conn = node.connections.in[j];
        var computation = "A[" + conn.from.index + "] * " + conn.weight;
        
        if (conn.gater != null) {
          computation += " * A[" + conn.gater.index + "]";
        }

        incoming.push(computation);
      }

      if (node.connections.self.weight) {
        var conn = node.connections.self;
        var computation = "S[" + i + "] * " + conn.weight;

        if (conn.gater != null) {
          computation += " * A[" + conn.gater.index + "]";
        }

        incoming.push(computation);
      }

      var line1 = "S[" + i + "] = " + incoming.join(' + ') + " + " + node.bias + ";";
      var line2 = "A[" + i + "] = F[" + functionIndex + "](S[" + i + "])" + (!node.mask ? ' * ' + node.mask : '') + ";";
      lines.push(line1);
      lines.push(line2);
    }

    var output = [];
    for (i = this.nodes.length - this.output; i < this.nodes.length; i++) {
      output.push("A[" + i + "]");
    }

    output = "return [" + output.join(',') + "];";
    lines.push(output);

    var total = '';
    
    total += "var F = [" + functions.toString() + "];\r\n"; 
    total += "var A = [" + activations.toString() + "];\r\n";
    total += "var S = [" + states.toString() + "];\r\n";
    total += "function activate(input){\r\n" + lines.join('\r\n') + "\r\n}";
    return total;
  },

  /**
   * Serialize to send to workers efficiently
   */
  serialize: function () {
    var activations = [];
    var states = [];
    var conns = [];
    var squashes = [
      'LOGISTIC', 'TANH', 'IDENTITY', 'STEP', 'RELU', 'SOFTSIGN', 'SINUSOID',
      'GAUSSIAN', 'BENT_IDENTITY', 'BIPOLAR', 'BIPOLAR_SIGMOID', 'HARD_TANH',
      'ABSOLUTE', 'INVERSE', 'SELU'
    ];

    conns.push(this.input);
    conns.push(this.output);

    var i;
    for (i = 0; i < this.nodes.length; i++) {
      var node = this.nodes[i];
      node.index = i;
      activations.push(node.activation);
      states.push(node.state);
    }

    for (i = this.input; i < this.nodes.length; i++) {
      var node = this.nodes[i];
      conns.push(node.index);
      conns.push(node.bias);
      conns.push(squashes.indexOf(node.squash.name));

      conns.push(node.connections.self.weight);
      conns.push(node.connections.self.gater == null ? -1 : node.connections.self.gater.index);

      for (var j = 0; j < node.connections.in.length; j++) {
        var conn = node.connections.in[j];

        conns.push(conn.from.index);
        conns.push(conn.weight);
        conns.push(conn.gater == null ? -1 : conn.gater.index);
      }

      conns.push(-2); // stop token -> next node
    }

    return [activations, states, conns];
  }
};

/**
 * Convert a json object to a network
 */
Network.fromJSON = function (json) {
  var network = new Network(json.input, json.output);
  network.dropout = json.dropout;
  network.nodes = [];
  network.connections = [];

  var i;
  for (i = 0; i < json.nodes.length; i++) {
    network.nodes.push(Node.fromJSON(json.nodes[i]));
  }

  for (i = 0; i < json.connections.length; i++) {
    var conn = json.connections[i];

    var connection = network.connect(network.nodes[conn.from], network.nodes[conn.to])[0];
    connection.weight = conn.weight;

    if (conn.gater != null) {
      network.gate(network.nodes[conn.gater], connection);
    }
  }

  return network;
};

/**
 * Merge two networks into one
 */
Network.merge = function (network1, network2) {
  // Create a copy of the networks
  network1 = Network.fromJSON(network1.toJSON());
  network2 = Network.fromJSON(network2.toJSON());

  // Check if output and input size are the same
  if (network1.output !== network2.input) {
    throw new Error('Output size of network1 should be the same as the input size of network2!');
  }

  // Redirect all connections from network2 input from network1 output
  var i;
  for (i = 0; i < network2.connections.length; i++) {
    var conn = network2.connections[i];
    if (conn.from.type === 'input') {
      var index = network2.nodes.indexOf(conn.from);

      // redirect
      conn.from = network1.nodes[network1.nodes.length - 1 - index];
    }
  }

  // Delete input nodes of network2
  for (i = network2.input - 1; i >= 0; i--) {
    network2.nodes.splice(i, 1);
  }

  // Change the node type of network1's output nodes (now hidden)
  for (i = network1.nodes.length - network1.output; i < network1.nodes.length; i++) {
    network1.nodes[i].type = 'hidden';
  }

  // Create one network from both networks
  network1.connections = network1.connections.concat(network2.connections);
  network1.nodes = network1.nodes.concat(network2.nodes);

  return network1;
};

/**
 * Create an offspring from two parent networks
 */
Network.crossOver = function (network1, network2, equal) {
  if (network1.input !== network2.input || network1.output !== network2.output) {
    throw new Error("Networks don't have the same input/output size!");
  }

  // Initialise offspring
  var offspring = new Network(network1.input, network1.output);
  offspring.connections = [];
  offspring.nodes = [];

  // Save scores and create a copy
  var score1 = network1.score || 0;
  var score2 = network2.score || 0;

  // Determine offspring node size
  var size;
  if (equal || score1 === score2) {
    var max = Math.max(network1.nodes.length, network2.nodes.length);
    var min = Math.min(network1.nodes.length, network2.nodes.length);
    size = Math.floor(Math.random() * (max - min + 1) + min);
  } else if (score1 > score2) {
    size = network1.nodes.length;
  } else {
    size = network2.nodes.length;
  }

  // Rename some variables for easier reading
  var outputSize = network1.output;

  // Set indexes so we don't need indexOf
  var i;
  for (i = 0; i < network1.nodes.length; i++) {
    network1.nodes[i].index = i;
  }

  for (i = 0; i < network2.nodes.length; i++) {
    network2.nodes[i].index = i;
  }

  // Assign nodes from parents to offspring
  for (i = 0; i < size; i++) {
    // Determine if an output node is needed
    var node;
    if (i < size - outputSize) {
      var random = Math.random();
      node = random >= 0.5 ? network1.nodes[i] : network2.nodes[i];
      var other = random < 0.5 ? network1.nodes[i] : network2.nodes[i];

      if (typeof node === 'undefined' || node.type === 'output') {
        node = other;
      }
    } else {
      if (Math.random() >= 0.5) {
        node = network1.nodes[network1.nodes.length + i - size];
      } else {
        node = network2.nodes[network2.nodes.length + i - size];
      }
    }

    var newNode = new Node();
    newNode.bias = node.bias;
    newNode.squash = node.squash;
    newNode.type = node.type;

    offspring.nodes.push(newNode);
  }

  // Create arrays of connection genes
  var n1conns = {};
  var n2conns = {};

  // Normal connections
  for (i = 0; i < network1.connections.length; i++) {
    var conn = network1.connections[i];
    var data = {
      weight: conn.weight,
      from: conn.from.index,
      to: conn.to.index,
      gater: conn.gater != null ? conn.gater.index : -1
    };
    n1conns[Connection.innovationID(data.from, data.to)] = data;
  }

  // Selfconnections
  for (i = 0; i < network1.selfconns.length; i++) {
    var conn = network1.selfconns[i];
    var data = {
      weight: conn.weight,
      from: conn.from.index,
      to: conn.to.index,
      gater: conn.gater != null ? conn.gater.index : -1
    };
    n1conns[Connection.innovationID(data.from, data.to)] = data;
  }

  // Normal connections
  for (i = 0; i < network2.connections.length; i++) {
    var conn = network2.connections[i];
    var data = {
      weight: conn.weight,
      from: conn.from.index,
      to: conn.to.index,
      gater: conn.gater != null ? conn.gater.index : -1
    };
    n2conns[Connection.innovationID(data.from, data.to)] = data;
  }

  // Selfconnections
  for (i = 0; i < network2.selfconns.length; i++) {
    var conn = network2.selfconns[i];
    var data = {
      weight: conn.weight,
      from: conn.from.index,
      to: conn.to.index,
      gater: conn.gater != null ? conn.gater.index : -1
    };
    n2conns[Connection.innovationID(data.from, data.to)] = data;
  }

  // Split common conn genes from disjoint or excess conn genes
  var connections = [];
  var keys1 = Object.keys(n1conns);
  var keys2 = Object.keys(n2conns);
  for (i = keys1.length - 1; i >= 0; i--) {
    // Common gene
    if (typeof n2conns[keys1[i]] !== 'undefined') {
      var conn = Math.random() >= 0.5 ? n1conns[keys1[i]] : n2conns[keys1[i]];
      connections.push(conn);

      // Because deleting is expensive, just set it to some value
      n2conns[keys1[i]] = undefined;
    } else if (score1 >= score2 || equal) {
      connections.push(n1conns[keys1[i]]);
    }
  }

  // Excess/disjoint gene
  if (score2 >= score1 || equal) {
    for (i = 0; i < keys2.length; i++) {
      if (typeof n2conns[keys2[i]] !== 'undefined') {
        connections.push(n2conns[keys2[i]]);
      }
    }
  }

  // Add common conn genes uniformly
  for (i = 0; i < connections.length; i++) {
    var connData = connections[i];
    if (connData.to < size && connData.from < size) {
      var from = offspring.nodes[connData.from];
      var to = offspring.nodes[connData.to];
      var conn = offspring.connect(from, to)[0];

      conn.weight = connData.weight;

      if (connData.gater !== -1 && connData.gater < size) {
        offspring.gate(offspring.nodes[connData.gater], conn);
      }
    }
  }

  return offspring;
};

/*******************************************************************************
                                        architect
*******************************************************************************/


var architect = {
  /**
   * Constructs a network from a given array of connected nodes
   */
  Construct: function (list) {
    // Create a network
    var network = new Network(0, 0);

    // Transform all groups into nodes
    var nodes = [];

    var i;
    for (i = 0; i < list.length; i++) {
      var j;
      if (list[i] instanceof Group) {
        for (j = 0; j < list[i].nodes.length; j++) {
          nodes.push(list[i].nodes[j]);
        }
      } else if (list[i] instanceof Layer) {
        for (j = 0; j < list[i].nodes.length; j++) {
          for (var k = 0; k < list[i].nodes[j].nodes.length; k++) {
            nodes.push(list[i].nodes[j].nodes[k]);
          }
        }
      } else if (list[i] instanceof Node) {
        nodes.push(list[i]);
      }
    }

    // Determine input and output nodes
    var inputs = [];
    var outputs = [];
    for (i = nodes.length - 1; i >= 0; i--) {
      if (nodes[i].type === 'output' || nodes[i].connections.out.length + nodes[i].connections.gated.length === 0) {
        nodes[i].type = 'output';
        network.output++;
        outputs.push(nodes[i]);
        nodes.splice(i, 1);
      } else if (nodes[i].type === 'input' || !nodes[i].connections.in.length) {
        nodes[i].type = 'input';
        network.input++;
        inputs.push(nodes[i]);
        nodes.splice(i, 1);
      }
    }

    // Input nodes are always first, output nodes are always last
    nodes = inputs.concat(nodes).concat(outputs);

    if (network.input === 0 || network.output === 0) {
      throw new Error('Given nodes have no clear input/output node!');
    }

    for (i = 0; i < nodes.length; i++) {
      var j;
      for (j = 0; j < nodes[i].connections.out.length; j++) {
        network.connections.push(nodes[i].connections.out[j]);
      }
      for (j = 0; j < nodes[i].connections.gated.length; j++) {
        network.gates.push(nodes[i].connections.gated[j]);
      }
      if (nodes[i].connections.self.weight !== 0) {
        network.selfconns.push(nodes[i].connections.self);
      }
    }

    network.nodes = nodes;

    return network;
  },

  /**
   * Creates a multilayer perceptron (MLP)
   */
  Perceptron: function () {
    // Convert arguments to Array
    var layers = Array.prototype.slice.call(arguments);
    if (layers.length < 3) {
      throw new Error('You have to specify at least 3 layers');
    }

    // Create a list of nodes/groups
    var nodes = [];
    nodes.push(new Group(layers[0]));

    for (var i = 1; i < layers.length; i++) {
      var layer = layers[i];
      layer = new Group(layer);
      nodes.push(layer);
      nodes[i - 1].connect(nodes[i], methods.connection.ALL_TO_ALL);
    }

    // Construct the network
    return architect.Construct(nodes);
  },

  /**
   * Creates a randomly connected network
   */
  Random: function (input, hidden, output, options) {
    options = options || {};

    var connections = options.connections || hidden * 2;
    var backconnections = options.backconnections || 0;
    var selfconnections = options.selfconnections || 0;
    var gates = options.gates || 0;

    var network = new Network(input, output);

    var i;
    for (i = 0; i < hidden; i++) {
      network.mutate(methods.mutation.ADD_NODE);
    }

    for (i = 0; i < connections - hidden; i++) {
      network.mutate(methods.mutation.ADD_CONN);
    }

    for (i = 0; i < backconnections; i++) {
      network.mutate(methods.mutation.ADD_BACK_CONN);
    }

    for (i = 0; i < selfconnections; i++) {
      network.mutate(methods.mutation.ADD_SELF_CONN);
    }

    for (i = 0; i < gates; i++) {
      network.mutate(methods.mutation.ADD_GATE);
    }

    return network;
  },

  /**
   * Creates a long short-term memory network
   */
  LSTM: function () {
    var args = Array.prototype.slice.call(arguments);
    if (args.length < 3) {
      throw new Error('You have to specify at least 3 layers');
    }

    var last = args.pop();

    var outputLayer;
    if (typeof last === 'number') {
      outputLayer = new Group(last);
      last = {};
    } else {
      outputLayer = new Group(args.pop()); // last argument
    }

    outputLayer.set({
      type: 'output'
    });

    var options = {};
    options.memoryToMemory = last.memoryToMemory || false;
    options.outputToMemory = last.outputToMemory || false;
    options.outputToGates = last.outputToGates || false;
    options.inputToOutput = last.inputToOutput === undefined ? true : last.inputToOutput;
    options.inputToDeep = last.inputToDeep === undefined ? true : last.inputToDeep;

    var inputLayer = new Group(args.shift()); // first argument
    inputLayer.set({
      type: 'input'
    });

    var blocks = args; // all the arguments in the middle

    var nodes = [];
    nodes.push(inputLayer);

    var previous = inputLayer;
    for (var i = 0; i < blocks.length; i++) {
      var block = blocks[i];

      // Init required nodes (in activation order)
      var inputGate = new Group(block);
      var forgetGate = new Group(block);
      var memoryCell = new Group(block);
      var outputGate = new Group(block);
      var outputBlock = i === blocks.length - 1 ? outputLayer : new Group(block);

      inputGate.set({
        bias: 1
      });
      forgetGate.set({
        bias: 1
      });
      outputGate.set({
        bias: 1
      });

      // Connect the input with all the nodes
      var input = previous.connect(memoryCell, methods.connection.ALL_TO_ALL);
      previous.connect(inputGate, methods.connection.ALL_TO_ALL);
      previous.connect(outputGate, methods.connection.ALL_TO_ALL);
      previous.connect(forgetGate, methods.connection.ALL_TO_ALL);

      // Set up internal connections
      memoryCell.connect(inputGate, methods.connection.ALL_TO_ALL);
      memoryCell.connect(forgetGate, methods.connection.ALL_TO_ALL);
      memoryCell.connect(outputGate, methods.connection.ALL_TO_ALL);
      var forget = memoryCell.connect(memoryCell, methods.connection.ONE_TO_ONE);
      var output = memoryCell.connect(outputBlock, methods.connection.ALL_TO_ALL);

      // Set up gates
      inputGate.gate(input, methods.gating.INPUT);
      forgetGate.gate(forget, methods.gating.SELF);
      outputGate.gate(output, methods.gating.OUTPUT);

      // Input to all memory cells
      if (options.inputToDeep && i > 0) {
        var input = inputLayer.connect(memoryCell, methods.connection.ALL_TO_ALL);
        inputGate.gate(input, methods.gating.INPUT);
      }

      // Optional connections
      if (options.memoryToMemory) {
        var input = memoryCell.connect(memoryCell, methods.connection.ALL_TO_ELSE);
        inputGate.gate(input, methods.gating.INPUT);
      }

      if (options.outputToMemory) {
        var input = outputLayer.connect(memoryCell, methods.connection.ALL_TO_ALL);
        inputGate.gate(input, methods.gating.INPUT);
      }

      if (options.outputToGates) {
        outputLayer.connect(inputGate, methods.connection.ALL_TO_ALL);
        outputLayer.connect(forgetGate, methods.connection.ALL_TO_ALL);
        outputLayer.connect(outputGate, methods.connection.ALL_TO_ALL);
      }

      // Add to array
      nodes.push(inputGate);
      nodes.push(forgetGate);
      nodes.push(memoryCell);
      nodes.push(outputGate);
      if (i !== blocks.length - 1) nodes.push(outputBlock);

      previous = outputBlock;
    }

    // input to output direct connection
    if (options.inputToOutput) {
      inputLayer.connect(outputLayer, methods.connection.ALL_TO_ALL);
    }

    nodes.push(outputLayer);
    return architect.Construct(nodes);
  },

  /**
   * Creates a gated recurrent unit network
   */
  GRU: function () {
    var args = Array.prototype.slice.call(arguments);
    if (args.length < 3) {
      throw new Error('not enough layers (minimum 3) !!');
    }

    var inputLayer = new Group(args.shift()); // first argument
    var outputLayer = new Group(args.pop()); // last argument
    var blocks = args; // all the arguments in the middle

    var nodes = [];
    nodes.push(inputLayer);

    var previous = inputLayer;
    for (var i = 0; i < blocks.length; i++) {
      var layer = new Layer.GRU(blocks[i]);
      previous.connect(layer);
      previous = layer;

      nodes.push(layer);
    }

    previous.connect(outputLayer);
    nodes.push(outputLayer);

    return architect.Construct(nodes);
  },

  /**
   * Creates a hopfield network of the given size
   */
  Hopfield: function (size) {
    var input = new Group(size);
    var output = new Group(size);

    input.connect(output, methods.connection.ALL_TO_ALL);

    input.set({
      type: 'input'
    });
    output.set({
      squash: methods.activation.STEP,
      type: 'output'
    });

    var network = new architect.Construct([input, output]);

    return network;
  },

  /**
   * Creates a NARX network (remember previous inputs/outputs)
   */
  NARX: function (inputSize, hiddenLayers, outputSize, previousInput, previousOutput) {
    if (!Array.isArray(hiddenLayers)) {
      hiddenLayers = [hiddenLayers];
    }

    var nodes = [];

    var input = new Layer.Dense(inputSize);
    var inputMemory = new Layer.Memory(inputSize, previousInput);
    var hidden = [];
    var output = new Layer.Dense(outputSize);
    var outputMemory = new Layer.Memory(outputSize, previousOutput);

    nodes.push(input);
    nodes.push(outputMemory);

    for (var i = 0; i < hiddenLayers.length; i++) {
      var hiddenLayer = new Layer.Dense(hiddenLayers[i]);
      hidden.push(hiddenLayer);
      nodes.push(hiddenLayer);
      if (typeof hidden[i - 1] !== 'undefined') {
        hidden[i - 1].connect(hiddenLayer, methods.connection.ALL_TO_ALL);
      }
    }

    nodes.push(inputMemory);
    nodes.push(output);

    input.connect(hidden[0], methods.connection.ALL_TO_ALL);
    input.connect(inputMemory, methods.connection.ONE_TO_ONE, 1);
    inputMemory.connect(hidden[0], methods.connection.ALL_TO_ALL);
    hidden[hidden.length - 1].connect(output, methods.connection.ALL_TO_ALL);
    output.connect(outputMemory, methods.connection.ONE_TO_ONE, 1);
    outputMemory.connect(hidden[0], methods.connection.ALL_TO_ALL);

    input.set({
      type: 'input'
    });
    output.set({
      type: 'output'
    });

    return architect.Construct(nodes);
  }
};




/*******************************************************************************
                                         NODE
*******************************************************************************/

function Node (type) {
  this.bias = (type === 'input') ? 0 : Math.random() * 0.2 - 0.1;
  this.squash = methods.activation.LOGISTIC;
  this.type = type || 'hidden';

  this.activation = 0;
  this.state = 0;
  this.old = 0;

  // For dropout
  this.mask = 1;

  // For tracking momentum
  this.previousDeltaBias = 0;

  // Batch training
  this.totalDeltaBias = 0;

  this.connections = {
    in: [],
    out: [],
    gated: [],
    self: new Connection(this, this, 0)
  };

  // Data for backpropagation
  this.error = {
    responsibility: 0,
    projected: 0,
    gated: 0
  };
}

Node.prototype = {
  /**
   * Activates the node
   */
  activate: function (input) {
    // Check if an input is given
    if (typeof input !== 'undefined') {
      this.activation = input;
      return this.activation;
    }

    this.old = this.state;

    // All activation sources coming from the node itself
    this.state = this.connections.self.gain * this.connections.self.weight * this.state + this.bias;

    // Activation sources coming from connections
    var i;
    for (i = 0; i < this.connections.in.length; i++) {
      var connection = this.connections.in[i];
      this.state += connection.from.activation * connection.weight * connection.gain;
    }

    // Squash the values received
    this.activation = this.squash(this.state) * this.mask;
    this.derivative = this.squash(this.state, true);

    // Update traces
    var nodes = [];
    var influences = [];

    for (i = 0; i < this.connections.gated.length; i++) {
      var conn = this.connections.gated[i];
      var node = conn.to;

      var index = nodes.indexOf(node);
      if (index > -1) {
        influences[index] += conn.weight * conn.from.activation;
      } else {
        nodes.push(node);
        influences.push(conn.weight * conn.from.activation +
          (node.connections.self.gater === this ? node.old : 0));
      }

      // Adjust the gain to this nodes' activation
      conn.gain = this.activation;
    }

    for (i = 0; i < this.connections.in.length; i++) {
      var connection = this.connections.in[i];

      // Elegibility trace
      connection.elegibility = this.connections.self.gain * this.connections.self.weight *
        connection.elegibility + connection.from.activation * connection.gain;

      // Extended trace
      for (var j = 0; j < nodes.length; j++) {
        var node = nodes[j];
        var influence = influences[j];

        var index = connection.xtrace.nodes.indexOf(node);

        if (index > -1) {
          connection.xtrace.values[index] = node.connections.self.gain * node.connections.self.weight *
            connection.xtrace.values[index] + this.derivative * connection.elegibility * influence;
        } else {
          // Does not exist there yet, might be through mutation
          connection.xtrace.nodes.push(node);
          connection.xtrace.values.push(this.derivative * connection.elegibility * influence);
        }
      }
    }

    return this.activation;
  },

  /**
   * Activates the node without calculating elegibility traces and such
   */
  noTraceActivate: function (input) {
    // Check if an input is given
    if (typeof input !== 'undefined') {
      this.activation = input;
      return this.activation;
    }

    // All activation sources coming from the node itself
    this.state = this.connections.self.gain * this.connections.self.weight * this.state + this.bias;

    // Activation sources coming from connections
    var i;
    for (i = 0; i < this.connections.in.length; i++) {
      var connection = this.connections.in[i];
      this.state += connection.from.activation * connection.weight * connection.gain;
    }

    // Squash the values received
    this.activation = this.squash(this.state);

    for (i = 0; i < this.connections.gated.length; i++) {
      this.connections.gated[i].gain = this.activation;
    }

    return this.activation;
  },

  /**
   * Back-propagate the error, aka learn
   */
  propagate: function (rate, momentum, update, target) {
    momentum = momentum || 0;
    rate = rate || 0.3;

    // Error accumulator
    var error = 0;

    // Output nodes get their error from the enviroment
    if (this.type === 'output') {
      this.error.responsibility = this.error.projected = target - this.activation;
    } else { // the rest of the nodes compute their error responsibilities by backpropagation
      // error responsibilities from all the connections projected from this node
      var i;
      for (i = 0; i < this.connections.out.length; i++) {
        var connection = this.connections.out[i];
        var node = connection.to;
        // Eq. 21
        error += node.error.responsibility * connection.weight * connection.gain;
      }

      // Projected error responsibility
      this.error.projected = this.derivative * error;

      // Error responsibilities from all connections gated by this neuron
      error = 0;

      for (i = 0; i < this.connections.gated.length; i++) {
        var conn = this.connections.gated[i];
        var node = conn.to;
        var influence = node.connections.self.gater === this ? node.old : 0;

        influence += conn.weight * conn.from.activation;
        error += node.error.responsibility * influence;
      }

      // Gated error responsibility
      this.error.gated = this.derivative * error;

      // Error responsibility
      this.error.responsibility = this.error.projected + this.error.gated;
    }

    if (this.type === 'constant') return;

    // Adjust all the node's incoming connections
    for (i = 0; i < this.connections.in.length; i++) {
      var connection = this.connections.in[i];

      var gradient = this.error.projected * connection.elegibility;

      for (var j = 0; j < connection.xtrace.nodes.length; j++) {
        var node = connection.xtrace.nodes[j];
        var value = connection.xtrace.values[j];
        gradient += node.error.responsibility * value;
      }

      // Adjust weight
      var deltaWeight = rate * gradient * this.mask;
      connection.totalDeltaWeight += deltaWeight;
      if (update) {
        connection.totalDeltaWeight += momentum * connection.previousDeltaWeight;
        connection.weight += connection.totalDeltaWeight;
        connection.previousDeltaWeight = connection.totalDeltaWeight;
        connection.totalDeltaWeight = 0;
      }
    }

    // Adjust bias
    var deltaBias = rate * this.error.responsibility;
    this.totalDeltaBias += deltaBias;
    if (update) {
      this.totalDeltaBias += momentum * this.previousDeltaBias;
      this.bias += this.totalDeltaBias;
      this.previousDeltaBias = this.totalDeltaBias;
      this.totalDeltaBias = 0;
    }
  },

  /**
   * Creates a connection from this node to the given node
   */
  connect: function (target, weight) {
    var connections = [];
    if (typeof target.bias !== 'undefined') { // must be a node!
      if (target === this) {
        // Turn on the self connection by setting the weight
        if (this.connections.self.weight !== 0) {
          if (config.warnings) console.warn('This connection already exists!');
        } else {
          this.connections.self.weight = weight || 1;
        }
        connections.push(this.connections.self);
      } else if (this.isProjectingTo(target)) {
        throw new Error('Already projecting a connection to this node!');
      } else {
        var connection = new Connection(this, target, weight);
        target.connections.in.push(connection);
        this.connections.out.push(connection);

        connections.push(connection);
      }
    } else { // should be a group
      for (var i = 0; i < target.nodes.length; i++) {
        var connection = new Connection(this, target.nodes[i], weight);
        target.nodes[i].connections.in.push(connection);
        this.connections.out.push(connection);
        target.connections.in.push(connection);

        connections.push(connection);
      }
    }
    return connections;
  },

  /**
   * Disconnects this node from the other node
   */
  disconnect: function (node, twosided) {
    if (this === node) {
      this.connections.self.weight = 0;
      return;
    }

    for (var i = 0; i < this.connections.out.length; i++) {
      var conn = this.connections.out[i];
      if (conn.to === node) {
        this.connections.out.splice(i, 1);
        var j = conn.to.connections.in.indexOf(conn);
        conn.to.connections.in.splice(j, 1);
        if (conn.gater !== null) conn.gater.ungate(conn);
        break;
      }
    }

    if (twosided) {
      node.disconnect(this);
    }
  },

  /**
   * Make this node gate a connection
   */
  gate: function (connections) {
    if (!Array.isArray(connections)) {
      connections = [connections];
    }

    for (var i = 0; i < connections.length; i++) {
      var connection = connections[i];

      this.connections.gated.push(connection);
      connection.gater = this;
    }
  },

  /**
   * Removes the gates from this node from the given connection(s)
   */
  ungate: function (connections) {
    if (!Array.isArray(connections)) {
      connections = [connections];
    }

    for (var i = connections.length - 1; i >= 0; i--) {
      var connection = connections[i];

      var index = this.connections.gated.indexOf(connection);
      this.connections.gated.splice(index, 1);
      connection.gater = null;
      connection.gain = 1;
    }
  },

  /**
   * Clear the context of the node
   */
  clear: function () {
    for (var i = 0; i < this.connections.in.length; i++) {
      var connection = this.connections.in[i];

      connection.elegibility = 0;
      connection.xtrace = {
        nodes: [],
        values: []
      };
    }

    for (i = 0; i < this.connections.gated.length; i++) {
      var conn = this.connections.gated[i];
      conn.gain = 0;
    }

    this.error.responsibility = this.error.projected = this.error.gated = 0;
    this.old = this.state = this.activation = 0;
  },

  /**
   * Mutates the node with the given method
   */
  mutate: function (method) {
    if (typeof method === 'undefined') {
      throw new Error('No mutate method given!');
    } else if (!(method.name in methods.mutation)) {
      throw new Error('This method does not exist!');
    }

    switch (method) {
      case methods.mutation.MOD_ACTIVATION:
        // Can't be the same squash
        var squash = method.allowed[(method.allowed.indexOf(this.squash) + Math.floor(Math.random() * (method.allowed.length - 1)) + 1) % method.allowed.length];
        this.squash = squash;
        break;
      case methods.mutation.MOD_BIAS:
        var modification = Math.random() * (method.max - method.min) + method.min;
        this.bias += modification;
        break;
    }
  },

  /**
   * Checks if this node is projecting to the given node
   */
  isProjectingTo: function (node) {
    if (node === this && this.connections.self.weight !== 0) return true;

    for (var i = 0; i < this.connections.out.length; i++) {
      var conn = this.connections.out[i];
      if (conn.to === node) {
        return true;
      }
    }
    return false;
  },

  /**
   * Checks if the given node is projecting to this node
   */
  isProjectedBy: function (node) {
    if (node === this && this.connections.self.weight !== 0) return true;

    for (var i = 0; i < this.connections.in.length; i++) {
      var conn = this.connections.in[i];
      if (conn.from === node) {
        return true;
      }
    }

    return false;
  },

  /**
   * Converts the node to a json object
   */
  toJSON: function () {
    var json = {
      bias: this.bias,
      type: this.type,
      squash: this.squash.name,
      mask: this.mask
    };

    return json;
  }
};

/**
 * Convert a json object to a node
 */
Node.fromJSON = function (json) {
  var node = new Node();
  node.bias = json.bias;
  node.type = json.type;
  node.mask = json.mask;
  node.squash = methods.activation[json.squash];

  return node;
};

/*******************************************************************************
                                         Group
*******************************************************************************/

function Layer () {
  this.output = null;

  this.nodes = [];
  this.connections = { in: [],
    out: [],
    self: []
  };
}

Layer.prototype = {
  /**
   * Activates all the nodes in the group
   */
  activate: function (value) {
    var values = [];

    if (typeof value !== 'undefined' && value.length !== this.nodes.length) {
      throw new Error('Array with values should be same as the amount of nodes!');
    }

    for (var i = 0; i < this.nodes.length; i++) {
      var activation;
      if (typeof value === 'undefined') {
        activation = this.nodes[i].activate();
      } else {
        activation = this.nodes[i].activate(value[i]);
      }

      values.push(activation);
    }

    return values;
  },

  /**
   * Propagates all the node in the group
   */
  propagate: function (rate, momentum, target) {
    if (typeof target !== 'undefined' && target.length !== this.nodes.length) {
      throw new Error('Array with values should be same as the amount of nodes!');
    }

    for (var i = this.nodes.length - 1; i >= 0; i--) {
      if (typeof target === 'undefined') {
        this.nodes[i].propagate(rate, momentum, true);
      } else {
        this.nodes[i].propagate(rate, momentum, true, target[i]);
      }
    }
  },

  /**
   * Connects the nodes in this group to nodes in another group or just a node
   */
  connect: function (target, method, weight) {
    var connections;
    if (target instanceof Group || target instanceof Node) {
      connections = this.output.connect(target, method, weight);
    } else if (target instanceof Layer) {
      connections = target.input(this, method, weight);
    }

    return connections;
  },

  /**
   * Make nodes from this group gate the given connection(s)
   */
  gate: function (connections, method) {
    this.output.gate(connections, method);
  },

  /**
   * Sets the value of a property for every node
   */
  set: function (values) {
    for (var i = 0; i < this.nodes.length; i++) {
      var node = this.nodes[i];

      if (node instanceof Node) {
        if (typeof values.bias !== 'undefined') {
          node.bias = values.bias;
        }

        node.squash = values.squash || node.squash;
        node.type = values.type || node.type;
      } else if (node instanceof Group) {
        node.set(values);
      }
    }
  },

  /**
   * Disconnects all nodes from this group from another given group/node
   */
  disconnect: function (target, twosided) {
    twosided = twosided || false;

    // In the future, disconnect will return a connection so indexOf can be used
    var i, j, k;
    if (target instanceof Group) {
      for (i = 0; i < this.nodes.length; i++) {
        for (j = 0; j < target.nodes.length; j++) {
          this.nodes[i].disconnect(target.nodes[j], twosided);

          for (k = this.connections.out.length - 1; k >= 0; k--) {
            var conn = this.connections.out[k];

            if (conn.from === this.nodes[i] && conn.to === target.nodes[j]) {
              this.connections.out.splice(k, 1);
              break;
            }
          }

          if (twosided) {
            for (k = this.connections.in.length - 1; k >= 0; k--) {
              var conn = this.connections.in[k];

              if (conn.from === target.nodes[j] && conn.to === this.nodes[i]) {
                this.connections.in.splice(k, 1);
                break;
              }
            }
          }
        }
      }
    } else if (target instanceof Node) {
      for (i = 0; i < this.nodes.length; i++) {
        this.nodes[i].disconnect(target, twosided);

        for (j = this.connections.out.length - 1; j >= 0; j--) {
          var conn = this.connections.out[j];

          if (conn.from === this.nodes[i] && conn.to === target) {
            this.connections.out.splice(j, 1);
            break;
          }
        }

        if (twosided) {
          for (k = this.connections.in.length - 1; k >= 0; k--) {
            var conn = this.connections.in[k];

            if (conn.from === target && conn.to === this.nodes[i]) {
              this.connections.in.splice(k, 1);
              break;
            }
          }
        }
      }
    }
  },

  /**
   * Clear the context of this group
   */
  clear: function () {
    for (var i = 0; i < this.nodes.length; i++) {
      this.nodes[i].clear();
    }
  }
};

Layer.Dense = function (size) {
  // Create the layer
  var layer = new Layer();

  // Init required nodes (in activation order)
  var block = new Group(size);

  layer.nodes.push(block);
  layer.output = block;

  layer.input = function (from, method, weight) {
    if (from instanceof Layer) from = from.output;
    method = method || methods.connection.ALL_TO_ALL;
    return from.connect(block, method, weight);
  };

  return layer;
};

Layer.LSTM = function (size) {
  // Create the layer
  var layer = new Layer();

  // Init required nodes (in activation order)
  var inputGate = new Group(size);
  var forgetGate = new Group(size);
  var memoryCell = new Group(size);
  var outputGate = new Group(size);
  var outputBlock = new Group(size);

  inputGate.set({
    bias: 1
  });
  forgetGate.set({
    bias: 1
  });
  outputGate.set({
    bias: 1
  });

  // Set up internal connections
  memoryCell.connect(inputGate, methods.connection.ALL_TO_ALL);
  memoryCell.connect(forgetGate, methods.connection.ALL_TO_ALL);
  memoryCell.connect(outputGate, methods.connection.ALL_TO_ALL);
  var forget = memoryCell.connect(memoryCell, methods.connection.ONE_TO_ONE);
  var output = memoryCell.connect(outputBlock, methods.connection.ALL_TO_ALL);

  // Set up gates
  forgetGate.gate(forget, methods.gating.SELF);
  outputGate.gate(output, methods.gating.OUTPUT);

  // Add to nodes array
  layer.nodes = [inputGate, forgetGate, memoryCell, outputGate, outputBlock];

  // Define output
  layer.output = outputBlock;

  layer.input = function (from, method, weight) {
    if (from instanceof Layer) from = from.output;
    method = method || methods.connection.ALL_TO_ALL;
    var connections = [];

    var input = from.connect(memoryCell, method, weight);
    connections = connections.concat(input);

    connections = connections.concat(from.connect(inputGate, method, weight));
    connections = connections.concat(from.connect(outputGate, method, weight));
    connections = connections.concat(from.connect(forgetGate, method, weight));

    inputGate.gate(input, methods.gating.INPUT);

    return connections;
  };

  return layer;
};

Layer.GRU = function (size) {
  // Create the layer
  var layer = new Layer();

  var updateGate = new Group(size);
  var inverseUpdateGate = new Group(size);
  var resetGate = new Group(size);
  var memoryCell = new Group(size);
  var output = new Group(size);
  var previousOutput = new Group(size);

  previousOutput.set({
    bias: 0,
    squash: methods.activation.IDENTITY,
    type: 'constant'
  });
  memoryCell.set({
    squash: methods.activation.TANH
  });
  inverseUpdateGate.set({
    bias: 0,
    squash: methods.activation.INVERSE,
    type: 'constant'
  });
  updateGate.set({
    bias: 1
  });
  resetGate.set({
    bias: 0
  });

  // Update gate calculation
  previousOutput.connect(updateGate, methods.connection.ALL_TO_ALL);

  // Inverse update gate calculation
  updateGate.connect(inverseUpdateGate, methods.connection.ONE_TO_ONE, 1);

  // Reset gate calculation
  previousOutput.connect(resetGate, methods.connection.ALL_TO_ALL);

  // Memory calculation
  var reset = previousOutput.connect(memoryCell, methods.connection.ALL_TO_ALL);

  resetGate.gate(reset, methods.gating.OUTPUT); // gate

  // Output calculation
  var update1 = previousOutput.connect(output, methods.connection.ALL_TO_ALL);
  var update2 = memoryCell.connect(output, methods.connection.ALL_TO_ALL);

  updateGate.gate(update1, methods.gating.OUTPUT);
  inverseUpdateGate.gate(update2, methods.gating.OUTPUT);

  // Previous output calculation
  output.connect(previousOutput, methods.connection.ONE_TO_ONE, 1);

  // Add to nodes array
  layer.nodes = [updateGate, inverseUpdateGate, resetGate, memoryCell, output, previousOutput];

  layer.output = output;

  layer.input = function (from, method, weight) {
    if (from instanceof Layer) from = from.output;
    method = method || methods.connection.ALL_TO_ALL;
    var connections = [];

    connections = connections.concat(from.connect(updateGate, method, weight));
    connections = connections.concat(from.connect(resetGate, method, weight));
    connections = connections.concat(from.connect(memoryCell, method, weight));

    return connections;
  };

  return layer;
};

Layer.Memory = function (size, memory) {
  // Create the layer
  var layer = new Layer();
  // Because the output can only be one group, we have to put the nodes all in óne group

  var previous = null;
  var i;
  for (i = 0; i < memory; i++) {
    var block = new Group(size);

    block.set({
      squash: methods.activation.IDENTITY,
      bias: 0,
      type: 'constant'
    });

    if (previous != null) {
      previous.connect(block, methods.connection.ONE_TO_ONE, 1);
    }

    layer.nodes.push(block);
    previous = block;
  }

  layer.nodes.reverse();

  for (i = 0; i < layer.nodes.length; i++) {
    layer.nodes[i].nodes.reverse();
  }

  // Because output can only be óne group, fit all memory nodes in óne group
  var outputGroup = new Group(0);
  for (var group in layer.nodes) {
    outputGroup.nodes = outputGroup.nodes.concat(layer.nodes[group].nodes);
  }
  layer.output = outputGroup;

  layer.input = function (from, method, weight) {
    if (from instanceof Layer) from = from.output;
    method = method || methods.connection.ALL_TO_ALL;

    if (from.nodes.length !== layer.nodes[layer.nodes.length - 1].nodes.length) {
      throw new Error('Previous layer size must be same as memory size');
    }

    return from.connect(layer.nodes[layer.nodes.length - 1], methods.connection.ONE_TO_ONE, 1);
  };

  return layer;
};


/*******************************************************************************
                                         Group
*******************************************************************************/

function Group (size) {
  this.nodes = [];
  this.connections = {
    in: [],
    out: [],
    self: []
  };

  for (var i = 0; i < size; i++) {
    this.nodes.push(new Node());
  }
}

Group.prototype = {
  /**
   * Activates all the nodes in the group
   */
  activate: function (value) {
    var values = [];

    if (typeof value !== 'undefined' && value.length !== this.nodes.length) {
      throw new Error('Array with values should be same as the amount of nodes!');
    }

    for (var i = 0; i < this.nodes.length; i++) {
      var activation;
      if (typeof value === 'undefined') {
        activation = this.nodes[i].activate();
      } else {
        activation = this.nodes[i].activate(value[i]);
      }

      values.push(activation);
    }

    return values;
  },

  /**
   * Propagates all the node in the group
   */
  propagate: function (rate, momentum, target) {
    if (typeof target !== 'undefined' && target.length !== this.nodes.length) {
      throw new Error('Array with values should be same as the amount of nodes!');
    }

    for (var i = this.nodes.length - 1; i >= 0; i--) {
      if (typeof target === 'undefined') {
        this.nodes[i].propagate(rate, momentum, true);
      } else {
        this.nodes[i].propagate(rate, momentum, true, target[i]);
      }
    }
  },

  /**
   * Connects the nodes in this group to nodes in another group or just a node
   */
  connect: function (target, method, weight) {
    var connections = [];
    var i, j;
    if (target instanceof Group) {
      if (typeof method === 'undefined') {
        if (this !== target) {
          if (config.warnings) console.warn('No group connection specified, using ALL_TO_ALL');
          method = methods.connection.ALL_TO_ALL;
        } else {
          if (config.warnings) console.warn('No group connection specified, using ONE_TO_ONE');
          method = methods.connection.ONE_TO_ONE;
        }
      }
      if (method === methods.connection.ALL_TO_ALL || method === methods.connection.ALL_TO_ELSE) {
        for (i = 0; i < this.nodes.length; i++) {
          for (j = 0; j < target.nodes.length; j++) {
            if (method === methods.connection.ALL_TO_ELSE && this.nodes[i] === target.nodes[j]) continue;
            var connection = this.nodes[i].connect(target.nodes[j], weight);
            this.connections.out.push(connection[0]);
            target.connections.in.push(connection[0]);
            connections.push(connection[0]);
          }
        }
      } else if (method === methods.connection.ONE_TO_ONE) {
        if (this.nodes.length !== target.nodes.length) {
          throw new Error('From and To group must be the same size!');
        }

        for (i = 0; i < this.nodes.length; i++) {
          var connection = this.nodes[i].connect(target.nodes[i], weight);
          this.connections.self.push(connection[0]);
          connections.push(connection[0]);
        }
      }
    } else if (target instanceof Layer) {
      connections = target.input(this, method, weight);
    } else if (target instanceof Node) {
      for (i = 0; i < this.nodes.length; i++) {
        var connection = this.nodes[i].connect(target, weight);
        this.connections.out.push(connection[0]);
        connections.push(connection[0]);
      }
    }

    return connections;
  },

  /**
   * Make nodes from this group gate the given connection(s)
   */
  gate: function (connections, method) {
    if (typeof method === 'undefined') {
      throw new Error('Please specify Gating.INPUT, Gating.OUTPUT');
    }

    if (!Array.isArray(connections)) {
      connections = [connections];
    }

    var nodes1 = [];
    var nodes2 = [];

    var i, j;
    for (i = 0; i < connections.length; i++) {
      var connection = connections[i];
      if (!nodes1.includes(connection.from)) nodes1.push(connection.from);
      if (!nodes2.includes(connection.to)) nodes2.push(connection.to);
    }

    switch (method) {
      case methods.gating.INPUT:
        for (i = 0; i < nodes2.length; i++) {
          var node = nodes2[i];
          var gater = this.nodes[i % this.nodes.length];

          for (j = 0; j < node.connections.in.length; j++) {
            var conn = node.connections.in[j];
            if (connections.includes(conn)) {
              gater.gate(conn);
            }
          }
        }
        break;
      case methods.gating.OUTPUT:
        for (i = 0; i < nodes1.length; i++) {
          var node = nodes1[i];
          var gater = this.nodes[i % this.nodes.length];

          for (j = 0; j < node.connections.out.length; j++) {
            var conn = node.connections.out[j];
            if (connections.includes(conn)) {
              gater.gate(conn);
            }
          }
        }
        break;
      case methods.gating.SELF:
        for (i = 0; i < nodes1.length; i++) {
          var node = nodes1[i];
          var gater = this.nodes[i % this.nodes.length];

          if (connections.includes(node.connections.self)) {
            gater.gate(node.connections.self);
          }
        }
    }
  },

  /**
   * Sets the value of a property for every node
   */
  set: function (values) {
    for (var i = 0; i < this.nodes.length; i++) {
      if (typeof values.bias !== 'undefined') {
        this.nodes[i].bias = values.bias;
      }

      this.nodes[i].squash = values.squash || this.nodes[i].squash;
      this.nodes[i].type = values.type || this.nodes[i].type;
    }
  },

  /**
   * Disconnects all nodes from this group from another given group/node
   */
  disconnect: function (target, twosided) {
    twosided = twosided || false;

    // In the future, disconnect will return a connection so indexOf can be used
    var i, j, k;
    if (target instanceof Group) {
      for (i = 0; i < this.nodes.length; i++) {
        for (j = 0; j < target.nodes.length; j++) {
          this.nodes[i].disconnect(target.nodes[j], twosided);

          for (k = this.connections.out.length - 1; k >= 0; k--) {
            var conn = this.connections.out[k];

            if (conn.from === this.nodes[i] && conn.to === target.nodes[j]) {
              this.connections.out.splice(k, 1);
              break;
            }
          }

          if (twosided) {
            for (k = this.connections.in.length - 1; k >= 0; k--) {
              var conn = this.connections.in[k];

              if (conn.from === target.nodes[j] && conn.to === this.nodes[i]) {
                this.connections.in.splice(k, 1);
                break;
              }
            }
          }
        }
      }
    } else if (target instanceof Node) {
      for (i = 0; i < this.nodes.length; i++) {
        this.nodes[i].disconnect(target, twosided);

        for (j = this.connections.out.length - 1; j >= 0; j--) {
          var conn = this.connections.out[j];

          if (conn.from === this.nodes[i] && conn.to === target) {
            this.connections.out.splice(j, 1);
            break;
          }
        }

        if (twosided) {
          for (j = this.connections.in.length - 1; j >= 0; j--) {
            var conn = this.connections.in[j];

            if (conn.from === target && conn.to === this.nodes[i]) {
              this.connections.in.splice(j, 1);
              break;
            }
          }
        }
      }
    }
  },

  /**
   * Clear the context of this group
   */
  clear: function () {
    for (var i = 0; i < this.nodes.length; i++) {
      this.nodes[i].clear();
    }
  }
};

/* Easier variable naming */
var selection = methods.selection;

/*******************************************************************************
                                         NEAT
*******************************************************************************/

function Neat (input, output, fitness, options) {
  this.input = input; // The input size of the networks
  this.output = output; // The output size of the networks
  this.fitness = fitness; // The fitness function to evaluate the networks

  // Configure options
  options = options || {};
  this.equal = options.equal || false;
  this.clear = options.clear || false;
  this.popsize = options.popsize || 50;
  this.elitism = options.elitism || 0;
  this.provenance = options.provenance || 0;
  this.mutationRate = options.mutationRate || 0.3;
  this.mutationAmount = options.mutationAmount || 1;

  this.fitnessPopulation = options.fitnessPopulation || false;

  this.selection = options.selection || methods.selection.POWER;
  this.crossover = options.crossover || [
    methods.crossover.SINGLE_POINT,
    methods.crossover.TWO_POINT,
    methods.crossover.UNIFORM,
    methods.crossover.AVERAGE
  ];
  this.mutation = options.mutation || methods.mutation.FFW;

  this.template = options.network || false;

  this.maxNodes = options.maxNodes || Infinity;
  this.maxConns = options.maxConns || Infinity;
  this.maxGates = options.maxGates || Infinity;

  // Custom mutation selection function if given
  this.selectMutationMethod = typeof options.mutationSelection === 'function' ? options.mutationSelection.bind(this) : this.selectMutationMethod;

  // Generation counter
  this.generation = 0;

  // Initialise the genomes
  this.createPool(this.template);
}

Neat.prototype = {
  /**
   * Create the initial pool of genomes
   */
  createPool: function (network) {
    this.population = [];

    for (var i = 0; i < this.popsize; i++) {
      var copy;
      if (this.template) {
        copy = Network.fromJSON(network.toJSON());
      } else {
        copy = new Network(this.input, this.output);
      }
      copy.score = undefined;
      this.population.push(copy);
    }
  },

  /**
   * Evaluates, selects, breeds and mutates population
   */
  evolve: function () {
    // Check if evaluated, sort the population
    if (typeof this.population[this.population.length - 1].score === 'undefined') {
      this.evaluate();
    }
    this.sort();

    var fittest = Network.fromJSON(this.population[0].toJSON());
    fittest.score = this.population[0].score;

    var newPopulation = [];

    // Elitism
    var elitists = [];
    for (var i = 0; i < this.elitism; i++) {
      elitists.push(this.population[i]);
    }

    // Provenance
    for (i = 0; i < this.provenance; i++) {
      newPopulation.push(Network.fromJSON(this.template.toJSON()));
    }

    // Breed the next individuals
    for (i = 0; i < this.popsize - this.elitism - this.provenance; i++) {
      newPopulation.push(this.getOffspring());
    }

    // Replace the old population with the new population
    this.population = newPopulation;
    this.mutate();

    // this.population.push(...elitists);
    var _this$population;
    (_this$population = this.population).push.apply(_this$population, elitists); 

    // Reset the scores
    for (i = 0; i < this.population.length; i++) {
      this.population[i].score = undefined;
    }

    this.generation++;

    return fittest;
  },

  /**
   * Breeds two parents into an offspring, population MUST be surted
   */
  getOffspring: function () {
    var parent1 = this.getParent();
    var parent2 = this.getParent();

    return Network.crossOver(parent1, parent2, this.equal);
  },

  /**
   * Selects a random mutation method for a genome according to the parameters
   */
  selectMutationMethod: function (genome) {
    var mutationMethod = this.mutation[Math.floor(Math.random() * this.mutation.length)];

    if (mutationMethod === methods.mutation.ADD_NODE && genome.nodes.length >= this.maxNodes) {
      if (config.warnings) console.warn('maxNodes exceeded!');
      return;
    }

    if (mutationMethod === methods.mutation.ADD_CONN && genome.connections.length >= this.maxConns) {
      if (config.warnings) console.warn('maxConns exceeded!');
      return;
    }

    if (mutationMethod === methods.mutation.ADD_GATE && genome.gates.length >= this.maxGates) {
      if (config.warnings) console.warn('maxGates exceeded!');
      return;
    }

    return mutationMethod;
  },

  /**
   * Mutates the given (or current) population
   */
  mutate: function () {
    // Elitist genomes should not be included
    for (var i = 0; i < this.population.length; i++) {
      if (Math.random() <= this.mutationRate) {
        for (var j = 0; j < this.mutationAmount; j++) {
          var mutationMethod = this.selectMutationMethod(this.population[i]);
          this.population[i].mutate(mutationMethod);
        }
      }
    }
  },

  /**
   * Evaluates the current population
   */
  evaluate: function () {
    var i;
    if (this.fitnessPopulation) {
      if (this.clear) {
        for (i = 0; i < this.population.length; i++) {
          this.population[i].clear();
        }
      }
      this.fitness(this.population);
    } else {
      for (i = 0; i < this.population.length; i++) {
        var genome = this.population[i];
        if (this.clear) genome.clear();
        genome.score = this.fitness(genome);
      }
    }
  },

  /**
   * Sorts the population by score
   */
  sort: function () {
    this.population.sort(function (a, b) {
      return b.score - a.score;
    });
  },

  /**
   * Returns the fittest genome of the current population
   */
  getFittest: function () {
    // Check if evaluated
    if (typeof this.population[this.population.length - 1].score === 'undefined') {
      this.evaluate();
    }
    if (this.population[0].score < this.population[1].score) {
      this.sort();
    }

    return this.population[0];
  },

  /**
   * Returns the average fitness of the current population
   */
  getAverage: function () {
    if (typeof this.population[this.population.length - 1].score === 'undefined') {
      this.evaluate();
    }

    var score = 0;
    for (var i = 0; i < this.population.length; i++) {
      score += this.population[i].score;
    }

    return score / this.population.length;
  },

  /**
   * Gets a genome based on the selection function
   * @return {Network} genome
   */
  getParent: function () {
    var i;
    switch (this.selection) {
      case selection.POWER:
        if (this.population[0].score < this.population[1].score) this.sort();

        var index = Math.floor(Math.pow(Math.random(), this.selection.power) * this.population.length);
        return this.population[index];
      case selection.FITNESS_PROPORTIONATE:
        // As negative fitnesses are possible
        // https://stackoverflow.com/questions/16186686/genetic-algorithm-handling-negative-fitness-values
        // this is unnecessarily run for every individual, should be changed

        var totalFitness = 0;
        var minimalFitness = 0;
        for (i = 0; i < this.population.length; i++) {
          var score = this.population[i].score;
          minimalFitness = score < minimalFitness ? score : minimalFitness;
          totalFitness += score;
        }

        minimalFitness = Math.abs(minimalFitness);
        totalFitness += minimalFitness * this.population.length;

        var random = Math.random() * totalFitness;
        var value = 0;

        for (i = 0; i < this.population.length; i++) {
          var genome = this.population[i];
          value += genome.score + minimalFitness;
          if (random < value) return genome;
        }

        // if all scores equal, return random genome
        return this.population[Math.floor(Math.random() * this.population.length)];
      case selection.TOURNAMENT:
        if (this.selection.size > this.popsize) {
          throw new Error('Your tournament size should be lower than the population size, please change methods.selection.TOURNAMENT.size');
        }

        // Create a tournament
        var individuals = [];
        for (i = 0; i < this.selection.size; i++) {
          var random = this.population[Math.floor(Math.random() * this.population.length)];
          individuals.push(random);
        }

        // Sort the tournament individuals by score
        individuals.sort(function (a, b) {
          return b.score - a.score;
        });

        // Select an individual
        for (i = 0; i < this.selection.size; i++) {
          if (Math.random() < this.selection.probability || i === this.selection.size - 1) {
            return individuals[i];
          }
        }
    }
  },
  
  
  test: function (L,data) {
    
  },

  /**
   * Export the current population to a json object
   */
  export: function () {
    var json = [];
    for (var i = 0; i < this.population.length; i++) {
      var genome = this.population[i];
      json.push(genome.toJSON());
    }

    return json;
  },

  /**
   * Import population from a json object
   */
  import: function (json) {
    var population = [];
    for (var i = 0; i < json.length; i++) {
      var genome = json[i];
      population.push(Network.fromJSON(genome));
    }
    this.population = population;
    this.popsize = population.length;
  }
};


var Neataptic = {
  methods: methods,
  Connection: Connection,
  architect: architect,
  Network: Network,
  config: config,
  Group: Group,
  Layer: Layer,
  Node: Node,
  Neat: Neat
};

module.exports = Neataptic
};
BundleModuleCode['ml/pca']=function (module,exports){
// https://github.com/bitanath/pca
var PCA = (function () {
    var options = {};
    /**
     * The first step is to subtract the mean and center data
     * 
     * @param {Array} matrix - data in an mXn matrix format
     * @returns 
     */
    function computeDeviationMatrix(matrix) {
        var unit = unitSquareMatrix(matrix.length);
        return subtract(matrix, scale(multiply(unit, matrix), 1 / matrix.length));
    }
    /**
     * Computes variance from deviation
     * 
     * @param {Array} deviation - data minus mean as calculated from computeDeviationMatrix
     * @returns 
     */
    function computeDeviationScores(deviation) {
        var devSumOfSquares = multiply(transpose(deviation), deviation);
        return devSumOfSquares;
    }
    /**
     * Calculates the var covar square matrix using either population or sample
     * 
     * @param {Array} devSumOfSquares 
     * @param {boolean} sample - true/false whether data is from sample or not
     * @returns 
     */
    function computeVarianceCovariance(devSumOfSquares, sample) {
        var varianceCovariance;
        if (sample)
            varianceCovariance = scale(devSumOfSquares, 1 / (devSumOfSquares.length - 1));
        else
            varianceCovariance = scale(devSumOfSquares, 1 / (devSumOfSquares.length));
        return varianceCovariance;
    }
    /**
     * Matrix is the deviation sum of squares as computed earlier
     * 
     * @param {Array} matrix - output of computeDeviationScores
     * @returns 
     */
    function computeSVD(matrix) {
        var result = svd(matrix);
        if (options.verbose) console.log(result)
        var eigenvectors = result.U;
        var eigenvalues = result.S;
        var results = eigenvalues.map(function (value, i) {
            var obj = {};
            obj.eigenvalue = value;
            obj.vector = eigenvectors.map(function (vector, j) {
                return -1 * vector[i]; //HACK prevent completely negative vectors
            });
            return obj;
        });
        return results;
    }
    /**
     * Get reduced dataset after removing some dimensions
     * 
     * @param {Array} data - initial matrix started out with
     * @param {rest} vectors - eigenvectors selected as part of process
     * @returns 
     */
    function computeAdjustedData(data) {
        for (var _len = arguments.length, vectorObjs = new Array(_len > 1 ? _len - 1 : 0), 
            _key = 1; _key < _len; _key++) {
            vectorObjs[_key - 1] = arguments[_key];
        }
        //FIXME no need to transpose vectors since they're already in row normal form
        var vectors = vectorObjs.map(function(v){return v.vector});
        var matrixMinusMean = computeDeviationMatrix(data);
        var adjustedData = multiply(vectors, transpose(matrixMinusMean));
        var unit = unitSquareMatrix(data.length);
        var avgData = scale(multiply(unit, data), -1 / data.length); //NOTE get the averages to add back

        var formattedAdjustData = formatData(adjustedData, 2);
        return {
            adjustedData: adjustedData,
            formattedAdjustedData: formattedAdjustData,
            avgData: avgData,
            selectedVectors: vectors
        };
    }

    /**
     * Get original data set from reduced data set (decompress)
     * @param {*} adjustedData = formatted or unformatted adjusted data
     * @param {*} vectors = selectedVectors
     * @param {*} avgData = avgData
     */
    function computeOriginalData(adjustedData, vectors, avgData) {
        var originalWithoutMean = transpose(multiply(transpose(vectors), adjustedData));
        var originalWithMean = subtract(originalWithoutMean, avgData);
        var formattedData = formatData(originalWithMean, 2);
        return {
            originalData: originalWithMean,
            formattedOriginalData: formattedData
        }
    }

    /**
     * Get percentage explained, or loss
     * @param {*} vectors 
     * @param {*} selected 
     */
    function computePercentageExplained(vectors) {
        for (var _len = arguments.length, selected = new Array(_len > 1 ? _len - 1 : 0),
             _key = 1; _key < _len; _key++) {
            selected[_key - 1] = arguments[_key];
        }
        var total = vectors.map(function (v) {
            return v.eigenvalue
        }).reduce(function (a, b) {
            return a + b;
        });
        var explained = selected.map(function (v) {
            return v.eigenvalue
        }).reduce(function (a, b) {
            return a + b;
        });
        return (explained / total);
    }

    function getEigenVectors(data) {
        return computeSVD(computeVarianceCovariance(computeDeviationScores(computeDeviationMatrix(data)), false));
    }

    function analyseTopResult(data) {
        var eigenVectors = getEigenVectors(data);
        var sorted = eigenVectors.sort(function (a, b) {
            return b.eigenvalue - a.eigenvalue;
        });
        console.log('Sorted Vectors', sorted);
        var selected = sorted[0].vector;
        return computeAdjustedData(data, selected);
    }

    function formatData(data, precision) {
        var TEN = Math.pow(10, precision || 2);
        return data.map(function (d, i) {
            return d.map(function (n) {
                return Math.round(n * TEN) / TEN;
            })
        })
    }
    /**
     * Multiplies AxB, where A and B are matrices of nXm and mXn dimensions
     * @param {} a 
     * @param {*} b 
     */
    function multiply(a, b) {
        if (!a[0] || !b[0] || !a.length || !b.length) {
            throw new Error('Both A and B should be matrices');
        }

        if (b.length !== a[0].length) {
            throw new Error('Columns in A should be the same as the number of rows in B');
        }
        var product = [];

        for (var i = 0; i < a.length; i++) {
            product[i] = []; //initialize a new row
            for (var j = 0; j < b[0].length; j++) {
                for (var k = 0; k < a[0].length; k++) {
                    (product[i])[j] = !!(product[i])[j] ? (product[i])[j] + (a[i])[k] * (b[k])[j] : (a[i])[k] * (b[k])[j];
                }
            }
        }
        return product;
    }
    /**
     * Utility function to subtract matrix b from a
     * 
     * @param {any} a 
     * @param {any} b 
     * @returns 
     */
    function subtract(a, b) {
        if (!(a.length === b.length && a[0].length === b[0].length))
            throw new Error('Both A and B should have the same dimensions');
        var result = [];
        for (var i = 0; i < a.length; i++) {
            result[i] = [];
            for (var j = 0; j < b[0].length; j++) {
                (result[i])[j] = (a[i])[j] - (b[i])[j];
            }
        }
        return result;
    }
    /**
     * Multiplies a matrix into a factor
     * 
     * @param {any} matrix 
     * @param {any} factor 
     * @returns 
     */
    function scale(matrix, factor) {
        var result = [];
        for (var i = 0; i < matrix.length; i++) {
            result[i] = [];
            for (var j = 0; j < matrix[0].length; j++) {
                (result[i])[j] = (matrix[i])[j] * factor;
            }
        }
        return result;
    }

    /**
     * Generates a unit square matrix
     * @param {*} rows = number of rows to fill
     */
    function unitSquareMatrix(rows) {
        var result = [];
        for (var i = 0; i < rows; i++) {
            result[i] = [];
            for (var j = 0; j < rows; j++) {
                (result[i])[j] = 1;
            }
        }
        return result;
    }
    /**
     * Transposes a matrix, converts rows to columns
     * @param {*} matrix 
     */
    function transpose(matrix) {
        var operated = clone(matrix);
        return operated[0].map(function (m, c) {
            return matrix.map(function (r) {
                return r[c];
            });
        });
    }
    /**
     * Deep Clones a matrix
     * @param {*} arr 
     */
    function clone(arr) {
        var string = JSON.stringify(arr);
        var result = JSON.parse(string);
        return result;
    }

    /**
     * Compute the thin SVD from G. H. Golub and C. Reinsch, Numer. Math. 14, 403-420 (1970)
     * From the Numeric JS Implementation Copyright (C) 2011 by Sébastien Loisel
     * The C implementation from which this has been taken may be found here: http://www.public.iastate.edu/~dicook/JSS/paper/code/svd.c
     * @param {*} A = m*n matrix
     */
    function svd(A) {
        var temp;
        var prec = Math.pow(2, -52) // assumes double prec
        var tolerance = 1.e-64 / prec;
        var itmax = 50;
        var c = 0;
        var i = 0;
        var j = 0;
        var k = 0;
        var l = 0;
        var u = clone(A);
        var m = u.length;
        var n = u[0].length;

        if (m < n) throw "Need more rows than columns"

        var e = new Array(n); //vector1
        var q = new Array(n); //vector2
        for (i = 0; i < n; i++) e[i] = q[i] = 0.0;
        var v = rep([n, n], 0);

        function pythag(a, b) {
            a = Math.abs(a)
            b = Math.abs(b)
            if (a > b)
                return a * Math.sqrt(1.0 + (b * b / a / a))
            else if (b == 0.0)
                return a
            return b * Math.sqrt(1.0 + (a * a / b / b))
        }

        //rep function
        function rep(s, v, k) {
            if (typeof k === "undefined") {
                k = 0;
            }
            var n = s[k],
                ret = Array(n),
                i;
            if (k === s.length - 1) {
                for (i = n - 2; i >= 0; i -= 2) {
                    ret[i + 1] = v;
                    ret[i] = v;
                }
                if (i === -1) {
                    ret[0] = v;
                }
                return ret;
            }
            for (i = n - 1; i >= 0; i--) {
                ret[i] = rep(s, v, k + 1);
            }
            return ret;
        }

        //Householder's reduction to bidiagonal form

        var f = 0.0;
        var g = 0.0;
        var h = 0.0;
        var x = 0.0;
        var y = 0.0;
        var z = 0.0;
        var s = 0.0;

        for (i = 0; i < n; i++) {
            e[i] = g; //vector
            s = 0.0; //sum
            l = i + 1; //stays i+1
            for (j = i; j < m; j++)
                s += (u[j][i] * u[j][i]);
            if (s <= tolerance)
                g = 0.0;
            else {
                f = u[i][i];
                g = Math.sqrt(s);
                if (f >= 0.0) g = -g;
                h = f * g - s
                u[i][i] = f - g;
                for (j = l; j < n; j++) {
                    s = 0.0
                    for (k = i; k < m; k++)
                        s += u[k][i] * u[k][j]
                    f = s / h
                    for (k = i; k < m; k++)
                        u[k][j] += f * u[k][i]
                }
            }
            q[i] = g
            s = 0.0
            for (j = l; j < n; j++)
                s = s + u[i][j] * u[i][j]
            if (s <= tolerance)
                g = 0.0
            else {
                f = u[i][i + 1]
                g = Math.sqrt(s)
                if (f >= 0.0) g = -g
                h = f * g - s
                u[i][i + 1] = f - g;
                for (j = l; j < n; j++) e[j] = u[i][j] / h
                for (j = l; j < m; j++) {
                    s = 0.0
                    for (k = l; k < n; k++)
                        s += (u[j][k] * u[i][k])
                    for (k = l; k < n; k++)
                        u[j][k] += s * e[k]
                }
            }
            y = Math.abs(q[i]) + Math.abs(e[i])
            if (y > x)
                x = y
        }

        // accumulation of right hand transformations
        for (i = n - 1; i != -1; i += -1) {
            if (g != 0.0) {
                h = g * u[i][i + 1]
                for (j = l; j < n; j++)
                    v[j][i] = u[i][j] / h //u is array, v is square of columns
                for (j = l; j < n; j++) {
                    s = 0.0
                    for (k = l; k < n; k++)
                        s += u[i][k] * v[k][j]
                    for (k = l; k < n; k++)
                        v[k][j] += (s * v[k][i])
                }
            }
            for (j = l; j < n; j++) {
                v[i][j] = 0;
                v[j][i] = 0;
            }
            v[i][i] = 1;
            g = e[i]
            l = i
        }

        // accumulation of left hand transformations
        for (i = n - 1; i != -1; i += -1) {
            l = i + 1
            g = q[i]
            for (j = l; j < n; j++)
                u[i][j] = 0;
            if (g != 0.0) {
                h = u[i][i] * g
                for (j = l; j < n; j++) {
                    s = 0.0
                    for (k = l; k < m; k++) s += u[k][i] * u[k][j];
                    f = s / h
                    for (k = i; k < m; k++) u[k][j] += f * u[k][i];
                }
                for (j = i; j < m; j++) u[j][i] = u[j][i] / g;
            } else
                for (j = i; j < m; j++) u[j][i] = 0;
            u[i][i] += 1;
        }

        // diagonalization of the bidiagonal form
        prec = prec * x
        for (k = n - 1; k != -1; k += -1) {
            for (var iteration = 0; iteration < itmax; iteration++) { // test f splitting
                var test_convergence = false
                for (l = k; l != -1; l += -1) {
                    if (Math.abs(e[l]) <= prec) {
                        test_convergence = true
                        break
                    }
                    if (Math.abs(q[l - 1]) <= prec)
                        break
                }
                if (!test_convergence) { // cancellation of e[l] if l>0
                    c = 0.0
                    s = 1.0
                    var l1 = l - 1
                    for (i = l; i < k + 1; i++) {
                        f = s * e[i]
                        e[i] = c * e[i]
                        if (Math.abs(f) <= prec)
                            break
                        g = q[i]
                        h = pythag(f, g)
                        q[i] = h
                        c = g / h
                        s = -f / h
                        for (j = 0; j < m; j++) {
                            y = u[j][l1]
                            z = u[j][i]
                            u[j][l1] = y * c + (z * s)
                            u[j][i] = -y * s + (z * c)
                        }
                    }
                }
                // test f convergence
                z = q[k]
                if (l == k) { //convergence
                    if (z < 0.0) { //q[k] is made non-negative
                        q[k] = -z
                        for (j = 0; j < n; j++)
                            v[j][k] = -v[j][k]
                    }
                    break //break out of iteration loop and move on to next k value
                }
                if (iteration >= itmax - 1)
                    throw 'Error: no convergence.'
                // shift from bottom 2x2 minor
                x = q[l]
                y = q[k - 1]
                g = e[k - 1]
                h = e[k]
                f = ((y - z) * (y + z) + (g - h) * (g + h)) / (2.0 * h * y)
                g = pythag(f, 1.0)
                if (f < 0.0)
                    f = ((x - z) * (x + z) + h * (y / (f - g) - h)) / x
                else
                    f = ((x - z) * (x + z) + h * (y / (f + g) - h)) / x
                // next QR transformation
                c = 1.0
                s = 1.0
                for (i = l + 1; i < k + 1; i++) {
                    g = e[i]
                    y = q[i]
                    h = s * g
                    g = c * g
                    z = pythag(f, h)
                    e[i - 1] = z
                    c = f / z
                    s = h / z
                    f = x * c + g * s
                    g = -x * s + g * c
                    h = y * s
                    y = y * c
                    for (j = 0; j < n; j++) {
                        x = v[j][i - 1]
                        z = v[j][i]
                        v[j][i - 1] = x * c + z * s
                        v[j][i] = -x * s + z * c
                    }
                    z = pythag(f, h)
                    q[i - 1] = z
                    c = f / z
                    s = h / z
                    f = c * g + s * y
                    x = -s * g + c * y
                    for (j = 0; j < m; j++) {
                        y = u[j][i - 1]
                        z = u[j][i]
                        u[j][i - 1] = y * c + z * s
                        u[j][i] = -y * s + z * c
                    }
                }
                e[l] = 0.0
                e[k] = f
                q[k] = x
            }
        }

        for (i = 0; i < q.length; i++)
            if (q[i] < prec) q[i] = 0

        //sort eigenvalues	
        for (i = 0; i < n; i++) {
            for (j = i - 1; j >= 0; j--) {
                if (q[j] < q[i]) {
                    c = q[j]
                    q[j] = q[i]
                    q[i] = c
                    for (k = 0; k < u.length; k++) {
                        temp = u[k][i];
                        u[k][i] = u[k][j];
                        u[k][j] = temp;
                    }
                    for (k = 0; k < v.length; k++) {
                        temp = v[k][i];
                        v[k][i] = v[k][j];
                        v[k][j] = temp;
                    }
                    i = j
                }
            }
        }

        return {
            U: u,
            S: q,
            V: v
        }
    }

    return {
        computeDeviationScores: computeDeviationScores,
        computeDeviationMatrix: computeDeviationMatrix,
        computeSVD: computeSVD,
        computePercentageExplained: computePercentageExplained,
        computeOriginalData: computeOriginalData,
        computeVarianceCovariance: computeVarianceCovariance,
        computeAdjustedData: computeAdjustedData,
        getEigenVectors: getEigenVectors,
        analyseTopResult: analyseTopResult,
        transpose: transpose,
        multiply: multiply,
        clone: clone,
        scale: scale,
        options:options
    }
})();

if(typeof module !== 'undefined')
module.exports = PCA;
};
BundleModuleCode['nn/nn']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2006-2017 bLAB
 **    $CREATED:     27-06-17 by sbosse.
 **    $RCS:         $Id$
 **    $VERSION:     1.1.4
 **
 **    $INFO:
 ** 
 ** Neuronal Network Module implementing agent defined neurons and neuronal networks.
 ** An agent must only save the state/configuration of neurons and
 ** network configurations. Neuronal Network configurations are processed by the
 ** agent plattform.
 **
 **    Example network with thre neuron nodes:
 ** 
 **    var n1 = nn.neuron({
 **      a:{decay:0.1,integrate:0.2},
 **      b:{},
 **      threshold:0.5  // Binary output
 **    });
 **    var n2 = nn.neuron({
 **      x:{decay:0.1,integrate:0.2,threshold:0.1},
 **      y:{weight:0.5}
 **    });
 **    var n3 = nn.neuron({
 **      a:{decay:0.1,integrate:0.2},
 **      b:{decay:0.1}
 **    });
 **
 **    var nw = nn.network([
 **      n1,n2,n3
 **      ],[
 **      // Connect ouput of internal neurons to other neuron inputs
 **      // Read as: output(n1) -> input.x(n2)
 **      {output:n1,x:n2},
 **      {output:n2,b:n3},
 **      // Define and connect network inputs to internal neuron inputs
 **      {input:n1,v1:'a',v2:'b'},
 **      {input:n2,v3:'y'},
 **      {input:n3,v4:'a'}
 **    ]);
 **   
 **    nn.compute(nw);
 **    
 **    Input parameters: weight, decay, integrate, threshold (discriminator), invert
 **    Output parameter: threshold (binary output)
 **    Output range: [-1.0,1.0] | {-1,0,1}
 **    $ENDOFINFO
 */

var Io = Require('com/io');
var Comp = Require('com/compat');
var current=none;
var Aios=none;


function inputFunction(flags) {
  return function (x,y0) {
    var p,y=0,w=1,c,i;
    if (x==undefined) return y0;
    for(p in flags) {
      var k=flags[p];
      switch (p) {
        case 'invert':      x = -x; break;
        case 'threshold':   x = (x>=k?x:0); break;
        case 'weight':      w = k; break;
        case 'decay':       c = k; break;
        case 'integrate':   i = k; break; 
      }
    }
   
    if (c!=undefined) y=y0-y0*c;
    if (i!=undefined) y=(c==undefined?y0:y)+x*i;
    else y=y+x;
    
    return {y:Math.max(-1.0,Math.min(1.0,y)),w:w};
  }
}

var nn = {
  compute: function (node,input) {
    var i,ys=0,yw,neuron,neuron_input,next,computed,more;
    // All input variables reday (values computed/available)?
    function ready(node) {
      var p;
      for(p in node.input) if (node.input[p].x==undefined) return false;
      return true;
    }
    
    switch (node.type) {
      case 'neuron':
        // console.log('compute '+node.id);
        if (!input) 
          // Get internal node input values; neuronal network nodes only
          {input={};for(p in node.input) input[p]=node.input[p].x};
        for(i in node.input) {
          if (input[i] == undefined) continue;
          yw=node.input[i].f(input[i],node.input[i].y);
          node.input[i].y=yw.y;
          node.input[i].x=undefined;
          ys += (yw.y*yw.w);
        }
        if (node.threshold != undefined) 
          node.output = (ys>=node.threshold?1:0);
        else
          node.output = Math.max(-1.0,Math.min(1.0,ys));
        
        break;
      case 'network':
        // Set inputs
        for(p in input) {
          if (node.input[p])
            neuron_input=node.input[p].param; // local neuron input
            neuron=node.nodes[node.input[p].node]; // target neuron
            if (neuron) neuron.input[neuron_input].x=input[p];
        }

        // Compute all nodes with a complete set of inputs
        more=1;
        while (more) {
          computed=0;
          for(i in node.nodes) {
            neuron=node.nodes[i];
            if (ready(neuron)) {
              nn.compute(neuron);
              computed++;
              if (neuron.connect) 
                for(p in neuron.connect) {
                  next=node.nodes[p];
                  if (next) next.input[neuron.connect[p]].x=neuron.output;
                }
            }
          }
          more=(computed != node.nodes.length && computed>0);
        }
        break;
    }
  },
  connect: function (node1,node2,input) {
    var c={};
    node1.connect[node2.id]=input;
  },
  
  /** Compose a network graph from neuron nodes.
  **  The network object will not contain recursive references or deep nested structures 
  **  to insure mobility.
  ** 
  */
  network: function (nodes,connect) {
    var i,n,p,conn,nw={type:'network',input:{}};
    
    // Remap neuron ids...
    for(i in nodes) nodes[i].id=i;
    
    function getNode (o) {
      var p;
      for(p in o) if (p!='output'  && p!='input') return o[p];
    }
    function getInput(o) {
      var p;
      for(p in o) if (p!='output' && p!='input') return p;    
    }
    function getIndex(o) {
      var i;
      for(i in nodes) if (nodes[i].id==o.id) return i;
    }
    
    nw.nodes=nodes;
    for(i in connect) {
      conn=connect[i];
      if (conn.output) {
        nn.connect(conn.output,getNode(conn),getInput(conn));
      }
      else if (conn.input) {
        for(p in conn) {
          if (p!='input') nw.input[p]={node:getIndex(conn.input),param:conn[p]};
        }
      }
    }
    return nw;
  },
  /**  neuron(a:{invert:true,weight:0.5,decay:0.2,integrate:0.1,threshold:0.5},
  **             threshold:0.9)
  **
  ** function neuron() -> {type,id:number,threshold?,connect:{},input:{},output:number}
  **
  ** type of connect = {<nodeid>:<input>,..}
  */
  neuron: function (settings) {
    var p,i,input,
      o= {
      type:'neuron',
      id:(Math.random()*1000000)|0,
      threshold:settings.threshold,
      // connect this output to other node inputs - spawns a computation graph
      connect:{},
      input:{},
      output:settings.init||0
    }
    for(p in settings) {
      if (p=='init' || p=='threshold') continue;
      input=settings[p];
      o.input[p]={x:undefined,y:0,f:inputFunction(input)};
    }
    return o;  
  }
}

/** 
 *
 */
module.exports = {
  agent:nn,
  compute:nn.compute,
  current:function (module) { current=module.current; Aios=module; }
}
};
BundleModuleCode['csp/csp']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2006-2019 BSSLAB
 **    $CREATED:     29-5-19 by sbosse.
 **    $VERSION:     1.1.1
 **
 **    $INFO:
 **
 **  JavaScript AIOS Constraint Solver Programming API
 **
 **    $ENDOFINFO
 */
 
var Io = Require('com/io');
var Comp = Require('com/compat');
var simple = Require('csp/cspS');
var current=none;
var Aios=none;

var CSP = {
  SIMPLE:'SIMPLE',
}
var options = {
  version: '1.1.1'
}


var csp = {
  /* Add constraint */
  C : function (model,v1,v2,f) {
    switch (model.algorithm) {
      case CSP.SIMPLE:
        model.constraints.push([v1,v2,f]);
        break;
    }    
  },
  V : function (model,name,val) {
    switch (model.algorithm) {
      case CSP.SIMPLE:
        model.variables[name]=val;
        break;
    }    
  },
  range : function (a,b,step) {
    var res=[];
    if (step==undefined) step=1;
    for(var i=a;i<=b;i=i+step) res.push(i);
    return res;
  },
  /* Create a new solver */
  solver : function (options) {
    var model={}
    options=checkOptions(options,{});
    options.algorithm=checkOption(options.algorithm,CSP.SIMPLE);
    model.algorithm=options.algorithm;
    switch (options.algorithm) {
      case CSP.SIMPLE:
        model.variables= {}
        model.constraints = []
        break;
    }
    return model
  },
  /* Solve the problem, return solutions */
  solve : function (model,options) {
    switch (model.algorithm) {
      case CSP.SIMPLE:
        return simple.solve(model)
    }  
  },
  CSP:CSP,
}

module.exports = {
  agent:csp,
  CSP:CSP,
  current:function (module) { current=module.current; Aios=module; }
}
};
BundleModuleCode['csp/cspS']=function (module,exports){
var CSP = {},
    FAILURE = 'FAILURE',
    stepCounter = 0;

CSP.solve = function solve(csp) {
  // Solves a constraint satisfaction problem.
  // `csp` is an object that should have the properties:
  //    `variables`  : object that holds variable names and their domain.
  //    `constraints`: list of constraints where each element is an 
  //                   array of [head node, tail node, constraint function]
  //    `cb`: optional callback function.

  var result = backtrack({}, csp.variables, csp);
  if (result == FAILURE) { return result; }
  // Unwrap values from array containers.
  for (var key in result) {
    result[key] = result[key][0];
  }
  if (csp.cb) csp.cb(result);
  return result;
}

function backtrack(_assigned, unassigned, csp) {
  // Backtracking search.
  
  // Copying assigned in necessary because we modify it. Without copying
  // the object over, modifying assigned would also change values for old
  // assigned objects (which are used in callbacks).
  var assigned = {};
  for (var key in _assigned) { assigned[key] = _assigned[key]; }

  if (finished(unassigned)) { return assigned; } // Base case.
  var nextKey = selectUnassignedVariable(unassigned),
      values = orderValues(nextKey, assigned, unassigned, csp);
  delete unassigned[nextKey];

  for (var i = 0; i < values.length; i++) {
    stepCounter++;
    assigned[nextKey] = [values[i]]; // Assign a value to a variable.
    var consistent = enforceConsistency(assigned, unassigned, csp);
    var newUnassigned = {}, newAssigned = {};
    for (var key in consistent) {
      if (assigned[key]) { newAssigned[key] = assigned[key].slice(); }
      else { newUnassigned[key] = consistent[key].slice(); }
    }
    if (anyEmpty(consistent)) { continue; } // Empty domains means failure.
    var result = backtrack(newAssigned, newUnassigned, csp);
    if (result != FAILURE) { return result; }
  }

  return FAILURE;
}

function finished(unassigned) {
  // Checks if there are no more variables to assign.
  return Object.keys(unassigned).length == 0;
}

function anyEmpty(consistent) {
  // Checks if any variable's domain is empty.
  for (var key in consistent) {
    if (consistent[key].length == 0) { return true; }
  }
  return false;
}

function partialAssignment(assigned, unassigned) {
  // Combine unassigned and assigned for use in enforceConsistency.
  var partial = {};
  for (var key in unassigned) { partial[key] = unassigned[key].slice(); }
  for (var key in assigned) { partial[key] = assigned[key].slice(); }
  return partial;
}

function enforceConsistency(assigned, unassigned, csp) {
  // Enforces arc consistency by removing inconsistent values from
  // every constraint's tail node.

  function removeInconsistentValues(head, tail, constraint, variables) {
    var hv,tv,validHeadValues,validTailValues,removed;
    if (tail) {
      // Removes inconsistent values from the tail node. A value is
      // inconsistent when if the `tail` is assigned that value, there are
      // no values in `head`'s domain that satisfies the constraint.
      // - binray constraint
      hv = variables[head], tv = variables[tail];
      validTailValues = tv.filter(function (t) {
        return hv.some(function (h) {
          return constraint(h, t);
        });
      });
      removed = tv.length != validTailValues.length;
      variables[tail] = validTailValues;
    } else {
      // unary constraint - modify head
      hv = variables[head];
      validHeadValues = hv.filter(function (h) {
          return constraint(h);
      });
      removed = hv.length != validHeadValues.length;
      variables[head] = validHeadValues;
    }
    return removed;
  }

  function incomingConstraints(node) {
    // Returns all the constraints where `node` is the head node.
    return csp.constraints.filter(function (c) {
      return c[0] == node;
    });
  }
  
  var queue = csp.constraints.slice(), 
      variables = partialAssignment(assigned, unassigned);
  while (queue.length) { // While there are more constraints to test.
    var c = queue.shift(), head = c[0], tail = c[1], constraint = c[2];
    if (removeInconsistentValues(head, tail, constraint, variables)) {
      // If values from the tail have been removed, incoming constraints
      // to the tail must be rechecked.
      queue = queue.concat(incomingConstraints(tail));
    }
  }
  return variables;
}

function selectUnassignedVariable(unassigned) {
  // Picks the next variable to assign according to the Minimum
  // Remaining Values heuristic. Pick the variable with the fewest
  // values remaining in its domain. This helps identify domain
  // failures earlier.
  var minKey = null, minLen = Number.POSITIVE_INFINITY;
  for (var key in unassigned) {
    var len = unassigned[key].length;
    if (len < minLen) { minKey = key, minLen = len; }
  }
  return minKey;
}

function orderValues(nextKey, assigned, unassigned, csp) {
  // Orders the values of an unassigned variable according to the
  // Least Constraining Values heuristic. Perform arc consistency
  // on each possible value, and order variables according to the
  // how many values were eliminated from all the domains (fewest
  // eliminated in the front). This helps makes success more likely
  // by keeping future options open.
  
  function countValues(vars) {
    var sum = 0;
    for (var key in vars) { sum += vars[key].length; }
    return sum;
  }

  function valuesEliminated(val) {
    assigned[nextKey] = [val];
    var newLength = countValues(enforceConsistency(assigned, unassigned, csp));
    delete assigned[nextKey];
    return newLength;
  }

  // Cache valuesEliminated to be used in sort.
  var cache = {}, values = unassigned[nextKey];
  values.forEach(function(val) {
    cache[val] = valuesEliminated(val);
  });
  // Descending order based on the number of domain values remaining.
  values.sort(function (a, b) { return cache[b] - cache[a]; });
  return values;
}

module.exports = CSP;
};
BundleModuleCode['logic/sat']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2006-2019 BSSLAB
 **    $CREATED:     29-5-19 by sbosse.
 **    $VERSION:     1.1.1
 **
 **    $INFO:
 **
 **  JavaScript AIOS SAT Logic Solver API
 **
 **    $ENDOFINFO
 */
var current=none;
var Aios=none;
var options = {
  version: '1.1.1'
}

var Logic = Require('logic/logic-solver');

var Solved = [];

var sat = {
  create : function (options) {
    var model = {
      id : Aios.aidgen(), // or agent id?
      life : Aios.time()
    }
    return model;
  },
  
  eval: function (model,what) {
    sat.setup(model);
    if (!Solved[model.id]) return;
    Solved[model.id].life=Aios.time();
    return Solved[model.id].evaluate(what)
  },
  
  gc : function () {
    var time = Aios.time();
    for(var p in Solved) {
      if (Solved[p] && (Solved[p].time+sat.lifetime)<time) {
        delete Solved[p];
        delete Logic._minisatSolvers[p];
        delete Logic._termifiers[p];
      }
    }
  },
  // forbid rule
  F: function (model,logic) {
    sat.setup(model);   
    Logic.Solver.forbid(model,logic)
  },
  
  L : Logic,
  
  // require rule
  R: function (model,logic) { 
    sat.setup(model);  
    Logic.Solver.require(model,logic)
  },
  
  solver : function (options) {
    var model = sat.create();
    Logic.Solver(model);
    return model
  },
  
  // solve current logic formulas
  solve : function (model,assume) {
    sat.setup(model);
    if (!Solved[model.id]) {
      model._numClausesAddedToMiniSat=0;
    }
    if (assume)
      Solved[model.id]=Logic.Solver.solveAssuming(model,assume)
    else
      Solved[model.id]=Logic.Solver.solve(model);
    if (Solved[model.id]) Solved[model.id].life=Aios.time();
    return Solved[model.id]?Solved[model.id].getTrueVars():null
  },
  
  lifetime : 1E9,
  
  // complete the solver environemnt
  setup : function (model) {
    if (!Logic._termifiers[model.id] || !Logic._minisatSolvers[model.id]) {
      model.id=Aios.aidgen();
      model.life=Aios.time();
      Logic._termifiers[model.id]=new Logic.Termifier(model);
      model._numClausesAddedToMiniSat=0;
      Logic._minisatSolvers[model.id]=new Logic.MiniSat();
    }       
  },
  
  true : function (model) {
    sat.setup(model);
    if (!Solved[model.id]) return;
    Solved[model.id].life=Aios.time();
    return Solved[model.id].getTrueVars()
  }
}

sat.Logic   = sat.L;
sat.require = sat.R;
sat.forbid  = sat.F;

module.exports = {
  agent:sat,
  Logic:Logic,
  current:function (module) { current=module.current; Aios=module; }
}
};
BundleModuleCode['logic/logic-solver']=function (module,exports){
/**
 * MiniSat Logic Solver - Procedural Version (dividing code and data)
 *
 * https://github.com/meteor/logic-solver
 *
 */
 
var MiniSat = Require("logic/minisat_wrapper.js");
var _       = Require("ext/underscore");
var parse   = Require('ext/jsep');

var Logic;
Logic = {};

Logic.MiniSat=MiniSat;

////////// TYPE TESTERS


// Set the `description` property of a tester function and return the function.
var withDescription = function (description, tester) {
  tester.description = description;
  return tester;
};

// Create a function (x) => (x instanceof constructor), but possibly before
// constructor is available.  For example, if Logic.Formula hasn't been
// assigned yet, passing Logic for `obj` and "Formula" for `constructorName`
// will still work.
var lazyInstanceofTester = function (description, obj, constructorName) {
  return withDescription(description, function (x) {
    return x instanceof obj[constructorName];
  });
};


///// PUBLIC TYPE TESTERS

// All variables have a name and a number.  The number is mainly used
// internally, and it's what's given to MiniSat.  Names and numbers
// are interchangeable, which is convenient for doing manipulation
// of terms in a way that works before or after variable names are
// converted to numbers.

// Term: a variable name or variable number, optionally
// negated (meaning "boolean not").  For example,
// `1`, `-1`, `"foo"`, or `"-foo"`.  All variables have
// internal numbers that start at 1, so "foo" might be
// variable number 1, for example.  Any number of leading
// "-" will be parsed in the string form, but we try to
// keep it to either one or zero of them.

Logic.isNumTerm = withDescription('a NumTerm (non-zero integer)',
                                  function (x) {
                                    // 32-bit integer, but not 0
                                    return (x === (x | 0)) && x !== 0;
                                  });

// NameTerm must not be empty, or just `-` characters, or look like a
// number.  Specifically, it can't be zero or more `-` followed by
// zero or more digits.
Logic.isNameTerm = withDescription('a NameTerm (string)',
                                   function (x) {
                                     return (typeof x === 'string') &&
                                       ! /^-*[0-9]*$/.test(x);
                                   });

Logic.isTerm = withDescription('a Term (appropriate string or number)',
                               function (x) {
                                 return Logic.isNumTerm(x) ||
                                   Logic.isNameTerm(x);
                               });

// WholeNumber: a non-negative integer (0 is allowed)
Logic.isWholeNumber = withDescription('a whole number (integer >= 0)',
                                      function (x) {
                                        return (x === (x | 0)) && x >= 0;
                                      });

Logic.isFormula = lazyInstanceofTester('a Formula', Logic, 'Formula');
Logic.isClause =  function (o) { return o && o.tag=='Clause' }// lazyInstanceofTester('a Clause', Logic, 'Clause');
Logic.isBits = lazyInstanceofTester('a Bits', Logic, 'Bits');

///// UNDOCUMENTED TYPE TESTERS

Logic._isInteger = withDescription(
  'an integer', function (x) { return x === (x | 0); });

Logic._isFunction = withDescription(
  'a Function', function (x) { return typeof x === 'function'; });

Logic._isString = withDescription(
  'a String', function (x) { return typeof x === 'string'; });

Logic._isArrayWhere = function (tester) {
  var description = 'an array';
  if (tester.description) {
    description += ' of ' + tester.description;
  }
  return withDescription(description, function (x) {
    if (! _.isArray(x)) {
      return false;
    } else {
      for (var i = 0; i < x.length; i++) {
        if (! tester(x[i])) {
          return false;
        }
      }
      return true;
    }
  });
};

Logic._isFormulaOrTerm = withDescription('a Formula or Term',
                                         function (x) {
                                           return Logic.isFormula(x) ||
                                             Logic.isTerm(x);
                                         });


Logic._isFormulaOrTermOrBits = withDescription('a Formula, Term, or Bits',
                                               function (x) {
                                                 return Logic.isFormula(x) ||
                                                   Logic.isBits(x) ||
                                                   Logic.isTerm(x);
                                               });
Logic._MiniSat = MiniSat; // Expose for testing and poking around

// import the private testers from types.js
var isInteger = Logic._isInteger;
var isFunction = Logic._isFunction;
var isString = Logic._isString;
var isArrayWhere = Logic._isArrayWhere;
var isFormulaOrTerm = Logic._isFormulaOrTerm;
var isFormulaOrTermOrBits = Logic._isFormulaOrTermOrBits;

Logic._assert = function (value, tester, description) {
  if (! tester(value)) {
    var displayValue = (typeof value === 'string' ? JSON.stringify(value) :
                        value);
    throw new Error(displayValue + " is not " +
                    (tester.description || description));
  }
};

// Call this as `if (assert) assertNumArgs(...)`
var assertNumArgs = function (actual, expected, funcName) {
  if (actual !== expected) {
    throw new Error("Expected " + expected + " args in " + funcName +
                    ", got " + actual);
  }
};

// Call `assert` as: `if (assert) assert(...)`.
// This local variable temporarily set to `null` inside
// `Logic.disablingAssertions`.
var assert = Logic._assert;

// Like `if (assert) assert(...)` but usable from other files in the package.
Logic._assertIfEnabled = function (value, tester, description) {
  if (assert) assert(value, tester, description);
};

// Disabling runtime assertions speeds up clause generation.  Assertions
// are disabled when the local variable `assert` is null instead of
// `Logic._assert`.
Logic.disablingAssertions = function (f) {
  var oldAssert = assert;
  try {
    assert = null;
    return f();
  } finally {
    assert = oldAssert;
  }
};

// Back-compat.
Logic._disablingTypeChecks = Logic.disablingAssertions;

////////////////////

// Takes a Formula or Term, returns a Formula or Term.
// Unlike other operators, if you give it a Term,
// you will get a Term back (of the same type, NameTerm
// or NumTerm).
Logic.not = function (operand) {
  if (assert) assert(operand, isFormulaOrTerm);

  if (operand instanceof Logic.Formula) {
    return new Logic.NotFormula(operand);
  } else {
    // Term
    if (typeof operand === 'number') {
      return -operand;
    } else if (operand.charAt(0) === '-') {
      return operand.slice(1);
    } else {
      return '-' + operand;
    }
  }
};

Logic.NAME_FALSE = "$F";
Logic.NAME_TRUE = "$T";
Logic.NUM_FALSE = 1;
Logic.NUM_TRUE = 2;

Logic.TRUE = Logic.NAME_TRUE;
Logic.FALSE = Logic.NAME_FALSE;

// Abstract base class.  Subclasses are created using _defineFormula.
Logic.Formula = function () {};

Logic._defineFormula = function (constructor, typeName, methods) {
  if (assert) assert(constructor, isFunction);
  if (assert) assert(typeName, isString);

  constructor.prototype = new Logic.Formula();
  constructor.prototype.type = typeName;
  if (methods) {
    _.extend(constructor.prototype, methods);
  }
};

// Returns a list of Clauses that together require the Formula to be
// true, or false (depending on isTrue; both cases must be
// implemented).  A single Clause may also be returned.  The
// implementation should call the termifier to convert terms and
// formulas to NumTerms specific to a solver instance, and use them to
// construct a Logic.Clause.
Logic.Formula.prototype.generateClauses = function (isTrue, termifier) {
  throw new Error("Cannot generate this Formula; it must be expanded");
};
// All Formulas have a globally-unique id so that Solvers can track them.
// It is assigned lazily.
Logic.Formula._nextGuid = 1;
Logic.Formula.prototype._guid = null;
Logic.Formula.prototype.guid = function () {
  if (this._guid === null) {
    this._guid = Logic.Formula._nextGuid++;
  }
  return this._guid;
};

// A "clause" is a disjunction of terms, e.g. "A or B or (not C)",
// which we write "A v B v -C".  Logic.Clause is mainly an internal
// Solver data structure, which is the final result of formula
// generation and mapping variable names to numbers, before passing
// the clauses to MiniSat.
Logic.Clause = function (/*formulaOrArray, ...*/) {
  var terms = _.flatten(arguments);
  if (assert) assert(terms, isArrayWhere(Logic.isNumTerm));

  // this.terms = terms; // immutable [NumTerm]
  return {
    tag   : 'Clause',
    terms : terms,
  }
};

// Returns a new Clause with the extra term or terms appended
if (0) Logic.Clause.prototype.append = function (/*formulaOrArray, ...*/) {
  return new Logic.Clause(this.terms.concat(_.flatten(arguments)));
};

Logic.Clause.append = function (/*formulaOrArray, ...*/) {
  var args = _.flatten(arguments);
  var self = args[0]; args=args.slice(1);
  return Logic.Clause(self.terms.concat(args));
};

var FormulaInfo = function () {
  // We generate a variable when a Formula is used.
  this.varName = null; // string name of variable
  this.varNum = null; // number of variable (always positive)

  // A formula variable that is used only in the positive or only
  // in the negative doesn't need the full set of clauses that
  // establish a bidirectional implication between the formula and the
  // variable.  For example, in the formula `Logic.or("A", "B")`, with the
  // formula variable `$or1`, the full set of clauses is `A v B v
  // -$or1; -A v $or1; -B v $or1`.  If both `$or1` and `-$or1` appear
  // elsewhere in the set of clauses, then all three of these clauses
  // are required.  However, somewhat surprisingly, if only `$or1` appears,
  // then only the first is necessary.  If only `-$or1` appears, then only
  // the second and third are necessary.
  //
  // Suppose the formula A v B is represented by the variable $or1,
  // and $or1 is only used positively. It's important that A v B being
  // false forces $or1 to be false, so that when $or1 is used it has
  // the appropriate effect. For example, if we have the clause $or1 v
  // C, then A v B being false should force $or1 to be false, which
  // forces C to be true. So we generate the clause A v B v
  // -$or1. (The implications of this clause are: If A v B is false,
  // $or1 must be false. If $or1 is true, A v B must be true.)
  //
  // However, in the case where A v B is true, we don't actually
  // need to insist that the solver set $or1 to true, as long as we
  // are ok with relaxing the relationship between A v B and $or1
  // and getting a "wrong" value for $or1 in the solution. Suppose
  // the solver goes to work and at some point determines A v B to
  // be true. It could set $or1 to true, satisfying all the clauses
  // where it appears, or it could set $or1 to false, which only
  // constrains the solution space and doesn't open up any new
  // solutions for other variables. If the solver happens to find a
  // solution where A v B is true and $or1 is false, we know there
  // is a similar solution that makes all the same assignments
  // except it assigns $or1 to true.
  //
  // If a formula is used only negatively, a similar argument applies
  // but with signs flipped, and if it is used both positively and
  // negatively, both kinds of clauses must be generated.
  //
  // See the mention of "polarity" in the MiniSat+ paper
  // (http://minisat.se/downloads/MiniSat+.pdf).
  //
  // These flags are set when generation has been done for the positive
  // case or the negative case, so that we only generate each one once.
  this.occursPositively = false;
  this.occursNegatively = false;

  // If a Formula has been directly required or forbidden, we can
  // replace it by TRUE or FALSE in subsequent clauses.  Track the
  // information here.
  this.isRequired = false;
  this.isForbidden = false;
};

// The "termifier" interface is provided to a Formula's
// generateClauses method, which must use it to generate Clause
// objects.
//
// The reason for this approach is that it gives the Formula control
// over the clauses returned, but it gives the Solver control over
// Formula generation.
Logic.Termifier = function (solver) {
  this.solver = solver;
};



// The main entry point, the `clause` method takes a list of
// FormulaOrTerms and converts it to a Clause containing NumTerms, *by
// replacing Formulas with their variables*, creating the variable if
// necessary.  For example, if an OrFormula is represented by the
// variable `$or1`, it will be replaced by the numeric version of
// `$or1` to make the Clause.  When the Clause is actually used, it
// will trigger generation of the clauses that relate `$or1` to the
// operands of the OrFormula.
Logic.Termifier.prototype.clause = function (/*args*/) {
  var self = this;
  var formulas = _.flatten(arguments);
  if (assert) assert(formulas, isArrayWhere(isFormulaOrTerm));

  return new Logic.Clause(_.map(formulas, function (f) {
    return self.term(f);
  }));
};

// The `term` method performs the mapping from FormulaOrTerm to
// NumTerm.  It's called by `clause` and could be called directly
// from a Formula's generateClauses if it was useful for some
// reason.
Logic.Termifier.prototype.term = function (formula) {
  return Logic.Solver._formulaToTerm(this.solver,formula);
};

// The `generate` method generates clauses for a Formula (or
// Term).  It should be used carefully, because it works quite
// differently from passing a Formula into `clause`, which is the
// normal way for one Formula to refer to another.  When you use a
// Formula in `clause`, it is replaced by the Formula's variable,
// and the Solver handles generating the Formula's clauses once.
// When you use `generate`, this system is bypassed, and the
// Formula's generateClauses method is called pretty much directly,
// returning the array of Clauses.
Logic.Termifier.prototype.generate = function (isTrue, formula) {
  return Logic.Solver._generateFormula(this.solver, isTrue, formula, this);
};


Logic._minisatSolvers=[];
Logic._termifiers=[];

/******************** LOGIC SOLVER **********************/
/* procedural version */

Logic.Solver = function (model) {
  var solver = model||{};
  solver.tag='Solver';
  solver.clauses = []; // mutable [Clause]
  solver._num2name = [null]; // no 0th var
  solver._name2num = {}; // (' '+vname) -> vnum
  
  // true and false
  var F = Logic.Solver.getVarNum(solver,Logic.NAME_FALSE, false, true); // 1
  var T = Logic.Solver.getVarNum(solver,Logic.NAME_TRUE, false, true); // 2
  if (F !== Logic.NUM_FALSE || T !== Logic.NUM_TRUE) {
    throw new Error("Assertion failure: $T and $F have wrong numeric value");
  }
  solver._F_used = false;
  solver._T_used = false;
  // It's important that these clauses are elements 0 and 1
  // of the clauses array, so that they can optionally be stripped
  // off.  For example, _clauseData takes advantage of this fact.
  solver.clauses.push(new Logic.Clause(-Logic.NUM_FALSE));
  solver.clauses.push(new Logic.Clause(Logic.NUM_TRUE));

  solver._formulaInfo = {}; // Formula guid -> FormulaInfo
  // For generating formula variables like "$or1", "$or2", "$and1", "$and2"
  solver._nextFormulaNumByType = {}; // Formula type -> next var id
  // Map of Formulas whose info has `false` for either
  // `occursPositively` or `occursNegatively`
  solver._ungeneratedFormulas = {}; // varNum -> Formula

  solver._numClausesAddedToMiniSat = 0;
  solver._unsat = false; // once true, no solution henceforth
  Logic._minisatSolvers[model.id] = new MiniSat(); // this takes some time; keep it decoupled from solver object

  Logic._termifiers[model.id] = new Logic.Termifier(solver);
  return solver;
};

// Get a var number for vname, assigning it a number if it is new.
// Setting "noCreate" to true causes the function to return 0 instead of
// creating a new variable.
// Setting "_createInternals" to true grants the ability to create $ variables.
Logic.Solver.getVarNum = function (solver, vname, noCreate, _createInternals) {
  var key = ' '+vname;
  if (_.has(solver._name2num, key)) {
    return solver._name2num[key];
  } else if (noCreate) {
    return 0;
  } else {
    if (vname.charAt(0) === "$" && ! _createInternals) {
      throw new Error("Only generated variable names can start with $");
    }
    var vnum = solver._num2name.length;
    solver._name2num[key] = vnum;
    solver._num2name.push(vname);
    return vnum;
  }
};

Logic.Solver.getVarName = function (solver,vnum) {
  if (assert) assert(vnum, isInteger);

  var num2name = solver._num2name;
  if (vnum < 1 || vnum >= num2name.length) {
    throw new Error("Bad variable num: " + vnum);
  } else {
    return num2name[vnum];
  }
};

// Converts a Term to a NumTerm (if it isn't already).  This is done
// when a Formula creates Clauses for a Solver, since Clauses require
// NumTerms.  NumTerms stay the same, while a NameTerm like "-foo"
// might become (say) the number -3.  If a NameTerm names a variable
// that doesn't exist, it is automatically created, unless noCreate
// is passed, in which case 0 is returned instead.
Logic.Solver.toNumTerm = function (solver, t, noCreate) {

  if (assert) assert(t, Logic.isTerm);

  if (typeof t === 'number') {
    return t;
  } else { // string
    var not = false;
    while (t.charAt(0) === '-') {
      t = t.slice(1);
      not = ! not;
    }
    var n = Logic.Solver.getVarNum(solver,t, noCreate);
    if (! n) {
      return 0; // must be the noCreate case
    } else {
      return (not ? -n : n);
    }
  }
};

// Converts a Term to a NameTerm (if it isn't already).
Logic.Solver.toNameTerm = function (solver,t) {
  if (assert) assert(t, Logic.isTerm);

  if (typeof t === 'string') {
    // canonicalize, removing leading "--"
    while (t.slice(0, 2) === '--') {
      t = t.slice(2);
    }
    return t;
  } else { // number
    var not = false;
    if (t < 0) {
      not = true;
      t = -t;
    }
    t = Logic.Solver.getVarName(solver,t);
    if (not) {
      t = '-' + t;
    }
    return t;
  }
};

Logic.Solver._addClause = function (solver,cls, _extraTerms,
                                                _useTermOverride) {

  if (assert) assert(cls, Logic.isClause);

  var extraTerms = null;
  if (_extraTerms) {
    extraTerms = _extraTerms;
    if (assert) assert(extraTerms, isArrayWhere(Logic.isNumTerm));
  }

  var usedF = false;
  var usedT = false;

  var numRealTerms = cls.terms.length;
  if (extraTerms) {
    // extraTerms are added to the clause as is.  Formula variables in
    // extraTerms do not cause Formula clause generation, which is
    // necessary to implement Formula clause generation.
    // cls = cls.append(extraTerms);
    cls = Logic.Clause.append(cls,extraTerms);
  }

  for (var i = 0; i < cls.terms.length; i++) {
    var t = cls.terms[i];
    var v = (t < 0) ? -t : t;
    if (v === Logic.NUM_FALSE) {
      usedF = true;
    } else if (v === Logic.NUM_TRUE) {
      usedT = true;
    } else if (v < 1 || v >= solver._num2name.length) {
      throw new Error("Bad variable number: " + v);
    } else if (i < numRealTerms) {
      if (_useTermOverride) {
        _useTermOverride(t);
      } else {
        Logic.Solver._useFormulaTerm(solver,t);
      }
    }
  }

  solver._F_used = (solver._F_used || usedF);
  solver._T_used = (solver._T_used || usedT);

  solver.clauses.push(cls);
};

// When we actually use a Formula variable, generate clauses for it,
// based on whether the usage is positive or negative.  For example,
// if the Formula `Logic.or("X", "Y")` is represented by `$or1`, which
// is variable number 5, then when you actually use 5 or -5 in a clause,
// the clauses "X v Y v -5" (when you use 5) or "-X v 5; -Y v 5"
// (when you use -5) will be generated.  The clause "X v Y v -5"
// is equivalent to "5 => X v Y" (or -(X v Y) => -5), while the clauses
// "-X v 5; -Y v 5" are equivalent to "-5 => -X; -5 => -Y" (or
// "X => 5; Y => 5").

Logic.Solver._useFormulaTerm = function (solver, t, _addClausesOverride) {
  if (assert) assert(t, Logic.isNumTerm);
  var v = (t < 0) ? -t : t;

  if (! _.has(solver._ungeneratedFormulas, v)) {
    return;
  }

  // using a Formula's var; maybe have to generate clauses
  // for the Formula
  var formula = solver._ungeneratedFormulas[v];
  var info = Logic.Solver._getFormulaInfo(solver, formula);
  var positive = t > 0;

  // To avoid overflowing the JS stack, defer calls to addClause.
  // The way we get overflows is when Formulas are deeply nested
  // (which happens naturally when you call Logic.sum or
  // Logic.weightedSum on a long list of terms), which causes
  // addClause to call useFormulaTerm to call addClause, and so
  // on.  Approach:  The outermost useFormulaTerm keeps a list
  // of clauses to add, and then adds them in a loop using a
  // special argument to addClause that passes a special argument
  // to useFormulaTerm that causes those clauses to go into the
  // list too.  Code outside of `_useFormulaTerm` and `_addClause(s)`
  // does not have to pass these special arguments to call them.
  var deferredAddClauses = null;
  var addClauses;
  if (! _addClausesOverride) {
    deferredAddClauses = [];
    addClauses = function (clauses, extraTerms) {
      deferredAddClauses.push({clauses: clauses,
                               extraTerms: extraTerms});
    };
  } else {
    addClauses = _addClausesOverride;
  }

  if (positive && ! info.occursPositively) {
    // generate clauses for the formula.
    // Eg, if we use variable `X` which represents the formula
    // `A v B`, add the clause `A v B v -X`.
    // By using the extraTerms argument to addClauses, we avoid
    // treating this as a negative occurrence of X.
    info.occursPositively = true;
    var clauses = Logic.Solver._generateFormula(solver, true, formula);
    addClauses(clauses, [-v]);
  } else if ((! positive) && ! info.occursNegatively) {
    // Eg, if we have the term `-X` where `X` represents the
    // formula `A v B`, add the clauses `-A v X` and `-B v X`.
    // By using the extraTerms argument to addClauses, we avoid
    // treating this as a positive occurrence of X.
    info.occursNegatively = true;
    var clauses = Logic.Solver._generateFormula(solver, false, formula);
    addClauses(clauses, [v]);
  }
  if (info.occursPositively && info.occursNegatively) {
    delete solver._ungeneratedFormulas[v];
  }

  if (! (deferredAddClauses && deferredAddClauses.length)) {
    return;
  }

  var useTerm = function (t) {
    Logic.Solver._useFormulaTerm(solver, t, addClauses);
  };
  // This is the loop that turns recursion into iteration.
  // When addClauses calls useTerm, which calls useFormulaTerm,
  // the nested useFormulaTerm will add any clauses to our
  // own deferredAddClauses list.
  while (deferredAddClauses.length) {
    var next = deferredAddClauses.pop();
    Logic.Solver._addClauses(solver, next.clauses, next.extraTerms, useTerm);
  }
};

Logic.Solver._addClauses = function (solver, array, _extraTerms,
                                                    _useTermOverride) {
  if (assert) assert(array, isArrayWhere(Logic.isClause));
  _.each(array, function (cls) {
    Logic.Solver._addClause(solver, cls, _extraTerms, _useTermOverride);
  });
};

Logic.Solver.require = function (/*formulaOrArray, ...*/) {
  var args = _.flatten(arguments);
  var solver = args[0]; args=args.slice(1);
  Logic.Solver._requireForbidImpl(solver, true, args);
};


Logic.Solver.forbid = function (/*formulaOrArray, ...*/) {
  var args = _.flatten(arguments);
  var solver = args[0]; args=args.slice(1);
  Logic.Solver._requireForbidImpl(solver, false, args);
};

Logic.Solver._requireForbidImpl = function (solver, isRequire, formulas) {
  if (assert) assert(formulas, isArrayWhere(isFormulaOrTerm));
  _.each(formulas, function (f) {
    if (f instanceof Logic.NotFormula) {
      Logic.Solver._requireForbidImpl(solver, !isRequire, [f.operand]);
    } else if (f instanceof Logic.Formula) {
      var info = Logic.Solver._getFormulaInfo(solver,f);
      if (info.varNum !== null) {
        var sign = isRequire ? 1 : -1;
        Logic.Solver._addClause(solver, new Logic.Clause(sign*info.varNum));
      } else {
        Logic.Solver._addClauses(solver,Logic.Solver._generateFormula(solver, isRequire, f));
      }
      if (isRequire) {
        info.isRequired = true;
      } else {
        info.isForbidden = true;
      }
    } else {
      Logic.Solver._addClauses(solver, Logic.Solver._generateFormula(solver, isRequire, f));
    }
  });
};

Logic.Solver._generateFormula = function (solver, isTrue, formula, _termifier) {
  if (assert) assert(formula, isFormulaOrTerm);

  if (formula instanceof Logic.NotFormula) {
    return Logic.Solver._generateFormula(solver, !isTrue, formula.operand);
  } else if (formula instanceof Logic.Formula) {
    var info = Logic.Solver._getFormulaInfo(solver, formula);
    if ((isTrue && info.isRequired) ||
        (!isTrue && info.isForbidden)) {
      return [];
      } else if ((isTrue && info.isForbidden) ||
                 (!isTrue && info.isRequired)) {
        return [new Logic.Clause()]; // never satisfied clause
      } else {
        var ret = formula.generateClauses(isTrue,
                                          _termifier || Logic._termifiers[solver.id]);
        return _.isArray(ret) ? ret : [ret];
      }
  } else { // Term
    var t = Logic.Solver.toNumTerm(solver, formula);
    var sign = isTrue ? 1 : -1;
    if (t === sign*Logic.NUM_TRUE || t === -sign*Logic.NUM_FALSE) {
      return [];
    } else if (t === sign*Logic.NUM_FALSE || t === -sign*Logic.NUM_TRUE) {
      return [new Logic.Clause()]; // never satisfied clause
    } else {
      return [new Logic.Clause(sign*t)];
    }
  }
};

// Get clause data as an array of arrays of integers,
// for testing and debugging purposes.
Logic.Solver._clauseData = function (solver) {
  var clauses = _.pluck(solver.clauses, 'terms');
  if (! solver._T_used) {
    clauses.splice(1, 1);
  }
  if (! solver._F_used) {
    clauses.splice(0, 1);
  }
  return clauses;
};

// Get clause data as an array of human-readable strings,
// for testing and debugging purposes.
// A clause might look like "A v -B" (where "v" represents
// and OR operator).
Logic.Solver._clauseStrings = function (solver) {
  var clauseData = Logic.Solver._clauseData(solver);
  return _.map(clauseData, function (clause) {
    return _.map(clause, function (nterm) {
      var str = Logic.Solver.toNameTerm(solver,nterm);
      if (/\s/.test(str)) {
        // write name in quotes for readability.  we don't bother
        // making this string machine-parsable in the general case.
        var sign = '';
        if (str.charAt(0) === '-') {
          // temporarily remove '-'
          sign = '-';
          str = str.slice(1);
        }
        str = sign + '"' + str + '"';
      }
      return str;
    }).join(' v ');
  });
};

Logic.Solver._getFormulaInfo = function (solver, formula, _noCreate) {
  var guid = formula.guid();
  if (!solver._formulaInfo[guid]) {
    if (_noCreate) {
      return null;
    }
    solver._formulaInfo[guid] = new FormulaInfo();
  }
  return solver._formulaInfo[guid];
};

// Takes a Formula or an array of Formulas, returns a NumTerm or
// array of NumTerms.
Logic.Solver._formulaToTerm = function (solver, formula) {

  if (_.isArray(formula)) {
    if (assert) assert(formula, isArrayWhere(isFormulaOrTerm));
    return _.map(formula, _.bind(solver._formulaToTerm, solver));   // Check it!
  } else {
    if (assert) assert(formula, isFormulaOrTerm);
  }

  if (formula instanceof Logic.NotFormula) {
    // shortcut that avoids creating a variable called
    // something like "$not1" when you use Logic.not(formula).
    return Logic.not(Logic.Solver._formulaToTerm(solver, formula.operand));
  } else if (formula instanceof Logic.Formula) {
    var info = Logic.Solver._getFormulaInfo(solver,formula);
    if (info.isRequired) {
      return Logic.NUM_TRUE;
    } else if (info.isForbidden) {
      return Logic.NUM_FALSE;
    } else if (info.varNum === null) {
      // generate a Solver-local formula variable like "$or1"
      var type = formula.type;
      if (! solver._nextFormulaNumByType[type]) {
        solver._nextFormulaNumByType[type] = 1;
      }
      var numForVarName = solver._nextFormulaNumByType[type]++;
      info.varName = "$" + formula.type + numForVarName;
      info.varNum = Logic.Solver.getVarNum(solver, info.varName, false, true);
      solver._ungeneratedFormulas[info.varNum] = formula;
    }
    return info.varNum;
  } else {
    // formula is a Term
    return Logic.Solver.toNumTerm(solver, formula);
  }
};

Logic.or = function (/*formulaOrArray, ...*/) {
  var args = _.flatten(arguments);
  if (args.length === 0) {
    return Logic.FALSE;
  } else if (args.length === 1) {
    if (assert) assert(args[0], isFormulaOrTerm);
    return args[0];
  } else {
    return new Logic.OrFormula(args);
  }
};

Logic.OrFormula = function (operands) {
  if (assert) assert(operands, isArrayWhere(isFormulaOrTerm));
  this.operands = operands;
};

Logic._defineFormula(Logic.OrFormula, 'or', {
  generateClauses: function (isTrue, t) {
    if (isTrue) {
      // eg A v B v C
      return t.clause(this.operands);
    } else {
      // eg -A; -B; -C
      var result = [];
      _.each(this.operands, function (o) {
        result.push.apply(result, t.generate(false, o));
      });
      return result;
    }
  }
});

Logic.NotFormula = function (operand) {
  if (assert) assert(operand, isFormulaOrTerm);
  this.operand = operand;
};

// No generation or simplification for 'not'; it is
// simplified away by the solver itself.
Logic._defineFormula(Logic.NotFormula, 'not');

Logic.and = function (/*formulaOrArray, ...*/) {
  var args = _.flatten(arguments);
  if (args.length === 0) {
    return Logic.TRUE;
  } else if (args.length === 1) {
    if (assert) assert(args[0], isFormulaOrTerm);
    return args[0];
  } else {
    return new Logic.AndFormula(args);
  }
};

Logic.AndFormula = function (operands) {
  if (assert) assert(operands, isArrayWhere(isFormulaOrTerm));
  this.operands = operands;
};

Logic._defineFormula(Logic.AndFormula, 'and', {
  generateClauses: function (isTrue, t) {
    if (isTrue) {
      // eg A; B; C
      var result = [];
      _.each(this.operands, function (o) {
        result.push.apply(result, t.generate(true, o));
      });
      return result;
    } else {
      // eg -A v -B v -C
      return t.clause(_.map(this.operands, Logic.not));
    }
  }
});

// Group `array` into groups of N, where the last group
// may be shorter than N.  group([a,b,c,d,e], 3) => [[a,b,c],[d,e]]
var group = function (array, N) {
  var ret = [];
  for (var i = 0; i < array.length; i += N) {
    ret.push(array.slice(i, i+N));
  }
  return ret;
};

Logic.xor = function (/*formulaOrArray, ...*/) {
  var args = _.flatten(arguments);
  if (args.length === 0) {
    return Logic.FALSE;
  } else if (args.length === 1) {
    if (assert) assert(args[0], isFormulaOrTerm);
    return args[0];
  } else {
    return new Logic.XorFormula(args);
  }
};

Logic.XorFormula = function (operands) {
  if (assert) assert(operands, isArrayWhere(isFormulaOrTerm));
  this.operands = operands;
};

Logic._defineFormula(Logic.XorFormula, 'xor', {
  generateClauses: function (isTrue, t) {
    var args = this.operands;
    var not = Logic.not;
    if (args.length > 3) {
      return t.generate(
        isTrue,
        Logic.xor(
          _.map(group(this.operands, 3), function (group) {
            return Logic.xor(group);
          })));
    } else if (isTrue) { // args.length <= 3
      if (args.length === 0) {
        return t.clause(); // always fail
      } else if (args.length === 1) {
        return t.clause(args[0]);
      } else if (args.length === 2) {
        var A = args[0], B = args[1];
        return [t.clause(A, B), // A v B
                t.clause(not(A), not(B))]; // -A v -B
      } else if (args.length === 3) {
        var A = args[0], B = args[1], C = args[2];
        return [t.clause(A, B, C), // A v B v C
                t.clause(A, not(B), not(C)), // A v -B v -C
                t.clause(not(A), B, not(C)), // -A v B v -C
                t.clause(not(A), not(B), C)]; // -A v -B v C
      }
    } else { // !isTrue, args.length <= 3
      if (args.length === 0) {
        return []; // always succeed
      } else if (args.length === 1) {
        return t.clause(not(args[0]));
      } else if (args.length === 2) {
        var A = args[0], B = args[1];
        return [t.clause(A, not(B)), // A v -B
                t.clause(not(A), B)]; // -A v B
      } else if (args.length === 3) {
        var A = args[0], B = args[1], C = args[2];
        return [t.clause(not(A), not(B), not(C)), // -A v -B v -C
                t.clause(not(A), B, C), // -A v B v C
                t.clause(A, not(B), C), // A v -B v C
                t.clause(A, B, not(C))]; // A v B v -C
      }
    }
  }
});

Logic.atMostOne = function (/*formulaOrArray, ...*/) {
  var args = _.flatten(arguments);
  if (args.length <= 1) {
    return Logic.TRUE;
  } else {
    return new Logic.AtMostOneFormula(args);
  }
};

Logic.AtMostOneFormula = function (operands) {
  if (assert) assert(operands, isArrayWhere(isFormulaOrTerm));
  this.operands = operands;
};

Logic._defineFormula(Logic.AtMostOneFormula, 'atMostOne', {
  generateClauses: function (isTrue, t) {
     var args = this.operands;
     var not = Logic.not;
     if (args.length <= 1) {
       return []; // always succeed
     } else if (args.length === 2) {
       return t.generate(isTrue, Logic.not(Logic.and(args)));
     } else if (isTrue && args.length === 3) {
       // Pick any two args; at least one is false (they aren't
       // both true).  This strategy would also work for
       // N>3, and could provide a speed-up by having more clauses
       // (N^2) but fewer propagation steps.  No speed-up was
       // observed on the Sudoku test from using this strategy
       // up to N=10.
       var clauses = [];
       for (var i = 0; i < args.length; i++) {
         for (var j = i+1; j < args.length; j++) {
           clauses.push(t.clause(not(args[i]), not(args[j])));
         }
       }
       return clauses;
     } else if ((! isTrue) && args.length === 3) {
       var A = args[0], B = args[1], C = args[2];
       // Pick any two args; at least one is true (they aren't
       // both false).  This only works for N=3.
       return [t.clause(A, B), t.clause(A, C), t.clause(B, C)];
     } else {
       // See the "commander variables" technique from:
       // http://www.cs.cmu.edu/~wklieber/papers/2007_efficient-cnf-encoding-for-selecting-1.pdf
       // But in short: At most one group has at least one "true",
       // and each group has at most one "true".  Formula generation
       // automatically generates the right implications.
       var groups = group(args, 3);
       var ors = _.map(groups, function (g) { return Logic.or(g); });
       if (groups[groups.length - 1].length < 2) {
         // Remove final group of length 1 so we don't generate
         // no-op clauses of one sort or another
         groups.pop();
       }
       var atMostOnes = _.map(groups, function (g) {
         return Logic.atMostOne(g);
       });
       return t.generate(isTrue, Logic.and(Logic.atMostOne(ors), atMostOnes));
     }
  }
});

Logic.implies = function (A, B) {
  if (assert) assertNumArgs(arguments.length, 2, "Logic.implies");
  return new Logic.ImpliesFormula(A, B);
};

Logic.ImpliesFormula = function (A, B) {
  if (assert) assert(A, isFormulaOrTerm);
  if (assert) assert(B, isFormulaOrTerm);
  if (assert) assertNumArgs(arguments.length, 2, "Logic.implies");
  this.A = A;
  this.B = B;
};

Logic._defineFormula(Logic.ImpliesFormula, 'implies', {
  generateClauses: function (isTrue, t) {
    return t.generate(isTrue, Logic.or(Logic.not(this.A), this.B));
  }
});

Logic.equiv = function (A, B) {
  if (assert) assertNumArgs(arguments.length, 2, "Logic.equiv");
  return new Logic.EquivFormula(A, B);
};

Logic.EquivFormula = function (A, B) {
  if (assert) assert(A, isFormulaOrTerm);
  if (assert) assert(B, isFormulaOrTerm);
  if (assert) assertNumArgs(arguments.length, 2, "Logic.equiv");
  this.A = A;
  this.B = B;
};

Logic._defineFormula(Logic.EquivFormula, 'equiv', {
  generateClauses: function (isTrue, t) {
    return t.generate(!isTrue, Logic.xor(this.A, this.B));
  }
});

Logic.exactlyOne = function (/*formulaOrArray, ...*/) {
  var args = _.flatten(arguments);
  if (args.length === 0) {
    return Logic.FALSE;
  } else if (args.length === 1) {
    if (assert) assert(args[0], isFormulaOrTerm);
    return args[0];
  } else {
    return new Logic.ExactlyOneFormula(args);
  }
};

Logic.ExactlyOneFormula = function (operands) {
  if (assert) assert(operands, isArrayWhere(isFormulaOrTerm));
  this.operands = operands;
};

Logic._defineFormula(Logic.ExactlyOneFormula, 'exactlyOne', {
  generateClauses: function (isTrue, t) {
    var args = this.operands;
    if (args.length < 3) {
      return t.generate(isTrue, Logic.xor(args));
    } else {
      return t.generate(isTrue, Logic.and(Logic.atMostOne(args),
                                          Logic.or(args)));
    }
  }
});

// List of 0 or more formulas or terms, which together represent
// a non-negative integer.  Least significant bit is first.  That is,
// the kth array element has a place value of 2^k.
Logic.Bits = function (formulaArray) {
  if (assert) assert(formulaArray, isArrayWhere(isFormulaOrTerm));
  this.bits = formulaArray; // public, immutable
};

Logic.constantBits = function (wholeNumber) {
  if (assert) assert(wholeNumber, Logic.isWholeNumber);
  var result = [];
  while (wholeNumber) {
    result.push((wholeNumber & 1) ? Logic.TRUE : Logic.FALSE);
    wholeNumber >>>= 1;
  }
  return new Logic.Bits(result);
};

Logic.variableBits = function (baseName, nbits) {
  if (assert) assert(nbits, Logic.isWholeNumber);
  var result = [];
  for (var i = 0; i < nbits; i++) {
    result.push(baseName + '$' + i);
  }
  return new Logic.Bits(result);
};

// bits1 <= bits2
Logic.lessThanOrEqual = function (bits1, bits2) {
  return new Logic.LessThanOrEqualFormula(bits1, bits2);
};

Logic.LessThanOrEqualFormula = function (bits1, bits2) {
  if (assert) assert(bits1, Logic.isBits);
  if (assert) assert(bits2, Logic.isBits);
  if (assert) assertNumArgs(arguments.length, 2, "Bits comparison function");
  this.bits1 = bits1;
  this.bits2 = bits2;
};

var genLTE = function (bits1, bits2, t, notEqual) {
  var ret = [];
  // clone so we can mutate them in place
  var A = bits1.bits.slice();
  var B = bits2.bits.slice();
  if (notEqual && ! bits2.bits.length) {
    // can't be less than 0
    return t.clause();
  }
  // if A is longer than B, the extra (high) bits
  // must be 0.
  while (A.length > B.length) {
    var hi = A.pop();
    ret.push(t.clause(Logic.not(hi)));
  }
  // now B.length >= A.length
  // Let xors[i] be (A[i] xor B[i]), or just
  // B[i] if A is too short.
  var xors = _.map(B, function (b, i) {
    if (i < A.length) {
      return Logic.xor(A[i], b);
    } else {
      return b;
    }
  });

  // Suppose we are comparing 3-bit numbers, requiring
  // that ABC <= XYZ.  Here is what we require:
  //
  // * It is false that A=1 and X=0.
  // * It is false that A=X, B=1, and Y=0.
  // * It is false that A=X, B=Y, C=1, and Y=0.
  //
  // Translating these into clauses using DeMorgan's law:
  //
  // * A=0 or X=1
  // * (A xor X) or B=0 or Y=1
  // * (A xor X) or (B xor Y) or C=0 or Y=1
  //
  // Since our arguments are LSB first, in the example
  // we would be given [C, B, A] and [Z, Y, X] as input.
  // We iterate over the first argument starting from
  // the right, and build up a clause by iterating over
  // the xors from the right.
  //
  // If we have ABC <= VWXYZ, then we still have three clauses,
  // but each one is prefixed with "V or W or", because V and W
  // are at the end of the xors array.  This is equivalent to
  // padding ABC with two zeros.

  for (var i = A.length-1; i >= 0; i--) {
    ret.push(t.clause(xors.slice(i+1), Logic.not(A[i]), B[i]));
  }
  if (notEqual) {
    ret.push.apply(ret, t.generate(true, Logic.or(xors)));
  }
  return ret;
};

Logic._defineFormula(Logic.LessThanOrEqualFormula, 'lte', {
  generateClauses: function (isTrue, t) {
    if (isTrue) {
      // bits1 <= bits2
      return genLTE(this.bits1, this.bits2, t, false);
    } else {
      // bits2 < bits1
      return genLTE(this.bits2, this.bits1, t, true);
    }
  }
});

// bits1 < bits2
Logic.lessThan = function (bits1, bits2) {
  return new Logic.LessThanFormula(bits1, bits2);
};

Logic.LessThanFormula = function (bits1, bits2) {
  if (assert) assert(bits1, Logic.isBits);
  if (assert) assert(bits2, Logic.isBits);
  if (assert) assertNumArgs(arguments.length, 2, "Bits comparison function");
  this.bits1 = bits1;
  this.bits2 = bits2;
};

Logic._defineFormula(Logic.LessThanFormula, 'lt', {
  generateClauses: function (isTrue, t) {
    if (isTrue) {
      // bits1 < bits2
      return genLTE(this.bits1, this.bits2, t, true);
    } else {
      // bits2 <= bits1
      return genLTE(this.bits2, this.bits1, t, false);
    }
  }
});

Logic.greaterThan = function (bits1, bits2) {
  return Logic.lessThan(bits2, bits1);
};

Logic.greaterThanOrEqual = function (bits1, bits2) {
  return Logic.lessThanOrEqual(bits2, bits1);
};

Logic.equalBits = function (bits1, bits2) {
  return new Logic.EqualBitsFormula(bits1, bits2);
};

Logic.EqualBitsFormula = function (bits1, bits2) {
  if (assert) assert(bits1, Logic.isBits);
  if (assert) assert(bits2, Logic.isBits);
  if (assert) assertNumArgs(arguments.length, 2, "Logic.equalBits");
  this.bits1 = bits1;
  this.bits2 = bits2;
};

Logic._defineFormula(Logic.EqualBitsFormula, 'equalBits', {
  generateClauses: function (isTrue, t) {
    var A = this.bits1.bits;
    var B = this.bits2.bits;
    var nbits = Math.max(A.length, B.length);
    var facts = [];
    for (var i = 0; i < nbits; i++) {
      if (i >= A.length) {
        facts.push(Logic.not(B[i]));
      } else if (i >= B.length) {
        facts.push(Logic.not(A[i]));
      } else {
        facts.push(Logic.equiv(A[i], B[i]));
      }
    }
    return t.generate(isTrue, Logic.and(facts));
  }
});

// Definition of full-adder and half-adder:
//
// A full-adder is a 3-input, 2-output gate producing the sum of its
// inputs as a 2-bit binary number. The most significant bit is called
// "carry", the least significant "sum". A half-adder does the same
// thing, but has only 2 inputs (and can therefore never output a
// "3").
//
// The half-adder sum bit is really just an XOR, and the carry bit
// is really just an AND.  However, they get their own formula types
// here to enhance readability of the generated clauses.

Logic.HalfAdderSum = function (formula1, formula2) {
  if (assert) assert(formula1, isFormulaOrTerm);
  if (assert) assert(formula2, isFormulaOrTerm);

  if (assert) assertNumArgs(arguments.length, 2, "Logic.HalfAdderSum");
  this.a = formula1;
  this.b = formula2;
};

Logic._defineFormula(Logic.HalfAdderSum, 'hsum', {
  generateClauses: function (isTrue, t) {
    return t.generate(isTrue, Logic.xor(this.a, this.b));
  }
});

Logic.HalfAdderCarry = function (formula1, formula2) {
  if (assert) assert(formula1, isFormulaOrTerm);
  if (assert) assert(formula2, isFormulaOrTerm);
  if (assert) assertNumArgs(arguments.length, 2, "Logic.HalfAdderCarry");
  this.a = formula1;
  this.b = formula2;
};

Logic._defineFormula(Logic.HalfAdderCarry, 'hcarry', {
  generateClauses: function (isTrue, t) {
    return t.generate(isTrue, Logic.and(this.a, this.b));
  }
});

Logic.FullAdderSum = function (formula1, formula2, formula3) {
  if (assert) assert(formula1, isFormulaOrTerm);
  if (assert) assert(formula2, isFormulaOrTerm);
  if (assert) assert(formula3, isFormulaOrTerm);
  if (assert) assertNumArgs(arguments.length, 3, "Logic.FullAdderSum");
  this.a = formula1;
  this.b = formula2;
  this.c = formula3;
};

Logic._defineFormula(Logic.FullAdderSum, 'fsum', {
  generateClauses: function (isTrue, t) {
    return t.generate(isTrue, Logic.xor(this.a, this.b, this.c));
  }
});

Logic.FullAdderCarry = function (formula1, formula2, formula3) {
  if (assert) assert(formula1, isFormulaOrTerm);
  if (assert) assert(formula2, isFormulaOrTerm);
  if (assert) assert(formula3, isFormulaOrTerm);
  if (assert) assertNumArgs(arguments.length, 3, "Logic.FullAdderCarry");
  this.a = formula1;
  this.b = formula2;
  this.c = formula3;
};

Logic._defineFormula(Logic.FullAdderCarry, 'fcarry', {
  generateClauses: function (isTrue, t) {
    return t.generate(! isTrue,
                      Logic.atMostOne(this.a, this.b, this.c));
  }
});

// Implements the Adder strategy from the MiniSat+ paper:
// http://minisat.se/downloads/MiniSat+.pdf
// "Translating Pseudo-boolean Constraints into SAT"
//
// Takes a list of list of Formulas.  The first list is bits
// to give weight 1; the second is bits to give weight 2;
// the third is bits to give weight 4; and so on.
//
// Returns an array of Logic.FormulaOrTerm.
var binaryWeightedSum = function (varsByWeight) {
  if (assert) assert(varsByWeight,
                     isArrayWhere(isArrayWhere(isFormulaOrTerm)));
  // initialize buckets to a two-level clone of varsByWeight
  var buckets = _.map(varsByWeight, _.clone);
  var lowestWeight = 0; // index of the first non-empty array
  var output = [];
  while (lowestWeight < buckets.length) {
    var bucket = buckets[lowestWeight];
    if (! bucket.length) {
      output.push(Logic.FALSE);
      lowestWeight++;
    } else if (bucket.length === 1) {
      output.push(bucket[0]);
      lowestWeight++;
    } else if (bucket.length === 2) {
      var sum = new Logic.HalfAdderSum(bucket[0], bucket[1]);
      var carry = new Logic.HalfAdderCarry(bucket[0], bucket[1]);
      bucket.length = 0;
      bucket.push(sum);
      pushToNth(buckets, lowestWeight+1, carry);
    } else {
      // Whether we take variables from the start or end of the
      // bucket (i.e. `pop` or `shift`) determines the shape of the tree.
      // Empirically, some logic problems are faster with `shift` (2x or so),
      // but `pop` gives an order-of-magnitude speed-up on the Meteor Version
      // Solver "benchmark-tests" suite (Slava's benchmarks based on data from
      // Rails).  So, `pop` it is.
      var c = bucket.pop();
      var b = bucket.pop();
      var a = bucket.pop();
      var sum = new Logic.FullAdderSum(a, b, c);
      var carry = new Logic.FullAdderCarry(a, b, c);
      bucket.push(sum);
      pushToNth(buckets, lowestWeight+1, carry);
    }
  }
  return output;
};

// Push `newItem` onto the array at arrayOfArrays[n],
// first ensuring that it exists by pushing empty
// arrays onto arrayOfArrays.
var pushToNth = function (arrayOfArrays, n, newItem) {
  while (n >= arrayOfArrays.length) {
    arrayOfArrays.push([]);
  }
  arrayOfArrays[n].push(newItem);
};

var checkWeightedSumArgs = function (formulas, weights) {
  if (assert) assert(formulas, isArrayWhere(isFormulaOrTerm));
  if (typeof weights === 'number') {
    if (assert) assert(weights, Logic.isWholeNumber);
  } else {
    if (assert) assert(weights, isArrayWhere(Logic.isWholeNumber));
    if (formulas.length !== weights.length) {
      throw new Error("Formula array and weight array must be same length" +
                      "; they are " + formulas.length + " and " + weights.length);
    }
  }
};

Logic.weightedSum = function (formulas, weights) {
  checkWeightedSumArgs(formulas, weights);

  if (formulas.length === 0) {
    return new Logic.Bits([]);
  }

  if (typeof weights === 'number') {
    weights = _.map(formulas, function () { return weights; });
  }

  var binaryWeighted = [];
  _.each(formulas, function (f, i) {
    var w = weights[i];
    var whichBit = 0;
    while (w) {
      if (w & 1) {
        pushToNth(binaryWeighted, whichBit, f);
      }
      w >>>= 1;
      whichBit++;
    }
  });

  return new Logic.Bits(binaryWeightedSum(binaryWeighted));
};

Logic.sum = function (/*formulaOrBitsOrArray, ...*/) {
  var things = _.flatten(arguments);
  if (assert) assert(things, isArrayWhere(isFormulaOrTermOrBits));

  var binaryWeighted = [];
  _.each(things, function (x) {
    if (x instanceof Logic.Bits) {
      _.each(x.bits, function (b, i) {
        pushToNth(binaryWeighted, i, b);
      });
    } else {
      pushToNth(binaryWeighted, 0, x);
    }
  });

  return new Logic.Bits(binaryWeightedSum(binaryWeighted));
};

// Parse text
// TODO: only simple expressions can be parsed
Logic.Parse = function (formulas) {
  var res=[];
  if (!(formulas instanceof Array)) formulas=[formulas];
  formulas.forEach(function (formula) {
    var ast = parse(formula);
    if (ast && ast.type == 'BinaryExpression') {
      switch (ast.operator) {
        case '^':
          res.push(Logic.atMostOne(ast.left.name, ast.right.name))
          break;
        case '|':
          res.push(Logic.or(ast.left.name, ast.right.name))
          break;
        case '&':
          res.push(Logic.and(ast.left.name, ast.right.name))
          break;
      }
    }
  });
  if (res.length==1) return res[0]; else return res;
}
////////////////////////////////////////

Logic.Solver.solve = function (solver, _assumpVar) {
  if (_assumpVar !== undefined) {
    if (! (_assumpVar >= 1)) {
      throw new Error("_assumpVar must be a variable number");
    }
  }

  if (solver._unsat) {
    return null;
  }

  while (solver._numClausesAddedToMiniSat < solver.clauses.length) {
    var i = solver._numClausesAddedToMiniSat;
    var terms = solver.clauses[i].terms;
    if (assert) assert(terms, isArrayWhere(Logic.isNumTerm));
    var stillSat = Logic._minisatSolvers[solver.id].addClause(terms);
    solver._numClausesAddedToMiniSat++;
    if (! stillSat) {
      solver._unsat = true;
      return null;
    }
  }

  if (assert) assert(solver._num2name.length - 1, Logic.isWholeNumber);
  Logic._minisatSolvers[solver.id].ensureVar(solver._num2name.length - 1);

  var stillSat = (_assumpVar ?
                  Logic._minisatSolvers[solver.id].solveAssuming(_assumpVar) :
                  Logic._minisatSolvers[solver.id].solve());
  if (! stillSat) {
    if (! _assumpVar) {
      solver._unsat = true;
    }
    return null;
  }

  return new Logic.Solution(solver, Logic._minisatSolvers[solver.id].getSolution());
};

Logic.Solver.solveAssuming = function (solver,formula) {
  if (assert) assert(formula, isFormulaOrTerm);

  // Wrap the formula in a formula of type Assumption, so that
  // we always generate a var like `$assump123`, regardless
  // of whether `formula` is a Term, a NotFormula, an already
  // required or forbidden Formula, etc.
  var assump = new Logic.Assumption(formula);
  var assumpVar = Logic.Solver._formulaToTerm(solver,assump);
  if (! (typeof assumpVar === 'number' && assumpVar > 0)) {
    throw new Error("Assertion failure: not a positive numeric term");
  }

  // Generate clauses as if we used the assumption variable in a
  // clause, in the positive.  So if we assume "A v B", we might get a
  // clause like "A v B v -$assump123" (or actually, "$or1 v
  // -$assump123"), as if we had used $assump123 in a clause.  Instead
  // of using it in a clause, though, we temporarily assume it to be
  // true.
  Logic.Solver._useFormulaTerm(solver,assumpVar);

  var result = Logic.Solver.solve(solver,assumpVar);
  // Tell MiniSat that we will never use assumpVar again.
  // The formula may be used again, however.  (For example, you
  // can solve assuming a formula F, and if it works, require F.)
  Logic._minisatSolvers[solver.id].retireVar(assumpVar);

  return result;
};

Logic.Assumption = function (formula) {
  if (assert) assert(formula, isFormulaOrTerm);
  this.formula = formula;
};

Logic._defineFormula(Logic.Assumption, 'assump', {
  generateClauses: function (isTrue, t) {
    if (isTrue) {
      return t.clause(this.formula);
    } else {
      return t.clause(Logic.not(this.formula));
    }
  }
});

Logic.Solution = function (_solver, _assignment) {
  var self = this;
  self._solver = _solver;
  self._assignment = _assignment;

  // save a snapshot of which formulas have variables designated
  // for them, but where we haven't generated clauses that constrain
  // those variables in both the positive and the negative direction.
  self._ungeneratedFormulas = _.clone(_solver._ungeneratedFormulas);

  self._formulaValueCache = {};
  self._termifier = new Logic.Termifier(self._solver);
  // Normally, when a Formula uses a Termifier to generate clauses that
  // refer to other Formulas, the Termifier replaces the Formulas with
  // their variables.  We hijack this mechanism to replace the Formulas
  // with their truth variables instead, leading to recursive evaluation.
  // Note that we cache the evaluated truth values of Formulas to avoid
  // redundant evaluation.
  self._termifier.term = function (formula) {
    return self.evaluate(formula) ? Logic.NUM_TRUE : Logic.NUM_FALSE;
  };

  // When true, evaluation doesn't throw errors when
  // `evaluate` or `getWeightedSum` encounter named variables that are
  // unknown or variables that weren't present when this Solution was
  // generated.  Instead, the unknown variables are assumed to be false.
  self._ignoreUnknownVariables = false;
};

Logic.Solution.prototype.ignoreUnknownVariables = function () {
  // We only make this settable one way (false to true).
  // Setting it back and forth would be questionable, since we keep
  // a cache of Formula evaluations.
  this._ignoreUnknownVariables = true;
};

// Get a map of variables to their assignments,
// such as `{A: true, B: false, C: true}`.
// Internal variables are excluded.
Logic.Solution.prototype.getMap = function () {
  var solver = this._solver;
  var assignment = this._assignment;
  var result = {};
  for (var i = 1; i < assignment.length; i++) {
    var name = Logic.Solver.getVarName(solver,i);
    if (name && name.charAt(0) !== '$') {
      result[name] = assignment[i];
    }
  }
  return result;
};

// Get an array of variables that are assigned
// `true` by this solution, sorted by name.
// Internal variables are excluded.
Logic.Solution.prototype.getTrueVars = function () {
  var solver = this._solver;
  var assignment = this._assignment;
  var result = [];
  for (var i = 1; i < assignment.length; i++) {
    if (assignment[i]) {
      var name = Logic.Solver.getVarName(solver,i);
      if (name && name.charAt(0) !== '$') {
        result.push(name);
      }
    }
  }
  result.sort();
  return result;
};

// Get a Formula that says that the variables are assigned
// according to this solution.  (Internal variables are
// excluded.)  By forbidding this Formula and solving again,
// you can see if there are other solutions.
Logic.Solution.prototype.getFormula = function () {
  var solver = this._solver;
  var assignment = this._assignment;
  var terms = [];
  for (var i = 1; i < assignment.length; i++) {
    var name = Logic.Solver.getVarName(solver,i);
    if (name && name.charAt(0) !== '$') {
      terms.push(assignment[i] ? i : -i);
    }
  }
  return Logic.and(terms);
};

// Returns a boolean if the argument is a Formula (or Term), and an integer
// if the argument is a Logic.Bits.
Logic.Solution.prototype.evaluate = function (formulaOrBits) {
  var self = this;
  if (assert) assert(formulaOrBits, isFormulaOrTermOrBits);

  if (formulaOrBits instanceof Logic.Bits) {
    // Evaluate to an integer
    var ret = 0;
    _.each(formulaOrBits.bits, function (f, i) {
      if (self.evaluate(f)) {
        ret += 1 << i;
      }
    });
    return ret;
  }

  var solver = self._solver;
  var ignoreUnknownVariables = self._ignoreUnknownVariables;
  var assignment = self._assignment;
  var formula = formulaOrBits;
  if (formula instanceof Logic.NotFormula) {
    return ! self.evaluate(formula.operand);
  } else if (formula instanceof Logic.Formula) {
    var cachedResult = self._formulaValueCache[formula.guid()];
    if (typeof cachedResult === 'boolean') {
      return cachedResult;
    } else {
      var value;
      var info = Logic.Solver._getFormulaInfo(solver, formula, true);
      if (info && info.varNum && info.varNum < assignment.length &&
          ! _.has(self._ungeneratedFormulas, info.varNum)) {
        // as an optimization, read the value of the formula directly
        // from a variable if the formula's clauses were completely
        // generated at the time of solving.  (We must be careful,
        // because if we didn't generate both the positive and the
        // negative polarity clauses for the formula, then the formula
        // variable is not actually constrained to have the right
        // value.)
        value = assignment[info.varNum];
      } else {
        var clauses = Logic.Solver._generateFormula(solver, true, formula, self._termifier);
        var value = _.all(clauses, function (cls) {
          return _.any(cls.terms, function (t) {
            return self.evaluate(t);
          });
        });
      }
      self._formulaValueCache[formula.guid()] = value;
      return value;
    }
  } else {
    // Term; convert to numeric (possibly negative), but throw
    // an error if the name is not found.  If `ignoreUnknownVariables`
    // is set, return false instead.
    var numTerm = Logic.Solver.toNumTerm(solver, formula, true);
    if (! numTerm) {
      if (ignoreUnknownVariables) {
        return false;
      } else {
        // formula must be a NameTerm naming a variable that doesn't exist
        var vname = String(formula).replace(/^-*/, '');
        throw new Error("No such variable: " + vname);
      }
    }
    var v = numTerm;
    var isNot = false;
    if (numTerm < 0) {
      v = -v;
      isNot = true;
    }
    if (v < 1 || v >= assignment.length) {
      var vname = v;
      if (v >= 1 && v < solver._num2name.length) {
        vname = solver._num2name[v];
      }
      if (ignoreUnknownVariables) {
        return false;
      } else {
        throw new Error("Variable not part of solution: " + vname);
      }
    }
    var ret = assignment[v];
    if (isNot) {
      ret = ! ret;
    }
    return ret;
  }
};

Logic.Solution.prototype.getWeightedSum = function (formulas, weights) {
  checkWeightedSumArgs(formulas, weights);

  var total = 0;
  if (typeof weights === 'number') {
    for (var i = 0; i < formulas.length; i++) {
      total += weights * (this.evaluate(formulas[i]) ? 1 : 0);
    }
  } else {
    for (var i = 0; i < formulas.length; i++) {
      total += weights[i] * (this.evaluate(formulas[i]) ? 1 : 0);
    }
  }
  return total;
};
var getNonZeroWeightedTerms = function (costTerms, costWeights) {
  if (typeof costWeights === 'number') {
    return costWeights ? costTerms : [];
  } else {
    var terms = [];
    for (var i = 0; i < costTerms.length; i++) {
      if (costWeights[i]) {
        terms.push(costTerms[i]);
      }
    }
    return terms;
  }
};

// See comments on minimizeWeightedSum and maximizeWeightedSum.
var minMaxWS = function (solver, solution, costTerms, costWeights, options,
                         isMin) {
  var curSolution = solution;
  var curCost = curSolution.getWeightedSum(costTerms, costWeights);

  var optFormula = options && options.formula;
  var weightedSum = (optFormula || Logic.weightedSum(costTerms, costWeights));

  var progress = options && options.progress;
  var strategy = options && options.strategy;

  // array of terms with non-zero weights, populated on demand
  var nonZeroTerms = null;

  if (isMin && curCost > 0) {
    // try to skip straight to 0 cost, because if it works, it could
    // save us some time
    if (progress) {
      progress('trying', 0);
    }
    var zeroSolution = null;
    nonZeroTerms = getNonZeroWeightedTerms(costTerms, costWeights);
    var zeroSolution = Logic.Solver.solveAssuming(solver,Logic.not(Logic.or(nonZeroTerms)));
    if (zeroSolution) {
      curSolution = zeroSolution;
      curCost = 0;
    }
  }

  if (isMin && strategy === 'bottom-up') {
    for (var trialCost = 1; trialCost < curCost; trialCost++) {
      if (progress) {
        progress('trying', trialCost);
      }
      var costIsTrialCost = Logic.equalBits(
        weightedSum, Logic.constantBits(trialCost));
      var newSolution = Logic.Solver.solveAssuming(solver,costIsTrialCost);
      if (newSolution) {
        curSolution = newSolution;
        curCost = trialCost;
        break;
      }
    }
  } else if (strategy && strategy !== 'default') {
    throw new Error("Bad strategy: " + strategy);
  } else {
    strategy = 'default';
  }

  if (strategy === 'default') {
    // for minimization, count down from current cost. for maximization,
    // count up.
    while (isMin ? curCost > 0 : true) {
      if (progress) {
        progress('improving', curCost);
      }
      var improvement = (isMin ? Logic.lessThan : Logic.greaterThan)(
        weightedSum, Logic.constantBits(curCost));
      var newSolution = Logic.Solver.solveAssuming(solver, improvement);
      if (! newSolution) {
        break;
      }
      Logic.Solver.require(solver,improvement);
      curSolution = newSolution;
      curCost = curSolution.getWeightedSum(costTerms, costWeights);
    }
  }

  if (isMin && curCost === 0) {
    // express the requirement that the weighted sum be 0 in an efficient
    // way for the solver (all terms with non-zero weights must be 0)
    if (! nonZeroTerms) {
      nonZeroTerms = getNonZeroWeightedTerms(costTerms, costWeights);
    }
    Logic.Solver.forbid(solver,nonZeroTerms);
  } else {
    Logic.Solver.require(solver,Logic.equalBits(weightedSum, Logic.constantBits(curCost)));
  }

  if (progress) {
    progress('finished', curCost);
  }

  return curSolution;
};

// Minimize (or maximize) the dot product of costTerms and
// costWeights, and further, require (as in solver.require) that the
// value of the dot product be equal to the optimum found.  Returns a
// valid solution where this optimum is achieved.
//
// `solution` must be a current valid solution as returned from
// `solve` or `solveAssuming`.  It is used as a starting point (to
// evaluate the current cost).
//
// costWeights is an array (of same length as costTerms) or a single
// WholeNumber.
//
// if the caller passes options.formula, it should be the formula
// Logic.weightedSum(costTerms, costWeights).  The optimizer will use
// this existing formula rather than generating a new one (for
// efficiency).  The optimizer still wants to know the terms and
// weights, because it is more efficient for it to evaluate the
// current cost using them directly rather than the formula.
//
// options.progress: a function that takes two arguments, to call at
// particular times during optimization.  Called with arguments
// ('improving', cost) when about to search for a way to improve on
// `cost`, and called with arguments ('finished', cost) when the
// optimum is reached.  There's also ('trying', cost) when a cost
// is tried directly (which is usually done with 0 right off the bat).
//
// options.strategy: a string hinting how to go about the optimization.
// the default strategy (option absent or 'default') is to work down
// from the current cost for minimization or up from the current cost
// for maximization, and iteratively insist that the cost be made lower
// if possible.  For minimization, the alternate strategy 'bottom-up' is
// available, which starts at 0 and tries ever higher costs until one
// works.  All strategies first try and see if a cost of 0 is possible.

// ("costTerms" is kind of a misnomer since they may be Formulas or Terms.)
Logic.Solver.minimizeWeightedSum = function (solver, solution, costTerms,
                                                     costWeights, options) {
  return minMaxWS(solver, solution, costTerms, costWeights, options, true);
};

Logic.Solver.maximizeWeightedSum = function (solver, solution, costTerms,
                                                     costWeights, options) {
  return minMaxWS(solver, solution, costTerms, costWeights, options, false);
};
module.exports = Logic;
};
BundleModuleCode['logic/minisat_wrapper.js']=function (module,exports){
var C_MINISAT = Require("logic/minisat.js");
var _ = Require("ext/underscore");
var MiniSat;
MiniSat = function () {
  // A MiniSat object wraps an instance of "native" MiniSat.  You can
  // have as many MiniSat objects as you want, and they are all
  // independent.
  //
  // C is the "module" object created by Emscripten.  We wrap the
  // output of Emscripten in a closure, so each call to C_MINISAT()
  // actually instantiates a separate C environment, including
  // the "native" heap.
  //
  // The methods available on `C` include the global functions we
  // define in `logic-solver.cc`, each prefixed with `_`, and a varied
  // assortment of helpers put there by Emscripten, some of which are
  // documented here:
  // http://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html
  //
  // See the README in the meteor/minisat repo for more notes about
  // our build of MiniSat.
  var C = this._C = C_MINISAT();

  this._native = {
    getStackPointer: function () {
      return C.Runtime.stackSave();
    },
    setStackPointer: function (ptr) {
      C.Runtime.stackRestore(ptr);
    },
    allocateBytes: function (len) {
      return C.allocate(len, 'i8', C.ALLOC_STACK);
    },
    pushString: function (str) {
      return this.allocateBytes(C.intArrayFromString(str));
    },
    savingStack: function (func) {
      var SP = this.getStackPointer();
      try {
        return func(this, C);
      } finally {
        this.setStackPointer(SP);
      }
    }
  };

  C._createTheSolver();

  // useful log for debugging and testing
  this._clauses = [];
};


// Make sure MiniSat has allocated space in its model for v,
// even if v is unused.  If we have variables A,B,C,D which
// are numbers 1,2,3,4, for example, but we never actually use
// C and D, calling ensureVar(4) will make MiniSat give us
// solution values for them anyway.
MiniSat.prototype.ensureVar = function (v) {
  this._C._ensureVar(v);
};

// Add a clause, in the form of an array of Logic.NumTerms.
// Returns true if the problem is still satisfiable
// (as far as we know without doing more work), and false if
// we can already tell that it is unsatisfiable.
MiniSat.prototype.addClause = function (terms) {
  this._clauses.push(terms);
  return this._native.savingStack(function (native, C) {
    var termsPtr = C.allocate((terms.length+1)*4, 'i32', C.ALLOC_STACK);
    _.each(terms, function (t, i) {
      C.setValue(termsPtr + i*4, t, 'i32');
    });
    C.setValue(termsPtr + terms.length*4, 0, 'i32'); // 0-terminate
    return C._addClause(termsPtr) ? true : false;
  });
};

MiniSat.prototype.solve = function () {
  return this._C._solve() ? true : false;
};

MiniSat.prototype.solveAssuming = function (v) {
  return this._C._solveAssuming(v) ? true : false;
};

MiniSat.prototype.getSolution = function () {
  var solution = [null]; // no 0th var
  var C = this._C;
  var numVars = C._getNumVars();
  var solPtr = C._getSolution();
  for (var i = 0; i < numVars; i++) {
    // 0 is Minisat::l_True (lifted "true").
    // Internally, Minisat has three states for a variable:
    // true, false, and undetermined.  It doesn't distinguish
    // between "false" and "undetermined" in solutions though
    // (I think it sets undetermined variables to false).
    solution[i+1] = (C.getValue(solPtr+i, 'i8') === 0);
  }
  return solution;
};

MiniSat.prototype.retireVar = function (v) {
  this._C._retireVar(v);
};

// The "conflict clause" feature of MiniSat is not what it sounds
// like, unfortunately -- it doesn't help explain conflicts.
// It only tells us which assumption vars are to blame for a failed
// solveAssuming (and we only ever pass one var).
// We keep this function around in case we discover a use for it.
MiniSat.prototype.getConflictClause = function () {
  var C = this._C;
  var numTerms = C._getConflictClauseSize();
  var clausePtr = C._getConflictClause();
  var terms = [];
  for (var i = 0; i < numTerms; i++) {
    var t = C.getValue(clausePtr + i*4, 'i32');
    var v = (t >>> 1);
    var s = (t & 1) ? -1 : 1;
    terms[i] = v * s;
  }
  return terms;
};
module.exports = MiniSat;
};
BundleModuleCode['logic/minisat.js']=function (module,exports){
var C_MINISAT;
// This file is generated by the meteor/minisat repo.
// See that repo's README for instructions for building it.
C_MINISAT=(function(){var module={};var require=(function(){});var process={argv:["node","minisat"],on:(function(){}),stdout:{write:(function(str){console.log("MINISAT-out:",str.replace(/\n$/,""))})},stderr:{write:(function(str){console.log("MINISAT-err:",str.replace(/\n$/,""))})}};var window=0;var Module;if(!Module)Module=(typeof Module!=="undefined"?Module:null)||{};var moduleOverrides={};for(var key in Module){if(Module.hasOwnProperty(key)){moduleOverrides[key]=Module[key]}}var ENVIRONMENT_IS_NODE=typeof process==="object"&&typeof require==="function";var ENVIRONMENT_IS_WEB=typeof window==="object";var ENVIRONMENT_IS_WORKER=typeof importScripts==="function";var ENVIRONMENT_IS_SHELL=!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_NODE&&!ENVIRONMENT_IS_WORKER;if(ENVIRONMENT_IS_NODE){if(!Module["print"])Module["print"]=function print(x){process["stdout"].write(x+"\n")};if(!Module["printErr"])Module["printErr"]=function printErr(x){process["stderr"].write(x+"\n")};var nodeFS=require("fs");var nodePath=require("path");Module["read"]=function read(filename,binary){filename=nodePath["normalize"](filename);var ret=nodeFS["readFileSync"](filename);if(!ret&&filename!=nodePath["resolve"](filename)){filename=path.join(__dirname,"..","src",filename);ret=nodeFS["readFileSync"](filename)}if(ret&&!binary)ret=ret.toString();return ret};Module["readBinary"]=function readBinary(filename){return Module["read"](filename,true)};Module["load"]=function load(f){globalEval(read(f))};if(process["argv"].length>1){Module["thisProgram"]=process["argv"][1].replace(/\\/g,"/")}else{Module["thisProgram"]="unknown-program"}Module["arguments"]=process["argv"].slice(2);if(typeof module!=="undefined"){module["exports"]=Module}process["on"]("uncaughtException",(function(ex){if(!(ex instanceof ExitStatus)){throw ex}}))}else if(ENVIRONMENT_IS_SHELL){if(!Module["print"])Module["print"]=print;if(typeof printErr!="undefined")Module["printErr"]=printErr;if(typeof read!="undefined"){Module["read"]=read}else{Module["read"]=function read(){throw"no read() available (jsc?)"}}Module["readBinary"]=function readBinary(f){if(typeof readbuffer==="function"){return new Uint8Array(readbuffer(f))}var data=read(f,"binary");assert(typeof data==="object");return data};if(typeof scriptArgs!="undefined"){Module["arguments"]=scriptArgs}else if(typeof arguments!="undefined"){Module["arguments"]=arguments}this["Module"]=Module}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){Module["read"]=function read(url){var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText};if(typeof arguments!="undefined"){Module["arguments"]=arguments}if(typeof console!=="undefined"){if(!Module["print"])Module["print"]=function print(x){console.log(x)};if(!Module["printErr"])Module["printErr"]=function printErr(x){console.log(x)}}else{var TRY_USE_DUMP=false;if(!Module["print"])Module["print"]=TRY_USE_DUMP&&typeof dump!=="undefined"?(function(x){dump(x)}):(function(x){})}if(ENVIRONMENT_IS_WEB){window["Module"]=Module}else{Module["load"]=importScripts}}else{throw"Unknown runtime environment. Where are we?"}function globalEval(x){eval.call(null,x)}if(!Module["load"]&&Module["read"]){Module["load"]=function load(f){globalEval(Module["read"](f))}}if(!Module["print"]){Module["print"]=(function(){})}if(!Module["printErr"]){Module["printErr"]=Module["print"]}if(!Module["arguments"]){Module["arguments"]=[]}if(!Module["thisProgram"]){Module["thisProgram"]="./this.program"}Module.print=Module["print"];Module.printErr=Module["printErr"];Module["preRun"]=[];Module["postRun"]=[];for(var key in moduleOverrides){if(moduleOverrides.hasOwnProperty(key)){Module[key]=moduleOverrides[key]}}var Runtime={setTempRet0:(function(value){tempRet0=value}),getTempRet0:(function(){return tempRet0}),stackSave:(function(){return STACKTOP}),stackRestore:(function(stackTop){STACKTOP=stackTop}),getNativeTypeSize:(function(type){switch(type){case"i1":case"i8":return 1;case"i16":return 2;case"i32":return 4;case"i64":return 8;case"float":return 4;case"double":return 8;default:{if(type[type.length-1]==="*"){return Runtime.QUANTUM_SIZE}else if(type[0]==="i"){var bits=parseInt(type.substr(1));assert(bits%8===0);return bits/8}else{return 0}}}}),getNativeFieldSize:(function(type){return Math.max(Runtime.getNativeTypeSize(type),Runtime.QUANTUM_SIZE)}),STACK_ALIGN:16,getAlignSize:(function(type,size,vararg){if(!vararg&&(type=="i64"||type=="double"))return 8;if(!type)return Math.min(size,8);return Math.min(size||(type?Runtime.getNativeFieldSize(type):0),Runtime.QUANTUM_SIZE)}),dynCall:(function(sig,ptr,args){if(args&&args.length){if(!args.splice)args=Array.prototype.slice.call(args);args.splice(0,0,ptr);return Module["dynCall_"+sig].apply(null,args)}else{return Module["dynCall_"+sig].call(null,ptr)}}),functionPointers:[],addFunction:(function(func){for(var i=0;i<Runtime.functionPointers.length;i++){if(!Runtime.functionPointers[i]){Runtime.functionPointers[i]=func;return 2*(1+i)}}throw"Finished up all reserved function pointers. Use a higher value for RESERVED_FUNCTION_POINTERS."}),removeFunction:(function(index){Runtime.functionPointers[(index-2)/2]=null}),getAsmConst:(function(code,numArgs){if(!Runtime.asmConstCache)Runtime.asmConstCache={};var func=Runtime.asmConstCache[code];if(func)return func;var args=[];for(var i=0;i<numArgs;i++){args.push(String.fromCharCode(36)+i)}var source=Pointer_stringify(code);if(source[0]==='"'){if(source.indexOf('"',1)===source.length-1){source=source.substr(1,source.length-2)}else{abort("invalid EM_ASM input |"+source+"|. Please use EM_ASM(..code..) (no quotes) or EM_ASM({ ..code($0).. }, input) (to input values)")}}try{var evalled=eval("(function(Module, FS) { return function("+args.join(",")+"){ "+source+" } })")(Module,typeof FS!=="undefined"?FS:null)}catch(e){Module.printErr("error in executing inline EM_ASM code: "+e+" on: \n\n"+source+"\n\nwith args |"+args+"| (make sure to use the right one out of EM_ASM, EM_ASM_ARGS, etc.)");throw e}return Runtime.asmConstCache[code]=evalled}),warnOnce:(function(text){if(!Runtime.warnOnce.shown)Runtime.warnOnce.shown={};if(!Runtime.warnOnce.shown[text]){Runtime.warnOnce.shown[text]=1;Module.printErr(text)}}),funcWrappers:{},getFuncWrapper:(function(func,sig){assert(sig);if(!Runtime.funcWrappers[sig]){Runtime.funcWrappers[sig]={}}var sigCache=Runtime.funcWrappers[sig];if(!sigCache[func]){sigCache[func]=function dynCall_wrapper(){return Runtime.dynCall(sig,func,arguments)}}return sigCache[func]}),UTF8Processor:(function(){var buffer=[];var needed=0;this.processCChar=(function(code){code=code&255;if(buffer.length==0){if((code&128)==0){return String.fromCharCode(code)}buffer.push(code);if((code&224)==192){needed=1}else if((code&240)==224){needed=2}else{needed=3}return""}if(needed){buffer.push(code);needed--;if(needed>0)return""}var c1=buffer[0];var c2=buffer[1];var c3=buffer[2];var c4=buffer[3];var ret;if(buffer.length==2){ret=String.fromCharCode((c1&31)<<6|c2&63)}else if(buffer.length==3){ret=String.fromCharCode((c1&15)<<12|(c2&63)<<6|c3&63)}else{var codePoint=(c1&7)<<18|(c2&63)<<12|(c3&63)<<6|c4&63;ret=String.fromCharCode(((codePoint-65536)/1024|0)+55296,(codePoint-65536)%1024+56320)}buffer.length=0;return ret});this.processJSString=function processJSString(string){string=unescape(encodeURIComponent(string));var ret=[];for(var i=0;i<string.length;i++){ret.push(string.charCodeAt(i))}return ret}}),getCompilerSetting:(function(name){throw"You must build with -s RETAIN_COMPILER_SETTINGS=1 for Runtime.getCompilerSetting or emscripten_get_compiler_setting to work"}),stackAlloc:(function(size){var ret=STACKTOP;STACKTOP=STACKTOP+size|0;STACKTOP=STACKTOP+15&-16;return ret}),staticAlloc:(function(size){var ret=STATICTOP;STATICTOP=STATICTOP+size|0;STATICTOP=STATICTOP+15&-16;return ret}),dynamicAlloc:(function(size){var ret=DYNAMICTOP;DYNAMICTOP=DYNAMICTOP+size|0;DYNAMICTOP=DYNAMICTOP+15&-16;if(DYNAMICTOP>=TOTAL_MEMORY)enlargeMemory();return ret}),alignMemory:(function(size,quantum){var ret=size=Math.ceil(size/(quantum?quantum:16))*(quantum?quantum:16);return ret}),makeBigInt:(function(low,high,unsigned){var ret=unsigned?+(low>>>0)+ +(high>>>0)*+4294967296:+(low>>>0)+ +(high|0)*+4294967296;return ret}),GLOBAL_BASE:8,QUANTUM_SIZE:4,__dummy__:0};Module["Runtime"]=Runtime;var __THREW__=0;var ABORT=false;var EXITSTATUS=0;var undef=0;var tempValue,tempInt,tempBigInt,tempInt2,tempBigInt2,tempPair,tempBigIntI,tempBigIntR,tempBigIntS,tempBigIntP,tempBigIntD,tempDouble,tempFloat;var tempI64,tempI64b;var tempRet0,tempRet1,tempRet2,tempRet3,tempRet4,tempRet5,tempRet6,tempRet7,tempRet8,tempRet9;function assert(condition,text){if(!condition){abort("Assertion failed: "+text)}}var globalScope=this;function getCFunc(ident){var func=Module["_"+ident];if(!func){try{func=eval("_"+ident)}catch(e){}}assert(func,"Cannot call unknown function "+ident+" (perhaps LLVM optimizations or closure removed it?)");return func}var cwrap,ccall;((function(){var JSfuncs={"stackSave":(function(){Runtime.stackSave()}),"stackRestore":(function(){Runtime.stackRestore()}),"arrayToC":(function(arr){var ret=Runtime.stackAlloc(arr.length);writeArrayToMemory(arr,ret);return ret}),"stringToC":(function(str){var ret=0;if(str!==null&&str!==undefined&&str!==0){ret=Runtime.stackAlloc((str.length<<2)+1);writeStringToMemory(str,ret)}return ret})};var toC={"string":JSfuncs["stringToC"],"array":JSfuncs["arrayToC"]};ccall=function ccallFunc(ident,returnType,argTypes,args){var func=getCFunc(ident);var cArgs=[];var stack=0;if(args){for(var i=0;i<args.length;i++){var converter=toC[argTypes[i]];if(converter){if(stack===0)stack=Runtime.stackSave();cArgs[i]=converter(args[i])}else{cArgs[i]=args[i]}}}var ret=func.apply(null,cArgs);if(returnType==="string")ret=Pointer_stringify(ret);if(stack!==0)Runtime.stackRestore(stack);return ret};var sourceRegex=/^function\s*\(([^)]*)\)\s*{\s*([^*]*?)[\s;]*(?:return\s*(.*?)[;\s]*)?}$/;function parseJSFunc(jsfunc){var parsed=jsfunc.toString().match(sourceRegex).slice(1);return{arguments:parsed[0],body:parsed[1],returnValue:parsed[2]}}var JSsource={};for(var fun in JSfuncs){if(JSfuncs.hasOwnProperty(fun)){JSsource[fun]=parseJSFunc(JSfuncs[fun])}}cwrap=function cwrap(ident,returnType,argTypes){argTypes=argTypes||[];var cfunc=getCFunc(ident);var numericArgs=argTypes.every((function(type){return type==="number"}));var numericRet=returnType!=="string";if(numericRet&&numericArgs){return cfunc}var argNames=argTypes.map((function(x,i){return"$"+i}));var funcstr="(function("+argNames.join(",")+") {";var nargs=argTypes.length;if(!numericArgs){funcstr+="var stack = "+JSsource["stackSave"].body+";";for(var i=0;i<nargs;i++){var arg=argNames[i],type=argTypes[i];if(type==="number")continue;var convertCode=JSsource[type+"ToC"];funcstr+="var "+convertCode.arguments+" = "+arg+";";funcstr+=convertCode.body+";";funcstr+=arg+"="+convertCode.returnValue+";"}}var cfuncname=parseJSFunc((function(){return cfunc})).returnValue;funcstr+="var ret = "+cfuncname+"("+argNames.join(",")+");";if(!numericRet){var strgfy=parseJSFunc((function(){return Pointer_stringify})).returnValue;funcstr+="ret = "+strgfy+"(ret);"}if(!numericArgs){funcstr+=JSsource["stackRestore"].body.replace("()","(stack)")+";"}funcstr+="return ret})";return eval(funcstr)}}))();Module["cwrap"]=cwrap;Module["ccall"]=ccall;function setValue(ptr,value,type,noSafe){type=type||"i8";if(type.charAt(type.length-1)==="*")type="i32";switch(type){case"i1":HEAP8[ptr>>0]=value;break;case"i8":HEAP8[ptr>>0]=value;break;case"i16":HEAP16[ptr>>1]=value;break;case"i32":HEAP32[ptr>>2]=value;break;case"i64":tempI64=[value>>>0,(tempDouble=value,+Math_abs(tempDouble)>=+1?tempDouble>+0?(Math_min(+Math_floor(tempDouble/+4294967296),+4294967295)|0)>>>0:~~+Math_ceil((tempDouble- +(~~tempDouble>>>0))/+4294967296)>>>0:0)],HEAP32[ptr>>2]=tempI64[0],HEAP32[ptr+4>>2]=tempI64[1];break;case"float":HEAPF32[ptr>>2]=value;break;case"double":HEAPF64[ptr>>3]=value;break;default:abort("invalid type for setValue: "+type)}}Module["setValue"]=setValue;function getValue(ptr,type,noSafe){type=type||"i8";if(type.charAt(type.length-1)==="*")type="i32";switch(type){case"i1":return HEAP8[ptr>>0];case"i8":return HEAP8[ptr>>0];case"i16":return HEAP16[ptr>>1];case"i32":return HEAP32[ptr>>2];case"i64":return HEAP32[ptr>>2];case"float":return HEAPF32[ptr>>2];case"double":return HEAPF64[ptr>>3];default:abort("invalid type for setValue: "+type)}return null}Module["getValue"]=getValue;var ALLOC_NORMAL=0;var ALLOC_STACK=1;var ALLOC_STATIC=2;var ALLOC_DYNAMIC=3;var ALLOC_NONE=4;Module["ALLOC_NORMAL"]=ALLOC_NORMAL;Module["ALLOC_STACK"]=ALLOC_STACK;Module["ALLOC_STATIC"]=ALLOC_STATIC;Module["ALLOC_DYNAMIC"]=ALLOC_DYNAMIC;Module["ALLOC_NONE"]=ALLOC_NONE;function allocate(slab,types,allocator,ptr){var zeroinit,size;if(typeof slab==="number"){zeroinit=true;size=slab}else{zeroinit=false;size=slab.length}var singleType=typeof types==="string"?types:null;var ret;if(allocator==ALLOC_NONE){ret=ptr}else{ret=[_malloc,Runtime.stackAlloc,Runtime.staticAlloc,Runtime.dynamicAlloc][allocator===undefined?ALLOC_STATIC:allocator](Math.max(size,singleType?1:types.length))}if(zeroinit){var ptr=ret,stop;assert((ret&3)==0);stop=ret+(size&~3);for(;ptr<stop;ptr+=4){HEAP32[ptr>>2]=0}stop=ret+size;while(ptr<stop){HEAP8[ptr++>>0]=0}return ret}if(singleType==="i8"){if(slab.subarray||slab.slice){HEAPU8.set(slab,ret)}else{HEAPU8.set(new Uint8Array(slab),ret)}return ret}var i=0,type,typeSize,previousType;while(i<size){var curr=slab[i];if(typeof curr==="function"){curr=Runtime.getFunctionIndex(curr)}type=singleType||types[i];if(type===0){i++;continue}if(type=="i64")type="i32";setValue(ret+i,curr,type);if(previousType!==type){typeSize=Runtime.getNativeTypeSize(type);previousType=type}i+=typeSize}return ret}Module["allocate"]=allocate;function Pointer_stringify(ptr,length){if(length===0||!ptr)return"";var hasUtf=false;var t;var i=0;while(1){t=HEAPU8[ptr+i>>0];if(t>=128)hasUtf=true;else if(t==0&&!length)break;i++;if(length&&i==length)break}if(!length)length=i;var ret="";if(!hasUtf){var MAX_CHUNK=1024;var curr;while(length>0){curr=String.fromCharCode.apply(String,HEAPU8.subarray(ptr,ptr+Math.min(length,MAX_CHUNK)));ret=ret?ret+curr:curr;ptr+=MAX_CHUNK;length-=MAX_CHUNK}return ret}var utf8=new Runtime.UTF8Processor;for(i=0;i<length;i++){t=HEAPU8[ptr+i>>0];ret+=utf8.processCChar(t)}return ret}Module["Pointer_stringify"]=Pointer_stringify;function UTF16ToString(ptr){var i=0;var str="";while(1){var codeUnit=HEAP16[ptr+i*2>>1];if(codeUnit==0)return str;++i;str+=String.fromCharCode(codeUnit)}}Module["UTF16ToString"]=UTF16ToString;function stringToUTF16(str,outPtr){for(var i=0;i<str.length;++i){var codeUnit=str.charCodeAt(i);HEAP16[outPtr+i*2>>1]=codeUnit}HEAP16[outPtr+str.length*2>>1]=0}Module["stringToUTF16"]=stringToUTF16;function UTF32ToString(ptr){var i=0;var str="";while(1){var utf32=HEAP32[ptr+i*4>>2];if(utf32==0)return str;++i;if(utf32>=65536){var ch=utf32-65536;str+=String.fromCharCode(55296|ch>>10,56320|ch&1023)}else{str+=String.fromCharCode(utf32)}}}Module["UTF32ToString"]=UTF32ToString;function stringToUTF32(str,outPtr){var iChar=0;for(var iCodeUnit=0;iCodeUnit<str.length;++iCodeUnit){var codeUnit=str.charCodeAt(iCodeUnit);if(codeUnit>=55296&&codeUnit<=57343){var trailSurrogate=str.charCodeAt(++iCodeUnit);codeUnit=65536+((codeUnit&1023)<<10)|trailSurrogate&1023}HEAP32[outPtr+iChar*4>>2]=codeUnit;++iChar}HEAP32[outPtr+iChar*4>>2]=0}Module["stringToUTF32"]=stringToUTF32;function demangle(func){var hasLibcxxabi=!!Module["___cxa_demangle"];if(hasLibcxxabi){try{var buf=_malloc(func.length);writeStringToMemory(func.substr(1),buf);var status=_malloc(4);var ret=Module["___cxa_demangle"](buf,0,0,status);if(getValue(status,"i32")===0&&ret){return Pointer_stringify(ret)}}catch(e){}finally{if(buf)_free(buf);if(status)_free(status);if(ret)_free(ret)}}var i=3;var basicTypes={"v":"void","b":"bool","c":"char","s":"short","i":"int","l":"long","f":"float","d":"double","w":"wchar_t","a":"signed char","h":"unsigned char","t":"unsigned short","j":"unsigned int","m":"unsigned long","x":"long long","y":"unsigned long long","z":"..."};var subs=[];var first=true;function dump(x){if(x)Module.print(x);Module.print(func);var pre="";for(var a=0;a<i;a++)pre+=" ";Module.print(pre+"^")}function parseNested(){i++;if(func[i]==="K")i++;var parts=[];while(func[i]!=="E"){if(func[i]==="S"){i++;var next=func.indexOf("_",i);var num=func.substring(i,next)||0;parts.push(subs[num]||"?");i=next+1;continue}if(func[i]==="C"){parts.push(parts[parts.length-1]);i+=2;continue}var size=parseInt(func.substr(i));var pre=size.toString().length;if(!size||!pre){i--;break}var curr=func.substr(i+pre,size);parts.push(curr);subs.push(curr);i+=pre+size}i++;return parts}function parse(rawList,limit,allowVoid){limit=limit||Infinity;var ret="",list=[];function flushList(){return"("+list.join(", ")+")"}var name;if(func[i]==="N"){name=parseNested().join("::");limit--;if(limit===0)return rawList?[name]:name}else{if(func[i]==="K"||first&&func[i]==="L")i++;var size=parseInt(func.substr(i));if(size){var pre=size.toString().length;name=func.substr(i+pre,size);i+=pre+size}}first=false;if(func[i]==="I"){i++;var iList=parse(true);var iRet=parse(true,1,true);ret+=iRet[0]+" "+name+"<"+iList.join(", ")+">"}else{ret=name}paramLoop:while(i<func.length&&limit-->0){var c=func[i++];if(c in basicTypes){list.push(basicTypes[c])}else{switch(c){case"P":list.push(parse(true,1,true)[0]+"*");break;case"R":list.push(parse(true,1,true)[0]+"&");break;case"L":{i++;var end=func.indexOf("E",i);var size=end-i;list.push(func.substr(i,size));i+=size+2;break};case"A":{var size=parseInt(func.substr(i));i+=size.toString().length;if(func[i]!=="_")throw"?";i++;list.push(parse(true,1,true)[0]+" ["+size+"]");break};case"E":break paramLoop;default:ret+="?"+c;break paramLoop}}}if(!allowVoid&&list.length===1&&list[0]==="void")list=[];if(rawList){if(ret){list.push(ret+"?")}return list}else{return ret+flushList()}}var parsed=func;try{if(func=="Object._main"||func=="_main"){return"main()"}if(typeof func==="number")func=Pointer_stringify(func);if(func[0]!=="_")return func;if(func[1]!=="_")return func;if(func[2]!=="Z")return func;switch(func[3]){case"n":return"operator new()";case"d":return"operator delete()"}parsed=parse()}catch(e){parsed+="?"}if(parsed.indexOf("?")>=0&&!hasLibcxxabi){Runtime.warnOnce("warning: a problem occurred in builtin C++ name demangling; build with  -s DEMANGLE_SUPPORT=1  to link in libcxxabi demangling")}return parsed}function demangleAll(text){return text.replace(/__Z[\w\d_]+/g,(function(x){var y=demangle(x);return x===y?x:x+" ["+y+"]"}))}function jsStackTrace(){var err=new Error;if(!err.stack){try{throw new Error(0)}catch(e){err=e}if(!err.stack){return"(no stack trace available)"}}return err.stack.toString()}function stackTrace(){return demangleAll(jsStackTrace())}Module["stackTrace"]=stackTrace;var PAGE_SIZE=4096;function alignMemoryPage(x){return x+4095&-4096}var HEAP;var HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;var STATIC_BASE=0,STATICTOP=0,staticSealed=false;var STACK_BASE=0,STACKTOP=0,STACK_MAX=0;var DYNAMIC_BASE=0,DYNAMICTOP=0;function enlargeMemory(){abort("Cannot enlarge memory arrays. Either (1) compile with -s TOTAL_MEMORY=X with X higher than the current value "+TOTAL_MEMORY+", (2) compile with ALLOW_MEMORY_GROWTH which adjusts the size at runtime but prevents some optimizations, or (3) set Module.TOTAL_MEMORY before the program runs.")}var TOTAL_STACK=Module["TOTAL_STACK"]||5242880;var TOTAL_MEMORY=Module["TOTAL_MEMORY"]||67108864;var FAST_MEMORY=Module["FAST_MEMORY"]||2097152;var totalMemory=64*1024;while(totalMemory<TOTAL_MEMORY||totalMemory<2*TOTAL_STACK){if(totalMemory<16*1024*1024){totalMemory*=2}else{totalMemory+=16*1024*1024}}if(totalMemory!==TOTAL_MEMORY){Module.printErr("increasing TOTAL_MEMORY to "+totalMemory+" to be compliant with the asm.js spec");TOTAL_MEMORY=totalMemory}assert(typeof Int32Array!=="undefined"&&typeof Float64Array!=="undefined"&&!!(new Int32Array(1))["subarray"]&&!!(new Int32Array(1))["set"],"JS engine does not provide full typed array support");var buffer=new ArrayBuffer(TOTAL_MEMORY);HEAP8=new Int8Array(buffer);HEAP16=new Int16Array(buffer);HEAP32=new Int32Array(buffer);HEAPU8=new Uint8Array(buffer);HEAPU16=new Uint16Array(buffer);HEAPU32=new Uint32Array(buffer);HEAPF32=new Float32Array(buffer);HEAPF64=new Float64Array(buffer);HEAP32[0]=255;assert(HEAPU8[0]===255&&HEAPU8[3]===0,"Typed arrays 2 must be run on a little-endian system");Module["HEAP"]=HEAP;Module["buffer"]=buffer;Module["HEAP8"]=HEAP8;Module["HEAP16"]=HEAP16;Module["HEAP32"]=HEAP32;Module["HEAPU8"]=HEAPU8;Module["HEAPU16"]=HEAPU16;Module["HEAPU32"]=HEAPU32;Module["HEAPF32"]=HEAPF32;Module["HEAPF64"]=HEAPF64;function callRuntimeCallbacks(callbacks){while(callbacks.length>0){var callback=callbacks.shift();if(typeof callback=="function"){callback();continue}var func=callback.func;if(typeof func==="number"){if(callback.arg===undefined){Runtime.dynCall("v",func)}else{Runtime.dynCall("vi",func,[callback.arg])}}else{func(callback.arg===undefined?null:callback.arg)}}}var __ATPRERUN__=[];var __ATINIT__=[];var __ATMAIN__=[];var __ATEXIT__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;var runtimeExited=false;function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function ensureInitRuntime(){if(runtimeInitialized)return;runtimeInitialized=true;callRuntimeCallbacks(__ATINIT__)}function preMain(){callRuntimeCallbacks(__ATMAIN__)}function exitRuntime(){callRuntimeCallbacks(__ATEXIT__);runtimeExited=true}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}Module["addOnPreRun"]=Module.addOnPreRun=addOnPreRun;function addOnInit(cb){__ATINIT__.unshift(cb)}Module["addOnInit"]=Module.addOnInit=addOnInit;function addOnPreMain(cb){__ATMAIN__.unshift(cb)}Module["addOnPreMain"]=Module.addOnPreMain=addOnPreMain;function addOnExit(cb){__ATEXIT__.unshift(cb)}Module["addOnExit"]=Module.addOnExit=addOnExit;function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}Module["addOnPostRun"]=Module.addOnPostRun=addOnPostRun;function intArrayFromString(stringy,dontAddNull,length){var ret=(new Runtime.UTF8Processor).processJSString(stringy);if(length){ret.length=length}if(!dontAddNull){ret.push(0)}return ret}Module["intArrayFromString"]=intArrayFromString;function intArrayToString(array){var ret=[];for(var i=0;i<array.length;i++){var chr=array[i];if(chr>255){chr&=255}ret.push(String.fromCharCode(chr))}return ret.join("")}Module["intArrayToString"]=intArrayToString;function writeStringToMemory(string,buffer,dontAddNull){var array=intArrayFromString(string,dontAddNull);var i=0;while(i<array.length){var chr=array[i];HEAP8[buffer+i>>0]=chr;i=i+1}}Module["writeStringToMemory"]=writeStringToMemory;function writeArrayToMemory(array,buffer){for(var i=0;i<array.length;i++){HEAP8[buffer+i>>0]=array[i]}}Module["writeArrayToMemory"]=writeArrayToMemory;function writeAsciiToMemory(str,buffer,dontAddNull){for(var i=0;i<str.length;i++){HEAP8[buffer+i>>0]=str.charCodeAt(i)}if(!dontAddNull)HEAP8[buffer+str.length>>0]=0}Module["writeAsciiToMemory"]=writeAsciiToMemory;function unSign(value,bits,ignore){if(value>=0){return value}return bits<=32?2*Math.abs(1<<bits-1)+value:Math.pow(2,bits)+value}function reSign(value,bits,ignore){if(value<=0){return value}var half=bits<=32?Math.abs(1<<bits-1):Math.pow(2,bits-1);if(value>=half&&(bits<=32||value>half)){value=-2*half+value}return value}if(!Math["imul"]||Math["imul"](4294967295,5)!==-5)Math["imul"]=function imul(a,b){var ah=a>>>16;var al=a&65535;var bh=b>>>16;var bl=b&65535;return al*bl+(ah*bl+al*bh<<16)|0};Math.imul=Math["imul"];var Math_abs=Math.abs;var Math_cos=Math.cos;var Math_sin=Math.sin;var Math_tan=Math.tan;var Math_acos=Math.acos;var Math_asin=Math.asin;var Math_atan=Math.atan;var Math_atan2=Math.atan2;var Math_exp=Math.exp;var Math_log=Math.log;var Math_sqrt=Math.sqrt;var Math_ceil=Math.ceil;var Math_floor=Math.floor;var Math_pow=Math.pow;var Math_imul=Math.imul;var Math_fround=Math.fround;var Math_min=Math.min;var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function addRunDependency(id){runDependencies++;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}}Module["addRunDependency"]=addRunDependency;function removeRunDependency(id){runDependencies--;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}Module["removeRunDependency"]=removeRunDependency;Module["preloadedImages"]={};Module["preloadedAudios"]={};var memoryInitializer=null;STATIC_BASE=8;STATICTOP=STATIC_BASE+5664;__ATINIT__.push({func:(function(){__GLOBAL__I_a()})},{func:(function(){__GLOBAL__I_a127()})});allocate([78,55,77,105,110,105,115,97,116,50,48,79,117,116,79,102,77,101,109,111,114,121,69,120,99,101,112,116,105,111,110,69,0,0,0,0,0,0,0,0,88,18,0,0,8,0,0,0,78,55,77,105,110,105,115,97,116,54,79,112,116,105,111,110,69,0,0,0,0,0,0,0,88,18,0,0,56,0,0,0,10,32,32,32,32,32,32,32,32,37,115,10,0,0,0,0,0,0,0,0,80,0,0,0,1,0,0,0,2,0,0,0,1,0,0,0,1,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,200,0,0,0,1,0,0,0,3,0,0,0,1,0,0,0,1,0,0,0,78,55,77,105,110,105,115,97,116,49,48,66,111,111,108,79,112,116,105,111,110,69,0,0,128,18,0,0,176,0,0,0,80,0,0,0,0,0,0,0,32,32,45,37,115,44,32,45,110,111,45,37,115,0,0,0,40,100,101,102,97,117,108,116,58,32,37,115,41,10,0,0,111,110,0,0,0,0,0,0,111,102,102,0,0,0,0,0,110,111,45,0,0,0,0,0,0,0,0,0,64,1,0,0,1,0,0,0,4,0,0,0,2,0,0,0,2,0,0,0,78,55,77,105,110,105,115,97,116,57,73,110,116,79,112,116,105,111,110,69,0,0,0,0,128,18,0,0,40,1,0,0,80,0,0,0,0,0,0,0,32,32,45,37,45,49,50,115,32,61,32,37,45,56,115,32,91,0,0,0,0,0,0,0,105,109,105,110,0,0,0,0,37,52,100,0,0,0,0,0,32,46,46,32,0,0,0,0,105,109,97,120,0,0,0,0,93,32,40,100,101,102,97,117,108,116,58,32,37,100,41,10,0,0,0,0,0,0,0,0,69,82,82,79,82,33,32,118,97,108,117,101,32,60,37,115,62,32,105,115,32,116,111,111,32,108,97,114,103,101,32,102,111,114,32,111,112,116,105,111,110,32,34,37,115,34,46,10,0,0,0,0,0,0,0,0,69,82,82,79,82,33,32,118,97,108,117,101,32,60,37,115,62,32,105,115,32,116,111,111,32,115,109,97,108,108,32,102,111,114,32,111,112,116,105,111,110,32,34,37,115,34,46,10,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,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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,118,97,114,45,100,101,99,97,121,0,0,0,0,0,0,0,84,104,101,32,118,97,114,105,97,98,108,101,32,97,99,116,105,118,105,116,121,32,100,101,99,97,121,32,102,97,99,116,111,114,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,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,99,108,97,45,100,101,99,97,121,0,0,0,0,0,0,0,84,104,101,32,99,108,97,117,115,101,32,97,99,116,105,118,105,116,121,32,100,101,99,97,121,32,102,97,99,116,111,114,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,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,114,110,100,45,102,114,101,113,0,0,0,0,0,0,0,0,84,104,101,32,102,114,101,113,117,101,110,99,121,32,119,105,116,104,32,119,104,105,99,104,32,116,104,101,32,100,101,99,105,115,105,111,110,32,104,101,117,114,105,115,116,105,99,32,116,114,105,101,115,32,116,111,32,99,104,111,111,115,101,32,97,32,114,97,110,100,111,109,32,118,97,114,105,97,98,108,101,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,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,114,110,100,45,115,101,101,100,0,0,0,0,0,0,0,0,85,115,101,100,32,98,121,32,116,104,101,32,114,97,110,100,111,109,32,118,97,114,105,97,98,108,101,32,115,101,108,101,99,116,105,111,110,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,0,0,0,99,99,109,105,110,45,109,111,100,101,0,0,0,0,0,0,67,111,110,116,114,111,108,115,32,99,111,110,102,108,105,99,116,32,99,108,97,117,115,101,32,109,105,110,105,109,105,122,97,116,105,111,110,32,40,48,61,110,111,110,101,44,32,49,61,98,97,115,105,99,44,32,50,61,100,101,101,112,41,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,0,112,104,97,115,101,45,115,97,118,105,110,103,0,0,0,0,67,111,110,116,114,111,108,115,32,116,104,101,32,108,101,118,101,108,32,111,102,32,112,104,97,115,101,32,115,97,118,105,110,103,32,40,48,61,110,111,110,101,44,32,49,61,108,105,109,105,116,101,100,44,32,50,61,102,117,108,108,41,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,114,110,100,45,105,110,105,116,0,0,0,0,0,0,0,0,82,97,110,100,111,109,105,122,101,32,116,104,101,32,105,110,105,116,105,97,108,32,97,99,116,105,118,105,116,121,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,108,117,98,121,0,0,0,0,85,115,101,32,116,104,101,32,76,117,98,121,32,114,101,115,116,97,114,116,32,115,101,113,117,101,110,99,101,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,0,0,0,114,102,105,114,115,116,0,0,84,104,101,32,98,97,115,101,32,114,101,115,116,97,114,116,32,105,110,116,101,114,118,97,108,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,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,114,105,110,99,0,0,0,0,82,101,115,116,97,114,116,32,105,110,116,101,114,118,97,108,32,105,110,99,114,101,97,115,101,32,102,97,99,116,111,114,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,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,103,99,45,102,114,97,99,0,84,104,101,32,102,114,97,99,116,105,111,110,32,111,102,32,119,97,115,116,101,100,32,109,101,109,111,114,121,32,97,108,108,111,119,101,100,32,98,101,102,111,114,101,32,97,32,103,97,114,98,97,103,101,32,99,111,108,108,101,99,116,105,111,110,32,105,115,32,116,114,105,103,103,101,114,101,100,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,0,0,109,105,110,45,108,101,97,114,110,116,115,0,0,0,0,0,77,105,110,105,109,117,109,32,108,101,97,114,110,116,32,99,108,97,117,115,101,32,108,105,109,105,116,0,0,0,0,0,0,0,0,0,192,7,0,0,5,0,0,0,6,0,0,0,7,0,0,0,0,0,0,0,124,32,37,57,100,32,124,32,37,55,100,32,37,56,100,32,37,56,100,32,124,32,37,56,100,32,37,56,100,32,37,54,46,48,102,32,124,32,37,54,46,51,102,32,37,37,32,124,10,0,0,0,0,0,0,0,124,32,32,71,97,114,98,97,103,101,32,99,111,108,108,101,99,116,105,111,110,58,32,32,32,37,49,50,100,32,98,121,116,101,115,32,61,62,32,37,49,50,100,32,98,121,116,101,115,32,32,32,32,32,32,32,32,32,32,32,32,32,124,10,0,0,0,0,0,0,0,0,78,55,77,105,110,105,115,97,116,54,83,111,108,118,101,114,69,0,0,0,0,0,0,0,88,18,0,0,168,7,0,0,60,98,111,111,108,62,0,0,10,32,32,32,32,32,32,32,32,37,115,10,0,0,0,0,60,105,110,116,51,50,62,0,69,82,82,79,82,33,32,118,97,108,117,101,32,60,37,115,62,32,105,115,32,116,111,111,32,108,97,114,103,101,32,102,111,114,32,111,112,116,105,111,110,32,34,37,115,34,46,10,0,0,0,0,0,0,0,0,69,82,82,79,82,33,32,118,97,108,117,101,32,60,37,115,62,32,105,115,32,116,111,111,32,115,109,97,108,108,32,102,111,114,32,111,112,116,105,111,110,32,34,37,115,34,46,10,0,0,0,0,0,0,0,0,67,79,82,69,0,0,0,0,60,100,111,117,98,108,101,62,0,0,0,0,0,0,0,0,0,0,0,0,168,8,0,0,1,0,0,0,8,0,0,0,3,0,0,0,3,0,0,0,78,55,77,105,110,105,115,97,116,49,50,68,111,117,98,108,101,79,112,116,105,111,110,69,0,0,0,0,0,0,0,0,128,18,0,0,136,8,0,0,80,0,0,0,0,0,0,0,32,32,45,37,45,49,50,115,32,61,32,37,45,56,115,32,37,99,37,52,46,50,103,32,46,46,32,37,52,46,50,103,37,99,32,40,100,101,102,97,117,108,116,58,32,37,103,41,10,0,0,0,0,0,0,0,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,91,32,83,101,97,114,99,104,32,83,116,97,116,105,115,116,105,99,115,32,93,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,0,124,32,67,111,110,102,108,105,99,116,115,32,124,32,32,32,32,32,32,32,32,32,32,79,82,73,71,73,78,65,76,32,32,32,32,32,32,32,32,32,124,32,32,32,32,32,32,32,32,32,32,76,69,65,82,78,84,32,32,32,32,32,32,32,32,32,32,124,32,80,114,111,103,114,101,115,115,32,124,0,124,32,32,32,32,32,32,32,32,32,32,32,124,32,32,32,32,86,97,114,115,32,32,67,108,97,117,115,101,115,32,76,105,116,101,114,97,108,115,32,124,32,32,32,32,76,105,109,105,116,32,32,67,108,97,117,115,101,115,32,76,105,116,47,67,108,32,124,32,32,32,32,32,32,32,32,32,32,124,0,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,97,115,121,109,109,0,0,0,83,104,114,105,110,107,32,99,108,97,117,115,101,115,32,98,121,32,97,115,121,109,109,101,116,114,105,99,32,98,114,97,110,99,104,105,110,103,46,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,114,99,104,101,99,107,0,0,67,104,101,99,107,32,105,102,32,97,32,99,108,97,117,115,101,32,105,115,32,97,108,114,101,97,100,121,32,105,109,112,108,105,101,100,46,32,40,99,111,115,116,108,121,41,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,101,108,105,109,0,0,0,0,80,101,114,102,111,114,109,32,118,97,114,105,97,98,108,101,32,101,108,105,109,105,110,97,116,105,111,110,46,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,0,0,0,103,114,111,119,0,0,0,0,65,108,108,111,119,32,97,32,118,97,114,105,97,98,108,101,32,101,108,105,109,105,110,97,116,105,111,110,32,115,116,101,112,32,116,111,32,103,114,111,119,32,98,121,32,97,32,110,117,109,98,101,114,32,111,102,32,99,108,97,117,115,101,115,46,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,0,0,0,0,0,0,0,99,108,45,108,105,109,0,0,86,97,114,105,97,98,108,101,115,32,97,114,101,32,110,111,116,32,101,108,105,109,105,110,97,116,101,100,32,105,102,32,105,116,32,112,114,111,100,117,99,101,115,32,97,32,114,101,115,111,108,118,101,110,116,32,119,105,116,104,32,97,32,108,101,110,103,116,104,32,97,98,111,118,101,32,116,104,105,115,32,108,105,109,105,116,46,32,45,49,32,109,101,97,110,115,32,110,111,32,108,105,109,105,116,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,0,0,0,0,0,0,0,115,117,98,45,108,105,109,0,68,111,32,110,111,116,32,99,104,101,99,107,32,105,102,32,115,117,98,115,117,109,112,116,105,111,110,32,97,103,97,105,110,115,116,32,97,32,99,108,97,117,115,101,32,108,97,114,103,101,114,32,116,104,97,110,32,116,104,105,115,46,32,45,49,32,109,101,97,110,115,32,110,111,32,108,105,109,105,116,46,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,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,115,105,109,112,45,103,99,45,102,114,97,99,0,0,0,0,84,104,101,32,102,114,97,99,116,105,111,110,32,111,102,32,119,97,115,116,101,100,32,109,101,109,111,114,121,32,97,108,108,111,119,101,100,32,98,101,102,111,114,101,32,97,32,103,97,114,98,97,103,101,32,99,111,108,108,101,99,116,105,111,110,32,105,115,32,116,114,105,103,103,101,114,101,100,32,100,117,114,105,110,103,32,115,105,109,112,108,105,102,105,99,97,116,105,111,110,46,0,0,0,0,0,0,0,120,14,0,0,9,0,0,0,10,0,0,0,11,0,0,0,0,0,0,0,115,117,98,115,117,109,112,116,105,111,110,32,108,101,102,116,58,32,37,49,48,100,32,40,37,49,48,100,32,115,117,98,115,117,109,101,100,44,32,37,49,48,100,32,100,101,108,101,116,101,100,32,108,105,116,101,114,97,108,115,41,13,0,0,101,108,105,109,105,110,97,116,105,111,110,32,108,101,102,116,58,32,37,49,48,100,13,0,124,32,32,69,108,105,109,105,110,97,116,101,100,32,99,108,97,117,115,101,115,58,32,32,32,32,32,37,49,48,46,50,102,32,77,98,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,124,10,0,0,0,0,124,32,32,71,97,114,98,97,103,101,32,99,111,108,108,101,99,116,105,111,110,58,32,32,32,37,49,50,100,32,98,121,116,101,115,32,61,62,32,37,49,50,100,32,98,121,116,101,115,32,32,32,32,32,32,32,32,32,32,32,32,32,124,10,0,0,0,0,0,0,0,0,78,55,77,105,110,105,115,97,116,49,48,83,105,109,112,83,111,108,118,101,114,69,0,0,128,18,0,0,96,14,0,0,192,7,0,0,0,0,0,0,60,100,111,117,98,108,101,62,0,0,0,0,0,0,0,0,60,105,110,116,51,50,62,0,83,73,77,80,0,0,0,0,60,98,111,111,108,62,0,0,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,89,79,33,0,0,0,0,0,2,0,0,0,0,0,0,0,48,15,0,0,0,0,0,0,117,110,99,97,117,103,104,116,0,0,0,0,0,0,0,0,116,101,114,109,105,110,97,116,105,110,103,32,119,105,116,104,32,37,115,32,101,120,99,101,112,116,105,111,110,32,111,102,32,116,121,112,101,32,37,115,58,32,37,115,0,0,0,0,116,101,114,109,105,110,97,116,105,110,103,32,119,105,116,104,32,37,115,32,101,120,99,101,112,116,105,111,110,32,111,102,32,116,121,112,101,32,37,115,0,0,0,0,0,0,0,0,116,101,114,109,105,110,97,116,105,110,103,32,119,105,116,104,32,37,115,32,102,111,114,101,105,103,110,32,101,120,99,101,112,116,105,111,110,0,0,0,116,101,114,109,105,110,97,116,105,110,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,112,116,104,114,101,97,100,95,111,110,99,101,32,102,97,105,108,117,114,101,32,105,110,32,95,95,99,120,97,95,103,101,116,95,103,108,111,98,97,108,115,95,102,97,115,116,40,41,0,0,0,0,0,0,0,0,99,97,110,110,111,116,32,99,114,101,97,116,101,32,112,116,104,114,101,97,100,32,107,101,121,32,102,111,114,32,95,95,99,120,97,95,103,101,116,95,103,108,111,98,97,108,115,40,41,0,0,0,0,0,0,0,99,97,110,110,111,116,32,122,101,114,111,32,111,117,116,32,116,104,114,101,97,100,32,118,97,108,117,101,32,102,111,114,32,95,95,99,120,97,95,103,101,116,95,103,108,111,98,97,108,115,40,41,0,0,0,0,0,0,0,0,200,16,0,0,12,0,0,0,13,0,0,0,1,0,0,0,0,0,0,0,115,116,100,58,58,98,97,100,95,97,108,108,111,99,0,0,83,116,57,98,97,100,95,97,108,108,111,99,0,0,0,0,128,18,0,0,184,16,0,0,80,17,0,0,0,0,0,0,116,101,114,109,105,110,97,116,101,95,104,97,110,100,108,101,114,32,117,110,101,120,112,101,99,116,101,100,108,121,32,114,101,116,117,114,110,101,100,0,116,101,114,109,105,110,97,116,101,95,104,97,110,100,108,101,114,32,117,110,101,120,112,101,99,116,101,100,108,121,32,116,104,114,101,119,32,97,110,32,101,120,99,101,112,116,105,111,110,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,83,116,57,101,120,99,101,112,116,105,111,110,0,0,0,0,88,18,0,0,64,17,0,0,83,116,57,116,121,112,101,95,105,110,102,111,0,0,0,0,88,18,0,0,88,17,0,0,78,49,48,95,95,99,120,120,97,98,105,118,49,49,54,95,95,115,104,105,109,95,116,121,112,101,95,105,110,102,111,69,0,0,0,0,0,0,0,0,128,18,0,0,112,17,0,0,104,17,0,0,0,0,0,0,78,49,48,95,95,99,120,120,97,98,105,118,49,49,55,95,95,99,108,97,115,115,95,116,121,112,101,95,105,110,102,111,69,0,0,0,0,0,0,0,128,18,0,0,168,17,0,0,152,17,0,0,0,0,0,0,78,49,48,95,95,99,120,120,97,98,105,118,49,49,57,95,95,112,111,105,110,116,101,114,95,116,121,112,101,95,105,110,102,111,69,0,0,0,0,0,78,49,48,95,95,99,120,120,97,98,105,118,49,49,55,95,95,112,98,97,115,101,95,116,121,112,101,95,105,110,102,111,69,0,0,0,0,0,0,0,128,18,0,0,8,18,0,0,152,17,0,0,0,0,0,0,128,18,0,0,224,17,0,0,48,18,0,0,0,0,0,0,0,0,0,0,208,17,0,0,14,0,0,0,15,0,0,0,16,0,0,0,17,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,200,18,0,0,14,0,0,0,18,0,0,0,16,0,0,0,17,0,0,0,1,0,0,0,2,0,0,0,2,0,0,0,2,0,0,0,78,49,48,95,95,99,120,120,97,98,105,118,49,50,48,95,95,115,105,95,99,108,97,115,115,95,116,121,112,101,95,105,110,102,111,69,0,0,0,0,128,18,0,0,160,18,0,0,208,17,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,1,2,3,4,5,6,7,8,9,255,255,255,255,255,255,255,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,255,255,255,255,255,255,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,1,2,4,7,3,6,5,0,0,0,0,0,0,0,0,105,110,102,105,110,105,116,121,0,0,0,0,0,0,0,0,110,97,110,0,0,0,0,0,95,112,137,0,255,9,47,15,10,0,0,0,100,0,0,0,232,3,0,0,16,39,0,0,160,134,1,0,64,66,15,0,128,150,152,0,0,225,245,5],"i8",ALLOC_NONE,Runtime.GLOBAL_BASE);var tempDoublePtr=Runtime.alignMemory(allocate(12,"i8",ALLOC_STATIC),8);assert(tempDoublePtr%8==0);function copyTempFloat(ptr){HEAP8[tempDoublePtr]=HEAP8[ptr];HEAP8[tempDoublePtr+1]=HEAP8[ptr+1];HEAP8[tempDoublePtr+2]=HEAP8[ptr+2];HEAP8[tempDoublePtr+3]=HEAP8[ptr+3]}function copyTempDouble(ptr){HEAP8[tempDoublePtr]=HEAP8[ptr];HEAP8[tempDoublePtr+1]=HEAP8[ptr+1];HEAP8[tempDoublePtr+2]=HEAP8[ptr+2];HEAP8[tempDoublePtr+3]=HEAP8[ptr+3];HEAP8[tempDoublePtr+4]=HEAP8[ptr+4];HEAP8[tempDoublePtr+5]=HEAP8[ptr+5];HEAP8[tempDoublePtr+6]=HEAP8[ptr+6];HEAP8[tempDoublePtr+7]=HEAP8[ptr+7]}function _atexit(func,arg){__ATEXIT__.unshift({func:func,arg:arg})}function ___cxa_atexit(){return _atexit.apply(null,arguments)}Module["_i64Subtract"]=_i64Subtract;var ___errno_state=0;function ___setErrNo(value){HEAP32[___errno_state>>2]=value;return value}var ERRNO_CODES={EPERM:1,ENOENT:2,ESRCH:3,EINTR:4,EIO:5,ENXIO:6,E2BIG:7,ENOEXEC:8,EBADF:9,ECHILD:10,EAGAIN:11,EWOULDBLOCK:11,ENOMEM:12,EACCES:13,EFAULT:14,ENOTBLK:15,EBUSY:16,EEXIST:17,EXDEV:18,ENODEV:19,ENOTDIR:20,EISDIR:21,EINVAL:22,ENFILE:23,EMFILE:24,ENOTTY:25,ETXTBSY:26,EFBIG:27,ENOSPC:28,ESPIPE:29,EROFS:30,EMLINK:31,EPIPE:32,EDOM:33,ERANGE:34,ENOMSG:42,EIDRM:43,ECHRNG:44,EL2NSYNC:45,EL3HLT:46,EL3RST:47,ELNRNG:48,EUNATCH:49,ENOCSI:50,EL2HLT:51,EDEADLK:35,ENOLCK:37,EBADE:52,EBADR:53,EXFULL:54,ENOANO:55,EBADRQC:56,EBADSLT:57,EDEADLOCK:35,EBFONT:59,ENOSTR:60,ENODATA:61,ETIME:62,ENOSR:63,ENONET:64,ENOPKG:65,EREMOTE:66,ENOLINK:67,EADV:68,ESRMNT:69,ECOMM:70,EPROTO:71,EMULTIHOP:72,EDOTDOT:73,EBADMSG:74,ENOTUNIQ:76,EBADFD:77,EREMCHG:78,ELIBACC:79,ELIBBAD:80,ELIBSCN:81,ELIBMAX:82,ELIBEXEC:83,ENOSYS:38,ENOTEMPTY:39,ENAMETOOLONG:36,ELOOP:40,EOPNOTSUPP:95,EPFNOSUPPORT:96,ECONNRESET:104,ENOBUFS:105,EAFNOSUPPORT:97,EPROTOTYPE:91,ENOTSOCK:88,ENOPROTOOPT:92,ESHUTDOWN:108,ECONNREFUSED:111,EADDRINUSE:98,ECONNABORTED:103,ENETUNREACH:101,ENETDOWN:100,ETIMEDOUT:110,EHOSTDOWN:112,EHOSTUNREACH:113,EINPROGRESS:115,EALREADY:114,EDESTADDRREQ:89,EMSGSIZE:90,EPROTONOSUPPORT:93,ESOCKTNOSUPPORT:94,EADDRNOTAVAIL:99,ENETRESET:102,EISCONN:106,ENOTCONN:107,ETOOMANYREFS:109,EUSERS:87,EDQUOT:122,ESTALE:116,ENOTSUP:95,ENOMEDIUM:123,EILSEQ:84,EOVERFLOW:75,ECANCELED:125,ENOTRECOVERABLE:131,EOWNERDEAD:130,ESTRPIPE:86};function _sysconf(name){switch(name){case 30:return PAGE_SIZE;case 132:case 133:case 12:case 137:case 138:case 15:case 235:case 16:case 17:case 18:case 19:case 20:case 149:case 13:case 10:case 236:case 153:case 9:case 21:case 22:case 159:case 154:case 14:case 77:case 78:case 139:case 80:case 81:case 79:case 82:case 68:case 67:case 164:case 11:case 29:case 47:case 48:case 95:case 52:case 51:case 46:return 200809;case 27:case 246:case 127:case 128:case 23:case 24:case 160:case 161:case 181:case 182:case 242:case 183:case 184:case 243:case 244:case 245:case 165:case 178:case 179:case 49:case 50:case 168:case 169:case 175:case 170:case 171:case 172:case 97:case 76:case 32:case 173:case 35:return-1;case 176:case 177:case 7:case 155:case 8:case 157:case 125:case 126:case 92:case 93:case 129:case 130:case 131:case 94:case 91:return 1;case 74:case 60:case 69:case 70:case 4:return 1024;case 31:case 42:case 72:return 32;case 87:case 26:case 33:return 2147483647;case 34:case 1:return 47839;case 38:case 36:return 99;case 43:case 37:return 2048;case 0:return 2097152;case 3:return 65536;case 28:return 32768;case 44:return 32767;case 75:return 16384;case 39:return 1e3;case 89:return 700;case 71:return 256;case 40:return 255;case 2:return 100;case 180:return 64;case 25:return 20;case 5:return 16;case 6:return 6;case 73:return 4;case 84:{if(typeof navigator==="object")return navigator["hardwareConcurrency"]||1;return 1}}___setErrNo(ERRNO_CODES.EINVAL);return-1}function __ZSt18uncaught_exceptionv(){return!!__ZSt18uncaught_exceptionv.uncaught_exception}var EXCEPTIONS={last:0,caught:[],infos:{},deAdjust:(function(adjusted){if(!adjusted||EXCEPTIONS.infos[adjusted])return adjusted;for(var ptr in EXCEPTIONS.infos){var info=EXCEPTIONS.infos[ptr];if(info.adjusted===adjusted){return ptr}}return adjusted}),addRef:(function(ptr){if(!ptr)return;var info=EXCEPTIONS.infos[ptr];info.refcount++}),decRef:(function(ptr){if(!ptr)return;var info=EXCEPTIONS.infos[ptr];assert(info.refcount>0);info.refcount--;if(info.refcount===0){if(info.destructor){Runtime.dynCall("vi",info.destructor,[ptr])}delete EXCEPTIONS.infos[ptr];___cxa_free_exception(ptr)}}),clearRef:(function(ptr){if(!ptr)return;var info=EXCEPTIONS.infos[ptr];info.refcount=0})};function ___resumeException(ptr){if(!EXCEPTIONS.last){EXCEPTIONS.last=ptr}EXCEPTIONS.clearRef(EXCEPTIONS.deAdjust(ptr));throw ptr+" - Exception catching is disabled, this exception cannot be caught. Compile with -s DISABLE_EXCEPTION_CATCHING=0 or DISABLE_EXCEPTION_CATCHING=2 to catch."}function ___cxa_find_matching_catch(){var thrown=EXCEPTIONS.last;if(!thrown){return(asm["setTempRet0"](0),0)|0}var info=EXCEPTIONS.infos[thrown];var throwntype=info.type;if(!throwntype){return(asm["setTempRet0"](0),thrown)|0}var typeArray=Array.prototype.slice.call(arguments);var pointer=Module["___cxa_is_pointer_type"](throwntype);if(!___cxa_find_matching_catch.buffer)___cxa_find_matching_catch.buffer=_malloc(4);HEAP32[___cxa_find_matching_catch.buffer>>2]=thrown;thrown=___cxa_find_matching_catch.buffer;for(var i=0;i<typeArray.length;i++){if(typeArray[i]&&Module["___cxa_can_catch"](typeArray[i],throwntype,thrown)){thrown=HEAP32[thrown>>2];info.adjusted=thrown;return(asm["setTempRet0"](typeArray[i]),thrown)|0}}thrown=HEAP32[thrown>>2];return(asm["setTempRet0"](throwntype),thrown)|0}function ___cxa_throw(ptr,type,destructor){EXCEPTIONS.infos[ptr]={ptr:ptr,adjusted:ptr,type:type,destructor:destructor,refcount:0};EXCEPTIONS.last=ptr;if(!("uncaught_exception"in __ZSt18uncaught_exceptionv)){__ZSt18uncaught_exceptionv.uncaught_exception=1}else{__ZSt18uncaught_exceptionv.uncaught_exception++}throw ptr+" - Exception catching is disabled, this exception cannot be caught. Compile with -s DISABLE_EXCEPTION_CATCHING=0 or DISABLE_EXCEPTION_CATCHING=2 to catch."}Module["_memset"]=_memset;Module["_bitshift64Shl"]=_bitshift64Shl;function _abort(){Module["abort"]()}var FS=undefined;var SOCKFS=undefined;function _send(fd,buf,len,flags){var sock=SOCKFS.getSocket(fd);if(!sock){___setErrNo(ERRNO_CODES.EBADF);return-1}return _write(fd,buf,len)}function _pwrite(fildes,buf,nbyte,offset){var stream=FS.getStream(fildes);if(!stream){___setErrNo(ERRNO_CODES.EBADF);return-1}try{var slab=HEAP8;return FS.write(stream,slab,buf,nbyte,offset)}catch(e){FS.handleFSError(e);return-1}}function _write(fildes,buf,nbyte){var stream=FS.getStream(fildes);if(!stream){___setErrNo(ERRNO_CODES.EBADF);return-1}try{var slab=HEAP8;return FS.write(stream,slab,buf,nbyte)}catch(e){FS.handleFSError(e);return-1}}function _fileno(stream){stream=FS.getStreamFromPtr(stream);if(!stream)return-1;return stream.fd}function _fwrite(ptr,size,nitems,stream){var bytesToWrite=nitems*size;if(bytesToWrite==0)return 0;var fd=_fileno(stream);var bytesWritten=_write(fd,ptr,bytesToWrite);if(bytesWritten==-1){var streamObj=FS.getStreamFromPtr(stream);if(streamObj)streamObj.error=true;return 0}else{return bytesWritten/size|0}}Module["_strlen"]=_strlen;function __reallyNegative(x){return x<0||x===0&&1/x===-Infinity}function __formatString(format,varargs){var textIndex=format;var argIndex=0;function getNextArg(type){var ret;if(type==="double"){ret=(HEAP32[tempDoublePtr>>2]=HEAP32[varargs+argIndex>>2],HEAP32[tempDoublePtr+4>>2]=HEAP32[varargs+(argIndex+4)>>2],+HEAPF64[tempDoublePtr>>3])}else if(type=="i64"){ret=[HEAP32[varargs+argIndex>>2],HEAP32[varargs+(argIndex+4)>>2]]}else{type="i32";ret=HEAP32[varargs+argIndex>>2]}argIndex+=Runtime.getNativeFieldSize(type);return ret}var ret=[];var curr,next,currArg;while(1){var startTextIndex=textIndex;curr=HEAP8[textIndex>>0];if(curr===0)break;next=HEAP8[textIndex+1>>0];if(curr==37){var flagAlwaysSigned=false;var flagLeftAlign=false;var flagAlternative=false;var flagZeroPad=false;var flagPadSign=false;flagsLoop:while(1){switch(next){case 43:flagAlwaysSigned=true;break;case 45:flagLeftAlign=true;break;case 35:flagAlternative=true;break;case 48:if(flagZeroPad){break flagsLoop}else{flagZeroPad=true;break};case 32:flagPadSign=true;break;default:break flagsLoop}textIndex++;next=HEAP8[textIndex+1>>0]}var width=0;if(next==42){width=getNextArg("i32");textIndex++;next=HEAP8[textIndex+1>>0]}else{while(next>=48&&next<=57){width=width*10+(next-48);textIndex++;next=HEAP8[textIndex+1>>0]}}var precisionSet=false,precision=-1;if(next==46){precision=0;precisionSet=true;textIndex++;next=HEAP8[textIndex+1>>0];if(next==42){precision=getNextArg("i32");textIndex++}else{while(1){var precisionChr=HEAP8[textIndex+1>>0];if(precisionChr<48||precisionChr>57)break;precision=precision*10+(precisionChr-48);textIndex++}}next=HEAP8[textIndex+1>>0]}if(precision<0){precision=6;precisionSet=false}var argSize;switch(String.fromCharCode(next)){case"h":var nextNext=HEAP8[textIndex+2>>0];if(nextNext==104){textIndex++;argSize=1}else{argSize=2}break;case"l":var nextNext=HEAP8[textIndex+2>>0];if(nextNext==108){textIndex++;argSize=8}else{argSize=4}break;case"L":case"q":case"j":argSize=8;break;case"z":case"t":case"I":argSize=4;break;default:argSize=null}if(argSize)textIndex++;next=HEAP8[textIndex+1>>0];switch(String.fromCharCode(next)){case"d":case"i":case"u":case"o":case"x":case"X":case"p":{var signed=next==100||next==105;argSize=argSize||4;var currArg=getNextArg("i"+argSize*8);var origArg=currArg;var argText;if(argSize==8){currArg=Runtime.makeBigInt(currArg[0],currArg[1],next==117)}if(argSize<=4){var limit=Math.pow(256,argSize)-1;currArg=(signed?reSign:unSign)(currArg&limit,argSize*8)}var currAbsArg=Math.abs(currArg);var prefix="";if(next==100||next==105){if(argSize==8&&i64Math)argText=i64Math.stringify(origArg[0],origArg[1],null);else argText=reSign(currArg,8*argSize,1).toString(10)}else if(next==117){if(argSize==8&&i64Math)argText=i64Math.stringify(origArg[0],origArg[1],true);else argText=unSign(currArg,8*argSize,1).toString(10);currArg=Math.abs(currArg)}else if(next==111){argText=(flagAlternative?"0":"")+currAbsArg.toString(8)}else if(next==120||next==88){prefix=flagAlternative&&currArg!=0?"0x":"";if(argSize==8&&i64Math){if(origArg[1]){argText=(origArg[1]>>>0).toString(16);var lower=(origArg[0]>>>0).toString(16);while(lower.length<8)lower="0"+lower;argText+=lower}else{argText=(origArg[0]>>>0).toString(16)}}else if(currArg<0){currArg=-currArg;argText=(currAbsArg-1).toString(16);var buffer=[];for(var i=0;i<argText.length;i++){buffer.push((15-parseInt(argText[i],16)).toString(16))}argText=buffer.join("");while(argText.length<argSize*2)argText="f"+argText}else{argText=currAbsArg.toString(16)}if(next==88){prefix=prefix.toUpperCase();argText=argText.toUpperCase()}}else if(next==112){if(currAbsArg===0){argText="(nil)"}else{prefix="0x";argText=currAbsArg.toString(16)}}if(precisionSet){while(argText.length<precision){argText="0"+argText}}if(currArg>=0){if(flagAlwaysSigned){prefix="+"+prefix}else if(flagPadSign){prefix=" "+prefix}}if(argText.charAt(0)=="-"){prefix="-"+prefix;argText=argText.substr(1)}while(prefix.length+argText.length<width){if(flagLeftAlign){argText+=" "}else{if(flagZeroPad){argText="0"+argText}else{prefix=" "+prefix}}}argText=prefix+argText;argText.split("").forEach((function(chr){ret.push(chr.charCodeAt(0))}));break};case"f":case"F":case"e":case"E":case"g":case"G":{var currArg=getNextArg("double");var argText;if(isNaN(currArg)){argText="nan";flagZeroPad=false}else if(!isFinite(currArg)){argText=(currArg<0?"-":"")+"inf";flagZeroPad=false}else{var isGeneral=false;var effectivePrecision=Math.min(precision,20);if(next==103||next==71){isGeneral=true;precision=precision||1;var exponent=parseInt(currArg.toExponential(effectivePrecision).split("e")[1],10);if(precision>exponent&&exponent>=-4){next=(next==103?"f":"F").charCodeAt(0);precision-=exponent+1}else{next=(next==103?"e":"E").charCodeAt(0);precision--}effectivePrecision=Math.min(precision,20)}if(next==101||next==69){argText=currArg.toExponential(effectivePrecision);if(/[eE][-+]\d$/.test(argText)){argText=argText.slice(0,-1)+"0"+argText.slice(-1)}}else if(next==102||next==70){argText=currArg.toFixed(effectivePrecision);if(currArg===0&&__reallyNegative(currArg)){argText="-"+argText}}var parts=argText.split("e");if(isGeneral&&!flagAlternative){while(parts[0].length>1&&parts[0].indexOf(".")!=-1&&(parts[0].slice(-1)=="0"||parts[0].slice(-1)==".")){parts[0]=parts[0].slice(0,-1)}}else{if(flagAlternative&&argText.indexOf(".")==-1)parts[0]+=".";while(precision>effectivePrecision++)parts[0]+="0"}argText=parts[0]+(parts.length>1?"e"+parts[1]:"");if(next==69)argText=argText.toUpperCase();if(currArg>=0){if(flagAlwaysSigned){argText="+"+argText}else if(flagPadSign){argText=" "+argText}}}while(argText.length<width){if(flagLeftAlign){argText+=" "}else{if(flagZeroPad&&(argText[0]=="-"||argText[0]=="+")){argText=argText[0]+"0"+argText.slice(1)}else{argText=(flagZeroPad?"0":" ")+argText}}}if(next<97)argText=argText.toUpperCase();argText.split("").forEach((function(chr){ret.push(chr.charCodeAt(0))}));break};case"s":{var arg=getNextArg("i8*");var argLength=arg?_strlen(arg):"(null)".length;if(precisionSet)argLength=Math.min(argLength,precision);if(!flagLeftAlign){while(argLength<width--){ret.push(32)}}if(arg){for(var i=0;i<argLength;i++){ret.push(HEAPU8[arg++>>0])}}else{ret=ret.concat(intArrayFromString("(null)".substr(0,argLength),true))}if(flagLeftAlign){while(argLength<width--){ret.push(32)}}break};case"c":{if(flagLeftAlign)ret.push(getNextArg("i8"));while(--width>0){ret.push(32)}if(!flagLeftAlign)ret.push(getNextArg("i8"));break};case"n":{var ptr=getNextArg("i32*");HEAP32[ptr>>2]=ret.length;break};case"%":{ret.push(curr);break};default:{for(var i=startTextIndex;i<textIndex+2;i++){ret.push(HEAP8[i>>0])}}}textIndex+=2}else{ret.push(curr);textIndex+=1}}return ret}function _fprintf(stream,format,varargs){var result=__formatString(format,varargs);var stack=Runtime.stackSave();var ret=_fwrite(allocate(result,"i8",ALLOC_STACK),1,result.length,stream);Runtime.stackRestore(stack);return ret}function _printf(format,varargs){var result=__formatString(format,varargs);var string=intArrayToString(result);if(string[string.length-1]==="\n")string=string.substr(0,string.length-1);Module.print(string);return result.length}function _pthread_once(ptr,func){if(!_pthread_once.seen)_pthread_once.seen={};if(ptr in _pthread_once.seen)return;Runtime.dynCall("v",func);_pthread_once.seen[ptr]=1}function _fputc(c,stream){var chr=unSign(c&255);HEAP8[_fputc.ret>>0]=chr;var fd=_fileno(stream);var ret=_write(fd,_fputc.ret,1);if(ret==-1){var streamObj=FS.getStreamFromPtr(stream);if(streamObj)streamObj.error=true;return-1}else{return chr}}var PTHREAD_SPECIFIC={};function _pthread_getspecific(key){return PTHREAD_SPECIFIC[key]||0}Module["_i64Add"]=_i64Add;function _fputs(s,stream){var fd=_fileno(stream);return _write(fd,s,_strlen(s))}var _stdout=allocate(1,"i32*",ALLOC_STATIC);function _puts(s){var result=Pointer_stringify(s);var string=result.substr(0);if(string[string.length-1]==="\n")string=string.substr(0,string.length-1);Module.print(string);return result.length}function _pthread_setspecific(key,value){if(!(key in PTHREAD_SPECIFIC)){return ERRNO_CODES.EINVAL}PTHREAD_SPECIFIC[key]=value;return 0}function __exit(status){Module["exit"](status)}function _exit(status){__exit(status)}var _UItoD=true;function _malloc(bytes){var ptr=Runtime.dynamicAlloc(bytes+8);return ptr+8&4294967288}Module["_malloc"]=_malloc;function ___cxa_allocate_exception(size){return _malloc(size)}function _fmod(x,y){return x%y}function _fmodl(){return _fmod.apply(null,arguments)}Module["_bitshift64Lshr"]=_bitshift64Lshr;function ___cxa_pure_virtual(){ABORT=true;throw"Pure virtual function called!"}function _time(ptr){var ret=Date.now()/1e3|0;if(ptr){HEAP32[ptr>>2]=ret}return ret}var PTHREAD_SPECIFIC_NEXT_KEY=1;function _pthread_key_create(key,destructor){if(key==0){return ERRNO_CODES.EINVAL}HEAP32[key>>2]=PTHREAD_SPECIFIC_NEXT_KEY;PTHREAD_SPECIFIC[PTHREAD_SPECIFIC_NEXT_KEY]=0;PTHREAD_SPECIFIC_NEXT_KEY++;return 0}function ___cxa_guard_acquire(variable){if(!HEAP8[variable>>0]){HEAP8[variable>>0]=1;return 1}return 0}function ___cxa_guard_release(){}function _vfprintf(s,f,va_arg){return _fprintf(s,f,HEAP32[va_arg>>2])}function ___cxa_begin_catch(ptr){__ZSt18uncaught_exceptionv.uncaught_exception--;EXCEPTIONS.caught.push(ptr);EXCEPTIONS.addRef(EXCEPTIONS.deAdjust(ptr));return ptr}function _emscripten_memcpy_big(dest,src,num){HEAPU8.set(HEAPU8.subarray(src,src+num),dest);return dest}Module["_memcpy"]=_memcpy;var _llvm_pow_f64=Math_pow;function _sbrk(bytes){var self=_sbrk;if(!self.called){DYNAMICTOP=alignMemoryPage(DYNAMICTOP);self.called=true;assert(Runtime.dynamicAlloc);self.alloc=Runtime.dynamicAlloc;Runtime.dynamicAlloc=(function(){abort("cannot dynamically allocate, sbrk now has control")})}var ret=DYNAMICTOP;if(bytes!=0)self.alloc(bytes);return ret}var _fabs=Math_abs;function ___errno_location(){return ___errno_state}var _BItoD=true;function _copysign(a,b){return __reallyNegative(a)===__reallyNegative(b)?a:-a}function _copysignl(){return _copysign.apply(null,arguments)}var ___dso_handle=allocate(1,"i32*",ALLOC_STATIC);var _stderr=allocate(1,"i32*",ALLOC_STATIC);___errno_state=Runtime.staticAlloc(4);HEAP32[___errno_state>>2]=0;_fputc.ret=allocate([0],"i8",ALLOC_STATIC);STACK_BASE=STACKTOP=Runtime.alignMemory(STATICTOP);staticSealed=true;STACK_MAX=STACK_BASE+TOTAL_STACK;DYNAMIC_BASE=DYNAMICTOP=Runtime.alignMemory(STACK_MAX);assert(DYNAMIC_BASE<TOTAL_MEMORY,"TOTAL_MEMORY not big enough for stack");var ctlz_i8=allocate([8,7,6,6,5,5,5,5,4,4,4,4,4,4,4,4,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,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,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,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,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],"i8",ALLOC_DYNAMIC);var cttz_i8=allocate([8,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,5,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,6,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,5,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,7,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,5,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,6,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,5,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0],"i8",ALLOC_DYNAMIC);function invoke_iiii(index,a1,a2,a3){try{return Module["dynCall_iiii"](index,a1,a2,a3)}catch(e){if(typeof e!=="number"&&e!=="longjmp")throw e;asm["setThrew"](1,0)}}function invoke_viiiii(index,a1,a2,a3,a4,a5){try{Module["dynCall_viiiii"](index,a1,a2,a3,a4,a5)}catch(e){if(typeof e!=="number"&&e!=="longjmp")throw e;asm["setThrew"](1,0)}}function invoke_vi(index,a1){try{Module["dynCall_vi"](index,a1)}catch(e){if(typeof e!=="number"&&e!=="longjmp")throw e;asm["setThrew"](1,0)}}function invoke_vii(index,a1,a2){try{Module["dynCall_vii"](index,a1,a2)}catch(e){if(typeof e!=="number"&&e!=="longjmp")throw e;asm["setThrew"](1,0)}}function invoke_ii(index,a1){try{return Module["dynCall_ii"](index,a1)}catch(e){if(typeof e!=="number"&&e!=="longjmp")throw e;asm["setThrew"](1,0)}}function invoke_v(index){try{Module["dynCall_v"](index)}catch(e){if(typeof e!=="number"&&e!=="longjmp")throw e;asm["setThrew"](1,0)}}function invoke_viiiiii(index,a1,a2,a3,a4,a5,a6){try{Module["dynCall_viiiiii"](index,a1,a2,a3,a4,a5,a6)}catch(e){if(typeof e!=="number"&&e!=="longjmp")throw e;asm["setThrew"](1,0)}}function invoke_iii(index,a1,a2){try{return Module["dynCall_iii"](index,a1,a2)}catch(e){if(typeof e!=="number"&&e!=="longjmp")throw e;asm["setThrew"](1,0)}}function invoke_viiii(index,a1,a2,a3,a4){try{Module["dynCall_viiii"](index,a1,a2,a3,a4)}catch(e){if(typeof e!=="number"&&e!=="longjmp")throw e;asm["setThrew"](1,0)}}Module.asmGlobalArg={"Math":Math,"Int8Array":Int8Array,"Int16Array":Int16Array,"Int32Array":Int32Array,"Uint8Array":Uint8Array,"Uint16Array":Uint16Array,"Uint32Array":Uint32Array,"Float32Array":Float32Array,"Float64Array":Float64Array};Module.asmLibraryArg={"abort":abort,"assert":assert,"min":Math_min,"invoke_iiii":invoke_iiii,"invoke_viiiii":invoke_viiiii,"invoke_vi":invoke_vi,"invoke_vii":invoke_vii,"invoke_ii":invoke_ii,"invoke_v":invoke_v,"invoke_viiiiii":invoke_viiiiii,"invoke_iii":invoke_iii,"invoke_viiii":invoke_viiii,"_fabs":_fabs,"_llvm_pow_f64":_llvm_pow_f64,"_send":_send,"_fmod":_fmod,"___cxa_guard_acquire":___cxa_guard_acquire,"___setErrNo":___setErrNo,"_vfprintf":_vfprintf,"___cxa_allocate_exception":___cxa_allocate_exception,"___cxa_find_matching_catch":___cxa_find_matching_catch,"___cxa_guard_release":___cxa_guard_release,"_pwrite":_pwrite,"__reallyNegative":__reallyNegative,"_sbrk":_sbrk,"___cxa_begin_catch":___cxa_begin_catch,"_emscripten_memcpy_big":_emscripten_memcpy_big,"_fileno":_fileno,"___resumeException":___resumeException,"__ZSt18uncaught_exceptionv":__ZSt18uncaught_exceptionv,"_sysconf":_sysconf,"_pthread_getspecific":_pthread_getspecific,"_atexit":_atexit,"_pthread_once":_pthread_once,"_puts":_puts,"_printf":_printf,"_pthread_key_create":_pthread_key_create,"_write":_write,"___errno_location":___errno_location,"_pthread_setspecific":_pthread_setspecific,"___cxa_atexit":___cxa_atexit,"_copysign":_copysign,"_fputc":_fputc,"___cxa_throw":___cxa_throw,"__exit":__exit,"_copysignl":_copysignl,"_abort":_abort,"_fwrite":_fwrite,"_time":_time,"_fprintf":_fprintf,"__formatString":__formatString,"_fputs":_fputs,"_exit":_exit,"___cxa_pure_virtual":___cxa_pure_virtual,"_fmodl":_fmodl,"STACKTOP":STACKTOP,"STACK_MAX":STACK_MAX,"tempDoublePtr":tempDoublePtr,"ABORT":ABORT,"cttz_i8":cttz_i8,"ctlz_i8":ctlz_i8,"NaN":NaN,"Infinity":Infinity,"___dso_handle":___dso_handle,"_stderr":_stderr};// EMSCRIPTEN_START_ASM
var asm=(function(global,env,buffer) {
"use asm";var a=new global.Int8Array(buffer);var b=new global.Int16Array(buffer);var c=new global.Int32Array(buffer);var d=new global.Uint8Array(buffer);var e=new global.Uint16Array(buffer);var f=new global.Uint32Array(buffer);var g=new global.Float32Array(buffer);var h=new global.Float64Array(buffer);var i=env.STACKTOP|0;var j=env.STACK_MAX|0;var k=env.tempDoublePtr|0;var l=env.ABORT|0;var m=env.cttz_i8|0;var n=env.ctlz_i8|0;var o=env.___dso_handle|0;var p=env._stderr|0;var q=0;var r=0;var s=0;var t=0;var u=+env.NaN,v=+env.Infinity;var w=0,x=0,y=0,z=0,A=0.0,B=0,C=0,D=0,E=0.0;var F=0;var G=0;var H=0;var I=0;var J=0;var K=0;var L=0;var M=0;var N=0;var O=0;var P=global.Math.floor;var Q=global.Math.abs;var R=global.Math.sqrt;var S=global.Math.pow;var T=global.Math.cos;var U=global.Math.sin;var V=global.Math.tan;var W=global.Math.acos;var X=global.Math.asin;var Y=global.Math.atan;var Z=global.Math.atan2;var _=global.Math.exp;var $=global.Math.log;var aa=global.Math.ceil;var ba=global.Math.imul;var ca=env.abort;var da=env.assert;var ea=env.min;var fa=env.invoke_iiii;var ga=env.invoke_viiiii;var ha=env.invoke_vi;var ia=env.invoke_vii;var ja=env.invoke_ii;var ka=env.invoke_v;var la=env.invoke_viiiiii;var ma=env.invoke_iii;var na=env.invoke_viiii;var oa=env._fabs;var pa=env._llvm_pow_f64;var qa=env._send;var ra=env._fmod;var sa=env.___cxa_guard_acquire;var ta=env.___setErrNo;var ua=env._vfprintf;var va=env.___cxa_allocate_exception;var wa=env.___cxa_find_matching_catch;var xa=env.___cxa_guard_release;var ya=env._pwrite;var za=env.__reallyNegative;var Aa=env._sbrk;var Ba=env.___cxa_begin_catch;var Ca=env._emscripten_memcpy_big;var Da=env._fileno;var Ea=env.___resumeException;var Fa=env.__ZSt18uncaught_exceptionv;var Ga=env._sysconf;var Ha=env._pthread_getspecific;var Ia=env._atexit;var Ja=env._pthread_once;var Ka=env._puts;var La=env._printf;var Ma=env._pthread_key_create;var Na=env._write;var Oa=env.___errno_location;var Pa=env._pthread_setspecific;var Qa=env.___cxa_atexit;var Ra=env._copysign;var Sa=env._fputc;var Ta=env.___cxa_throw;var Ua=env.__exit;var Va=env._copysignl;var Wa=env._abort;var Xa=env._fwrite;var Ya=env._time;var Za=env._fprintf;var _a=env.__formatString;var $a=env._fputs;var ab=env._exit;var bb=env.___cxa_pure_virtual;var cb=env._fmodl;var db=0.0;
// EMSCRIPTEN_START_FUNCS
function nb(a){a=a|0;var b=0;b=i;i=i+a|0;i=i+15&-16;return b|0}function ob(){return i|0}function pb(a){a=a|0;i=a}function qb(a,b){a=a|0;b=b|0;if(!q){q=a;r=b}}function rb(b){b=b|0;a[k>>0]=a[b>>0];a[k+1>>0]=a[b+1>>0];a[k+2>>0]=a[b+2>>0];a[k+3>>0]=a[b+3>>0]}function sb(b){b=b|0;a[k>>0]=a[b>>0];a[k+1>>0]=a[b+1>>0];a[k+2>>0]=a[b+2>>0];a[k+3>>0]=a[b+3>>0];a[k+4>>0]=a[b+4>>0];a[k+5>>0]=a[b+5>>0];a[k+6>>0]=a[b+6>>0];a[k+7>>0]=a[b+7>>0]}function tb(a){a=a|0;F=a}function ub(){return F|0}function vb(a){a=a|0;Ba(a|0)|0;ud()}function wb(a){a=a|0;return}function xb(b,d,e,f,g){b=b|0;d=d|0;e=e|0;f=f|0;g=g|0;var h=0;h=i;c[b>>2]=112;c[b+4>>2]=d;c[b+8>>2]=e;c[b+12>>2]=f;c[b+16>>2]=g;if((a[144]|0)==0?(sa(144)|0)!=0:0){c[32]=0;c[33]=0;c[34]=0;Qa(19,128,o|0)|0;xa(144)}g=c[33]|0;if((g|0)==(c[34]|0)){f=(g>>1)+2&-2;f=(f|0)<2?2:f;if((f|0)>(2147483647-g|0)){d=va(1)|0;Ta(d|0,48,0)}e=c[32]|0;d=f+g|0;c[34]=d;d=Ud(e,d<<2)|0;c[32]=d;if((d|0)==0?(c[(Oa()|0)>>2]|0)==12:0){d=va(1)|0;Ta(d|0,48,0)}g=c[33]|0}c[33]=g+1;g=(c[32]|0)+(g<<2)|0;if(!g){i=h;return}c[g>>2]=b;i=h;return}function yb(a){a=a|0;var b=0;b=i;pd(a);i=b;return}function zb(a){a=a|0;var b=0,d=0;b=i;d=c[a>>2]|0;if(!d){i=b;return}c[a+4>>2]=0;Td(d);c[a>>2]=0;c[a+8>>2]=0;i=b;return}function Ab(a){a=a|0;var b=0;b=i;pd(a);i=b;return}function Bb(b,d){b=b|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,k=0;e=i;if((a[d>>0]|0)!=45){k=0;i=e;return k|0}f=d+1|0;g=110;j=f;k=0;while(1){h=k+1|0;if((a[j>>0]|0)!=g<<24>>24){g=1;break}j=d+(k+2)|0;if((h|0)==3){g=0;f=j;break}else{g=a[264+h>>0]|0;k=h}}if(ee(f,c[b+4>>2]|0)|0){k=0;i=e;return k|0}a[b+20>>0]=g;k=1;i=e;return k|0}function Cb(b,d){b=b|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,k=0;h=i;i=i+16|0;e=h;f=c[p>>2]|0;g=b+4|0;j=c[g>>2]|0;c[e>>2]=j;c[e+4>>2]=j;Za(f|0,216,e|0)|0;j=0;while(1){k=j>>>0<(32-((me(c[g>>2]|0)|0)<<1)|0)>>>0;Sa(32,f|0)|0;if(k)j=j+1|0;else break}c[e>>2]=(a[b+20>>0]|0)!=0?248:256;Za(f|0,232,e|0)|0;if(!d){i=h;return}c[e>>2]=c[b+8>>2];Za(f|0,88,e|0)|0;Sa(10,f|0)|0;i=h;return}function Db(a){a=a|0;var b=0;b=i;pd(a);i=b;return}function Eb(b,d){b=b|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0;e=i;i=i+16|0;h=e;g=e+8|0;if((a[d>>0]|0)!=45){n=0;i=e;return n|0}l=d+1|0;f=b+4|0;j=c[f>>2]|0;k=a[j>>0]|0;a:do if(k<<24>>24){m=0;while(1){n=m;m=m+1|0;if((a[l>>0]|0)!=k<<24>>24){b=0;break}k=a[j+m>>0]|0;l=d+(n+2)|0;if(!(k<<24>>24))break a}i=e;return b|0}while(0);if((a[l>>0]|0)!=61){n=0;i=e;return n|0}d=l+1|0;j=de(d,g,10)|0;if(!(c[g>>2]|0)){n=0;i=e;return n|0}if((j|0)>(c[b+24>>2]|0)){n=c[p>>2]|0;m=c[f>>2]|0;c[h>>2]=d;c[h+4>>2]=m;Za(n|0,416,h|0)|0;ab(1)}if((j|0)<(c[b+20>>2]|0)){n=c[p>>2]|0;m=c[f>>2]|0;c[h>>2]=d;c[h+4>>2]=m;Za(n|0,472,h|0)|0;ab(1)}c[b+28>>2]=j;n=1;i=e;return n|0}function Fb(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0;d=i;i=i+16|0;e=d;f=c[p>>2]|0;g=c[a+16>>2]|0;c[e>>2]=c[a+4>>2];c[e+4>>2]=g;Za(f|0,336,e|0)|0;g=c[a+20>>2]|0;if((g|0)==-2147483648)Xa(360,4,1,f|0)|0;else{c[e>>2]=g;Za(f|0,368,e|0)|0}Xa(376,4,1,f|0)|0;g=c[a+24>>2]|0;if((g|0)==2147483647)Xa(384,4,1,f|0)|0;else{c[e>>2]=g;Za(f|0,368,e|0)|0}c[e>>2]=c[a+28>>2];Za(f|0,392,e|0)|0;if(!b){i=d;return}c[e>>2]=c[a+8>>2];Za(f|0,88,e|0)|0;Sa(10,f|0)|0;i=d;return}function Gb(b){b=b|0;var d=0,e=0,f=0,g=0,j=0;g=i;c[b>>2]=1816;f=b+4|0;e=b+32|0;j=b+48|0;c[f+0>>2]=0;c[f+4>>2]=0;c[f+8>>2]=0;c[f+12>>2]=0;c[f+16>>2]=0;c[f+20>>2]=0;c[e+0>>2]=0;c[e+4>>2]=0;c[e+8>>2]=0;c[e+12>>2]=0;h[j>>3]=+h[75];h[b+56>>3]=+h[89];h[b+64>>3]=+h[103];h[b+72>>3]=+h[123];a[b+80>>0]=a[1364]|0;c[b+84>>2]=c[269];c[b+88>>2]=c[297];a[b+92>>0]=0;a[b+93>>0]=a[1292]|0;h[b+96>>3]=+h[204];c[b+104>>2]=c[439];c[b+108>>2]=c[359];h[b+112>>3]=+h[191];h[b+120>>3]=.3333333333333333;h[b+128>>3]=1.1;c[b+136>>2]=100;h[b+144>>3]=1.5;j=b+316|0;c[b+332>>2]=0;c[b+336>>2]=0;c[b+340>>2]=0;c[b+348>>2]=0;c[b+352>>2]=0;c[b+356>>2]=0;c[b+364>>2]=0;c[b+368>>2]=0;c[b+372>>2]=0;c[b+380>>2]=0;c[b+384>>2]=0;c[b+388>>2]=0;c[b+396>>2]=0;c[b+400>>2]=0;c[b+404>>2]=0;e=b+544|0;c[b+412>>2]=0;c[b+416>>2]=0;c[b+420>>2]=0;c[b+428>>2]=0;c[b+432>>2]=0;c[b+436>>2]=0;c[b+444>>2]=0;c[b+448>>2]=0;c[b+452>>2]=0;ke(b+152|0,0,176)|0;c[b+456>>2]=e;f=b+460|0;c[f+0>>2]=0;c[f+4>>2]=0;c[f+8>>2]=0;c[f+12>>2]=0;c[f+16>>2]=0;c[f+20>>2]=0;c[b+488>>2]=j;a[b+492>>0]=1;h[b+496>>3]=1.0;h[b+504>>3]=1.0;c[b+512>>2]=0;c[b+516>>2]=-1;j=b+520|0;f=b+536|0;c[j+0>>2]=0;c[j+4>>2]=0;c[j+8>>2]=0;c[j+12>>2]=0;a[f>>0]=1;f=b+540|0;c[f+0>>2]=0;c[f+4>>2]=0;c[f+8>>2]=0;c[f+12>>2]=0;c[f+16>>2]=0;gc(e,1048576);a[b+560>>0]=0;e=b+604|0;f=b+664|0;j=b+564|0;d=j+36|0;do{c[j>>2]=0;j=j+4|0}while((j|0)<(d|0));j=e+0|0;d=j+36|0;do{c[j>>2]=0;j=j+4|0}while((j|0)<(d|0));j=b+680|0;c[f+0>>2]=-1;c[f+4>>2]=-1;c[f+8>>2]=-1;c[f+12>>2]=-1;a[j>>0]=0;i=g;return}function Hb(a){a=a|0;var b=0;b=i;Ib(a);pd(a);i=b;return}function Ib(a){a=a|0;var b=0,d=0,e=0;b=i;c[a>>2]=1816;d=a+628|0;e=c[d>>2]|0;if(e){c[a+632>>2]=0;Td(e);c[d>>2]=0;c[a+636>>2]=0}d=a+616|0;e=c[d>>2]|0;if(e){c[a+620>>2]=0;Td(e);c[d>>2]=0;c[a+624>>2]=0}d=a+604|0;e=c[d>>2]|0;if(e){c[a+608>>2]=0;Td(e);c[d>>2]=0;c[a+612>>2]=0}d=a+588|0;e=c[d>>2]|0;if(e){c[a+592>>2]=0;Td(e);c[d>>2]=0;c[a+596>>2]=0}d=a+576|0;e=c[d>>2]|0;if(e){c[a+580>>2]=0;Td(e);c[d>>2]=0;c[a+584>>2]=0}d=a+564|0;e=c[d>>2]|0;if(e){c[a+568>>2]=0;Td(e);c[d>>2]=0;c[a+572>>2]=0}d=c[a+544>>2]|0;if(d)Td(d);d=a+472|0;e=c[d>>2]|0;if(e){c[a+476>>2]=0;Td(e);c[d>>2]=0;c[a+480>>2]=0}d=a+460|0;e=c[d>>2]|0;if(e){c[a+464>>2]=0;Td(e);c[d>>2]=0;c[a+468>>2]=0}hc(a+412|0);d=a+396|0;e=c[d>>2]|0;if(e){c[a+400>>2]=0;Td(e);c[d>>2]=0;c[a+404>>2]=0}d=a+380|0;e=c[d>>2]|0;if(e){c[a+384>>2]=0;Td(e);c[d>>2]=0;c[a+388>>2]=0}e=a+364|0;d=c[e>>2]|0;if(d){c[a+368>>2]=0;Td(d);c[e>>2]=0;c[a+372>>2]=0}d=a+348|0;e=c[d>>2]|0;if(e){c[a+352>>2]=0;Td(e);c[d>>2]=0;c[a+356>>2]=0}d=a+332|0;e=c[d>>2]|0;if(e){c[a+336>>2]=0;Td(e);c[d>>2]=0;c[a+340>>2]=0}d=a+316|0;e=c[d>>2]|0;if(e){c[a+320>>2]=0;Td(e);c[d>>2]=0;c[a+324>>2]=0}d=a+304|0;e=c[d>>2]|0;if(e){c[a+308>>2]=0;Td(e);c[d>>2]=0;c[a+312>>2]=0}d=a+292|0;e=c[d>>2]|0;if(e){c[a+296>>2]=0;Td(e);c[d>>2]=0;c[a+300>>2]=0}d=a+280|0;e=c[d>>2]|0;if(e){c[a+284>>2]=0;Td(e);c[d>>2]=0;c[a+288>>2]=0}d=a+268|0;e=c[d>>2]|0;if(e){c[a+272>>2]=0;Td(e);c[d>>2]=0;c[a+276>>2]=0}d=a+256|0;e=c[d>>2]|0;if(e){c[a+260>>2]=0;Td(e);c[d>>2]=0;c[a+264>>2]=0}d=a+32|0;e=c[d>>2]|0;if(e){c[a+36>>2]=0;Td(e);c[d>>2]=0;c[a+40>>2]=0}d=a+16|0;e=c[d>>2]|0;if(e){c[a+20>>2]=0;Td(e);c[d>>2]=0;c[a+24>>2]=0}e=a+4|0;d=c[e>>2]|0;if(!d){i=b;return}c[a+8>>2]=0;Td(d);c[e>>2]=0;c[a+12>>2]=0;i=b;return}function Jb(b,d,e){b=b|0;d=d|0;e=e|0;var f=0,g=0,j=0,k=0,l=0.0,m=0,n=0,o=0,p=0,q=0,r=0;f=i;i=i+16|0;k=f+4|0;j=f;g=b+580|0;m=c[g>>2]|0;if((m|0)>0){o=m+ -1|0;p=c[(c[b+576>>2]|0)+(o<<2)>>2]|0;c[g>>2]=o;g=p}else{p=b+540|0;g=c[p>>2]|0;c[p>>2]=g+1}m=b+412|0;p=g<<1;c[k>>2]=p;ic(m,k);c[j>>2]=p|1;ic(m,j);k=b+332|0;m=a[544]|0;j=g+1|0;jc(k,j);a[(c[k>>2]|0)+g>>0]=m;k=b+396|0;m=b+400|0;if((c[m>>2]|0)<(j|0)){o=b+404|0;p=c[o>>2]|0;if((p|0)<(j|0)){q=g+2-p&-2;n=(p>>1)+2&-2;n=(q|0)>(n|0)?q:n;if((n|0)>(2147483647-p|0)){q=va(1)|0;Ta(q|0,48,0)}r=c[k>>2]|0;q=n+p|0;c[o>>2]=q;q=Ud(r,q<<3)|0;c[k>>2]=q;if((q|0)==0?(c[(Oa()|0)>>2]|0)==12:0){r=va(1)|0;Ta(r|0,48,0)}}o=c[m>>2]|0;if((o|0)<(j|0))do{n=(c[k>>2]|0)+(o<<3)|0;if(n){r=n;c[r>>2]=0;c[r+4>>2]=0}o=o+1|0}while((o|0)!=(j|0));c[m>>2]=j}m=(c[k>>2]|0)+(g<<3)|0;c[m>>2]=-1;c[m+4>>2]=0;m=b+316|0;if(!(a[b+93>>0]|0))l=0.0;else{r=b+72|0;l=+h[r>>3]*1389796.0;l=l- +(~~(l/2147483647.0)|0)*2147483647.0;h[r>>3]=l;l=l/2147483647.0*1.0e-5}k=b+320|0;if((c[k>>2]|0)<(j|0)){n=b+324|0;o=c[n>>2]|0;if((o|0)<(j|0)){r=g+2-o&-2;p=(o>>1)+2&-2;p=(r|0)>(p|0)?r:p;if((p|0)>(2147483647-o|0)){r=va(1)|0;Ta(r|0,48,0)}q=c[m>>2]|0;r=p+o|0;c[n>>2]=r;r=Ud(q,r<<3)|0;c[m>>2]=r;if((r|0)==0?(c[(Oa()|0)>>2]|0)==12:0){r=va(1)|0;Ta(r|0,48,0)}}p=c[k>>2]|0;if((p|0)<(j|0)){n=c[m>>2]|0;do{o=n+(p<<3)|0;if(o)h[o>>3]=0.0;p=p+1|0}while((p|0)!=(j|0))}c[k>>2]=j}h[(c[m>>2]|0)+(g<<3)>>3]=l;kc(b+588|0,g,0);kc(b+348|0,g,1);k=b+364|0;d=a[d>>0]|0;jc(k,j);a[(c[k>>2]|0)+g>>0]=d;k=b+380|0;d=b+384|0;if((c[d>>2]|0)<(j|0)){m=b+388|0;o=c[m>>2]|0;if((o|0)<(j|0)){r=g+2-o&-2;n=(o>>1)+2&-2;n=(r|0)>(n|0)?r:n;if((n|0)>(2147483647-o|0)){r=va(1)|0;Ta(r|0,48,0)}q=c[k>>2]|0;r=n+o|0;c[m>>2]=r;r=Ud(q,r)|0;c[k>>2]=r;if((r|0)==0?(c[(Oa()|0)>>2]|0)==12:0){r=va(1)|0;Ta(r|0,48,0)}}m=c[d>>2]|0;if((m|0)<(j|0))do{n=(c[k>>2]|0)+m|0;if(n)a[n>>0]=0;m=m+1|0}while((m|0)!=(j|0));c[d>>2]=j}d=b+288|0;k=c[d>>2]|0;if((k|0)<(j|0)){r=g+2-k&-2;j=(k>>1)+2&-2;j=(r|0)>(j|0)?r:j;if((j|0)>(2147483647-k|0)){r=va(1)|0;Ta(r|0,48,0)}q=b+280|0;p=c[q>>2]|0;r=j+k|0;c[d>>2]=r;r=Ud(p,r<<2)|0;c[q>>2]=r;if((r|0)==0?(c[(Oa()|0)>>2]|0)==12:0){r=va(1)|0;Ta(r|0,48,0)}}j=b+380|0;d=(c[j>>2]|0)+g|0;k=(a[d>>0]|0)==0;if(e){if(k){r=b+200|0;q=r;q=ne(c[q>>2]|0,c[q+4>>2]|0,1,0)|0;c[r>>2]=q;c[r+4>>2]=F}}else if(!k){r=b+200|0;q=r;q=ne(c[q>>2]|0,c[q+4>>2]|0,-1,-1)|0;c[r>>2]=q;c[r+4>>2]=F}a[d>>0]=e&1;e=b+460|0;if((c[b+476>>2]|0)>(g|0)?(c[(c[b+472>>2]|0)+(g<<2)>>2]|0)>-1:0){i=f;return g|0}if(!(a[(c[j>>2]|0)+g>>0]|0)){i=f;return g|0}lc(e,g);i=f;return g|0}function Kb(b,e){b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;f=i;i=i+16|0;k=f+1|0;j=f;g=b+492|0;if(!(a[g>>0]|0)){s=0;i=f;return s|0}s=c[e>>2]|0;h=e+4|0;l=c[h>>2]|0;a[k+0>>0]=a[j+0>>0]|0;oc(s,l,k);l=c[h>>2]|0;a:do if((l|0)>0){k=b+332|0;j=a[528]|0;m=0;n=0;p=-2;while(1){s=c[e>>2]|0;o=c[s+(m<<2)>>2]|0;r=d[(c[k>>2]|0)+(o>>1)>>0]|0;t=r^o&1;q=t&255;u=j&255;if((o|0)==(p^1|0)?1:(q<<24>>24==j<<24>>24&(u>>>1^1)|u&2&t|0)!=0){b=1;break}t=a[536]|0;u=t&255;if((o|0)!=(p|0)?((u>>>1^1)&q<<24>>24==t<<24>>24|r&2&u|0)==0:0){c[s+(n<<2)>>2]=o;l=c[h>>2]|0;n=n+1|0}else o=p;m=m+1|0;if((m|0)<(l|0))p=o;else break a}i=f;return b|0}else{m=0;n=0}while(0);j=m-n|0;if((j|0)>0){l=l-j|0;c[h>>2]=l}if(!l){a[g>>0]=0;u=0;i=f;return u|0}else if((l|0)==1){t=c[c[e>>2]>>2]|0;s=t>>1;a[(c[b+332>>2]|0)+s>>0]=(t&1^1)&255^1;u=c[b+296>>2]|0;s=(c[b+396>>2]|0)+(s<<3)|0;c[s>>2]=-1;c[s+4>>2]=u;s=b+284|0;u=c[s>>2]|0;c[s>>2]=u+1;c[(c[b+280>>2]|0)+(u<<2)>>2]=t;u=(Mb(b)|0)==-1;a[g>>0]=u&1;i=f;return u|0}else{e=pc(b+544|0,e,0)|0;h=b+256|0;g=b+260|0;k=c[g>>2]|0;j=b+264|0;if((k|0)==(c[j>>2]|0)){l=(k>>1)+2&-2;l=(l|0)<2?2:l;if((l|0)>(2147483647-k|0)){u=va(1)|0;Ta(u|0,48,0)}t=c[h>>2]|0;u=l+k|0;c[j>>2]=u;u=Ud(t,u<<2)|0;c[h>>2]=u;if((u|0)==0?(c[(Oa()|0)>>2]|0)==12:0){u=va(1)|0;Ta(u|0,48,0)}k=c[g>>2]|0}c[g>>2]=k+1;g=(c[h>>2]|0)+(k<<2)|0;if(g)c[g>>2]=e;Nb(b,e);u=1;i=f;return u|0}return 0}function Lb(b,d,e){b=b|0;d=d|0;e=e|0;var f=0,g=0;f=c[d>>2]|0;d=f>>1;a[(c[b+332>>2]|0)+d>>0]=(f&1^1)&255^1;g=c[b+296>>2]|0;d=(c[b+396>>2]|0)+(d<<3)|0;c[d>>2]=e;c[d+4>>2]=g;e=b+284|0;d=c[e>>2]|0;c[e>>2]=d+1;c[(c[b+280>>2]|0)+(d<<2)>>2]=f;return}function Mb(b){b=b|0;var e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0;k=i;i=i+16|0;r=k;h=b+512|0;t=c[h>>2]|0;q=b+284|0;if((t|0)>=(c[q>>2]|0)){M=0;K=0;O=-1;N=b+184|0;I=N;L=I;L=c[L>>2]|0;I=I+4|0;I=c[I>>2]|0;I=ne(L|0,I|0,M|0,K|0)|0;L=F;J=N;c[J>>2]=I;N=N+4|0;c[N>>2]=L;N=b+520|0;L=N;J=L;J=c[J>>2]|0;L=L+4|0;L=c[L>>2]|0;K=je(J|0,L|0,M|0,K|0)|0;M=F;L=N;c[L>>2]=K;N=N+4|0;c[N>>2]=M;i=k;return O|0}o=b+280|0;j=b+428|0;g=b+412|0;l=b+332|0;m=b+544|0;n=r+4|0;e=b+396|0;p=b+296|0;f=b+456|0;z=-1;s=0;do{c[h>>2]=t+1;w=c[(c[o>>2]|0)+(t<<2)>>2]|0;if(a[(c[j>>2]|0)+w>>0]|0){u=c[g>>2]|0;t=u+(w*12|0)+4|0;y=c[t>>2]|0;if((y|0)>0){u=u+(w*12|0)|0;v=0;x=0;do{B=c[u>>2]|0;A=B+(v<<3)|0;if((c[(c[c[f>>2]>>2]|0)+(c[A>>2]<<2)>>2]&3|0)!=1){N=A;O=c[N+4>>2]|0;y=B+(x<<3)|0;c[y>>2]=c[N>>2];c[y+4>>2]=O;y=c[t>>2]|0;x=x+1|0}v=v+1|0}while((v|0)<(y|0))}else{v=0;x=0}u=v-x|0;if((u|0)>0)c[t>>2]=y-u;a[(c[j>>2]|0)+w>>0]=0}t=c[g>>2]|0;s=s+1|0;u=c[t+(w*12|0)>>2]|0;t=t+(w*12|0)+4|0;x=c[t>>2]|0;v=u+(x<<3)|0;a:do if(!x){v=u;y=u}else{w=w^1;x=(x<<3)+ -1|0;B=u;y=u;while(1){while(1){b:while(1){H=c[B+4>>2]|0;O=d[(c[l>>2]|0)+(H>>1)>>0]^H&1;J=a[528]|0;I=J&255;K=I&2;I=I>>>1^1;if((O&255)<<24>>24==J<<24>>24&I|K&O){E=19;break}A=c[B>>2]|0;E=c[m>>2]|0;G=E+(A<<2)|0;C=E+(A+1<<2)|0;D=c[C>>2]|0;if((D|0)==(w|0)){O=E+(A+2<<2)|0;D=c[O>>2]|0;c[C>>2]=D;c[O>>2]=w}C=B+8|0;c[r>>2]=A;c[n>>2]=D;if((D|0)!=(H|0)?(O=d[(c[l>>2]|0)+(D>>1)>>0]^D&1,((O&255)<<24>>24==J<<24>>24&I|K&O|0)!=0):0){E=27;break}K=c[G>>2]|0;if(K>>>0<=95){E=31;break}I=c[l>>2]|0;J=a[536]|0;H=J&255;O=H&2;H=H>>>1^1;N=2;while(1){L=G+(N<<2)+4|0;M=c[L>>2]|0;P=d[I+(M>>1)>>0]^M&1;N=N+1|0;if(!((P&255)<<24>>24==J<<24>>24&H|O&P))break;if((N|0)>=(K>>>5|0)){E=32;break b}}P=E+(A+2<<2)|0;c[P>>2]=M;c[L>>2]=w;qc((c[g>>2]|0)+((c[P>>2]^1)*12|0)|0,r);if((C|0)==(v|0))break a;else B=C}if((E|0)==19){E=0;N=B;O=c[N+4>>2]|0;P=y;c[P>>2]=c[N>>2];c[P+4>>2]=O;B=B+8|0;y=y+8|0}else if((E|0)==27){E=0;O=r;P=c[O+4>>2]|0;B=y;c[B>>2]=c[O>>2];c[B+4>>2]=P;B=C;y=y+8|0}else if((E|0)==31){J=a[536]|0;E=32}if((E|0)==32){E=y+8|0;G=r;I=c[G+4>>2]|0;H=y;c[H>>2]=c[G>>2];c[H+4>>2]=I;H=D>>1;I=D&1;G=(c[l>>2]|0)+H|0;P=d[G>>0]^I;O=J&255;if((P&255)<<24>>24==J<<24>>24&(O>>>1^1)|O&2&P)break;a[G>>0]=(I^1)&255^1;y=c[p>>2]|0;B=(c[e>>2]|0)+(H<<3)|0;c[B>>2]=A;c[B+4>>2]=y;B=c[q>>2]|0;c[q>>2]=B+1;c[(c[o>>2]|0)+(B<<2)>>2]=D;B=C;y=E}if((B|0)==(v|0))break a}c[h>>2]=c[q>>2];if(C>>>0<v>>>0){z=(u+(x-C)|0)>>>3;while(1){N=C;C=C+8|0;O=c[N+4>>2]|0;P=E;c[P>>2]=c[N>>2];c[P+4>>2]=O;if(C>>>0>=v>>>0)break;else E=E+8|0}B=B+(z+2<<3)|0;y=y+(z+2<<3)|0}else{B=C;y=E}if((B|0)==(v|0)){z=A;break}else z=A}}while(0);u=v-y|0;if((u|0)>0)c[t>>2]=(c[t>>2]|0)-(u>>3);t=c[h>>2]|0}while((t|0)<(c[q>>2]|0));N=s;L=((s|0)<0)<<31>>31;P=z;O=b+184|0;J=O;M=J;M=c[M>>2]|0;J=J+4|0;J=c[J>>2]|0;J=ne(M|0,J|0,N|0,L|0)|0;M=F;K=O;c[K>>2]=J;O=O+4|0;c[O>>2]=M;O=b+520|0;M=O;K=M;K=c[K>>2]|0;M=M+4|0;M=c[M>>2]|0;L=je(K|0,M|0,N|0,L|0)|0;N=F;M=O;c[M>>2]=L;O=O+4|0;c[O>>2]=N;i=k;return P|0}function Nb(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0;d=i;i=i+16|0;k=d+8|0;f=d;g=c[a+544>>2]|0;e=g+(b<<2)|0;h=g+(b+1<<2)|0;j=a+412|0;l=(c[j>>2]|0)+((c[h>>2]^1)*12|0)|0;g=g+(b+2<<2)|0;m=c[g>>2]|0;c[k>>2]=b;c[k+4>>2]=m;qc(l,k);g=(c[j>>2]|0)+((c[g>>2]^1)*12|0)|0;h=c[h>>2]|0;c[f>>2]=b;c[f+4>>2]=h;qc(g,f);if(!(c[e>>2]&4)){m=a+208|0;l=m;l=ne(c[l>>2]|0,c[l+4>>2]|0,1,0)|0;c[m>>2]=l;c[m+4>>2]=F;m=a+224|0;l=m;l=ne((c[e>>2]|0)>>>5|0,0,c[l>>2]|0,c[l+4>>2]|0)|0;c[m>>2]=l;c[m+4>>2]=F;i=d;return}else{m=a+216|0;l=m;l=ne(c[l>>2]|0,c[l+4>>2]|0,1,0)|0;c[m>>2]=l;c[m+4>>2]=F;m=a+232|0;l=m;l=ne((c[e>>2]|0)>>>5|0,0,c[l>>2]|0,c[l+4>>2]|0)|0;c[m>>2]=l;c[m+4>>2]=F;i=d;return}}function Ob(b,d,e){b=b|0;d=d|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;g=i;i=i+16|0;l=g+4|0;j=g;h=c[b+544>>2]|0;f=h+(d<<2)|0;k=c[h+(d+1<<2)>>2]^1;if(!e){c[l>>2]=k;e=b+428|0;m=c[e>>2]|0;k=m+k|0;if(!(a[k>>0]|0)){a[k>>0]=1;mc(b+444|0,l);m=c[e>>2]|0}d=c[h+(d+2<<2)>>2]^1;c[j>>2]=d;d=m+d|0;if(!(a[d>>0]|0)){a[d>>0]=1;mc(b+444|0,j)}}else{j=b+412|0;e=c[j>>2]|0;l=e+(k*12|0)|0;h=h+(d+2<<2)|0;k=e+(k*12|0)+4|0;m=c[k>>2]|0;a:do if((m|0)>0){p=c[l>>2]|0;o=0;while(1){n=o+1|0;if((c[p+(o<<3)>>2]|0)==(d|0)){n=o;break a}if((n|0)<(m|0))o=n;else break}}else n=0;while(0);m=m+ -1|0;if((n|0)<(m|0)){do{e=c[l>>2]|0;m=n;n=n+1|0;o=e+(n<<3)|0;p=c[o+4>>2]|0;m=e+(m<<3)|0;c[m>>2]=c[o>>2];c[m+4>>2]=p;m=(c[k>>2]|0)+ -1|0}while((n|0)<(m|0));e=c[j>>2]|0}c[k>>2]=m;j=c[h>>2]^1;h=e+(j*12|0)|0;j=e+(j*12|0)+4|0;k=c[j>>2]|0;b:do if((k|0)>0){e=c[h>>2]|0;m=0;while(1){l=m+1|0;if((c[e+(m<<3)>>2]|0)==(d|0)){l=m;break b}if((l|0)<(k|0))m=l;else break}}else l=0;while(0);d=k+ -1|0;if((l|0)<(d|0))do{n=c[h>>2]|0;d=l;l=l+1|0;o=n+(l<<3)|0;p=c[o+4>>2]|0;d=n+(d<<3)|0;c[d>>2]=c[o>>2];c[d+4>>2]=p;d=(c[j>>2]|0)+ -1|0}while((l|0)<(d|0));c[j>>2]=d}if(!(c[f>>2]&4)){p=b+208|0;o=p;o=ne(c[o>>2]|0,c[o+4>>2]|0,-1,-1)|0;c[p>>2]=o;c[p+4>>2]=F;p=b+224|0;o=p;o=je(c[o>>2]|0,c[o+4>>2]|0,(c[f>>2]|0)>>>5|0,0)|0;c[p>>2]=o;c[p+4>>2]=F;i=g;return}else{p=b+216|0;o=p;o=ne(c[o>>2]|0,c[o+4>>2]|0,-1,-1)|0;c[p>>2]=o;c[p+4>>2]=F;p=b+232|0;o=p;o=je(c[o>>2]|0,c[o+4>>2]|0,(c[f>>2]|0)>>>5|0,0)|0;c[p>>2]=o;c[p+4>>2]=F;i=g;return}}function Pb(b,e){b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;g=b+544|0;m=c[g>>2]|0;f=m+(e<<2)|0;Ob(b,e,0);m=c[m+(e+1<<2)>>2]|0;j=m>>1;m=(d[(c[b+332>>2]|0)+j>>0]|0)^m&1;o=a[528]|0;n=o&255;if((((m&255)<<24>>24==o<<24>>24&(n>>>1^1)|n&2&m|0)!=0?(k=(c[b+396>>2]|0)+(j<<3)|0,l=c[k>>2]|0,(l|0)!=-1):0)?((c[g>>2]|0)+(l<<2)|0)==(f|0):0)c[k>>2]=-1;c[f>>2]=c[f>>2]&-4|1;n=c[(c[g>>2]|0)+(e<<2)>>2]|0;o=b+556|0;c[o>>2]=((((n>>>3&1)+(n>>>5)<<2)+4|0)>>>2)+(c[o>>2]|0);i=h;return}function Qb(b,e){b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0;f=i;g=c[e>>2]|0;if(g>>>0<=31){l=0;i=f;return l|0}h=c[b+332>>2]|0;j=a[528]|0;k=j&255;l=k&2;k=k>>>1^1;b=0;while(1){m=c[e+(b<<2)+4>>2]|0;m=(d[h+(m>>1)>>0]|0)^m&1;b=b+1|0;if((m&255)<<24>>24==j<<24>>24&k|l&m){g=1;e=5;break}if((b|0)>=(g>>>5|0)){g=0;e=5;break}}if((e|0)==5){i=f;return g|0}return 0}function Rb(b,d){b=b|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;g=i;e=b+296|0;if((c[e>>2]|0)<=(d|0)){i=g;return}f=b+284|0;s=c[f>>2]|0;j=b+292|0;t=c[j>>2]|0;u=c[t+(d<<2)>>2]|0;if((s|0)>(u|0)){r=b+280|0;m=b+332|0;l=b+88|0;k=b+348|0;n=b+460|0;p=b+476|0;q=b+472|0;o=b+380|0;do{s=s+ -1|0;u=c[(c[r>>2]|0)+(s<<2)>>2]>>1;a[(c[m>>2]|0)+u>>0]=a[544]|0;t=c[l>>2]|0;if((t|0)<=1){if((t|0)==1?(s|0)>(c[(c[j>>2]|0)+((c[e>>2]|0)+ -1<<2)>>2]|0):0)h=7}else h=7;if((h|0)==7){h=0;a[(c[k>>2]|0)+u>>0]=c[(c[r>>2]|0)+(s<<2)>>2]&1}if(!((c[p>>2]|0)>(u|0)?(c[(c[q>>2]|0)+(u<<2)>>2]|0)>-1:0))h=11;if((h|0)==11?(h=0,(a[(c[o>>2]|0)+u>>0]|0)!=0):0)lc(n,u);t=c[j>>2]|0;u=c[t+(d<<2)>>2]|0}while((s|0)>(u|0));s=c[f>>2]|0}c[b+512>>2]=u;b=c[t+(d<<2)>>2]|0;if((s-b|0)>0)c[f>>2]=b;if(((c[e>>2]|0)-d|0)<=0){i=g;return}c[e>>2]=d;i=g;return}function Sb(b){b=b|0;var d=0,e=0,f=0,g=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0.0,r=0;d=i;f=b+72|0;q=+h[f>>3]*1389796.0;q=q- +(~~(q/2147483647.0)|0)*2147483647.0;h[f>>3]=q;l=b+464|0;if(q/2147483647.0<+h[b+64>>3]?(m=c[l>>2]|0,(m|0)!=0):0){q=q*1389796.0;q=q- +(~~(q/2147483647.0)|0)*2147483647.0;h[f>>3]=q;m=c[(c[b+460>>2]|0)+(~~(+(m|0)*(q/2147483647.0))<<2)>>2]|0;o=a[(c[b+332>>2]|0)+m>>0]|0;n=a[544]|0;p=n&255;if(((p>>>1^1)&o<<24>>24==n<<24>>24|o&2&p|0)!=0?(a[(c[b+380>>2]|0)+m>>0]|0)!=0:0){p=b+176|0;o=p;o=ne(c[o>>2]|0,c[o+4>>2]|0,1,0)|0;c[p>>2]=o;c[p+4>>2]=F}}else m=-1;n=b+460|0;p=b+332|0;o=b+380|0;while(1){if(((m|0)!=-1?(r=a[(c[p>>2]|0)+m>>0]|0,j=a[544]|0,e=j&255,g=e>>>1^1,(g&r<<24>>24==j<<24>>24|r&2&e|0)!=0):0)?(a[(c[o>>2]|0)+m>>0]|0)!=0:0)break;if(!(c[l>>2]|0)){e=-2;k=17;break}m=rc(n)|0}if((k|0)==17){i=d;return e|0}l=a[(c[b+364>>2]|0)+m>>0]|0;k=l&255;if(!(g&l<<24>>24==j<<24>>24|e&2&k)){p=a[528]|0;r=p&255;r=((r>>>1^1)&l<<24>>24==p<<24>>24|k&2&r|0)!=0|m<<1;i=d;return r|0}if(!(a[b+92>>0]|0)){r=(a[(c[b+348>>2]|0)+m>>0]|0)!=0|m<<1;i=d;return r|0}else{q=+h[f>>3]*1389796.0;q=q- +(~~(q/2147483647.0)|0)*2147483647.0;h[f>>3]=q;r=q/2147483647.0<.5|m<<1;i=d;return r|0}return 0}function Tb(b,d,e,f){b=b|0;d=d|0;e=e|0;f=f|0;var j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0.0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0,S=0,T=0.0,U=0;j=i;i=i+16|0;p=j+8|0;t=j+4|0;n=j;m=e+4|0;k=c[m>>2]|0;l=e+8|0;if((k|0)==(c[l>>2]|0)){q=(k>>1)+2&-2;q=(q|0)<2?2:q;if((q|0)>(2147483647-k|0)){S=va(1)|0;Ta(S|0,48,0)}R=c[e>>2]|0;S=q+k|0;c[l>>2]=S;S=Ud(R,S<<2)|0;c[e>>2]=S;if((S|0)==0?(c[(Oa()|0)>>2]|0)==12:0){S=va(1)|0;Ta(S|0,48,0)}k=c[m>>2]|0}l=(c[e>>2]|0)+(k<<2)|0;if(l){c[l>>2]=0;k=c[m>>2]|0}c[m>>2]=k+1;q=b+544|0;H=b+280|0;k=b+588|0;l=b+396|0;C=b+504|0;E=b+316|0;D=b+540|0;B=b+476|0;A=b+472|0;z=b+460|0;y=b+488|0;x=b+296|0;v=b+496|0;w=b+272|0;G=b+268|0;J=-2;I=(c[b+284>>2]|0)+ -1|0;K=0;do{L=c[q>>2]|0;d=L+(d<<2)|0;M=c[d>>2]|0;if((M&4|0)!=0?(r=+h[v>>3],S=d+(M>>>5<<2)+4|0,T=r+ +g[S>>2],g[S>>2]=T,T>1.0e20):0){O=c[w>>2]|0;if((O|0)>0){N=c[G>>2]|0;M=0;do{S=L+(c[N+(M<<2)>>2]<<2)|0;S=S+((c[S>>2]|0)>>>5<<2)+4|0;g[S>>2]=+g[S>>2]*1.0e-20;M=M+1|0}while((M|0)!=(O|0))}h[v>>3]=r*1.0e-20}J=(J|0)!=-2&1;if(J>>>0<(c[d>>2]|0)>>>5>>>0)do{M=c[d+(J<<2)+4>>2]|0;c[t>>2]=M;M=M>>1;L=(c[k>>2]|0)+M|0;do if((a[L>>0]|0)==0?(c[(c[l>>2]|0)+(M<<3)+4>>2]|0)>0:0){O=c[E>>2]|0;S=O+(M<<3)|0;T=+h[C>>3]+ +h[S>>3];h[S>>3]=T;if(T>1.0e+100){P=c[D>>2]|0;if((P|0)>0){N=0;do{S=O+(N<<3)|0;h[S>>3]=+h[S>>3]*1.0e-100;N=N+1|0}while((N|0)!=(P|0))}h[C>>3]=+h[C>>3]*1.0e-100}if((c[B>>2]|0)>(M|0)?(u=c[A>>2]|0,s=c[u+(M<<2)>>2]|0,(s|0)>-1):0){N=c[z>>2]|0;O=c[N+(s<<2)>>2]|0;a:do if(!s)R=0;else{S=s;while(1){R=S;S=S+ -1>>1;Q=N+(S<<2)|0;P=c[Q>>2]|0;U=c[c[y>>2]>>2]|0;if(!(+h[U+(O<<3)>>3]>+h[U+(P<<3)>>3]))break a;c[N+(R<<2)>>2]=P;c[u+(c[Q>>2]<<2)>>2]=R;if(!S){R=0;break}}}while(0);c[N+(R<<2)>>2]=O;c[u+(O<<2)>>2]=R}a[L>>0]=1;if((c[(c[l>>2]|0)+(M<<3)+4>>2]|0)<(c[x>>2]|0)){mc(e,t);break}else{K=K+1|0;break}}while(0);J=J+1|0}while((J|0)<((c[d>>2]|0)>>>5|0));d=c[H>>2]|0;L=c[k>>2]|0;do{J=I;I=I+ -1|0;J=c[d+(J<<2)>>2]|0;N=J>>1;M=L+N|0}while((a[M>>0]|0)==0);d=c[(c[l>>2]|0)+(N<<3)>>2]|0;a[M>>0]=0;K=K+ -1|0}while((K|0)>0);c[c[e>>2]>>2]=J^1;t=b+616|0;v=c[t>>2]|0;s=b+620|0;if(!v)w=c[s>>2]|0;else{c[s>>2]=0;w=0}u=c[m>>2]|0;if((w|0)<(u|0)){y=b+624|0;x=c[y>>2]|0;if((x|0)<(u|0)){U=u+1-x&-2;w=(x>>1)+2&-2;w=(U|0)>(w|0)?U:w;if((w|0)>(2147483647-x|0)){U=va(1)|0;Ta(U|0,48,0)}U=w+x|0;c[y>>2]=U;v=Ud(v,U<<2)|0;c[t>>2]=v;if((v|0)==0?(c[(Oa()|0)>>2]|0)==12:0){U=va(1)|0;Ta(U|0,48,0)}}w=c[s>>2]|0;b:do if((w|0)<(u|0))while(1){v=v+(w<<2)|0;if(v)c[v>>2]=0;w=w+1|0;if((w|0)==(u|0))break b;v=c[t>>2]|0}while(0);c[s>>2]=u;u=c[m>>2]|0}if((u|0)>0){w=c[t>>2]|0;v=c[e>>2]|0;x=0;do{c[w+(x<<2)>>2]=c[v+(x<<2)>>2];x=x+1|0;u=c[m>>2]|0}while((x|0)<(u|0))}v=c[b+84>>2]|0;if((v|0)==1)if((u|0)>1){n=c[e>>2]|0;o=1;v=1;while(1){u=c[n+(o<<2)>>2]|0;p=c[l>>2]|0;w=c[p+(u>>1<<3)>>2]|0;c:do if((w|0)!=-1){x=(c[q>>2]|0)+(w<<2)|0;y=c[x>>2]|0;if(y>>>0>63){w=c[k>>2]|0;z=1;while(1){U=c[x+(z<<2)+4>>2]>>1;if((a[w+U>>0]|0)==0?(c[p+(U<<3)+4>>2]|0)>0:0)break;z=z+1|0;if((z|0)>=(y>>>5|0))break c}c[n+(v<<2)>>2]=u;v=v+1|0}}else{c[n+(v<<2)>>2]=u;v=v+1|0}while(0);o=o+1|0;p=c[m>>2]|0;if((o|0)>=(p|0)){n=p;break}}}else{n=u;o=1;v=1}else if((v|0)==2)if((u|0)>1){q=1;v=1;do{w=c[e>>2]|0;u=c[w+(q<<2)>>2]|0;if((c[(c[l>>2]|0)+(u>>1<<3)>>2]|0)!=-1){c[n>>2]=u;c[p+0>>2]=c[n+0>>2];if(!(Ub(b,p)|0)){u=c[e>>2]|0;w=u;u=c[u+(q<<2)>>2]|0;o=62}}else o=62;if((o|0)==62){o=0;c[w+(v<<2)>>2]=u;v=v+1|0}q=q+1|0;u=c[m>>2]|0}while((q|0)<(u|0));n=u;o=q}else{n=u;o=1;v=1}else{n=u;o=u;v=u}U=b+240|0;S=U;S=ne(c[S>>2]|0,c[S+4>>2]|0,n|0,((n|0)<0)<<31>>31|0)|0;c[U>>2]=S;c[U+4>>2]=F;o=o-v|0;if((o|0)>0){n=n-o|0;c[m>>2]=n}U=b+248|0;S=U;S=ne(c[S>>2]|0,c[S+4>>2]|0,n|0,((n|0)<0)<<31>>31|0)|0;c[U>>2]=S;c[U+4>>2]=F;if((n|0)==1)e=0;else{e=c[e>>2]|0;if((n|0)>2){b=c[l>>2]|0;m=2;o=1;do{o=(c[b+(c[e+(m<<2)>>2]>>1<<3)+4>>2]|0)>(c[b+(c[e+(o<<2)>>2]>>1<<3)+4>>2]|0)?m:o;m=m+1|0}while((m|0)<(n|0))}else o=1;S=e+(o<<2)|0;U=c[S>>2]|0;e=e+4|0;c[S>>2]=c[e>>2];c[e>>2]=U;e=c[(c[l>>2]|0)+(U>>1<<3)+4>>2]|0}c[f>>2]=e;if((c[s>>2]|0)>0)f=0;else{i=j;return}do{a[(c[k>>2]|0)+(c[(c[t>>2]|0)+(f<<2)>>2]>>1)>>0]=0;f=f+1|0}while((f|0)<(c[s>>2]|0));i=j;return}function Ub(b,d){b=b|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;e=i;n=c[d>>2]|0;l=b+396|0;q=c[l>>2]|0;k=b+544|0;s=(c[k>>2]|0)+(c[q+(n>>1<<3)>>2]<<2)|0;h=b+604|0;f=b+608|0;if(c[h>>2]|0)c[f>>2]=0;g=b+588|0;j=b+612|0;b=b+616|0;o=1;while(1){if(o>>>0<(c[s>>2]|0)>>>5>>>0){r=c[s+(o<<2)+4>>2]|0;p=r>>1;if((c[q+(p<<3)+4>>2]|0)!=0?(m=a[(c[g>>2]|0)+p>>0]|0,(m+ -1<<24>>24&255)>=2):0){s=c[f>>2]|0;t=(s|0)==(c[j>>2]|0);if(m<<24>>24==3?1:(c[q+(p<<3)>>2]|0)==-1){k=8;break}if(t){q=(s>>1)+2&-2;q=(q|0)<2?2:q;if((q|0)>(2147483647-s|0)){k=24;break}u=c[h>>2]|0;t=q+s|0;c[j>>2]=t;t=Ud(u,t<<3)|0;c[h>>2]=t;if((t|0)==0?(c[(Oa()|0)>>2]|0)==12:0){k=24;break}s=c[f>>2]|0}c[f>>2]=s+1;q=(c[h>>2]|0)+(s<<3)|0;if(q){u=q;c[u>>2]=o;c[u+4>>2]=n}c[d>>2]=r;s=c[l>>2]|0;n=r;q=s;s=(c[k>>2]|0)+(c[s+(p<<3)>>2]<<2)|0;o=0}}else{n=(c[g>>2]|0)+(n>>1)|0;if(!(a[n>>0]|0)){a[n>>0]=2;mc(b,d)}n=c[f>>2]|0;if(!n){f=1;k=34;break}u=n+ -1|0;n=c[h>>2]|0;o=c[n+(u<<3)>>2]|0;n=c[n+(u<<3)+4>>2]|0;c[d>>2]=n;q=c[l>>2]|0;s=(c[k>>2]|0)+(c[q+(n>>1<<3)>>2]<<2)|0;c[f>>2]=u}o=o+1|0}if((k|0)==8){if(t){k=(s>>1)+2&-2;k=(k|0)<2?2:k;if((k|0)>(2147483647-s|0)){u=va(1)|0;Ta(u|0,48,0)}t=c[h>>2]|0;u=k+s|0;c[j>>2]=u;u=Ud(t,u<<3)|0;c[h>>2]=u;if((u|0)==0?(c[(Oa()|0)>>2]|0)==12:0){u=va(1)|0;Ta(u|0,48,0)}s=c[f>>2]|0}j=s+1|0;c[f>>2]=j;k=(c[h>>2]|0)+(s<<3)|0;if(k){j=k;c[j>>2]=0;c[j+4>>2]=n;j=c[f>>2]|0}if((j|0)>0)k=0;else{u=0;i=e;return u|0}do{l=(c[g>>2]|0)+(c[(c[h>>2]|0)+(k<<3)+4>>2]>>1)|0;if(!(a[l>>0]|0)){a[l>>0]=3;mc(b,(c[h>>2]|0)+(k<<3)+4|0);j=c[f>>2]|0}k=k+1|0}while((k|0)<(j|0));f=0;i=e;return f|0}else if((k|0)==24)Ta(va(1)|0,48,0);else if((k|0)==34){i=e;return f|0}return 0}function Vb(b,d,e){b=b|0;d=d|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;j=i;i=i+32|0;h=j+16|0;g=j+12|0;k=j+8|0;f=j;n=e+20|0;l=e+16|0;if((c[n>>2]|0)>0){m=0;do{a[(c[e>>2]|0)+(c[(c[l>>2]|0)+(m<<2)>>2]|0)>>0]=0;m=m+1|0}while((m|0)<(c[n>>2]|0))}if(c[l>>2]|0)c[n>>2]=0;m=c[d>>2]|0;c[k>>2]=m;c[g>>2]=m;c[h+0>>2]=c[g+0>>2];sc(e,h,0);l=(c[e>>2]|0)+m|0;if(!(a[l>>0]|0)){a[l>>0]=1;mc(e+16|0,k)}if(!(c[b+296>>2]|0)){i=j;return}d=m>>1;o=b+588|0;a[(c[o>>2]|0)+d>>0]=1;p=c[b+284>>2]|0;n=b+292|0;s=c[c[n>>2]>>2]|0;if((p|0)>(s|0)){k=b+280|0;l=b+396|0;m=e+16|0;b=b+544|0;do{p=p+ -1|0;r=c[(c[k>>2]|0)+(p<<2)>>2]|0;q=r>>1;if(a[(c[o>>2]|0)+q>>0]|0){s=c[l>>2]|0;t=c[s+(q<<3)>>2]|0;a:do if((t|0)==-1){r=r^1;c[f>>2]=r;c[g>>2]=r;c[h+0>>2]=c[g+0>>2];sc(e,h,0);r=(c[e>>2]|0)+r|0;if(!(a[r>>0]|0)){a[r>>0]=1;mc(m,f)}}else{r=(c[b>>2]|0)+(t<<2)|0;t=c[r>>2]|0;if(t>>>0>63){u=1;while(1){v=c[r+(u<<2)+4>>2]>>1;if((c[s+(v<<3)+4>>2]|0)>0){a[(c[o>>2]|0)+v>>0]=1;t=c[r>>2]|0}u=u+1|0;if((u|0)>=(t>>>5|0))break a;s=c[l>>2]|0}}}while(0);a[(c[o>>2]|0)+q>>0]=0;s=c[c[n>>2]>>2]|0}}while((p|0)>(s|0))}a[(c[o>>2]|0)+d>>0]=0;i=j;return}function Wb(b){b=b|0;var e=0,f=0,j=0,k=0,l=0,m=0,n=0.0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0;f=i;i=i+16|0;p=f+4|0;u=f;e=b+272|0;w=c[e>>2]|0;n=+h[b+496>>3]/+(w|0);k=b+544|0;l=b+268|0;v=c[l>>2]|0;c[u>>2]=k;c[p+0>>2]=c[u+0>>2];tc(v,w,p);p=c[e>>2]|0;if((p|0)>0){m=b+332|0;o=b+396|0;q=0;v=0;do{t=c[l>>2]|0;u=c[t+(q<<2)>>2]|0;w=c[k>>2]|0;r=w+(u<<2)|0;s=c[r>>2]|0;do if(s>>>0>95){x=c[w+(u+1<<2)>>2]|0;w=x>>1;x=(d[(c[m>>2]|0)+w>>0]|0)^x&1;z=a[528]|0;y=z&255;if(((x&255)<<24>>24==z<<24>>24&(y>>>1^1)|y&2&x|0)!=0?(z=c[(c[o>>2]|0)+(w<<3)>>2]|0,(z|0)!=-1&(z|0)==(u|0)):0){j=9;break}if((q|0)>=((p|0)/2|0|0)?!(+g[r+(s>>>5<<2)+4>>2]<n):0){j=9;break}Pb(b,u)}else j=9;while(0);if((j|0)==9){j=0;c[t+(v<<2)>>2]=u;v=v+1|0}q=q+1|0;p=c[e>>2]|0}while((q|0)<(p|0))}else{q=0;v=0}j=q-v|0;if((j|0)>0)c[e>>2]=p-j;if(!(+((c[b+556>>2]|0)>>>0)>+h[b+96>>3]*+((c[b+548>>2]|0)>>>0))){i=f;return}gb[c[(c[b>>2]|0)+8>>2]&31](b);i=f;return}function Xb(b,e){b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0;f=i;g=e+4|0;m=c[g>>2]|0;if((m|0)>0){j=b+544|0;h=b+332|0;k=0;l=0;do{u=c[e>>2]|0;p=c[u+(k<<2)>>2]|0;m=(c[j>>2]|0)+(p<<2)|0;o=c[m>>2]|0;do if(o>>>0>31){v=c[h>>2]|0;r=a[528]|0;q=r&255;w=q&2;q=q>>>1^1;s=o>>>5;t=0;do{x=c[m+(t<<2)+4>>2]|0;x=(d[v+(x>>1)>>0]|0)^x&1;t=t+1|0;if((x&255)<<24>>24==r<<24>>24&q|w&x){n=7;break}}while((t|0)<(s|0));if((n|0)==7){n=0;Pb(b,p);break}if(o>>>0>95){n=a[536]|0;q=o>>>5;p=2;do{r=m+(p<<2)+4|0;x=c[r>>2]|0;x=(d[(c[h>>2]|0)+(x>>1)>>0]|0)^x&1;w=n&255;if((x&255)<<24>>24==n<<24>>24&(w>>>1^1)|w&2&x){c[r>>2]=c[m+(q+ -1<<2)+4>>2];o=c[m>>2]|0;if(o&8){o=o>>>5;c[m+(o+ -1<<2)+4>>2]=c[m+(o<<2)+4>>2];o=c[m>>2]|0}o=o+ -32|0;c[m>>2]=o;p=p+ -1|0}p=p+1|0;q=o>>>5}while((p|0)<(q|0));p=c[e>>2]|0;u=p;p=c[p+(k<<2)>>2]|0;n=16}else n=16}else n=16;while(0);if((n|0)==16){n=0;c[u+(l<<2)>>2]=p;l=l+1|0}k=k+1|0;m=c[g>>2]|0}while((k|0)<(m|0))}else{k=0;l=0}e=k-l|0;if((e|0)<=0){i=f;return}c[g>>2]=m-e;i=f;return}function Yb(b){b=b|0;var d=0,e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;g=i;i=i+16|0;e=g+4|0;h=g;c[e>>2]=0;d=e+4|0;c[d>>2]=0;f=e+8|0;c[f>>2]=0;c[h>>2]=0;j=b+540|0;n=c[j>>2]|0;if((n|0)>0){l=b+380|0;k=b+332|0;m=0;do{if((a[(c[l>>2]|0)+m>>0]|0)!=0?(p=a[(c[k>>2]|0)+m>>0]|0,q=a[544]|0,o=q&255,((o>>>1^1)&p<<24>>24==q<<24>>24|p&2&o|0)!=0):0){nc(e,h);n=c[j>>2]|0}m=m+1|0;c[h>>2]=m}while((m|0)<(n|0))}uc(b+460|0,e);b=c[e>>2]|0;if(!b){i=g;return}c[d>>2]=0;Td(b);c[e>>2]=0;c[f>>2]=0;i=g;return}function Zb(b){b=b|0;var d=0,e=0,f=0,g=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;d=i;f=b+492|0;if((a[f>>0]|0)!=0?(Mb(b)|0)==-1:0){f=b+284|0;g=b+516|0;if((c[f>>2]|0)==(c[g>>2]|0)){s=1;i=d;return s|0}j=b+520|0;s=j;r=c[s+4>>2]|0;if((r|0)>0|(r|0)==0&(c[s>>2]|0)>>>0>0){s=1;i=d;return s|0}Xb(b,b+268|0);if(a[b+536>>0]|0){Xb(b,b+256|0);l=b+564|0;k=b+568|0;if((c[k>>2]|0)>0){n=b+588|0;m=0;do{a[(c[n>>2]|0)+(c[(c[l>>2]|0)+(m<<2)>>2]|0)>>0]=1;m=m+1|0}while((m|0)<(c[k>>2]|0))}p=c[f>>2]|0;if((p|0)>0){m=c[b+280>>2]|0;n=c[b+588>>2]|0;q=0;o=0;do{r=c[m+(q<<2)>>2]|0;if(!(a[n+(r>>1)>>0]|0)){c[m+(o<<2)>>2]=r;p=c[f>>2]|0;o=o+1|0}q=q+1|0}while((q|0)<(p|0))}else{q=0;o=0}m=q-o|0;if((m|0)>0){p=p-m|0;c[f>>2]=p}c[b+512>>2]=p;a:do if((c[k>>2]|0)>0){o=b+588|0;m=0;do{a[(c[o>>2]|0)+(c[(c[l>>2]|0)+(m<<2)>>2]|0)>>0]=0;m=m+1|0;n=c[k>>2]|0}while((m|0)<(n|0));if((n|0)>0){n=b+580|0;o=b+584|0;m=b+576|0;p=0;while(1){r=c[n>>2]|0;if((r|0)==(c[o>>2]|0)){q=(r>>1)+2&-2;q=(q|0)<2?2:q;if((q|0)>(2147483647-r|0)){e=28;break}s=c[m>>2]|0;q=q+r|0;c[o>>2]=q;q=Ud(s,q<<2)|0;c[m>>2]=q;if((q|0)==0?(c[(Oa()|0)>>2]|0)==12:0){e=28;break}r=c[n>>2]|0}else q=c[m>>2]|0;s=q+(r<<2)|0;if(s){c[s>>2]=0;r=c[n>>2]|0}c[n>>2]=r+1;s=c[l>>2]|0;c[q+(r<<2)>>2]=c[s+(p<<2)>>2];p=p+1|0;if((p|0)>=(c[k>>2]|0))break a}if((e|0)==28)Ta(va(1)|0,48,0)}else e=21}else e=21;while(0);if((e|0)==21)s=c[l>>2]|0;if(s)c[k>>2]=0}if(+((c[b+556>>2]|0)>>>0)>+h[b+96>>3]*+((c[b+548>>2]|0)>>>0))gb[c[(c[b>>2]|0)+8>>2]&31](b);Yb(b);c[g>>2]=c[f>>2];r=b+224|0;s=b+232|0;r=ne(c[s>>2]|0,c[s+4>>2]|0,c[r>>2]|0,c[r+4>>2]|0)|0;s=j;c[s>>2]=r;c[s+4>>2]=F;s=1;i=d;return s|0}a[f>>0]=0;s=0;i=d;return s|0}function _b(b,e,f){b=b|0;e=e|0;f=f|0;var j=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0,T=0,U=0,V=0,W=0,X=0,Y=0,Z=0,_=0,$=0,aa=0,ba=0,ca=0,da=0,ea=0,fa=0.0,ga=0,ha=0,ia=0,ja=0.0,ka=0,la=0,ma=0,na=0,oa=0,pa=0,qa=0.0,ra=0,sa=0,ta=0.0;n=i;i=i+64|0;_=n;G=n+60|0;B=n+56|0;j=n+44|0;$=n+40|0;c[j>>2]=0;m=j+4|0;c[m>>2]=0;l=j+8|0;c[l>>2]=0;N=e+160|0;M=N;M=ne(c[M>>2]|0,c[M+4>>2]|0,1,0)|0;c[N>>2]=M;c[N+4>>2]=F;N=(f|0)<0;M=e+680|0;L=e+664|0;K=e+672|0;q=e+296|0;w=e+272|0;o=e+284|0;I=e+640|0;E=e+308|0;D=e+304|0;r=e+332|0;H=e+292|0;ba=e+168|0;t=e+396|0;v=e+280|0;J=e+184|0;C=e+192|0;u=e+48|0;U=e+504|0;Y=e+56|0;aa=e+496|0;ca=e+656|0;O=e+144|0;P=e+648|0;Q=e+128|0;R=e+44|0;T=e+200|0;V=e+208|0;W=e+224|0;X=e+216|0;s=e+232|0;Z=e+540|0;p=e+292|0;x=e+544|0;z=e+276|0;y=e+268|0;A=e+268|0;da=0;a:while(1){ea=N|(da|0)<(f|0);while(1){ga=Mb(e)|0;if((ga|0)!=-1)break;if(!ea){ga=41;break a}if(a[M>>0]|0){ga=41;break a}ga=L;ha=c[ga+4>>2]|0;if((ha|0)>=0?(sa=C,ra=c[sa+4>>2]|0,!(ra>>>0<ha>>>0|((ra|0)==(ha|0)?(c[sa>>2]|0)>>>0<(c[ga>>2]|0)>>>0:0))):0){ga=41;break a}ga=K;ha=c[ga+4>>2]|0;if((ha|0)>=0?(sa=J,ra=c[sa+4>>2]|0,!(ra>>>0<ha>>>0|((ra|0)==(ha|0)?(c[sa>>2]|0)>>>0<(c[ga>>2]|0)>>>0:0))):0){ga=41;break a}if((c[q>>2]|0)==0?!(Zb(e)|0):0){ga=50;break a}if(+((c[w>>2]|0)-(c[o>>2]|0)|0)>=+h[I>>3])Wb(e);while(1){ga=c[q>>2]|0;if((ga|0)>=(c[E>>2]|0)){ga=59;break}ka=c[(c[D>>2]|0)+(ga<<2)>>2]|0;ha=d[(c[r>>2]|0)+(ka>>1)>>0]|0;sa=ha^ka&1;ia=sa&255;pa=a[528]|0;ra=pa&255;if(!(ia<<24>>24==pa<<24>>24&(ra>>>1^1)|ra&2&sa)){ga=56;break}c[G>>2]=c[o>>2];nc(H,G)}if((ga|0)==56){ga=0;ra=a[536]|0;sa=ra&255;if((sa>>>1^1)&ia<<24>>24==ra<<24>>24|ha&2&sa){ga=57;break a}if((ka|0)==-2)ga=59}if((ga|0)==59){sa=ba;sa=ne(c[sa>>2]|0,c[sa+4>>2]|0,1,0)|0;ka=ba;c[ka>>2]=sa;c[ka+4>>2]=F;ka=Sb(e)|0;if((ka|0)==-2){ga=60;break a}}c[_>>2]=c[o>>2];nc(H,_);sa=ka>>1;a[(c[r>>2]|0)+sa>>0]=(ka&1^1)&255^1;ra=c[q>>2]|0;sa=(c[t>>2]|0)+(sa<<3)|0;c[sa>>2]=-1;c[sa+4>>2]=ra;sa=c[o>>2]|0;c[o>>2]=sa+1;c[(c[v>>2]|0)+(sa<<2)>>2]=ka}ra=C;ra=ne(c[ra>>2]|0,c[ra+4>>2]|0,1,0)|0;sa=C;c[sa>>2]=ra;c[sa+4>>2]=F;da=da+1|0;if(!(c[q>>2]|0)){ga=5;break}if(c[j>>2]|0)c[m>>2]=0;Tb(e,ga,j,B);Rb(e,c[B>>2]|0);if((c[m>>2]|0)==1){ra=c[c[j>>2]>>2]|0;sa=ra>>1;a[(c[r>>2]|0)+sa>>0]=(ra&1^1)&255^1;pa=c[q>>2]|0;sa=(c[t>>2]|0)+(sa<<3)|0;c[sa>>2]=-1;c[sa+4>>2]=pa;sa=c[o>>2]|0;c[o>>2]=sa+1;c[(c[v>>2]|0)+(sa<<2)>>2]=ra}else{ea=pc(x,j,1)|0;ga=c[w>>2]|0;if((ga|0)==(c[z>>2]|0)){ha=(ga>>1)+2&-2;ha=(ha|0)<2?2:ha;if((ha|0)>(2147483647-ga|0)){ga=14;break}ra=c[y>>2]|0;sa=ha+ga|0;c[z>>2]=sa;sa=Ud(ra,sa<<2)|0;c[y>>2]=sa;if((sa|0)==0?(c[(Oa()|0)>>2]|0)==12:0){ga=14;break}ga=c[w>>2]|0}c[w>>2]=ga+1;ga=(c[y>>2]|0)+(ga<<2)|0;if(ga)c[ga>>2]=ea;Nb(e,ea);ia=c[x>>2]|0;sa=ia+(ea<<2)|0;fa=+h[aa>>3];sa=sa+((c[sa>>2]|0)>>>5<<2)+4|0;ta=fa+ +g[sa>>2];g[sa>>2]=ta;if(ta>1.0e20){ha=c[w>>2]|0;if((ha|0)>0){ga=c[A>>2]|0;ka=0;do{sa=ia+(c[ga+(ka<<2)>>2]<<2)|0;sa=sa+((c[sa>>2]|0)>>>5<<2)+4|0;g[sa>>2]=+g[sa>>2]*1.0e-20;ka=ka+1|0}while((ka|0)!=(ha|0))}h[aa>>3]=fa*1.0e-20}ra=c[c[j>>2]>>2]|0;sa=ra>>1;a[(c[r>>2]|0)+sa>>0]=(ra&1^1)&255^1;pa=c[q>>2]|0;sa=(c[t>>2]|0)+(sa<<3)|0;c[sa>>2]=ea;c[sa+4>>2]=pa;sa=c[o>>2]|0;c[o>>2]=sa+1;c[(c[v>>2]|0)+(sa<<2)>>2]=ra}h[U>>3]=1.0/+h[u>>3]*+h[U>>3];h[aa>>3]=1.0/+h[Y>>3]*+h[aa>>3];sa=(c[ca>>2]|0)+ -1|0;c[ca>>2]=sa;if(sa)continue;fa=+h[O>>3]*+h[P>>3];h[P>>3]=fa;c[ca>>2]=~~fa;fa=+h[Q>>3]*+h[I>>3];h[I>>3]=fa;if((c[R>>2]|0)<=0)continue;ga=c[C>>2]|0;ea=c[T>>2]|0;oa=c[q>>2]|0;if(!oa)ha=o;else ha=c[p>>2]|0;ha=c[ha>>2]|0;na=c[V>>2]|0;ma=c[W>>2]|0;la=c[X>>2]|0;ka=s;ia=c[ka>>2]|0;ka=c[ka+4>>2]|0;ja=+(c[Z>>2]|0);qa=1.0/ja;if((oa|0)<0)ta=0.0;else{pa=0;ta=0.0;while(1){if(!pa)ra=0;else ra=c[(c[p>>2]|0)+(pa+ -1<<2)>>2]|0;if((pa|0)==(oa|0))sa=o;else sa=(c[p>>2]|0)+(pa<<2)|0;ta=ta+ +S(+qa,+(+(pa|0)))*+((c[sa>>2]|0)-ra|0);if((pa|0)==(oa|0))break;else pa=pa+1|0}}c[_>>2]=ga;c[_+4>>2]=ea-ha;c[_+8>>2]=na;c[_+12>>2]=ma;c[_+16>>2]=~~fa;c[_+20>>2]=la;sa=_+24|0;h[k>>3]=(+(ia>>>0)+4294967296.0*+(ka>>>0))/+(la|0);c[sa>>2]=c[k>>2];c[sa+4>>2]=c[k+4>>2];sa=_+32|0;h[k>>3]=ta/ja*100.0;c[sa>>2]=c[k>>2];c[sa+4>>2]=c[k+4>>2];La(1832,_|0)|0}if((ga|0)==5)a[b>>0]=a[536]|0;else if((ga|0)==14)Ta(va(1)|0,48,0);else if((ga|0)==41){fa=+(c[Z>>2]|0);ja=1.0/fa;r=c[q>>2]|0;if((r|0)<0)qa=0.0;else{q=0;qa=0.0;while(1){if(!q)s=0;else s=c[(c[p>>2]|0)+(q+ -1<<2)>>2]|0;if((q|0)==(r|0))t=o;else t=(c[p>>2]|0)+(q<<2)|0;qa=qa+ +S(+ja,+(+(q|0)))*+((c[t>>2]|0)-s|0);if((q|0)==(r|0))break;else q=q+1|0}}h[e+528>>3]=qa/fa;Rb(e,0);a[b>>0]=a[544]|0}else if((ga|0)==50)a[b>>0]=a[536]|0;else if((ga|0)==57){c[$>>2]=ka^1;sa=e+16|0;c[_+0>>2]=c[$+0>>2];Vb(e,_,sa);a[b>>0]=a[536]|0}else if((ga|0)==60)a[b>>0]=a[528]|0;b=c[j>>2]|0;if(!b){i=n;return}c[m>>2]=0;Td(b);c[j>>2]=0;c[l>>2]=0;i=n;return}function $b(b,d){b=b|0;d=d|0;var e=0,f=0,g=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0.0,w=0,x=0,y=0,z=0.0,A=0,B=0;f=i;i=i+16|0;j=f;e=d+4|0;if(c[e>>2]|0)c[d+8>>2]=0;g=d+36|0;k=d+32|0;if((c[g>>2]|0)>0){l=d+16|0;m=0;do{a[(c[l>>2]|0)+(c[(c[k>>2]|0)+(m<<2)>>2]|0)>>0]=0;m=m+1|0}while((m|0)<(c[g>>2]|0))}if(c[k>>2]|0)c[g>>2]=0;k=d+492|0;if(!(a[k>>0]|0)){a[b>>0]=a[536]|0;i=f;return}l=d+152|0;y=l;y=ne(c[y>>2]|0,c[y+4>>2]|0,1,0)|0;c[l>>2]=y;c[l+4>>2]=F;z=+h[d+120>>3]*+(c[d+208>>2]|0);l=d+640|0;h[l>>3]=z;v=+(c[d+104>>2]|0);if(z<v)h[l>>3]=v;w=c[d+136>>2]|0;h[d+648>>3]=+(w|0);c[d+656>>2]=w;w=a[544]|0;l=d+44|0;if((c[l>>2]|0)>0){Ka(2288)|0;Ka(2368)|0;Ka(2448)|0;Ka(2528)|0;o=a[544]|0}else o=w;n=d+192|0;m=d+184|0;y=o&255;a:do if((y>>>1^1)&w<<24>>24==o<<24>>24|w&2&y){q=d+80|0;t=d+112|0;p=d+108|0;o=d+680|0;r=d+664|0;s=d+672|0;u=0;while(1){v=+h[t>>3];if(!(a[q>>0]|0))v=+S(+v,+(+(u|0)));else{y=u+1|0;if((u|0)>0){x=0;w=1;do{x=x+1|0;w=w<<1|1}while((w|0)<(y|0));y=w+ -1|0}else{x=0;y=0}if((y|0)!=(u|0)){w=u;do{A=y>>1;x=x+ -1|0;w=(w|0)%(A|0)|0;y=A+ -1|0}while((y|0)!=(w|0))}v=+S(+v,+(+(x|0)))}_b(j,d,~~(v*+(c[p>>2]|0)));w=a[j>>0]|0;if(a[o>>0]|0)break a;y=r;x=c[y+4>>2]|0;if((x|0)>=0?(A=n,B=c[A+4>>2]|0,!(B>>>0<x>>>0|((B|0)==(x|0)?(c[A>>2]|0)>>>0<(c[y>>2]|0)>>>0:0))):0)break a;y=s;x=c[y+4>>2]|0;if((x|0)>=0?(B=m,A=c[B+4>>2]|0,!(A>>>0<x>>>0|((A|0)==(x|0)?(c[B>>2]|0)>>>0<(c[y>>2]|0)>>>0:0))):0)break a;A=a[544]|0;B=A&255;if(!((B>>>1^1)&w<<24>>24==A<<24>>24|w&2&B))break;else u=u+1|0}}while(0);if((c[l>>2]|0)>0)Ka(2528)|0;A=a[528]|0;B=A&255;j=w&2;if(!((B>>>1^1)&w<<24>>24==A<<24>>24|j&B)){A=a[536]|0;B=A&255;if(((B>>>1^1)&w<<24>>24==A<<24>>24|j&B|0)!=0?(c[g>>2]|0)==0:0)a[k>>0]=0}else{g=d+540|0;jc(e,c[g>>2]|0);if((c[g>>2]|0)>0){j=d+332|0;k=0;do{a[(c[e>>2]|0)+k>>0]=a[(c[j>>2]|0)+k>>0]|0;k=k+1|0}while((k|0)<(c[g>>2]|0))}}Rb(d,0);a[b>>0]=w;i=f;return}function ac(b,e){b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0;f=i;h=b+412|0;vc(h);k=b+540|0;if((c[k>>2]|0)>0){j=b+544|0;g=0;do{l=g<<1;n=c[h>>2]|0;m=n+(l*12|0)+4|0;if((c[m>>2]|0)>0){p=n+(l*12|0)|0;o=0;do{s=(c[p>>2]|0)+(o<<3)|0;n=c[s>>2]|0;q=c[j>>2]|0;r=q+(n<<2)|0;if(!(c[r>>2]&16)){t=wc(e,r)|0;c[s>>2]=t;c[r>>2]=c[r>>2]|16;c[q+(n+1<<2)>>2]=t}else c[s>>2]=c[q+(n+1<<2)>>2];o=o+1|0}while((o|0)<(c[m>>2]|0));m=c[h>>2]|0}else m=n;n=l|1;l=m+(n*12|0)+4|0;if((c[l>>2]|0)>0){r=m+(n*12|0)|0;q=0;do{m=(c[r>>2]|0)+(q<<3)|0;p=c[m>>2]|0;o=c[j>>2]|0;n=o+(p<<2)|0;if(!(c[n>>2]&16)){t=wc(e,n)|0;c[m>>2]=t;c[n>>2]=c[n>>2]|16;c[o+(p+1<<2)>>2]=t}else c[m>>2]=c[o+(p+1<<2)>>2];q=q+1|0}while((q|0)<(c[l>>2]|0))}g=g+1|0}while((g|0)<(c[k>>2]|0))}g=b+284|0;if((c[g>>2]|0)>0){l=b+280|0;k=b+396|0;j=b+544|0;h=b+332|0;m=0;do{r=c[k>>2]|0;p=r+(c[(c[l>>2]|0)+(m<<2)>>2]>>1<<3)|0;q=c[p>>2]|0;do if((q|0)!=-1){t=c[j>>2]|0;s=t+(q<<2)|0;o=(c[s>>2]&16|0)==0;if(o){u=c[t+(q+1<<2)>>2]|0;n=u>>1;u=(d[(c[h>>2]|0)+n>>0]|0)^u&1;w=a[528]|0;v=w&255;if(!((u&255)<<24>>24==w<<24>>24&(v>>>1^1)|v&2&u))break;w=c[r+(n<<3)>>2]|0;if(!((w|0)!=-1&(w|0)==(q|0)))break;if(o){w=wc(e,s)|0;c[p>>2]=w;c[s>>2]=c[s>>2]|16;c[t+(q+1<<2)>>2]=w;break}}c[p>>2]=c[t+(q+1<<2)>>2]}while(0);m=m+1|0}while((m|0)<(c[g>>2]|0))}g=b+272|0;n=c[g>>2]|0;if((n|0)>0){j=b+268|0;h=b+544|0;m=c[j>>2]|0;k=0;l=0;do{p=m+(k<<2)|0;o=c[p>>2]|0;r=c[h>>2]|0;q=r+(o<<2)|0;s=c[q>>2]|0;if((s&3|0)!=1){if(!(s&16)){n=wc(e,q)|0;c[p>>2]=n;c[q>>2]=c[q>>2]|16;c[r+(o+1<<2)>>2]=n;n=c[j>>2]|0;m=n;n=c[n+(k<<2)>>2]|0}else{n=c[r+(o+1<<2)>>2]|0;c[p>>2]=n}c[m+(l<<2)>>2]=n;n=c[g>>2]|0;l=l+1|0}k=k+1|0}while((k|0)<(n|0))}else{k=0;l=0}h=k-l|0;if((h|0)>0)c[g>>2]=n-h;g=b+260|0;m=c[g>>2]|0;if((m|0)>0){h=b+256|0;b=b+544|0;l=c[h>>2]|0;j=0;k=0;do{n=l+(j<<2)|0;p=c[n>>2]|0;o=c[b>>2]|0;r=o+(p<<2)|0;q=c[r>>2]|0;if((q&3|0)!=1){if(!(q&16)){m=wc(e,r)|0;c[n>>2]=m;c[r>>2]=c[r>>2]|16;c[o+(p+1<<2)>>2]=m;m=c[h>>2]|0;l=m;m=c[m+(j<<2)>>2]|0}else{m=c[o+(p+1<<2)>>2]|0;c[n>>2]=m}c[l+(k<<2)>>2]=m;m=c[g>>2]|0;k=k+1|0}j=j+1|0}while((j|0)<(m|0))}else{j=0;k=0}e=j-k|0;if((e|0)<=0){i=f;return}c[g>>2]=m-e;i=f;return}function bc(b){b=b|0;var d=0,e=0,f=0,g=0,h=0,j=0,k=0;g=i;i=i+32|0;j=g;d=g+8|0;e=b+548|0;f=b+556|0;h=(c[e>>2]|0)-(c[f>>2]|0)|0;c[d+0>>2]=0;c[d+4>>2]=0;c[d+8>>2]=0;c[d+12>>2]=0;gc(d,h);h=d+16|0;a[h>>0]=0;ac(b,d);if((c[b+44>>2]|0)>1){k=c[d+4>>2]<<2;c[j>>2]=c[e>>2]<<2;c[j+4>>2]=k;La(1888,j|0)|0}a[b+560>>0]=a[h>>0]|0;h=b+544|0;j=c[h>>2]|0;if(j)Td(j);c[h>>2]=c[d>>2];c[e>>2]=c[d+4>>2];c[b+552>>2]=c[d+8>>2];c[f>>2]=c[d+12>>2];i=g;return}function cc(){var d=0,e=0,f=0;d=i;i=i+16|0;e=d;a[528]=0;a[536]=1;a[544]=2;xb(552,608,624,2136,2144);c[138]=2168;h[72]=0.0;h[73]=1.0;a[592]=0;a[593]=0;b[297]=b[e+0>>1]|0;b[298]=b[e+2>>1]|0;b[299]=b[e+4>>1]|0;h[75]=.95;xb(664,720,736,2136,2144);c[166]=2168;h[86]=0.0;h[87]=1.0;a[704]=0;a[705]=0;b[353]=b[e+0>>1]|0;b[354]=b[e+2>>1]|0;b[355]=b[e+4>>1]|0;h[89]=.999;xb(776,832,848,2136,2144);c[194]=2168;h[100]=0.0;h[101]=1.0;a[816]=1;a[817]=1;b[409]=b[e+0>>1]|0;b[410]=b[e+2>>1]|0;b[411]=b[e+4>>1]|0;h[103]=0.0;xb(936,992,1008,2136,2144);c[234]=2168;h[120]=0.0;h[121]=v;a[976]=0;a[977]=0;b[489]=b[e+0>>1]|0;b[490]=b[e+2>>1]|0;b[491]=b[e+4>>1]|0;h[123]=91648253.0;xb(1048,1080,1096,2136,2016);c[262]=280;f=1068|0;c[f>>2]=0;c[f+4>>2]=2;c[269]=2;xb(1160,1192,1208,2136,2016);c[290]=280;f=1180|0;c[f>>2]=0;c[f+4>>2]=2;c[297]=2;xb(1272,1296,1312,2136,1992);c[318]=160;a[1292]=0;xb(1344,1368,1376,2136,1992);c[336]=160;a[1364]=1;xb(1408,1440,1448,2136,2016);c[352]=280;f=1428|0;c[f>>2]=1;c[f+4>>2]=2147483647;c[359]=100;xb(1480,1536,1544,2136,2144);c[370]=2168;h[188]=1.0;h[189]=v;a[1520]=0;a[1521]=0;b[761]=b[e+0>>1]|0;b[762]=b[e+2>>1]|0;b[763]=b[e+4>>1]|0;h[191]=2.0;xb(1584,1640,1648,2136,2144);c[396]=2168;h[201]=0.0;h[202]=v;a[1624]=0;a[1625]=0;b[813]=b[e+0>>1]|0;b[814]=b[e+2>>1]|0;b[815]=b[e+4>>1]|0;h[204]=.2;xb(1728,1760,1776,2136,2016);c[432]=280;e=1748|0;c[e>>2]=0;c[e+4>>2]=2147483647;c[439]=0;i=d;return}function dc(a){a=a|0;var b=0;b=i;pd(a);i=b;return}function ec(b,d){b=b|0;d=d|0;var e=0,f=0,g=0,j=0,k=0,l=0,m=0,n=0,o=0,q=0.0,r=0.0;e=i;i=i+16|0;j=e;g=e+8|0;if((a[d>>0]|0)!=45){o=0;i=e;return o|0}m=d+1|0;f=b+4|0;k=c[f>>2]|0;l=a[k>>0]|0;a:do if(l<<24>>24){n=0;while(1){o=n;n=n+1|0;if((a[m>>0]|0)!=l<<24>>24){b=0;break}l=a[k+n>>0]|0;m=d+(o+2)|0;if(!(l<<24>>24))break a}i=e;return b|0}while(0);if((a[m>>0]|0)!=61){o=0;i=e;return o|0}k=m+1|0;q=+ce(k,g);if(!(c[g>>2]|0)){o=0;i=e;return o|0}r=+h[b+32>>3];if(q>=r?(a[b+41>>0]|0)==0|q!=r:0){o=c[p>>2]|0;n=c[f>>2]|0;c[j>>2]=k;c[j+4>>2]=n;Za(o|0,2024,j|0)|0;ab(1)}r=+h[b+24>>3];if(q<=r?(a[b+40>>0]|0)==0|q!=r:0){o=c[p>>2]|0;n=c[f>>2]|0;c[j>>2]=k;c[j+4>>2]=n;Za(o|0,2080,j|0)|0;ab(1)}h[b+48>>3]=q;o=1;i=e;return o|0}function fc(b,d){b=b|0;d=d|0;var e=0,f=0,g=0,j=0,l=0.0,m=0,n=0.0,o=0.0,q=0;e=i;i=i+48|0;f=e;g=c[p>>2]|0;q=c[b+16>>2]|0;m=(a[b+40>>0]|0)!=0?91:40;o=+h[b+24>>3];n=+h[b+32>>3];j=(a[b+41>>0]|0)!=0?93:41;l=+h[b+48>>3];c[f>>2]=c[b+4>>2];c[f+4>>2]=q;c[f+8>>2]=m;m=f+12|0;h[k>>3]=o;c[m>>2]=c[k>>2];c[m+4>>2]=c[k+4>>2];m=f+20|0;h[k>>3]=n;c[m>>2]=c[k>>2];c[m+4>>2]=c[k+4>>2];c[f+28>>2]=j;j=f+32|0;h[k>>3]=l;c[j>>2]=c[k>>2];c[j+4>>2]=c[k+4>>2];Za(g|0,2232,f|0)|0;if(!d){i=e;return}c[f>>2]=c[b+8>>2];Za(g|0,2e3,f|0)|0;Sa(10,g|0)|0;i=e;return}function gc(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0;d=i;e=a+8|0;f=c[e>>2]|0;if(f>>>0<b>>>0)h=f;else{i=d;return}while(1){if(h>>>0>=b>>>0)break;h=((h>>>3)+2+(h>>>1)&-2)+h|0;c[e>>2]=h;if(h>>>0<=f>>>0){g=4;break}}if((g|0)==4)Ta(va(1)|0,48,0);e=Ud(c[a>>2]|0,h<<2)|0;if((e|0)==0?(c[(Oa()|0)>>2]|0)==12:0)Ta(va(1)|0,48,0);c[a>>2]=e;i=d;return}function hc(a){a=a|0;var b=0,d=0,e=0,f=0,g=0,h=0,j=0;b=i;e=a+32|0;d=c[e>>2]|0;if(d){c[a+36>>2]=0;Td(d);c[e>>2]=0;c[a+40>>2]=0}e=a+16|0;d=c[e>>2]|0;if(d){c[a+20>>2]=0;Td(d);c[e>>2]=0;c[a+24>>2]=0}e=c[a>>2]|0;if(!e){i=b;return}d=a+4|0;g=c[d>>2]|0;if((g|0)>0){f=0;do{j=e+(f*12|0)|0;h=c[j>>2]|0;if(h){c[e+(f*12|0)+4>>2]=0;Td(h);c[j>>2]=0;c[e+(f*12|0)+8>>2]=0;e=c[a>>2]|0;g=c[d>>2]|0}f=f+1|0}while((f|0)<(g|0))}c[d>>2]=0;Td(e);c[a>>2]=0;c[a+8>>2]=0;i=b;return}function ic(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0;f=i;i=i+16|0;e=f+4|0;d=f;l=c[b>>2]|0;h=l+1|0;g=a+4|0;if((c[g>>2]|0)<(h|0)){k=a+8|0;j=c[k>>2]|0;if((j|0)<(h|0)){m=l+2-j&-2;l=(j>>1)+2&-2;l=(m|0)>(l|0)?m:l;if((l|0)>(2147483647-j|0)){m=va(1)|0;Ta(m|0,48,0)}n=c[a>>2]|0;m=l+j|0;c[k>>2]=m;m=Ud(n,m*12|0)|0;c[a>>2]=m;if((m|0)==0?(c[(Oa()|0)>>2]|0)==12:0){n=va(1)|0;Ta(n|0,48,0)}}k=c[g>>2]|0;if((k|0)<(h|0)){j=c[a>>2]|0;do{l=j+(k*12|0)|0;if(l){c[l>>2]=0;c[j+(k*12|0)+4>>2]=0;c[j+(k*12|0)+8>>2]=0}k=k+1|0}while((k|0)!=(h|0))}c[g>>2]=h;l=c[b>>2]|0}g=c[a>>2]|0;if(!(c[g+(l*12|0)>>2]|0)){m=l;n=a+16|0;c[d>>2]=m;c[e+0>>2]=c[d+0>>2];sc(n,e,0);i=f;return}c[g+(l*12|0)+4>>2]=0;m=c[b>>2]|0;n=a+16|0;c[d>>2]=m;c[e+0>>2]=c[d+0>>2];sc(n,e,0);i=f;return}function jc(b,d){b=b|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,k=0,l=0;f=i;e=b+4|0;if((c[e>>2]|0)>=(d|0)){i=f;return}h=b+8|0;g=c[h>>2]|0;if((g|0)<(d|0)){k=d+1-g&-2;j=(g>>1)+2&-2;j=(k|0)>(j|0)?k:j;if((j|0)>(2147483647-g|0)){k=va(1)|0;Ta(k|0,48,0)}l=c[b>>2]|0;k=j+g|0;c[h>>2]=k;k=Ud(l,k)|0;c[b>>2]=k;if((k|0)==0?(c[(Oa()|0)>>2]|0)==12:0){l=va(1)|0;Ta(l|0,48,0)}}g=c[e>>2]|0;if((g|0)<(d|0)){b=c[b>>2]|0;do{h=b+g|0;if(h)a[h>>0]=0;g=g+1|0}while((g|0)!=(d|0))}c[e>>2]=d;i=f;return}function kc(b,d,e){b=b|0;d=d|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0;h=i;g=d+1|0;f=b+4|0;if((c[f>>2]|0)>=(g|0)){l=c[b>>2]|0;l=l+d|0;a[l>>0]=e;i=h;return}k=b+8|0;j=c[k>>2]|0;if((j|0)<(g|0)){m=d+2-j&-2;l=(j>>1)+2&-2;l=(m|0)>(l|0)?m:l;if((l|0)>(2147483647-j|0)){m=va(1)|0;Ta(m|0,48,0)}n=c[b>>2]|0;m=l+j|0;c[k>>2]=m;m=Ud(n,m)|0;c[b>>2]=m;if((m|0)==0?(c[(Oa()|0)>>2]|0)==12:0){n=va(1)|0;Ta(n|0,48,0)}}j=c[f>>2]|0;if((j|0)<(g|0))do{k=(c[b>>2]|0)+j|0;if(k)a[k>>0]=0;j=j+1|0}while((j|0)!=(g|0));c[f>>2]=g;n=c[b>>2]|0;n=n+d|0;a[n>>0]=e;i=h;return}function lc(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,j=0,k=0,l=0,m=0,n=0,o=0;d=i;i=i+16|0;g=d;c[g>>2]=b;j=a+12|0;f=b+1|0;e=a+16|0;if((c[e>>2]|0)<(f|0)){l=a+20|0;k=c[l>>2]|0;if((k|0)<(f|0)){n=b+2-k&-2;m=(k>>1)+2&-2;m=(n|0)>(m|0)?n:m;if((m|0)>(2147483647-k|0)){n=va(1)|0;Ta(n|0,48,0)}o=c[j>>2]|0;n=m+k|0;c[l>>2]=n;n=Ud(o,n<<2)|0;c[j>>2]=n;if((n|0)==0?(c[(Oa()|0)>>2]|0)==12:0){o=va(1)|0;Ta(o|0,48,0)}}k=c[e>>2]|0;if((f|0)>(k|0))ke((c[j>>2]|0)+(k<<2)|0,-1,f-k<<2|0)|0;c[e>>2]=f}c[(c[j>>2]|0)+(b<<2)>>2]=c[a+4>>2];nc(a,g);e=c[j>>2]|0;g=c[e+(b<<2)>>2]|0;b=c[a>>2]|0;f=c[b+(g<<2)>>2]|0;if(!g){n=0;o=b+(n<<2)|0;c[o>>2]=f;o=e+(f<<2)|0;c[o>>2]=n;i=d;return}a=a+28|0;while(1){j=g;g=g+ -1>>1;k=b+(g<<2)|0;l=c[k>>2]|0;o=c[c[a>>2]>>2]|0;if(!(+h[o+(f<<3)>>3]>+h[o+(l<<3)>>3])){a=14;break}c[b+(j<<2)>>2]=l;c[e+(c[k>>2]<<2)>>2]=j;if(!g){j=0;a=14;break}}if((a|0)==14){o=b+(j<<2)|0;c[o>>2]=f;o=e+(f<<2)|0;c[o>>2]=j;i=d;return}}function mc(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,j=0;d=i;e=a+4|0;f=c[e>>2]|0;g=a+8|0;h=c[g>>2]|0;if((f|0)==(h|0)&(h|0)<(f+1|0)){h=(f>>1)+2&-2;h=(h|0)<2?2:h;if((h|0)>(2147483647-f|0)){h=va(1)|0;Ta(h|0,48,0)}j=c[a>>2]|0;f=h+f|0;c[g>>2]=f;f=Ud(j,f<<2)|0;c[a>>2]=f;if((f|0)==0?(c[(Oa()|0)>>2]|0)==12:0){j=va(1)|0;Ta(j|0,48,0)}}else f=c[a>>2]|0;j=c[e>>2]|0;c[e>>2]=j+1;e=f+(j<<2)|0;if(!e){i=d;return}c[e>>2]=c[b>>2];i=d;return}function nc(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,j=0;d=i;e=a+4|0;f=c[e>>2]|0;g=a+8|0;h=c[g>>2]|0;if((f|0)==(h|0)&(h|0)<(f+1|0)){h=(f>>1)+2&-2;h=(h|0)<2?2:h;if((h|0)>(2147483647-f|0)){h=va(1)|0;Ta(h|0,48,0)}j=c[a>>2]|0;f=h+f|0;c[g>>2]=f;f=Ud(j,f<<2)|0;c[a>>2]=f;if((f|0)==0?(c[(Oa()|0)>>2]|0)==12:0){j=va(1)|0;Ta(j|0,48,0)}}else f=c[a>>2]|0;j=c[e>>2]|0;c[e>>2]=j+1;e=f+(j<<2)|0;if(!e){i=d;return}c[e>>2]=c[b>>2];i=d;return}function oc(b,d,e){b=b|0;d=d|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;e=i;i=i+16|0;f=e+2|0;h=e+1|0;g=e;if((d|0)<16){g=d+ -1|0;if((g|0)>0)h=0;else{i=e;return}do{f=h;h=h+1|0;if((h|0)<(d|0)){k=f;j=h;do{k=(c[b+(j<<2)>>2]|0)<(c[b+(k<<2)>>2]|0)?j:k;j=j+1|0}while((j|0)!=(d|0))}else k=f;n=b+(f<<2)|0;o=c[n>>2]|0;p=b+(k<<2)|0;c[n>>2]=c[p>>2];c[p>>2]=o}while((h|0)!=(g|0));i=e;return}j=c[b+(((d|0)/2|0)<<2)>>2]|0;m=-1;n=d;while(1){do{m=m+1|0;l=b+(m<<2)|0;k=c[l>>2]|0}while((k|0)<(j|0));do{n=n+ -1|0;o=b+(n<<2)|0;p=c[o>>2]|0}while((j|0)<(p|0));if((m|0)>=(n|0))break;c[l>>2]=p;c[o>>2]=k}a[f+0>>0]=a[h+0>>0]|0;oc(b,m,f);p=d-m|0;a[f+0>>0]=a[g+0>>0]|0;oc(l,p,f);i=e;return}function pc(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,h=0,j=0,k=0,l=0,m=0;f=i;k=e&1;j=d[a+16>>0]|0|k;h=b+4|0;l=((j+(c[h>>2]|0)<<2)+4|0)>>>2;m=a+4|0;gc(a,l+(c[m>>2]|0)|0);e=c[m>>2]|0;l=l+e|0;c[m>>2]=l;if(l>>>0<e>>>0)Ta(va(1)|0,48,0);a=(c[a>>2]|0)+(e<<2)|0;if(!a){i=f;return e|0}j=j<<3|k<<2;c[a>>2]=c[a>>2]&-32|j;j=c[h>>2]<<5|j;c[a>>2]=j;if((c[h>>2]|0)>0){j=c[b>>2]|0;b=0;do{c[a+(b<<2)+4>>2]=c[j+(b<<2)>>2];b=b+1|0}while((b|0)<(c[h>>2]|0));j=c[a>>2]|0}if(!(j&8)){i=f;return e|0}h=j>>>5;if(j&4){g[a+(h<<2)+4>>2]=0.0;i=f;return e|0}if(!h){h=0;j=0}else{j=0;b=0;do{j=1<<((c[a+(b<<2)+4>>2]|0)>>>1&31)|j;b=b+1|0}while((b|0)<(h|0))}c[a+(h<<2)+4>>2]=j;i=f;return e|0}function qc(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,j=0;d=i;e=a+4|0;f=c[e>>2]|0;g=a+8|0;h=c[g>>2]|0;if((f|0)==(h|0)&(h|0)<(f+1|0)){h=(f>>1)+2&-2;h=(h|0)<2?2:h;if((h|0)>(2147483647-f|0)){h=va(1)|0;Ta(h|0,48,0)}j=c[a>>2]|0;f=h+f|0;c[g>>2]=f;f=Ud(j,f<<3)|0;c[a>>2]=f;if((f|0)==0?(c[(Oa()|0)>>2]|0)==12:0){j=va(1)|0;Ta(j|0,48,0)}}else f=c[a>>2]|0;j=c[e>>2]|0;c[e>>2]=j+1;e=f+(j<<3)|0;if(!e){i=d;return}g=b;h=c[g+4>>2]|0;j=e;c[j>>2]=c[g>>2];c[j+4>>2]=h;i=d;return}function rc(a){a=a|0;var b=0,d=0,e=0,f=0,g=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0.0,r=0.0,s=0;b=i;d=c[a>>2]|0;f=c[d>>2]|0;k=a+4|0;o=c[d+((c[k>>2]|0)+ -1<<2)>>2]|0;c[d>>2]=o;e=c[a+12>>2]|0;c[e+(o<<2)>>2]=0;c[e+(f<<2)>>2]=-1;o=(c[k>>2]|0)+ -1|0;c[k>>2]=o;if((o|0)<=1){i=b;return f|0}g=c[d>>2]|0;l=a+28|0;a=0;m=1;while(1){n=(a<<1)+2|0;if((n|0)<(o|0)){p=c[d+(n<<2)>>2]|0;s=c[d+(m<<2)>>2]|0;o=c[c[l>>2]>>2]|0;q=+h[o+(p<<3)>>3];r=+h[o+(s<<3)>>3];if(!(q>r)){p=s;q=r;j=6}}else{o=c[c[l>>2]>>2]|0;j=c[d+(m<<2)>>2]|0;p=j;q=+h[o+(j<<3)>>3];j=6}if((j|0)==6){j=0;n=m}if(!(q>+h[o+(g<<3)>>3]))break;c[d+(a<<2)>>2]=p;c[e+(p<<2)>>2]=a;m=n<<1|1;o=c[k>>2]|0;if((m|0)>=(o|0)){a=n;break}else a=n}c[d+(a<<2)>>2]=g;c[e+(g<<2)>>2]=a;i=b;return f|0}function sc(b,d,e){b=b|0;d=d|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0;f=i;k=c[d>>2]|0;d=k+1|0;g=b+4|0;if((c[g>>2]|0)>=(d|0)){i=f;return}j=b+8|0;h=c[j>>2]|0;if((h|0)<(d|0)){l=k+2-h&-2;k=(h>>1)+2&-2;k=(l|0)>(k|0)?l:k;if((k|0)>(2147483647-h|0)){l=va(1)|0;Ta(l|0,48,0)}m=c[b>>2]|0;l=k+h|0;c[j>>2]=l;l=Ud(m,l)|0;c[b>>2]=l;if((l|0)==0?(c[(Oa()|0)>>2]|0)==12:0){m=va(1)|0;Ta(m|0,48,0)}}h=c[g>>2]|0;if((h|0)<(d|0))do{a[(c[b>>2]|0)+h>>0]=e;h=h+1|0}while((h|0)!=(d|0));c[g>>2]=d;i=f;return}function tc(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;e=i;i=i+16|0;h=e+8|0;f=e+4|0;j=e;if((b|0)<16){f=b+ -1|0;if((f|0)<=0){i=e;return}h=c[d>>2]|0;d=0;do{j=d;d=d+1|0;if((d|0)<(b|0)){k=c[h>>2]|0;m=j;l=d;do{n=k+(c[a+(l<<2)>>2]<<2)|0;u=c[n>>2]|0;q=u>>>5;if(u>>>0>95){o=k+(c[a+(m<<2)>>2]<<2)|0;p=(c[o>>2]|0)>>>5;if((p|0)==2)m=l;else m=+g[n+(q<<2)+4>>2]<+g[o+(p<<2)+4>>2]?l:m}l=l+1|0}while((l|0)!=(b|0))}else m=j;s=a+(j<<2)|0;t=c[s>>2]|0;u=a+(m<<2)|0;c[s>>2]=c[u>>2];c[u>>2]=t}while((d|0)!=(f|0));i=e;return}k=c[a+(((b|0)/2|0)<<2)>>2]|0;q=-1;o=b;while(1){t=q+1|0;p=a+(t<<2)|0;u=c[p>>2]|0;l=c[d>>2]|0;m=c[l>>2]|0;s=m+(u<<2)|0;r=c[s>>2]|0;q=m+(k<<2)|0;n=c[q>>2]|0;a:do if(r>>>0>95)while(1){v=n>>>5;if((v|0)!=2?!(+g[s+(r>>>5<<2)+4>>2]<+g[q+(v<<2)+4>>2]):0){q=t;break a}t=t+1|0;p=a+(t<<2)|0;u=c[p>>2]|0;s=m+(u<<2)|0;r=c[s>>2]|0;if(r>>>0<=95){q=t;break}}else q=t;while(0);o=o+ -1|0;s=a+(o<<2)|0;r=m+(k<<2)|0;b:do if(n>>>0>95)while(1){t=m+(c[s>>2]<<2)|0;v=(c[t>>2]|0)>>>5;if((v|0)!=2?!(+g[r+(n>>>5<<2)+4>>2]<+g[t+(v<<2)+4>>2]):0)break b;v=o+ -1|0;s=a+(v<<2)|0;o=v}while(0);if((q|0)>=(o|0))break;c[p>>2]=c[s>>2];c[s>>2]=u}c[f>>2]=l;c[h+0>>2]=c[f+0>>2];tc(a,q,h);v=b-q|0;c[j>>2]=l;c[h+0>>2]=c[j+0>>2];tc(p,v,h);i=e;return}function uc(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0.0,r=0.0,s=0;e=i;f=a+4|0;j=c[f>>2]|0;g=c[a>>2]|0;if((j|0)>0){l=c[a+12>>2]|0;k=0;do{c[l+(c[g+(k<<2)>>2]<<2)>>2]=-1;k=k+1|0;j=c[f>>2]|0}while((k|0)<(j|0))}if(g){c[f>>2]=0;j=0}g=b+4|0;if((c[g>>2]|0)>0){k=a+12|0;j=0;do{s=(c[b>>2]|0)+(j<<2)|0;c[(c[k>>2]|0)+(c[s>>2]<<2)>>2]=j;nc(a,s);j=j+1|0}while((j|0)<(c[g>>2]|0));j=c[f>>2]|0}if((j|0)<=1){i=e;return}g=c[a>>2]|0;b=a+28|0;a=a+12|0;o=j;k=(j|0)/2|0;while(1){k=k+ -1|0;j=c[g+(k<<2)>>2]|0;m=k<<1|1;a:do if((m|0)<(o|0)){l=k;while(1){n=(l<<1)+2|0;if((n|0)<(o|0)){p=c[g+(n<<2)>>2]|0;s=c[g+(m<<2)>>2]|0;o=c[c[b>>2]>>2]|0;q=+h[o+(p<<3)>>3];r=+h[o+(s<<3)>>3];if(!(q>r)){p=s;q=r;d=16}}else{o=c[c[b>>2]>>2]|0;d=c[g+(m<<2)>>2]|0;p=d;q=+h[o+(d<<3)>>3];d=16}if((d|0)==16){d=0;n=m}if(!(q>+h[o+(j<<3)>>3]))break a;c[g+(l<<2)>>2]=p;c[(c[a>>2]|0)+(p<<2)>>2]=l;m=n<<1|1;o=c[f>>2]|0;if((m|0)>=(o|0)){l=n;break}else l=n}}else l=k;while(0);c[g+(l<<2)>>2]=j;c[(c[a>>2]|0)+(j<<2)>>2]=l;if((k|0)<=0)break;o=c[f>>2]|0}i=e;return}function vc(b){b=b|0;var d=0,e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;e=i;d=b+36|0;l=c[d>>2]|0;f=b+32|0;n=c[f>>2]|0;if((l|0)>0){h=b+16|0;g=b+44|0;j=0;do{k=n+(j<<2)|0;m=c[k>>2]|0;if(a[(c[h>>2]|0)+m>>0]|0){n=c[b>>2]|0;l=n+(m*12|0)+4|0;p=c[l>>2]|0;if((p|0)>0){m=n+(m*12|0)|0;n=0;o=0;do{q=c[m>>2]|0;r=q+(n<<3)|0;if((c[(c[c[g>>2]>>2]|0)+(c[r>>2]<<2)>>2]&3|0)!=1){s=r;r=c[s+4>>2]|0;p=q+(o<<3)|0;c[p>>2]=c[s>>2];c[p+4>>2]=r;p=c[l>>2]|0;o=o+1|0}n=n+1|0}while((n|0)<(p|0))}else{n=0;o=0}m=n-o|0;if((m|0)>0)c[l>>2]=p-m;a[(c[h>>2]|0)+(c[k>>2]|0)>>0]=0;l=c[d>>2]|0;n=c[f>>2]|0}j=j+1|0}while((j|0)<(l|0))}if(!n){i=e;return}c[d>>2]=0;i=e;return}function wc(a,b){a=a|0;b=b|0;var e=0,f=0,h=0,j=0,k=0;f=i;j=c[b>>2]|0;h=j>>>2&1|(d[a+16>>0]|0);j=((h+(j>>>5)<<2)+4|0)>>>2;k=a+4|0;gc(a,j+(c[k>>2]|0)|0);e=c[k>>2]|0;j=j+e|0;c[k>>2]=j;if(j>>>0<e>>>0)Ta(va(1)|0,48,0);a=(c[a>>2]|0)+(e<<2)|0;if(!a){i=f;return e|0}h=c[b>>2]&-9|h<<3;c[a>>2]=h;if((c[b>>2]|0)>>>0>31){h=0;do{c[a+(h<<2)+4>>2]=c[b+(h<<2)+4>>2];h=h+1|0}while((h|0)<((c[b>>2]|0)>>>5|0));h=c[a>>2]|0}if(!(h&8)){i=f;return e|0}j=h>>>5;b=b+(j<<2)+4|0;if(!(h&4)){c[a+(j<<2)+4>>2]=c[b>>2];i=f;return e|0}else{g[a+(j<<2)+4>>2]=+g[b>>2];i=f;return e|0}return 0}function xc(b){b=b|0;var d=0,e=0,f=0,g=0,j=0,k=0;d=i;i=i+16|0;g=d;Gb(b);c[b>>2]=3424;c[b+684>>2]=c[719];c[b+688>>2]=c[747];c[b+692>>2]=c[785];h[b+696>>3]=+h[411];a[b+704>>0]=a[2652]|0;a[b+705>>0]=a[2724]|0;a[b+706>>0]=a[2804]|0;a[b+707>>0]=1;c[b+708>>2]=0;c[b+712>>2]=0;c[b+716>>2]=0;c[b+720>>2]=1;a[b+724>>0]=1;e=b+732|0;k=b+544|0;c[b+760>>2]=0;c[b+764>>2]=0;c[b+768>>2]=0;c[b+776>>2]=0;c[b+780>>2]=0;c[b+784>>2]=0;c[b+792>>2]=0;c[b+796>>2]=0;c[b+800>>2]=0;j=b+804|0;c[e+0>>2]=0;c[e+4>>2]=0;c[e+8>>2]=0;c[e+12>>2]=0;c[e+16>>2]=0;c[e+20>>2]=0;c[j>>2]=k;j=b+808|0;c[j>>2]=0;c[b+812>>2]=0;c[b+816>>2]=0;e=b+824|0;c[e+0>>2]=0;c[e+4>>2]=0;c[e+8>>2]=0;c[e+12>>2]=0;c[e+16>>2]=0;c[e+20>>2]=0;c[b+852>>2]=j;Qc(b+856|0,1);j=b+868|0;e=b+892|0;c[b+920>>2]=0;c[b+924>>2]=0;c[j+0>>2]=0;c[j+4>>2]=0;c[j+8>>2]=0;c[j+12>>2]=0;c[j+16>>2]=0;c[e+0>>2]=0;c[e+4>>2]=0;c[e+8>>2]=0;c[e+12>>2]=0;c[e+16>>2]=0;c[e+20>>2]=0;e=g+4|0;c[e>>2]=0;j=g+8|0;c[j>>2]=2;f=Ud(0,8)|0;c[g>>2]=f;if((f|0)==0?(c[(Oa()|0)>>2]|0)==12:0)Ta(va(1)|0,48,0);c[f>>2]=-2;c[e>>2]=1;a[b+560>>0]=1;c[b+928>>2]=pc(k,g,0)|0;a[b+536>>0]=0;if(!f){i=d;return}c[e>>2]=0;Td(f);c[g>>2]=0;c[j>>2]=0;i=d;return}function yc(a){a=a|0;var b=0;b=i;zc(a);pd(a);i=b;return}function zc(a){a=a|0;var b=0,d=0,e=0;b=i;c[a>>2]=3424;d=a+904|0;e=c[d>>2]|0;if(e){c[a+908>>2]=0;Td(e);c[d>>2]=0;c[a+912>>2]=0}d=a+892|0;e=c[d>>2]|0;if(e){c[a+896>>2]=0;Td(e);c[d>>2]=0;c[a+900>>2]=0}d=a+876|0;e=c[d>>2]|0;if(e){c[a+880>>2]=0;Td(e);c[d>>2]=0;c[a+884>>2]=0}d=a+856|0;e=c[d>>2]|0;if(e){c[a+860>>2]=0;Td(e);c[d>>2]=0;c[a+864>>2]=0}e=a+836|0;d=c[e>>2]|0;if(d){c[a+840>>2]=0;Td(d);c[e>>2]=0;c[a+844>>2]=0}d=a+824|0;e=c[d>>2]|0;if(e){c[a+828>>2]=0;Td(e);c[d>>2]=0;c[a+832>>2]=0}d=a+808|0;e=c[d>>2]|0;if(e){c[a+812>>2]=0;Td(e);c[d>>2]=0;c[a+816>>2]=0}Rc(a+760|0);d=a+744|0;e=c[d>>2]|0;if(e){c[a+748>>2]=0;Td(e);c[d>>2]=0;c[a+752>>2]=0}d=a+732|0;e=c[d>>2]|0;if(!e){Ib(a);i=b;return}c[a+736>>2]=0;Td(e);c[d>>2]=0;c[a+740>>2]=0;Ib(a);i=b;return}function Ac(b,d,e){b=b|0;d=d|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0;f=i;i=i+32|0;h=f+12|0;k=f+8|0;l=f+16|0;g=f+4|0;j=f;a[l>>0]=a[d>>0]|0;a[h+0>>0]=a[l+0>>0]|0;e=Jb(b,h,e)|0;c[k>>2]=e;kc(b+876|0,e,0);kc(b+904|0,e,0);if(!(a[b+724>>0]|0)){i=f;return e|0}l=b+808|0;d=e<<1;c[g>>2]=d;c[h+0>>2]=c[g+0>>2];Sc(l,h,0);c[j>>2]=d|1;c[h+0>>2]=c[j+0>>2];Sc(l,h,0);Tc(b+760|0,k);kc(b+744|0,e,0);Uc(b+824|0,e);i=f;return e|0}function Bc(b,e,f,g){b=b|0;e=e|0;f=f|0;g=g|0;var h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0;k=i;i=i+32|0;h=k+4|0;r=k;p=k+16|0;c[h>>2]=0;j=h+4|0;c[j>>2]=0;l=h+8|0;c[l>>2]=0;s=a[2608]|0;a[b>>0]=s;m=e+724|0;f=(d[m>>0]&(f&1)|0)!=0;if(f){u=e+308|0;x=c[u>>2]|0;if((x|0)>0){t=e+304|0;s=e+876|0;v=0;do{w=c[(c[t>>2]|0)+(v<<2)>>2]>>1;c[r>>2]=w;w=(c[s>>2]|0)+w|0;if(!(a[w>>0]|0)){a[w>>0]=1;nc(h,r);x=c[u>>2]|0}v=v+1|0}while((v|0)<(x|0))}r=(Cc(e,g)|0)&1^1;a[b>>0]=r;g=a[2608]|0}else{g=s;r=s}x=g&255;if(!((x>>>1^1)&r<<24>>24==g<<24>>24|x&2&(r&255))){if((c[e+44>>2]|0)>0)Ka(3760)|0}else{$b(p,e);r=a[p>>0]|0;a[b>>0]=r}w=a[2608]|0;x=w&255;if((((x>>>1^1)&r<<24>>24==w<<24>>24|x&2&(r&255)|0)!=0?(a[e+707>>0]|0)!=0:0)?(q=(c[e+736>>2]|0)+ -1|0,(q|0)>0):0){b=e+732|0;p=e+4|0;do{g=c[b>>2]|0;u=c[g+(q<<2)>>2]|0;v=q+ -1|0;w=c[g+(v<<2)>>2]|0;q=c[p>>2]|0;a:do if((u|0)>1){s=a[2616]|0;r=s&255;t=r&2;r=r>>>1^1;x=v;while(1){w=d[q+(w>>1)>>0]^w&1;v=u+ -1|0;if(!((w&255)<<24>>24==s<<24>>24&r|t&w))break a;u=x+ -1|0;w=c[g+(u<<2)>>2]|0;if((v|0)>1){x=u;u=v}else{x=u;u=v;o=20;break}}}else{x=v;o=20}while(0);if((o|0)==20){o=0;a[q+(w>>1)>>0]=(w&1^1)&255^1}q=x-u|0}while((q|0)>0)}if(f?(n=c[j>>2]|0,(n|0)>0):0){o=c[h>>2]|0;f=e+876|0;p=0;do{b=c[o+(p<<2)>>2]|0;a[(c[f>>2]|0)+b>>0]=0;if(a[m>>0]|0)Vc(e,b);p=p+1|0}while((p|0)<(n|0))}e=c[h>>2]|0;if(!e){i=k;return}c[j>>2]=0;Td(e);c[h>>2]=0;c[l>>2]=0;i=k;return}function Cc(b,d){b=b|0;d=d|0;var e=0,f=0,g=0,j=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0;m=i;i=i+16|0;j=m;if(!(Zb(b)|0)){H=0;i=m;return H|0}l=b+724|0;if(!(a[l>>0]|0)){H=1;i=m;return H|0}x=b+924|0;v=b+872|0;w=b+868|0;u=b+860|0;r=b+680|0;y=b+824|0;g=b+828|0;o=b+836|0;z=b+904|0;A=b+332|0;e=b+44|0;B=b+704|0;D=b+706|0;E=b+696|0;p=b+556|0;q=b+548|0;C=b+876|0;s=b+920|0;t=b+284|0;a:while(1){if(((c[x>>2]|0)<=0?(c[s>>2]|0)>=(c[t>>2]|0):0)?(c[g>>2]|0)<=0:0)break;Ic(b);G=c[v>>2]|0;H=c[w>>2]|0;F=G-H|0;if((G|0)<(H|0))F=(c[u>>2]|0)+F|0;if(!((F|0)<=0?(c[s>>2]|0)>=(c[t>>2]|0):0))n=11;if((n|0)==11?(n=0,!(Jc(b,1)|0)):0){n=12;break}H=c[g>>2]|0;if(a[r>>0]|0){n=15;break}if(!H)continue;else F=0;while(1){J=c[y>>2]|0;G=c[J>>2]|0;I=c[J+(H+ -1<<2)>>2]|0;c[J>>2]=I;H=c[o>>2]|0;c[H+(I<<2)>>2]=0;c[H+(G<<2)>>2]=-1;H=(c[g>>2]|0)+ -1|0;c[g>>2]=H;if((H|0)>1)Wc(y,0);if(a[r>>0]|0)continue a;if((a[(c[z>>2]|0)+G>>0]|0)==0?(I=a[(c[A>>2]|0)+G>>0]|0,H=a[2624]|0,J=H&255,((J>>>1^1)&I<<24>>24==H<<24>>24|I&2&J|0)!=0):0){if((c[e>>2]|0)>1&((F|0)%100|0|0)==0){c[j>>2]=c[g>>2];La(3504,j|0)|0}if(a[B>>0]|0){J=(c[C>>2]|0)+G|0;H=a[J>>0]|0;a[J>>0]=1;if(!(Lc(b,G)|0)){n=29;break a}a[(c[C>>2]|0)+G>>0]=H<<24>>24!=0&1}if((((a[D>>0]|0)!=0?(I=a[(c[A>>2]|0)+G>>0]|0,H=a[2624]|0,J=H&255,((J>>>1^1)&I<<24>>24==H<<24>>24|I&2&J|0)!=0):0)?(a[(c[C>>2]|0)+G>>0]|0)==0:0)?!(Mc(b,G)|0):0){n=35;break a}if(+((c[p>>2]|0)>>>0)>+h[E>>3]*+((c[q>>2]|0)>>>0))gb[c[(c[b>>2]|0)+8>>2]&31](b)}H=c[g>>2]|0;if(!H)continue a;else F=F+1|0}}do if((n|0)==12)a[b+492>>0]=0;else if((n|0)==15){r=c[b+824>>2]|0;if((H|0)<=0){if(!r)break}else{t=c[o>>2]|0;s=0;do{c[t+(c[r+(s<<2)>>2]<<2)>>2]=-1;s=s+1|0}while((s|0)<(c[g>>2]|0))}c[g>>2]=0}else if((n|0)==29)a[b+492>>0]=0;else if((n|0)==35)a[b+492>>0]=0;while(0);if(!d){if(+((c[p>>2]|0)>>>0)>+h[b+96>>3]*+((c[q>>2]|0)>>>0))gb[c[(c[b>>2]|0)+8>>2]&31](b)}else{d=b+744|0;p=c[d>>2]|0;if(p){c[b+748>>2]=0;Td(p);c[d>>2]=0;c[b+752>>2]=0}Xc(b+760|0,1);d=b+808|0;p=c[d>>2]|0;if(p){c[b+812>>2]=0;Td(p);c[d>>2]=0;c[b+816>>2]=0}p=b+824|0;d=c[p>>2]|0;if((c[g>>2]|0)<=0){if(d)n=48}else{n=c[o>>2]|0;o=0;do{c[n+(c[d+(o<<2)>>2]<<2)>>2]=-1;o=o+1|0}while((o|0)<(c[g>>2]|0));n=48}if((n|0)==48){c[g>>2]=0;Td(d);c[p>>2]=0;c[b+832>>2]=0}Yc(b+856|0,1);a[l>>0]=0;a[b+536>>0]=1;a[b+560>>0]=0;c[b+728>>2]=c[b+540>>2];Yb(b);gb[c[(c[b>>2]|0)+8>>2]&31](b)}if((c[e>>2]|0)>0?(f=c[b+736>>2]|0,(f|0)>0):0){h[k>>3]=+(f<<2>>>0)*9.5367431640625e-7;c[j>>2]=c[k>>2];c[j+4>>2]=c[k+4>>2];La(3528,j|0)|0}J=(a[b+492>>0]|0)!=0;i=m;return J|0}function Dc(b,d){b=b|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;e=i;i=i+16|0;g=e;j=b+256|0;k=b+260|0;h=c[k>>2]|0;if((a[b+705>>0]|0)!=0?Ec(b,d)|0:0){p=1;i=e;return p|0}if(!(Kb(b,d)|0)){p=0;i=e;return p|0}if(!(a[b+724>>0]|0)){p=1;i=e;return p|0}d=c[k>>2]|0;if((d|0)!=(h+1|0)){p=1;i=e;return p|0}p=c[(c[j>>2]|0)+(d+ -1<<2)>>2]|0;c[g>>2]=p;m=(c[b+544>>2]|0)+(p<<2)|0;Zc(b+856|0,p);if((c[m>>2]|0)>>>0<=31){p=1;i=e;return p|0}l=b+760|0;k=b+808|0;j=b+744|0;h=b+924|0;d=b+824|0;n=b+840|0;b=b+836|0;o=0;do{p=m+(o<<2)+4|0;_c((c[l>>2]|0)+((c[p>>2]>>1)*12|0)|0,g);q=(c[k>>2]|0)+(c[p>>2]<<2)|0;c[q>>2]=(c[q>>2]|0)+1;a[(c[j>>2]|0)+(c[p>>2]>>1)>>0]=1;c[h>>2]=(c[h>>2]|0)+1;p=c[p>>2]>>1;if((c[n>>2]|0)>(p|0)?(f=c[(c[b>>2]|0)+(p<<2)>>2]|0,(f|0)>-1):0)Wc(d,f);o=o+1|0}while((o|0)<((c[m>>2]|0)>>>5|0));f=1;i=e;return f|0}function Ec(b,e){b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;k=i;i=i+16|0;h=k+8|0;j=k+4|0;g=k;c[j>>2]=c[b+284>>2];nc(b+292|0,j);j=e+4|0;m=c[j>>2]|0;a:do if((m|0)>0){f=b+332|0;l=0;while(1){n=c[(c[e>>2]|0)+(l<<2)>>2]|0;p=d[(c[f>>2]|0)+(n>>1)>>0]|0;q=p^n&1;o=q&255;s=a[2608]|0;r=s&255;if(o<<24>>24==s<<24>>24&(r>>>1^1)|r&2&q)break;r=a[2616]|0;s=r&255;if(!((s>>>1^1)&o<<24>>24==r<<24>>24|p&2&s)){c[g>>2]=n^1;c[h+0>>2]=c[g+0>>2];Lb(b,h,-1);m=c[j>>2]|0}l=l+1|0;if((l|0)>=(m|0))break a}Rb(b,0);s=1;i=k;return s|0}while(0);s=(Mb(b)|0)!=-1;Rb(b,0);i=k;return s|0}function Fc(b,d){b=b|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0;e=i;i=i+16|0;g=e;f=(c[b+544>>2]|0)+(d<<2)|0;if(!(a[b+724>>0]|0)){Pb(b,d);i=e;return}if((c[f>>2]|0)>>>0<=31){Pb(b,d);i=e;return}j=b+808|0;k=b+776|0;h=b+792|0;l=0;do{m=f+(l<<2)+4|0;n=(c[j>>2]|0)+(c[m>>2]<<2)|0;c[n>>2]=(c[n>>2]|0)+ -1;Vc(b,c[m>>2]>>1);m=c[m>>2]>>1;c[g>>2]=m;m=(c[k>>2]|0)+m|0;if(!(a[m>>0]|0)){a[m>>0]=1;nc(h,g)}l=l+1|0}while((l|0)<((c[f>>2]|0)>>>5|0));Pb(b,d);i=e;return}function Gc(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;g=i;i=i+16|0;j=g+4|0;h=g;l=c[b+544>>2]|0;k=l+(e<<2)|0;Zc(b+856|0,e);if((c[k>>2]&-32|0)==64){Fc(b,e);p=c[f>>2]|0;f=c[k>>2]|0;a:do if(f>>>0>31){m=f>>>5;n=0;while(1){o=n+1|0;if((c[k+(n<<2)+4>>2]|0)==(p|0)){o=n;break a}if((o|0)<(m|0))n=o;else break}}else{m=0;o=0}while(0);n=m+ -1|0;if((o|0)<(n|0))do{f=o;o=o+1|0;c[k+(f<<2)+4>>2]=c[k+(o<<2)+4>>2];f=c[k>>2]|0;m=f>>>5;n=m+ -1|0}while((o|0)<(n|0));if(f&8){c[k+(n<<2)+4>>2]=c[k+(m<<2)+4>>2];f=c[k>>2]|0}m=f+ -32|0;c[k>>2]=m;m=m>>>5;if(!m){m=0;f=0}else{f=0;n=0;do{f=1<<((c[k+(n<<2)+4>>2]|0)>>>1&31)|f;n=n+1|0}while((n|0)<(m|0))}c[k+(m<<2)+4>>2]=f}else{Ob(b,e,1);f=c[f>>2]|0;n=c[k>>2]|0;b:do if(n>>>0>31){m=n>>>5;o=0;while(1){p=o+1|0;if((c[k+(o<<2)+4>>2]|0)==(f|0)){p=o;break b}if((p|0)<(m|0))o=p;else break}}else{m=0;p=0}while(0);o=m+ -1|0;if((p|0)<(o|0))do{n=p;p=p+1|0;c[k+(n<<2)+4>>2]=c[k+(p<<2)+4>>2];n=c[k>>2]|0;m=n>>>5;o=m+ -1|0}while((p|0)<(o|0));if(n&8){c[k+(o<<2)+4>>2]=c[k+(m<<2)+4>>2];n=c[k>>2]|0}o=n+ -32|0;c[k>>2]=o;o=o>>>5;if(!o){o=0;m=0}else{m=0;n=0;do{m=1<<((c[k+(n<<2)+4>>2]|0)>>>1&31)|m;n=n+1|0}while((n|0)<(o|0))}c[k+(o<<2)+4>>2]=m;Nb(b,e);m=f>>1;n=c[b+760>>2]|0;o=n+(m*12|0)|0;n=n+(m*12|0)+4|0;p=c[n>>2]|0;c:do if((p|0)>0){s=c[o>>2]|0;q=0;while(1){r=q+1|0;if((c[s+(q<<2)>>2]|0)==(e|0))break c;if((r|0)<(p|0))q=r;else{q=r;break}}}else q=0;while(0);p=p+ -1|0;if((q|0)<(p|0)){o=c[o>>2]|0;do{p=q;q=q+1|0;c[o+(p<<2)>>2]=c[o+(q<<2)>>2];p=(c[n>>2]|0)+ -1|0}while((q|0)<(p|0))}c[n>>2]=p;s=(c[b+808>>2]|0)+(f<<2)|0;c[s>>2]=(c[s>>2]|0)+ -1;Vc(b,m)}if((c[k>>2]&-32|0)!=32){s=1;i=g;return s|0}l=c[l+(e+1<<2)>>2]|0;k=d[(c[b+332>>2]|0)+(l>>1)>>0]|0;s=k^l&1;e=s&255;q=a[2624]|0;r=q&255;if(!(e<<24>>24==q<<24>>24&(r>>>1^1)|r&2&s)){r=a[2616]|0;s=r&255;if((s>>>1^1)&e<<24>>24==r<<24>>24|k&2&s){s=0;i=g;return s|0}}else{c[h>>2]=l;c[j+0>>2]=c[h+0>>2];Lb(b,j,-1)}s=(Mb(b)|0)==-1;i=g;return s|0}function Hc(a,b,d,e,f){a=a|0;b=b|0;d=d|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;g=i;i=i+16|0;j=g+4|0;h=g;o=a+708|0;c[o>>2]=(c[o>>2]|0)+1;if(c[f>>2]|0)c[f+4>>2]=0;k=(c[b>>2]|0)>>>5>>>0<(c[d>>2]|0)>>>5>>>0;a=k?d:b;b=k?b:d;k=c[b>>2]|0;a:do if(k>>>0>31){d=0;b:while(1){l=c[b+(d<<2)+4>>2]|0;c:do if((l>>1|0)!=(e|0)){m=c[a>>2]|0;d:do if(m>>>0>31){n=0;while(1){o=c[a+(n<<2)+4>>2]|0;n=n+1|0;if((l^o)>>>0<2)break;if((n|0)>=(m>>>5|0))break d}if((o|0)==(l^1|0)){f=0;break b}else break c}while(0);c[j>>2]=l;mc(f,j);k=c[b>>2]|0}while(0);d=d+1|0;if((d|0)>=(k>>>5|0))break a}i=g;return f|0}while(0);d=c[a>>2]|0;if(d>>>0<=31){o=1;i=g;return o|0}j=0;do{b=c[a+(j<<2)+4>>2]|0;if((b>>1|0)!=(e|0)){c[h>>2]=b;mc(f,h);d=c[a>>2]|0}j=j+1|0}while((j|0)<(d>>>5|0));f=1;i=g;return f|0}function Ic(b){b=b|0;var d=0,e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0;d=i;k=b+924|0;if(!(c[k>>2]|0)){i=d;return}h=b+856|0;e=b+872|0;f=b+868|0;j=b+860|0;g=b+544|0;l=0;while(1){w=c[e>>2]|0;m=c[f>>2]|0;n=w-m|0;if((w|0)<(m|0))n=(c[j>>2]|0)+n|0;if((l|0)>=(n|0))break;n=(c[g>>2]|0)+(c[(c[h>>2]|0)+(((m+l|0)%(c[j>>2]|0)|0)<<2)>>2]<<2)|0;m=c[n>>2]|0;if(!(m&3))c[n>>2]=m&-4|2;l=l+1|0}l=b+540|0;q=c[l>>2]|0;if((q|0)>0){n=b+744|0;o=b+776|0;m=b+760|0;b=b+804|0;p=0;do{if(a[(c[n>>2]|0)+p>>0]|0){r=(c[o>>2]|0)+p|0;if(a[r>>0]|0){s=c[m>>2]|0;q=s+(p*12|0)+4|0;u=c[q>>2]|0;if((u|0)>0){s=c[s+(p*12|0)>>2]|0;v=0;t=0;do{w=c[s+(v<<2)>>2]|0;if((c[(c[c[b>>2]>>2]|0)+(w<<2)>>2]&3|0)!=1){c[s+(t<<2)>>2]=w;u=c[q>>2]|0;t=t+1|0}v=v+1|0}while((v|0)<(u|0))}else{v=0;t=0}s=v-t|0;if((s|0)>0)c[q>>2]=u-s;a[r>>0]=0}r=c[m>>2]|0;q=r+(p*12|0)+4|0;t=c[q>>2]|0;if((t|0)>0){r=r+(p*12|0)|0;s=0;do{u=c[(c[r>>2]|0)+(s<<2)>>2]|0;if(!(c[(c[g>>2]|0)+(u<<2)>>2]&3)){Zc(h,u);t=(c[g>>2]|0)+(c[(c[r>>2]|0)+(s<<2)>>2]<<2)|0;c[t>>2]=c[t>>2]&-4|2;t=c[q>>2]|0}s=s+1|0}while((s|0)<(t|0))}a[(c[n>>2]|0)+p>>0]=0;q=c[l>>2]|0}p=p+1|0}while((p|0)<(q|0));l=0}else l=0;while(1){w=c[e>>2]|0;m=c[f>>2]|0;n=w-m|0;if((w|0)<(m|0))n=(c[j>>2]|0)+n|0;if((l|0)>=(n|0))break;m=(c[g>>2]|0)+(c[(c[h>>2]|0)+(((m+l|0)%(c[j>>2]|0)|0)<<2)>>2]<<2)|0;n=c[m>>2]|0;if((n&3|0)==2)c[m>>2]=n&-4;l=l+1|0}c[k>>2]=0;i=d;return}function Jc(b,d){b=b|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0;e=i;i=i+16|0;m=e;x=e+12|0;g=b+856|0;l=b+872|0;q=b+868|0;j=b+860|0;u=b+680|0;f=b+920|0;h=b+284|0;t=b+280|0;r=b+544|0;s=b+928|0;o=b+44|0;n=b+776|0;v=b+692|0;p=b+804|0;k=b+760|0;C=0;F=0;D=0;a:while(1){E=c[q>>2]|0;do{A=c[l>>2]|0;B=(A|0)<(E|0);A=A-E|0;if(B)G=(c[j>>2]|0)+A|0;else G=A;if((G|0)<=0?(c[f>>2]|0)>=(c[h>>2]|0):0){f=1;j=53;break a}if(a[u>>0]|0){j=8;break a}if(B)A=(c[j>>2]|0)+A|0;if((A|0)==0?(z=c[f>>2]|0,(z|0)<(c[h>>2]|0)):0){c[f>>2]=z+1;c[(c[r>>2]|0)+((c[s>>2]|0)+1<<2)>>2]=c[(c[t>>2]|0)+(z<<2)>>2];A=(c[r>>2]|0)+(c[s>>2]<<2)|0;B=(c[A>>2]|0)>>>5;if(!B){B=0;G=0}else{G=0;E=0;do{G=1<<((c[A+(E<<2)+4>>2]|0)>>>1&31)|G;E=E+1|0}while((E|0)<(B|0))}c[A+(B<<2)+4>>2]=G;Zc(g,c[s>>2]|0);E=c[q>>2]|0}A=c[(c[g>>2]|0)+(E<<2)>>2]|0;E=E+1|0;J=c[j>>2]|0;E=(E|0)==(J|0)?0:E;c[q>>2]=E;G=c[r>>2]|0;B=G+(A<<2)|0;I=c[B>>2]|0}while((I&3|0)!=0);if(d?(c[o>>2]|0)>1:0){H=C+1|0;if(!((C|0)%1e3|0)){I=c[l>>2]|0;c[m>>2]=I-E+((I|0)<(E|0)?J:0);c[m+4>>2]=D;c[m+8>>2]=F;La(3440,m|0)|0;I=c[B>>2]|0;C=H}else C=H}E=G+(A+1<<2)|0;G=c[E>>2]>>1;if(I>>>0>63){H=c[k>>2]|0;I=I>>>5;J=1;do{P=c[B+(J<<2)+4>>2]>>1;G=(c[H+(P*12|0)+4>>2]|0)<(c[H+(G*12|0)+4>>2]|0)?P:G;J=J+1|0}while((J|0)<(I|0))}I=(c[n>>2]|0)+G|0;if(a[I>>0]|0){J=c[k>>2]|0;H=J+(G*12|0)+4|0;M=c[H>>2]|0;if((M|0)>0){J=c[J+(G*12|0)>>2]|0;L=0;K=0;do{N=c[J+(L<<2)>>2]|0;if((c[(c[c[p>>2]>>2]|0)+(N<<2)>>2]&3|0)!=1){c[J+(K<<2)>>2]=N;M=c[H>>2]|0;K=K+1|0}L=L+1|0}while((L|0)<(M|0))}else{L=0;K=0}J=L-K|0;if((J|0)>0)c[H>>2]=M-J;a[I>>0]=0}I=c[k>>2]|0;H=c[I+(G*12|0)>>2]|0;I=I+(G*12|0)+4|0;if((c[I>>2]|0)>0)J=0;else continue;while(1){N=c[B>>2]|0;if(N&3)continue a;K=c[H+(J<<2)>>2]|0;L=c[r>>2]|0;O=L+(K<<2)|0;M=c[O>>2]|0;b:do if(((!((M&3|0)!=0|(K|0)==(A|0))?(P=c[v>>2]|0,y=M>>>5,(P|0)==-1|(y|0)<(P|0)):0)?(w=N>>>5,y>>>0>=w>>>0):0)?(c[B+(w<<2)+4>>2]&~c[O+(y<<2)+4>>2]|0)==0:0){L=L+(K+1<<2)|0;do if(N>>>0>31){if(M>>>0>31){O=-2;M=0}else break b;while(1){N=c[E+(M<<2)>>2]|0;c:do if((O|0)==-2){P=0;while(1){O=c[L+(P<<2)>>2]|0;if((N|0)==(O|0)){N=-2;break c}P=P+1|0;if((N|0)==(O^1|0))break c;if(P>>>0>=y>>>0)break b}}else{P=0;while(1){if((N|0)==(c[L+(P<<2)>>2]|0)){N=O;break c}P=P+1|0;if(P>>>0>=y>>>0)break b}}while(0);M=M+1|0;if(M>>>0>=w>>>0)break;else O=N}if((N|0)==-2)break;else if((N|0)==-1)break b;c[x>>2]=N^1;c[m+0>>2]=c[x+0>>2];if(!(Gc(b,K,m)|0)){f=0;j=53;break a}F=F+1|0;J=(((N>>1|0)==(G|0))<<31>>31)+J|0;break b}while(0);Fc(b,K);D=D+1|0}while(0);J=J+1|0;if((J|0)>=(c[I>>2]|0))continue a}}if((j|0)==8){Yc(g,0);c[f>>2]=c[h>>2];P=1;i=e;return P|0}else if((j|0)==53){i=e;return f|0}return 0}function Kc(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;h=i;i=i+16|0;g=h+12|0;m=h+8|0;k=h+4|0;j=h;l=(c[b+544>>2]|0)+(f<<2)|0;if(c[l>>2]&3){r=1;i=h;return r|0}if(Qb(b,l)|0){r=1;i=h;return r|0}c[m>>2]=c[b+284>>2];nc(b+292|0,m);p=c[l>>2]|0;if(p>>>0>31){m=b+332|0;n=0;o=-2;do{q=c[l+(n<<2)+4>>2]|0;r=q>>1;if((r|0)!=(e|0)?(r=(d[(c[m>>2]|0)+r>>0]|0)^q&1,t=a[2616]|0,s=t&255,((r&255)<<24>>24==t<<24>>24&(s>>>1^1)|s&2&r|0)==0):0){c[k>>2]=q^1;c[g+0>>2]=c[k+0>>2];Lb(b,g,-1);p=c[l>>2]|0}else o=q;n=n+1|0}while((n|0)<(p>>>5|0))}else o=-2;t=(Mb(b)|0)==-1;Rb(b,0);if(!t){t=b+712|0;c[t>>2]=(c[t>>2]|0)+1;c[j>>2]=o;c[g+0>>2]=c[j+0>>2];if(!(Gc(b,f,g)|0)){t=0;i=h;return t|0}}t=1;i=h;return t|0}function Lc(b,d){b=b|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;e=i;h=(c[b+776>>2]|0)+d|0;f=b+760|0;if(a[h>>0]|0){k=c[f>>2]|0;g=k+(d*12|0)+4|0;n=c[g>>2]|0;if((n|0)>0){j=b+804|0;k=c[k+(d*12|0)>>2]|0;m=0;l=0;do{o=c[k+(m<<2)>>2]|0;if((c[(c[c[j>>2]>>2]|0)+(o<<2)>>2]&3|0)!=1){c[k+(l<<2)>>2]=o;n=c[g>>2]|0;l=l+1|0}m=m+1|0}while((m|0)<(n|0))}else{m=0;l=0}j=m-l|0;if((j|0)>0)c[g>>2]=n-j;a[h>>0]=0}g=c[f>>2]|0;n=a[(c[b+332>>2]|0)+d>>0]|0;m=a[2624]|0;o=m&255;if(!((o>>>1^1)&n<<24>>24==m<<24>>24|n&2&o)){o=1;i=e;return o|0}f=g+(d*12|0)+4|0;h=c[f>>2]|0;if(!h){o=1;i=e;return o|0}a:do if((h|0)>0){g=g+(d*12|0)|0;h=0;while(1){if(!(Kc(b,d,c[(c[g>>2]|0)+(h<<2)>>2]|0)|0)){b=0;break}h=h+1|0;if((h|0)>=(c[f>>2]|0))break a}i=e;return b|0}while(0);o=Jc(b,0)|0;i=e;return o|0}function Mc(b,d){b=b|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0,S=0,T=0,U=0,V=0,W=0,X=0,Y=0,Z=0;e=i;i=i+48|0;s=e+36|0;r=e+32|0;t=e+28|0;u=e+24|0;f=e+12|0;g=e;n=(c[b+776>>2]|0)+d|0;m=b+760|0;if(a[n>>0]|0){q=c[m>>2]|0;o=q+(d*12|0)+4|0;y=c[o>>2]|0;if((y|0)>0){p=b+804|0;q=c[q+(d*12|0)>>2]|0;w=0;v=0;do{z=c[q+(w<<2)>>2]|0;if((c[(c[c[p>>2]>>2]|0)+(z<<2)>>2]&3|0)!=1){c[q+(v<<2)>>2]=z;y=c[o>>2]|0;v=v+1|0}w=w+1|0}while((w|0)<(y|0))}else{w=0;v=0}p=w-v|0;if((p|0)>0)c[o>>2]=y-p;a[n>>0]=0}v=c[m>>2]|0;w=v+(d*12|0)|0;c[f>>2]=0;n=f+4|0;c[n>>2]=0;o=f+8|0;c[o>>2]=0;c[g>>2]=0;q=g+4|0;c[q>>2]=0;p=g+8|0;c[p>>2]=0;v=v+(d*12|0)+4|0;a:do if((c[v>>2]|0)>0){y=b+544|0;B=d<<1;A=0;do{C=(c[w>>2]|0)+(A<<2)|0;E=(c[y>>2]|0)+(c[C>>2]<<2)|0;Z=c[E>>2]|0;z=Z>>>5;b:do if(Z>>>0>31){G=0;while(1){D=G+1|0;if((c[E+(G<<2)+4>>2]|0)==(B|0)){D=G;break b}if((D|0)<(z|0))G=D;else break}}else D=0;while(0);_c((D|0)<(z|0)?f:g,C);A=A+1|0;z=c[v>>2]|0}while((A|0)<(z|0));y=c[n>>2]|0;B=(y|0)>0;if(B){C=c[q>>2]|0;K=(C|0)>0;J=b+544|0;D=c[f>>2]|0;A=c[g>>2]|0;E=b+708|0;I=b+684|0;H=b+688|0;P=0;G=0;while(1){if(K){M=D+(G<<2)|0;L=c[J>>2]|0;N=c[E>>2]|0;O=0;do{S=L+(c[M>>2]<<2)|0;U=L+(c[A+(O<<2)>>2]<<2)|0;N=N+1|0;c[E>>2]=N;Q=(c[S>>2]|0)>>>5>>>0<(c[U>>2]|0)>>>5>>>0;R=Q?U:S;U=Q?S:U;S=R+4|0;Q=U+4|0;R=c[R>>2]|0;T=R>>>5;W=T+ -1|0;U=c[U>>2]|0;c:do if(U>>>0>31){V=0;while(1){Z=c[Q+(V<<2)>>2]|0;d:do if((Z>>1|0)!=(d|0)){e:do if(R>>>0>31){Y=0;while(1){X=c[S+(Y<<2)>>2]|0;Y=Y+1|0;if((X^Z)>>>0<2)break;if((Y|0)>=(T|0))break e}if((X|0)==(Z^1|0))break c;else break d}while(0);W=W+1|0}while(0);V=V+1|0;if((V|0)>=(U>>>5|0)){x=28;break}}}else x=28;while(0);if((x|0)==28){x=0;if((P|0)>=((c[I>>2]|0)+z|0)){b=1;break a}Z=c[H>>2]|0;if((Z|0)!=-1&(W|0)>(Z|0)){b=1;break a}else P=P+1|0}O=O+1|0}while((O|0)<(C|0))}G=G+1|0;if((G|0)>=(y|0)){x=32;break}}}else{B=0;x=32}}else{y=0;B=0;x=32}while(0);f:do if((x|0)==32){a[(c[b+904>>2]|0)+d>>0]=1;z=b+380|0;A=(c[z>>2]|0)+d|0;if(a[A>>0]|0){Z=b+200|0;Y=Z;Y=ne(c[Y>>2]|0,c[Y+4>>2]|0,-1,-1)|0;c[Z>>2]=Y;c[Z+4>>2]=F}a[A>>0]=0;A=b+460|0;if(!((c[b+476>>2]|0)>(d|0)?(c[(c[b+472>>2]|0)+(d<<2)>>2]|0)>-1:0))x=36;if((x|0)==36?(a[(c[z>>2]|0)+d>>0]|0)!=0:0)lc(A,d);x=b+716|0;c[x>>2]=(c[x>>2]|0)+1;x=c[q>>2]|0;if((y|0)>(x|0)){A=b+732|0;if((x|0)>0){u=b+544|0;t=c[g>>2]|0;E=b+736|0;D=0;do{C=(c[u>>2]|0)+(c[t+(D<<2)>>2]<<2)|0;z=c[E>>2]|0;if((c[C>>2]|0)>>>0>31){G=0;H=-1;do{Z=C+(G<<2)+4|0;c[s>>2]=c[Z>>2];_c(A,s);H=(c[Z>>2]>>1|0)==(d|0)?G+z|0:H;G=G+1|0}while((G|0)<((c[C>>2]|0)>>>5|0))}else H=-1;Z=c[A>>2]|0;X=Z+(H<<2)|0;Y=c[X>>2]|0;Z=Z+(z<<2)|0;c[X>>2]=c[Z>>2];c[Z>>2]=Y;c[r>>2]=(c[C>>2]|0)>>>5;_c(A,r);D=D+1|0}while((D|0)<(x|0))}c[s>>2]=d<<1;_c(A,s);c[r>>2]=1;_c(A,r)}else{D=b+732|0;if(B){G=b+544|0;E=c[f>>2]|0;z=b+736|0;H=0;do{C=(c[G>>2]|0)+(c[E+(H<<2)>>2]<<2)|0;A=c[z>>2]|0;if((c[C>>2]|0)>>>0>31){I=0;J=-1;do{Z=C+(I<<2)+4|0;c[s>>2]=c[Z>>2];_c(D,s);J=(c[Z>>2]>>1|0)==(d|0)?I+A|0:J;I=I+1|0}while((I|0)<((c[C>>2]|0)>>>5|0))}else J=-1;Z=c[D>>2]|0;X=Z+(J<<2)|0;Y=c[X>>2]|0;Z=Z+(A<<2)|0;c[X>>2]=c[Z>>2];c[Z>>2]=Y;c[r>>2]=(c[C>>2]|0)>>>5;_c(D,r);H=H+1|0}while((H|0)<(y|0))}c[t>>2]=d<<1|1;_c(D,t);c[u>>2]=1;_c(D,u)}if((c[v>>2]|0)>0){r=0;do{Fc(b,c[(c[w>>2]|0)+(r<<2)>>2]|0);r=r+1|0}while((r|0)<(c[v>>2]|0))}r=b+628|0;g:do if(B){s=b+544|0;w=c[f>>2]|0;A=c[g>>2]|0;if((x|0)>0)v=0;else{r=0;while(1){r=r+1|0;if((r|0)>=(y|0))break g}}do{u=w+(v<<2)|0;t=0;do{Z=c[s>>2]|0;if(Hc(b,Z+(c[u>>2]<<2)|0,Z+(c[A+(t<<2)>>2]<<2)|0,d,r)|0?!(Dc(b,r)|0):0){b=0;break f}t=t+1|0}while((t|0)<(x|0));v=v+1|0}while((v|0)<(y|0))}while(0);r=c[m>>2]|0;m=r+(d*12|0)|0;s=c[m>>2]|0;if(s){c[r+(d*12|0)+4>>2]=0;Td(s);c[m>>2]=0;c[r+(d*12|0)+8>>2]=0}m=b+412|0;d=d<<1;s=c[m>>2]|0;r=s+(d*12|0)+4|0;if((c[r>>2]|0)==0?(l=s+(d*12|0)|0,k=c[l>>2]|0,(k|0)!=0):0){c[r>>2]=0;Td(k);c[l>>2]=0;c[s+(d*12|0)+8>>2]=0;s=c[m>>2]|0}k=d|1;l=s+(k*12|0)+4|0;if((c[l>>2]|0)==0?(j=s+(k*12|0)|0,h=c[j>>2]|0,(h|0)!=0):0){c[l>>2]=0;Td(h);c[j>>2]=0;c[s+(k*12|0)+8>>2]=0}b=Jc(b,0)|0;A=c[g>>2]|0}while(0);if(A){c[q>>2]=0;Td(A);c[g>>2]=0;c[p>>2]=0}g=c[f>>2]|0;if(!g){i=e;return b|0}c[n>>2]=0;Td(g);c[f>>2]=0;c[o>>2]=0;i=e;return b|0}function Nc(b,d){b=b|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;e=i;if(!(a[b+724>>0]|0)){i=e;return}l=b+540|0;if((c[l>>2]|0)>0){j=b+760|0;f=b+804|0;g=b+776|0;k=b+544|0;h=0;do{n=c[j>>2]|0;m=n+(h*12|0)+4|0;p=c[m>>2]|0;if((p|0)>0){n=c[n+(h*12|0)>>2]|0;q=0;o=0;do{r=c[n+(q<<2)>>2]|0;if((c[(c[c[f>>2]>>2]|0)+(r<<2)>>2]&3|0)!=1){c[n+(o<<2)>>2]=r;p=c[m>>2]|0;o=o+1|0}q=q+1|0}while((q|0)<(p|0))}else{q=0;o=0}n=q-o|0;if((n|0)>0)c[m>>2]=p-n;a[(c[g>>2]|0)+h>>0]=0;n=c[j>>2]|0;m=n+(h*12|0)+4|0;if((c[m>>2]|0)>0){r=n+(h*12|0)|0;p=0;do{n=(c[r>>2]|0)+(p<<2)|0;o=c[n>>2]|0;q=c[k>>2]|0;s=q+(o<<2)|0;if(!(c[s>>2]&16)){t=wc(d,s)|0;c[n>>2]=t;c[s>>2]=c[s>>2]|16;c[q+(o+1<<2)>>2]=t}else c[n>>2]=c[q+(o+1<<2)>>2];p=p+1|0}while((p|0)<(c[m>>2]|0))}h=h+1|0}while((h|0)<(c[l>>2]|0))}f=b+856|0;t=c[b+872>>2]|0;g=b+868|0;m=c[g>>2]|0;k=t-m|0;if((t|0)<(m|0))k=(c[b+860>>2]|0)+k|0;a:do if((k|0)>0){h=b+860|0;j=b+544|0;while(1){l=c[(c[f>>2]|0)+(m<<2)>>2]|0;n=m+1|0;c[g>>2]=(n|0)==(c[h>>2]|0)?0:n;n=c[j>>2]|0;o=n+(l<<2)|0;m=c[o>>2]|0;if(!(m&3)){if(!(m&16)){t=wc(d,o)|0;c[o>>2]=c[o>>2]|16;c[n+(l+1<<2)>>2]=t;l=t}else l=c[n+(l+1<<2)>>2]|0;Zc(f,l)}k=k+ -1|0;if((k|0)<=0)break a;m=c[g>>2]|0}}else j=b+544|0;while(0);b=b+928|0;f=c[b>>2]|0;h=c[j>>2]|0;g=h+(f<<2)|0;if(!(c[g>>2]&16)){t=wc(d,g)|0;c[b>>2]=t;c[g>>2]=c[g>>2]|16;c[h+(f+1<<2)>>2]=t;i=e;return}else{c[b>>2]=c[h+(f+1<<2)>>2];i=e;return}}function Oc(b){b=b|0;var d=0,e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0;h=i;i=i+32|0;l=h;d=h+8|0;e=b+544|0;f=b+548|0;g=b+556|0;j=(c[f>>2]|0)-(c[g>>2]|0)|0;c[d+0>>2]=0;c[d+4>>2]=0;c[d+8>>2]=0;c[d+12>>2]=0;gc(d,j);j=d+16|0;k=b+560|0;a[j>>0]=a[k>>0]|0;Nc(b,d);ac(b,d);if((c[b+44>>2]|0)>1){m=c[d+4>>2]<<2;c[l>>2]=c[f>>2]<<2;c[l+4>>2]=m;La(3608,l|0)|0}a[k>>0]=a[j>>0]|0;j=c[e>>2]|0;if(j)Td(j);c[e>>2]=c[d>>2];c[f>>2]=c[d+4>>2];c[b+552>>2]=c[d+8>>2];c[g>>2]=c[d+12>>2];i=h;return}function Pc(){var d=0,e=0,f=0;d=i;i=i+16|0;e=d;a[2608]=0;a[2616]=1;a[2624]=2;xb(2632,2656,2664,3744,3752);c[658]=160;a[2652]=0;xb(2704,2728,2736,3744,3752);c[676]=160;a[2724]=0;xb(2784,2808,2816,3744,3752);c[696]=160;a[2804]=1;xb(2848,2880,2888,3744,3736);c[712]=280;f=2868|0;c[f>>2]=-2147483648;c[f+4>>2]=2147483647;c[719]=0;xb(2960,2992,3e3,3744,3736);c[740]=280;f=2980|0;c[f>>2]=-1;c[f+4>>2]=2147483647;c[747]=20;xb(3112,3144,3152,3744,3736);c[778]=280;f=3132|0;c[f>>2]=-1;c[f+4>>2]=2147483647;c[785]=1e3;xb(3240,3296,3312,3744,3720);c[810]=2168;h[408]=0.0;h[409]=v;a[3280]=0;a[3281]=0;b[1641]=b[e+0>>1]|0;b[1642]=b[e+2>>1]|0;b[1643]=b[e+4>>1]|0;h[411]=.5;i=d;return}function Qc(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0;d=i;c[a>>2]=0;e=a+4|0;c[e>>2]=0;f=a+8|0;c[f>>2]=0;if((b|0)<=0){i=d;return}g=b+1&-2;g=(g|0)>2?g:2;c[f>>2]=g;f=Ud(0,g<<2)|0;c[a>>2]=f;if((f|0)==0?(c[(Oa()|0)>>2]|0)==12:0)Ta(va(1)|0,48,0);a=c[e>>2]|0;if((a|0)<(b|0))do{g=f+(a<<2)|0;if(g)c[g>>2]=0;a=a+1|0}while((a|0)!=(b|0));c[e>>2]=b;i=d;return}function Rc(a){a=a|0;var b=0,d=0,e=0,f=0,g=0,h=0,j=0;b=i;e=a+32|0;d=c[e>>2]|0;if(d){c[a+36>>2]=0;Td(d);c[e>>2]=0;c[a+40>>2]=0}e=a+16|0;d=c[e>>2]|0;if(d){c[a+20>>2]=0;Td(d);c[e>>2]=0;c[a+24>>2]=0}e=c[a>>2]|0;if(!e){i=b;return}d=a+4|0;g=c[d>>2]|0;if((g|0)>0){f=0;do{j=e+(f*12|0)|0;h=c[j>>2]|0;if(h){c[e+(f*12|0)+4>>2]=0;Td(h);c[j>>2]=0;c[e+(f*12|0)+8>>2]=0;e=c[a>>2]|0;g=c[d>>2]|0}f=f+1|0}while((f|0)<(g|0))}c[d>>2]=0;Td(e);c[a>>2]=0;c[a+8>>2]=0;i=b;return}function Sc(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0;e=i;b=c[b>>2]|0;g=b+1|0;f=a+4|0;if((c[f>>2]|0)>=(g|0)){k=c[a>>2]|0;k=k+(b<<2)|0;c[k>>2]=d;i=e;return}h=a+8|0;k=c[h>>2]|0;if((k|0)<(g|0)){l=b+2-k&-2;j=(k>>1)+2&-2;j=(l|0)>(j|0)?l:j;if((j|0)>(2147483647-k|0)){l=va(1)|0;Ta(l|0,48,0)}m=c[a>>2]|0;l=j+k|0;c[h>>2]=l;l=Ud(m,l<<2)|0;c[a>>2]=l;if((l|0)==0?(c[(Oa()|0)>>2]|0)==12:0){m=va(1)|0;Ta(m|0,48,0)}}k=c[f>>2]|0;if((k|0)<(g|0)){h=c[a>>2]|0;do{j=h+(k<<2)|0;if(j)c[j>>2]=0;k=k+1|0}while((k|0)!=(g|0))}c[f>>2]=g;m=c[a>>2]|0;m=m+(b<<2)|0;c[m>>2]=d;i=e;return}function Tc(b,d){b=b|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0;e=i;k=c[d>>2]|0;g=k+1|0;f=b+4|0;if((c[f>>2]|0)<(g|0)){j=b+8|0;h=c[j>>2]|0;if((h|0)<(g|0)){l=k+2-h&-2;k=(h>>1)+2&-2;k=(l|0)>(k|0)?l:k;if((k|0)>(2147483647-h|0)){l=va(1)|0;Ta(l|0,48,0)}m=c[b>>2]|0;l=k+h|0;c[j>>2]=l;l=Ud(m,l*12|0)|0;c[b>>2]=l;if((l|0)==0?(c[(Oa()|0)>>2]|0)==12:0){m=va(1)|0;Ta(m|0,48,0)}}j=c[f>>2]|0;if((j|0)<(g|0)){h=c[b>>2]|0;do{k=h+(j*12|0)|0;if(k){c[k>>2]=0;c[h+(j*12|0)+4>>2]=0;c[h+(j*12|0)+8>>2]=0}j=j+1|0}while((j|0)!=(g|0))}c[f>>2]=g;h=c[d>>2]|0}else h=k;f=c[b>>2]|0;if(c[f+(h*12|0)>>2]|0){c[f+(h*12|0)+4>>2]=0;h=c[d>>2]|0}d=b+16|0;f=h+1|0;g=b+20|0;if((c[g>>2]|0)>=(f|0)){i=e;return}j=b+24|0;b=c[j>>2]|0;if((b|0)<(f|0)){m=h+2-b&-2;h=(b>>1)+2&-2;h=(m|0)>(h|0)?m:h;if((h|0)>(2147483647-b|0)){m=va(1)|0;Ta(m|0,48,0)}l=c[d>>2]|0;m=h+b|0;c[j>>2]=m;m=Ud(l,m)|0;c[d>>2]=m;if((m|0)==0?(c[(Oa()|0)>>2]|0)==12:0){m=va(1)|0;Ta(m|0,48,0)}}b=c[g>>2]|0;if((b|0)<(f|0))do{a[(c[d>>2]|0)+b>>0]=0;b=b+1|0}while((b|0)!=(f|0));c[g>>2]=f;i=e;return}function Uc(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;d=i;i=i+16|0;g=d;c[g>>2]=b;f=a+12|0;e=b+1|0;h=a+16|0;if((c[h>>2]|0)<(e|0)){k=a+20|0;j=c[k>>2]|0;if((j|0)<(e|0)){m=b+2-j&-2;l=(j>>1)+2&-2;l=(m|0)>(l|0)?m:l;if((l|0)>(2147483647-j|0)){m=va(1)|0;Ta(m|0,48,0)}n=c[f>>2]|0;m=l+j|0;c[k>>2]=m;m=Ud(n,m<<2)|0;c[f>>2]=m;if((m|0)==0?(c[(Oa()|0)>>2]|0)==12:0){n=va(1)|0;Ta(n|0,48,0)}}j=c[h>>2]|0;if((e|0)>(j|0))ke((c[f>>2]|0)+(j<<2)|0,-1,e-j<<2|0)|0;c[h>>2]=e}c[(c[f>>2]|0)+(b<<2)>>2]=c[a+4>>2];nc(a,g);e=c[f>>2]|0;j=c[e+(b<<2)>>2]|0;b=c[a>>2]|0;f=c[b+(j<<2)>>2]|0;if(!j){m=0;n=b+(m<<2)|0;c[n>>2]=f;n=e+(f<<2)|0;c[n>>2]=m;i=d;return}a=a+28|0;g=f<<1;h=g|1;while(1){m=j;j=j+ -1>>1;l=b+(j<<2)|0;k=c[l>>2]|0;r=c[c[a>>2]>>2]|0;o=c[r+(g<<2)>>2]|0;q=c[r+(h<<2)>>2]|0;o=we(q|0,((q|0)<0)<<31>>31|0,o|0,((o|0)<0)<<31>>31|0)|0;q=F;p=k<<1;n=c[r+(p<<2)>>2]|0;p=c[r+((p|1)<<2)>>2]|0;n=we(p|0,((p|0)<0)<<31>>31|0,n|0,((n|0)<0)<<31>>31|0)|0;p=F;if(!(q>>>0<p>>>0|(q|0)==(p|0)&o>>>0<n>>>0)){a=14;break}c[b+(m<<2)>>2]=k;c[e+(c[l>>2]<<2)>>2]=m;if(!j){m=0;a=14;break}}if((a|0)==14){r=b+(m<<2)|0;c[r>>2]=f;r=e+(f<<2)|0;c[r>>2]=m;i=d;return}}function Vc(b,d){b=b|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;e=i;h=b+824|0;l=(c[b+840>>2]|0)>(d|0);if(l?(c[(c[b+836>>2]|0)+(d<<2)>>2]|0)>-1:0)j=7;else j=3;do if((j|0)==3){if(a[(c[b+876>>2]|0)+d>>0]|0){i=e;return}if(a[(c[b+904>>2]|0)+d>>0]|0){i=e;return}o=a[(c[b+332>>2]|0)+d>>0]|0;n=a[2624]|0;p=n&255;if((p>>>1^1)&o<<24>>24==n<<24>>24|o&2&p)if(l){j=7;break}else break;else{i=e;return}}while(0);if((j|0)==7?(f=c[b+836>>2]|0,g=f+(d<<2)|0,k=c[g>>2]|0,(k|0)>-1):0){d=c[h>>2]|0;j=c[d+(k<<2)>>2]|0;a:do if(!k)o=0;else{l=b+852|0;m=j<<1;b=m|1;while(1){o=k;k=k+ -1>>1;p=d+(k<<2)|0;n=c[p>>2]|0;u=c[c[l>>2]>>2]|0;r=c[u+(m<<2)>>2]|0;t=c[u+(b<<2)>>2]|0;r=we(t|0,((t|0)<0)<<31>>31|0,r|0,((r|0)<0)<<31>>31|0)|0;t=F;s=n<<1;q=c[u+(s<<2)>>2]|0;s=c[u+((s|1)<<2)>>2]|0;q=we(s|0,((s|0)<0)<<31>>31|0,q|0,((q|0)<0)<<31>>31|0)|0;s=F;if(!(t>>>0<s>>>0|(t|0)==(s|0)&r>>>0<q>>>0))break a;c[d+(o<<2)>>2]=n;c[f+(c[p>>2]<<2)>>2]=o;if(!k){o=0;break}}}while(0);c[d+(o<<2)>>2]=j;c[f+(j<<2)>>2]=o;Wc(h,c[g>>2]|0);i=e;return}Uc(h,d);i=e;return}function Wc(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;d=i;e=c[a>>2]|0;f=c[e+(b<<2)>>2]|0;m=b<<1|1;l=a+4|0;o=c[l>>2]|0;if((m|0)>=(o|0)){p=b;q=a+12|0;o=e+(p<<2)|0;c[o>>2]=f;q=c[q>>2]|0;q=q+(f<<2)|0;c[q>>2]=p;i=d;return}h=a+28|0;k=f<<1;j=k|1;a=a+12|0;while(1){n=(b<<1)+2|0;if((n|0)<(o|0)){p=c[e+(n<<2)>>2]|0;q=c[e+(m<<2)>>2]|0;u=p<<1;o=c[c[h>>2]>>2]|0;s=c[o+(u<<2)>>2]|0;u=c[o+((u|1)<<2)>>2]|0;s=we(u|0,((u|0)<0)<<31>>31|0,s|0,((s|0)<0)<<31>>31|0)|0;u=F;t=q<<1;r=c[o+(t<<2)>>2]|0;t=c[o+((t|1)<<2)>>2]|0;r=we(t|0,((t|0)<0)<<31>>31|0,r|0,((r|0)<0)<<31>>31|0)|0;t=F;if(!(u>>>0<t>>>0|(u|0)==(t|0)&s>>>0<r>>>0)){p=q;g=7}}else{p=c[e+(m<<2)>>2]|0;o=c[c[h>>2]>>2]|0;g=7}if((g|0)==7){g=0;n=m}r=p<<1;t=c[o+(r<<2)>>2]|0;r=c[o+((r|1)<<2)>>2]|0;t=we(r|0,((r|0)<0)<<31>>31|0,t|0,((t|0)<0)<<31>>31|0)|0;r=F;u=c[o+(k<<2)>>2]|0;s=c[o+(j<<2)>>2]|0;u=we(s|0,((s|0)<0)<<31>>31|0,u|0,((u|0)<0)<<31>>31|0)|0;s=F;if(!(r>>>0<s>>>0|(r|0)==(s|0)&t>>>0<u>>>0)){g=10;break}c[e+(b<<2)>>2]=p;c[(c[a>>2]|0)+(p<<2)>>2]=b;m=n<<1|1;o=c[l>>2]|0;if((m|0)>=(o|0)){b=n;g=10;break}else b=n}if((g|0)==10){u=e+(b<<2)|0;c[u>>2]=f;u=c[a>>2]|0;u=u+(f<<2)|0;c[u>>2]=b;i=d;return}}function Xc(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,j=0,k=0;d=i;h=c[a>>2]|0;if(h){e=a+4|0;f=c[e>>2]|0;a:do if((f|0)>0){g=0;while(1){j=h+(g*12|0)|0;k=c[j>>2]|0;if(k){c[h+(g*12|0)+4>>2]=0;Td(k);c[j>>2]=0;c[h+(g*12|0)+8>>2]=0;f=c[e>>2]|0}g=g+1|0;if((g|0)>=(f|0))break a;h=c[a>>2]|0}}while(0);c[e>>2]=0;if(b){Td(c[a>>2]|0);c[a>>2]=0;c[a+8>>2]=0}}e=a+16|0;f=c[e>>2]|0;if((f|0)!=0?(c[a+20>>2]=0,b):0){Td(f);c[e>>2]=0;c[a+24>>2]=0}f=a+32|0;e=c[f>>2]|0;if(!e){i=d;return}c[a+36>>2]=0;if(!b){i=d;return}Td(e);c[f>>2]=0;c[a+40>>2]=0;i=d;return}function Yc(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,j=0;e=i;f=c[a>>2]|0;d=a+4|0;if(f){c[d>>2]=0;if(b){Td(f);c[a>>2]=0;c[a+8>>2]=0;f=0}}else f=0;if((c[d>>2]|0)>=1){h=a+16|0;c[h>>2]=0;h=a+12|0;c[h>>2]=0;i=e;return}h=a+8|0;g=c[h>>2]|0;if((g|0)<1){j=2-g&-2;b=(g>>1)+2&-2;b=(j|0)>(b|0)?j:b;if((b|0)>(2147483647-g|0)){j=va(1)|0;Ta(j|0,48,0)}j=b+g|0;c[h>>2]=j;f=Ud(f,j<<2)|0;c[a>>2]=f;if((f|0)==0?(c[(Oa()|0)>>2]|0)==12:0){j=va(1)|0;Ta(j|0,48,0)}}b=c[d>>2]|0;if((b|0)<1)while(1){g=f+(b<<2)|0;if(g)c[g>>2]=0;if(!b)break;else b=b+1|0}c[d>>2]=1;j=a+16|0;c[j>>2]=0;j=a+12|0;c[j>>2]=0;i=e;return}function Zc(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0;e=i;i=i+16|0;d=e;f=a+16|0;j=c[f>>2]|0;c[f>>2]=j+1;c[(c[a>>2]|0)+(j<<2)>>2]=b;j=c[f>>2]|0;b=a+4|0;h=c[b>>2]|0;if((j|0)==(h|0)){c[f>>2]=0;j=0}g=a+12|0;if((c[g>>2]|0)!=(j|0)){i=e;return}Qc(d,(h*3|0)+1>>1);l=c[g>>2]|0;m=c[b>>2]|0;if((l|0)<(m|0)){j=c[a>>2]|0;k=c[d>>2]|0;m=0;while(1){h=m+1|0;c[k+(m<<2)>>2]=c[j+(l<<2)>>2];l=l+1|0;m=c[b>>2]|0;if((l|0)>=(m|0)){k=h;break}else m=h}}else k=0;h=c[a>>2]|0;if((c[f>>2]|0)>0){j=c[d>>2]|0;l=0;while(1){c[j+(k<<2)>>2]=c[h+(l<<2)>>2];l=l+1|0;if((l|0)>=(c[f>>2]|0))break;else k=k+1|0}m=c[b>>2]|0}c[g>>2]=0;c[f>>2]=m;if(!h)f=a+8|0;else{c[b>>2]=0;Td(h);c[a>>2]=0;f=a+8|0;c[f>>2]=0}c[a>>2]=c[d>>2];l=d+4|0;c[b>>2]=c[l>>2];m=d+8|0;c[f>>2]=c[m>>2];c[d>>2]=0;c[l>>2]=0;c[m>>2]=0;i=e;return}function _c(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,j=0;d=i;e=a+4|0;f=c[e>>2]|0;g=a+8|0;h=c[g>>2]|0;if((f|0)==(h|0)&(h|0)<(f+1|0)){h=(f>>1)+2&-2;h=(h|0)<2?2:h;if((h|0)>(2147483647-f|0)){h=va(1)|0;Ta(h|0,48,0)}j=c[a>>2]|0;f=h+f|0;c[g>>2]=f;f=Ud(j,f<<2)|0;c[a>>2]=f;if((f|0)==0?(c[(Oa()|0)>>2]|0)==12:0){j=va(1)|0;Ta(j|0,48,0)}}else f=c[a>>2]|0;j=c[e>>2]|0;c[e>>2]=j+1;e=f+(j<<2)|0;if(!e){i=d;return}c[e>>2]=c[b>>2];i=d;return}function $c(){var a=0,b=0;b=i;Ka(3864)|0;a=od(936)|0;xc(a);i=b;return a|0}function ad(a){a=a|0;var b=0;b=i;if(!a){i=b;return}gb[c[(c[a>>2]|0)+4>>2]&31](a);i=b;return}function bd(){var b=0,d=0,e=0;b=i;i=i+16|0;d=b;e=od(936)|0;xc(e);c[964]=e;Cc(e,1)|0;e=c[964]|0;a[d+0>>0]=a[3840]|0;Ac(e,d,1)|0;i=b;return}function cd(b){b=b|0;var d=0,e=0,f=0;d=i;i=i+16|0;e=d;if((c[962]|0)>=(b|0)){i=d;return}do{f=c[964]|0;a[e+0>>0]=a[3840]|0;Ac(f,e,1)|0;f=(c[962]|0)+1|0;c[962]=f}while((f|0)<(b|0));i=d;return}function dd(b){b=b|0;var d=0,e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0;g=i;i=i+32|0;h=g+16|0;e=g+4|0;j=g;c[e>>2]=0;f=e+4|0;c[f>>2]=0;d=e+8|0;c[d>>2]=0;k=c[b>>2]|0;if(k)do{l=(k|0)<0?0-k|0:k;if((c[962]|0)<(l|0))do{m=c[964]|0;a[h+0>>0]=a[3840]|0;Ac(m,h,1)|0;m=(c[962]|0)+1|0;c[962]=m}while((m|0)<(l|0));c[j>>2]=l<<1|k>>>31;mc(e,j);b=b+4|0;k=c[b>>2]|0}while((k|0)!=0);j=c[964]|0;h=j+628|0;ld(e,h);h=Dc(j,h)|0;j=c[e>>2]|0;if(!j){i=g;return h|0}c[f>>2]=0;Td(j);c[e>>2]=0;c[d>>2]=0;i=g;return h|0}function ed(){var b=0,d=0,e=0,f=0;d=i;i=i+16|0;b=d;e=c[964]|0;f=e+664|0;c[f+0>>2]=-1;c[f+4>>2]=-1;c[f+8>>2]=-1;c[f+12>>2]=-1;if(c[e+304>>2]|0)c[e+308>>2]=0;Bc(b,e,1,0);i=d;return(a[b>>0]|0)==0|0}function fd(){return(c[(c[964]|0)+4>>2]|0)+1|0}function gd(){return c[962]|0}function hd(b){b=b|0;var d=0,e=0,f=0,g=0,h=0,j=0;d=i;i=i+32|0;h=d+16|0;f=d+4|0;j=d;c[f>>2]=0;e=f+4|0;c[e>>2]=0;g=f+8|0;c[g>>2]=0;c[j>>2]=b<<1;mc(f,j);b=c[964]|0;j=b+664|0;c[j+0>>2]=-1;c[j+4>>2]=-1;c[j+8>>2]=-1;c[j+12>>2]=-1;ld(f,b+304|0);Bc(h,b,1,0);b=(a[h>>0]|0)==0;h=c[f>>2]|0;if(!h){i=d;return b|0}c[e>>2]=0;Td(h);c[f>>2]=0;c[g>>2]=0;i=d;return b|0}function id(a){a=a|0;var b=0,d=0,e=0;b=i;i=i+16|0;e=b;d=c[964]|0;c[e>>2]=a<<1|1;a=d+628|0;if(c[a>>2]|0)c[d+632>>2]=0;mc(a,e);Dc(d,a)|0;i=b;return}function jd(){return c[(c[964]|0)+36>>2]|0}function kd(){return c[(c[964]|0)+32>>2]|0}function ld(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0;d=i;h=c[b>>2]|0;e=b+4|0;if(!h)j=c[e>>2]|0;else{c[e>>2]=0;j=0}e=a+4|0;f=c[e>>2]|0;g=b+4|0;if((j|0)<(f|0)){k=b+8|0;j=c[k>>2]|0;if((j|0)<(f|0)){m=f+1-j&-2;l=(j>>1)+2&-2;l=(m|0)>(l|0)?m:l;if((l|0)>(2147483647-j|0)){m=va(1)|0;Ta(m|0,48,0)}m=l+j|0;c[k>>2]=m;h=Ud(h,m<<2)|0;c[b>>2]=h;if((h|0)==0?(c[(Oa()|0)>>2]|0)==12:0){m=va(1)|0;Ta(m|0,48,0)}}j=c[g>>2]|0;a:do if((j|0)<(f|0))while(1){h=h+(j<<2)|0;if(h)c[h>>2]=0;j=j+1|0;if((j|0)==(f|0))break a;h=c[b>>2]|0}while(0);c[g>>2]=f;f=c[e>>2]|0}if((f|0)<=0){i=d;return}b=c[b>>2]|0;a=c[a>>2]|0;f=0;do{c[b+(f<<2)>>2]=c[a+(f<<2)>>2];f=f+1|0}while((f|0)<(c[e>>2]|0));i=d;return}function md(a,b){a=a|0;b=b|0;var d=0;d=i;i=i+16|0;c[d>>2]=b;b=c[p>>2]|0;ua(b|0,a|0,d|0)|0;Sa(10,b|0)|0;Wa()}function nd(){var a=0,b=0;a=i;i=i+16|0;if(!(Ja(4064,3)|0)){b=Ha(c[1014]|0)|0;i=a;return b|0}else md(4072,a);return 0}function od(a){a=a|0;var b=0,d=0;b=i;a=(a|0)==0?1:a;d=Sd(a)|0;if(d){i=b;return d|0}while(1){d=vd()|0;if(!d){a=4;break}jb[d&3]();d=Sd(a)|0;if(d){a=5;break}}if((a|0)==4){d=va(4)|0;c[d>>2]=4248;Ta(d|0,4296,12)}else if((a|0)==5){i=b;return d|0}return 0}function pd(a){a=a|0;var b=0;b=i;Td(a);i=b;return}function qd(a){a=a|0;var b=0;b=i;pd(a);i=b;return}function rd(a){a=a|0;return}function sd(a){a=a|0;return 4264}function td(a){a=a|0;var b=0;b=i;i=i+16|0;jb[a&3]();md(4312,b)}function ud(){var a=0,b=0;b=nd()|0;if(((b|0)!=0?(a=c[b>>2]|0,(a|0)!=0):0)?(b=a+48|0,(c[b>>2]&-256|0)==1126902528?(c[b+4>>2]|0)==1129074247:0):0)td(c[a+12>>2]|0);b=c[968]|0;c[968]=b+0;td(b)}function vd(){var a=0;a=c[1102]|0;c[1102]=a+0;return a|0}function wd(a){a=a|0;return}function xd(a){a=a|0;return}function yd(a){a=a|0;return}function zd(a){a=a|0;return}function Ad(a){a=a|0;return}function Bd(a){a=a|0;var b=0;b=i;pd(a);i=b;return}function Cd(a){a=a|0;var b=0;b=i;pd(a);i=b;return}function Dd(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0;e=i;i=i+64|0;f=e;if((a|0)==(b|0)){h=1;i=e;return h|0}if(!b){h=0;i=e;return h|0}b=Hd(b,4504,4560,0)|0;if(!b){h=0;i=e;return h|0}h=f+0|0;g=h+56|0;do{c[h>>2]=0;h=h+4|0}while((h|0)<(g|0));c[f>>2]=b;c[f+8>>2]=a;c[f+12>>2]=-1;c[f+48>>2]=1;mb[c[(c[b>>2]|0)+28>>2]&3](b,f,c[d>>2]|0,1);if((c[f+24>>2]|0)!=1){h=0;i=e;return h|0}c[d>>2]=c[f+16>>2];h=1;i=e;return h|0}function Ed(b,d,e,f){b=b|0;d=d|0;e=e|0;f=f|0;var g=0,h=0;b=i;g=d+16|0;h=c[g>>2]|0;if(!h){c[g>>2]=e;c[d+24>>2]=f;c[d+36>>2]=1;i=b;return}if((h|0)!=(e|0)){h=d+36|0;c[h>>2]=(c[h>>2]|0)+1;c[d+24>>2]=2;a[d+54>>0]=1;i=b;return}e=d+24|0;if((c[e>>2]|0)!=2){i=b;return}c[e>>2]=f;i=b;return}function Fd(a,b,d,e){a=a|0;b=b|0;d=d|0;e=e|0;var f=0;f=i;if((c[b+8>>2]|0)!=(a|0)){i=f;return}Ed(0,b,d,e);i=f;return}function Gd(a,b,d,e){a=a|0;b=b|0;d=d|0;e=e|0;var f=0;f=i;if((a|0)==(c[b+8>>2]|0)){Ed(0,b,d,e);i=f;return}else{a=c[a+8>>2]|0;mb[c[(c[a>>2]|0)+28>>2]&3](a,b,d,e);i=f;return}}function Hd(d,e,f,g){d=d|0;e=e|0;f=f|0;g=g|0;var h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;h=i;i=i+64|0;j=h;k=c[d>>2]|0;l=d+(c[k+ -8>>2]|0)|0;k=c[k+ -4>>2]|0;c[j>>2]=f;c[j+4>>2]=d;c[j+8>>2]=e;c[j+12>>2]=g;n=j+16|0;o=j+20|0;e=j+24|0;m=j+28|0;g=j+32|0;d=j+40|0;p=(k|0)==(f|0);q=n+0|0;f=q+36|0;do{c[q>>2]=0;q=q+4|0}while((q|0)<(f|0));b[n+36>>1]=0;a[n+38>>0]=0;if(p){c[j+48>>2]=1;kb[c[(c[k>>2]|0)+20>>2]&3](k,j,l,l,1,0);q=(c[e>>2]|0)==1?l:0;i=h;return q|0}fb[c[(c[k>>2]|0)+24>>2]&3](k,j,l,1,0);j=c[j+36>>2]|0;if(!j){q=(c[d>>2]|0)==1&(c[m>>2]|0)==1&(c[g>>2]|0)==1?c[o>>2]|0:0;i=h;return q|0}else if((j|0)==1){if((c[e>>2]|0)!=1?!((c[d>>2]|0)==0&(c[m>>2]|0)==1&(c[g>>2]|0)==1):0){q=0;i=h;return q|0}q=c[n>>2]|0;i=h;return q|0}else{q=0;i=h;return q|0}return 0}function Id(b,d,e,f,g){b=b|0;d=d|0;e=e|0;f=f|0;g=g|0;var h=0;b=i;a[d+53>>0]=1;if((c[d+4>>2]|0)!=(f|0)){i=b;return}a[d+52>>0]=1;f=d+16|0;h=c[f>>2]|0;if(!h){c[f>>2]=e;c[d+24>>2]=g;c[d+36>>2]=1;if(!((g|0)==1?(c[d+48>>2]|0)==1:0)){i=b;return}a[d+54>>0]=1;i=b;return}if((h|0)!=(e|0)){h=d+36|0;c[h>>2]=(c[h>>2]|0)+1;a[d+54>>0]=1;i=b;return}e=d+24|0;f=c[e>>2]|0;if((f|0)==2)c[e>>2]=g;else g=f;if(!((g|0)==1?(c[d+48>>2]|0)==1:0)){i=b;return}a[d+54>>0]=1;i=b;return}function Jd(b,d,e,f,g){b=b|0;d=d|0;e=e|0;f=f|0;g=g|0;var h=0,j=0,k=0,l=0,m=0;h=i;if((b|0)==(c[d+8>>2]|0)){if((c[d+4>>2]|0)!=(e|0)){i=h;return}j=d+28|0;if((c[j>>2]|0)==1){i=h;return}c[j>>2]=f;i=h;return}if((b|0)!=(c[d>>2]|0)){l=c[b+8>>2]|0;fb[c[(c[l>>2]|0)+24>>2]&3](l,d,e,f,g);i=h;return}if((c[d+16>>2]|0)!=(e|0)?(k=d+20|0,(c[k>>2]|0)!=(e|0)):0){c[d+32>>2]=f;f=d+44|0;if((c[f>>2]|0)==4){i=h;return}l=d+52|0;a[l>>0]=0;m=d+53|0;a[m>>0]=0;b=c[b+8>>2]|0;kb[c[(c[b>>2]|0)+20>>2]&3](b,d,e,e,1,g);if(a[m>>0]|0){if(!(a[l>>0]|0)){b=1;j=13}}else{b=0;j=13}do if((j|0)==13){c[k>>2]=e;m=d+40|0;c[m>>2]=(c[m>>2]|0)+1;if((c[d+36>>2]|0)==1?(c[d+24>>2]|0)==2:0){a[d+54>>0]=1;if(b)break}else j=16;if((j|0)==16?b:0)break;c[f>>2]=4;i=h;return}while(0);c[f>>2]=3;i=h;return}if((f|0)!=1){i=h;return}c[d+32>>2]=1;i=h;return}function Kd(b,d,e,f,g){b=b|0;d=d|0;e=e|0;f=f|0;g=g|0;var h=0;g=i;if((c[d+8>>2]|0)==(b|0)){if((c[d+4>>2]|0)!=(e|0)){i=g;return}d=d+28|0;if((c[d>>2]|0)==1){i=g;return}c[d>>2]=f;i=g;return}if((c[d>>2]|0)!=(b|0)){i=g;return}if((c[d+16>>2]|0)!=(e|0)?(h=d+20|0,(c[h>>2]|0)!=(e|0)):0){c[d+32>>2]=f;c[h>>2]=e;b=d+40|0;c[b>>2]=(c[b>>2]|0)+1;if((c[d+36>>2]|0)==1?(c[d+24>>2]|0)==2:0)a[d+54>>0]=1;c[d+44>>2]=4;i=g;return}if((f|0)!=1){i=g;return}c[d+32>>2]=1;i=g;return}function Ld(a,b,d,e,f,g){a=a|0;b=b|0;d=d|0;e=e|0;f=f|0;g=g|0;var h=0;h=i;if((a|0)==(c[b+8>>2]|0)){Id(0,b,d,e,f);i=h;return}else{a=c[a+8>>2]|0;kb[c[(c[a>>2]|0)+20>>2]&3](a,b,d,e,f,g);i=h;return}}function Md(a,b,d,e,f,g){a=a|0;b=b|0;d=d|0;e=e|0;f=f|0;g=g|0;g=i;if((c[b+8>>2]|0)!=(a|0)){i=g;return}Id(0,b,d,e,f);i=g;return}function Nd(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0;e=i;i=i+16|0;f=e;c[f>>2]=c[d>>2];a=eb[c[(c[a>>2]|0)+16>>2]&1](a,b,f)|0;b=a&1;if(!a){i=e;return b|0}c[d>>2]=c[f>>2];i=e;return b|0}function Od(a){a=a|0;var b=0;b=i;if(!a)a=0;else a=(Hd(a,4504,4672,0)|0)!=0;i=b;return a&1|0}function Pd(){var a=0,b=0,d=0,e=0,f=0;a=i;i=i+16|0;b=a;a=a+12|0;d=nd()|0;if(!d)md(4040,b);d=c[d>>2]|0;if(!d)md(4040,b);f=d+48|0;e=c[f>>2]|0;f=c[f+4>>2]|0;if(!((e&-256|0)==1126902528&(f|0)==1129074247)){c[b>>2]=c[970];md(4e3,b)}if((e|0)==1126902529&(f|0)==1129074247)e=c[d+44>>2]|0;else e=d+80|0;c[a>>2]=e;f=c[d>>2]|0;d=c[f+4>>2]|0;if(eb[c[(c[4432>>2]|0)+16>>2]&1](4432,f,a)|0){f=c[a>>2]|0;e=c[970]|0;f=ib[c[(c[f>>2]|0)+8>>2]&1](f)|0;c[b>>2]=e;c[b+4>>2]=d;c[b+8>>2]=f;md(3904,b)}else{c[b>>2]=c[970];c[b+4>>2]=d;md(3952,b)}}function Qd(){var a=0;a=i;i=i+16|0;if(!(Ma(4056,20)|0)){i=a;return}else md(4128,a)}function Rd(a){a=a|0;var b=0;b=i;i=i+16|0;Td(a);if(!(Pa(c[1014]|0,0)|0)){i=b;return}else md(4184,b)}function Sd(a){a=a|0;var b=0,d=0,e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0;b=i;do if(a>>>0<245){if(a>>>0<11)a=16;else a=a+11&-8;x=a>>>3;p=c[1206]|0;w=p>>>x;if(w&3){g=(w&1^1)+x|0;f=g<<1;d=4864+(f<<2)|0;f=4864+(f+2<<2)|0;h=c[f>>2]|0;j=h+8|0;e=c[j>>2]|0;do if((d|0)!=(e|0)){if(e>>>0<(c[1210]|0)>>>0)Wa();k=e+12|0;if((c[k>>2]|0)==(h|0)){c[k>>2]=d;c[f>>2]=e;break}else Wa()}else c[1206]=p&~(1<<g);while(0);H=g<<3;c[h+4>>2]=H|3;H=h+(H|4)|0;c[H>>2]=c[H>>2]|1;H=j;i=b;return H|0}v=c[1208]|0;if(a>>>0>v>>>0){if(w){h=2<<x;h=w<<x&(h|0-h);h=(h&0-h)+ -1|0;d=h>>>12&16;h=h>>>d;j=h>>>5&8;h=h>>>j;f=h>>>2&4;h=h>>>f;g=h>>>1&2;h=h>>>g;e=h>>>1&1;e=(j|d|f|g|e)+(h>>>e)|0;h=e<<1;g=4864+(h<<2)|0;h=4864+(h+2<<2)|0;f=c[h>>2]|0;d=f+8|0;j=c[d>>2]|0;do if((g|0)!=(j|0)){if(j>>>0<(c[1210]|0)>>>0)Wa();k=j+12|0;if((c[k>>2]|0)==(f|0)){c[k>>2]=g;c[h>>2]=j;E=c[1208]|0;break}else Wa()}else{c[1206]=p&~(1<<e);E=v}while(0);H=e<<3;e=H-a|0;c[f+4>>2]=a|3;g=f+a|0;c[f+(a|4)>>2]=e|1;c[f+H>>2]=e;if(E){f=c[1211]|0;l=E>>>3;j=l<<1;h=4864+(j<<2)|0;k=c[1206]|0;l=1<<l;if(k&l){j=4864+(j+2<<2)|0;k=c[j>>2]|0;if(k>>>0<(c[1210]|0)>>>0)Wa();else{D=j;C=k}}else{c[1206]=k|l;D=4864+(j+2<<2)|0;C=h}c[D>>2]=f;c[C+12>>2]=f;c[f+8>>2]=C;c[f+12>>2]=h}c[1208]=e;c[1211]=g;H=d;i=b;return H|0}p=c[1207]|0;if(p){d=(p&0-p)+ -1|0;G=d>>>12&16;d=d>>>G;F=d>>>5&8;d=d>>>F;H=d>>>2&4;d=d>>>H;f=d>>>1&2;d=d>>>f;e=d>>>1&1;e=c[5128+((F|G|H|f|e)+(d>>>e)<<2)>>2]|0;d=(c[e+4>>2]&-8)-a|0;f=e;while(1){g=c[f+16>>2]|0;if(!g){g=c[f+20>>2]|0;if(!g)break}f=(c[g+4>>2]&-8)-a|0;H=f>>>0<d>>>0;d=H?f:d;f=g;e=H?g:e}h=c[1210]|0;if(e>>>0<h>>>0)Wa();f=e+a|0;if(e>>>0>=f>>>0)Wa();g=c[e+24>>2]|0;k=c[e+12>>2]|0;do if((k|0)==(e|0)){k=e+20|0;j=c[k>>2]|0;if(!j){k=e+16|0;j=c[k>>2]|0;if(!j){B=0;break}}while(1){l=j+20|0;m=c[l>>2]|0;if(m){j=m;k=l;continue}l=j+16|0;m=c[l>>2]|0;if(!m)break;else{j=m;k=l}}if(k>>>0<h>>>0)Wa();else{c[k>>2]=0;B=j;break}}else{j=c[e+8>>2]|0;if(j>>>0<h>>>0)Wa();h=j+12|0;if((c[h>>2]|0)!=(e|0))Wa();l=k+8|0;if((c[l>>2]|0)==(e|0)){c[h>>2]=k;c[l>>2]=j;B=k;break}else Wa()}while(0);do if(g){j=c[e+28>>2]|0;h=5128+(j<<2)|0;if((e|0)==(c[h>>2]|0)){c[h>>2]=B;if(!B){c[1207]=c[1207]&~(1<<j);break}}else{if(g>>>0<(c[1210]|0)>>>0)Wa();h=g+16|0;if((c[h>>2]|0)==(e|0))c[h>>2]=B;else c[g+20>>2]=B;if(!B)break}h=c[1210]|0;if(B>>>0<h>>>0)Wa();c[B+24>>2]=g;g=c[e+16>>2]|0;do if(g)if(g>>>0<h>>>0)Wa();else{c[B+16>>2]=g;c[g+24>>2]=B;break}while(0);g=c[e+20>>2]|0;if(g)if(g>>>0<(c[1210]|0)>>>0)Wa();else{c[B+20>>2]=g;c[g+24>>2]=B;break}}while(0);if(d>>>0<16){H=d+a|0;c[e+4>>2]=H|3;H=e+(H+4)|0;c[H>>2]=c[H>>2]|1}else{c[e+4>>2]=a|3;c[e+(a|4)>>2]=d|1;c[e+(d+a)>>2]=d;h=c[1208]|0;if(h){g=c[1211]|0;k=h>>>3;l=k<<1;h=4864+(l<<2)|0;j=c[1206]|0;k=1<<k;if(j&k){j=4864+(l+2<<2)|0;k=c[j>>2]|0;if(k>>>0<(c[1210]|0)>>>0)Wa();else{A=j;z=k}}else{c[1206]=j|k;A=4864+(l+2<<2)|0;z=h}c[A>>2]=g;c[z+12>>2]=g;c[g+8>>2]=z;c[g+12>>2]=h}c[1208]=d;c[1211]=f}H=e+8|0;i=b;return H|0}}}else if(a>>>0<=4294967231){z=a+11|0;a=z&-8;B=c[1207]|0;if(B){A=0-a|0;z=z>>>8;if(z)if(a>>>0>16777215)C=31;else{G=(z+1048320|0)>>>16&8;H=z<<G;F=(H+520192|0)>>>16&4;H=H<<F;C=(H+245760|0)>>>16&2;C=14-(F|G|C)+(H<<C>>>15)|0;C=a>>>(C+7|0)&1|C<<1}else C=0;D=c[5128+(C<<2)>>2]|0;a:do if(!D){F=0;z=0}else{if((C|0)==31)z=0;else z=25-(C>>>1)|0;F=0;E=a<<z;z=0;while(1){G=c[D+4>>2]&-8;H=G-a|0;if(H>>>0<A>>>0)if((G|0)==(a|0)){A=H;F=D;z=D;break a}else{A=H;z=D}H=c[D+20>>2]|0;D=c[D+(E>>>31<<2)+16>>2]|0;F=(H|0)==0|(H|0)==(D|0)?F:H;if(!D)break;else E=E<<1}}while(0);if((F|0)==0&(z|0)==0){H=2<<C;B=B&(H|0-H);if(!B)break;H=(B&0-B)+ -1|0;D=H>>>12&16;H=H>>>D;C=H>>>5&8;H=H>>>C;E=H>>>2&4;H=H>>>E;G=H>>>1&2;H=H>>>G;F=H>>>1&1;F=c[5128+((C|D|E|G|F)+(H>>>F)<<2)>>2]|0}if(F)while(1){H=(c[F+4>>2]&-8)-a|0;B=H>>>0<A>>>0;A=B?H:A;z=B?F:z;B=c[F+16>>2]|0;if(B){F=B;continue}F=c[F+20>>2]|0;if(!F)break}if((z|0)!=0?A>>>0<((c[1208]|0)-a|0)>>>0:0){f=c[1210]|0;if(z>>>0<f>>>0)Wa();d=z+a|0;if(z>>>0>=d>>>0)Wa();e=c[z+24>>2]|0;g=c[z+12>>2]|0;do if((g|0)==(z|0)){h=z+20|0;g=c[h>>2]|0;if(!g){h=z+16|0;g=c[h>>2]|0;if(!g){x=0;break}}while(1){j=g+20|0;k=c[j>>2]|0;if(k){g=k;h=j;continue}j=g+16|0;k=c[j>>2]|0;if(!k)break;else{g=k;h=j}}if(h>>>0<f>>>0)Wa();else{c[h>>2]=0;x=g;break}}else{h=c[z+8>>2]|0;if(h>>>0<f>>>0)Wa();j=h+12|0;if((c[j>>2]|0)!=(z|0))Wa();f=g+8|0;if((c[f>>2]|0)==(z|0)){c[j>>2]=g;c[f>>2]=h;x=g;break}else Wa()}while(0);do if(e){f=c[z+28>>2]|0;g=5128+(f<<2)|0;if((z|0)==(c[g>>2]|0)){c[g>>2]=x;if(!x){c[1207]=c[1207]&~(1<<f);break}}else{if(e>>>0<(c[1210]|0)>>>0)Wa();f=e+16|0;if((c[f>>2]|0)==(z|0))c[f>>2]=x;else c[e+20>>2]=x;if(!x)break}f=c[1210]|0;if(x>>>0<f>>>0)Wa();c[x+24>>2]=e;e=c[z+16>>2]|0;do if(e)if(e>>>0<f>>>0)Wa();else{c[x+16>>2]=e;c[e+24>>2]=x;break}while(0);e=c[z+20>>2]|0;if(e)if(e>>>0<(c[1210]|0)>>>0)Wa();else{c[x+20>>2]=e;c[e+24>>2]=x;break}}while(0);b:do if(A>>>0>=16){c[z+4>>2]=a|3;c[z+(a|4)>>2]=A|1;c[z+(A+a)>>2]=A;f=A>>>3;if(A>>>0<256){h=f<<1;e=4864+(h<<2)|0;g=c[1206]|0;f=1<<f;do if(!(g&f)){c[1206]=g|f;w=4864+(h+2<<2)|0;v=e}else{f=4864+(h+2<<2)|0;g=c[f>>2]|0;if(g>>>0>=(c[1210]|0)>>>0){w=f;v=g;break}Wa()}while(0);c[w>>2]=d;c[v+12>>2]=d;c[z+(a+8)>>2]=v;c[z+(a+12)>>2]=e;break}e=A>>>8;if(e)if(A>>>0>16777215)e=31;else{G=(e+1048320|0)>>>16&8;H=e<<G;F=(H+520192|0)>>>16&4;H=H<<F;e=(H+245760|0)>>>16&2;e=14-(F|G|e)+(H<<e>>>15)|0;e=A>>>(e+7|0)&1|e<<1}else e=0;f=5128+(e<<2)|0;c[z+(a+28)>>2]=e;c[z+(a+20)>>2]=0;c[z+(a+16)>>2]=0;g=c[1207]|0;h=1<<e;if(!(g&h)){c[1207]=g|h;c[f>>2]=d;c[z+(a+24)>>2]=f;c[z+(a+12)>>2]=d;c[z+(a+8)>>2]=d;break}h=c[f>>2]|0;if((e|0)==31)e=0;else e=25-(e>>>1)|0;c:do if((c[h+4>>2]&-8|0)!=(A|0)){e=A<<e;while(1){g=h+(e>>>31<<2)+16|0;f=c[g>>2]|0;if(!f)break;if((c[f+4>>2]&-8|0)==(A|0)){p=f;break c}else{e=e<<1;h=f}}if(g>>>0<(c[1210]|0)>>>0)Wa();else{c[g>>2]=d;c[z+(a+24)>>2]=h;c[z+(a+12)>>2]=d;c[z+(a+8)>>2]=d;break b}}else p=h;while(0);f=p+8|0;e=c[f>>2]|0;H=c[1210]|0;if(p>>>0>=H>>>0&e>>>0>=H>>>0){c[e+12>>2]=d;c[f>>2]=d;c[z+(a+8)>>2]=e;c[z+(a+12)>>2]=p;c[z+(a+24)>>2]=0;break}else Wa()}else{H=A+a|0;c[z+4>>2]=H|3;H=z+(H+4)|0;c[H>>2]=c[H>>2]|1}while(0);H=z+8|0;i=b;return H|0}}}else a=-1;while(0);p=c[1208]|0;if(p>>>0>=a>>>0){e=p-a|0;d=c[1211]|0;if(e>>>0>15){c[1211]=d+a;c[1208]=e;c[d+(a+4)>>2]=e|1;c[d+p>>2]=e;c[d+4>>2]=a|3}else{c[1208]=0;c[1211]=0;c[d+4>>2]=p|3;H=d+(p+4)|0;c[H>>2]=c[H>>2]|1}H=d+8|0;i=b;return H|0}p=c[1209]|0;if(p>>>0>a>>>0){G=p-a|0;c[1209]=G;H=c[1212]|0;c[1212]=H+a;c[H+(a+4)>>2]=G|1;c[H+4>>2]=a|3;H=H+8|0;i=b;return H|0}do if(!(c[1324]|0)){p=Ga(30)|0;if(!(p+ -1&p)){c[1326]=p;c[1325]=p;c[1327]=-1;c[1328]=-1;c[1329]=0;c[1317]=0;c[1324]=(Ya(0)|0)&-16^1431655768;break}else Wa()}while(0);x=a+48|0;p=c[1326]|0;w=a+47|0;A=p+w|0;p=0-p|0;v=A&p;if(v>>>0<=a>>>0){H=0;i=b;return H|0}z=c[1316]|0;if((z|0)!=0?(G=c[1314]|0,H=G+v|0,H>>>0<=G>>>0|H>>>0>z>>>0):0){H=0;i=b;return H|0}d:do if(!(c[1317]&4)){B=c[1212]|0;e:do if(B){z=5272|0;while(1){C=c[z>>2]|0;if(C>>>0<=B>>>0?(y=z+4|0,(C+(c[y>>2]|0)|0)>>>0>B>>>0):0)break;z=c[z+8>>2]|0;if(!z){o=181;break e}}if(z){A=A-(c[1209]|0)&p;if(A>>>0<2147483647){p=Aa(A|0)|0;if((p|0)==((c[z>>2]|0)+(c[y>>2]|0)|0)){z=A;o=190}else{z=A;o=191}}else z=0}else o=181}else o=181;while(0);do if((o|0)==181){y=Aa(0)|0;if((y|0)!=(-1|0)){A=y;z=c[1325]|0;p=z+ -1|0;if(!(p&A))z=v;else z=v-A+(p+A&0-z)|0;p=c[1314]|0;A=p+z|0;if(z>>>0>a>>>0&z>>>0<2147483647){H=c[1316]|0;if((H|0)!=0?A>>>0<=p>>>0|A>>>0>H>>>0:0){z=0;break}p=Aa(z|0)|0;if((p|0)==(y|0)){p=y;o=190}else o=191}else z=0}else z=0}while(0);f:do if((o|0)==190){if((p|0)!=(-1|0)){q=z;o=201;break d}}else if((o|0)==191){o=0-z|0;do if((p|0)!=(-1|0)&z>>>0<2147483647&x>>>0>z>>>0?(u=c[1326]|0,u=w-z+u&0-u,u>>>0<2147483647):0)if((Aa(u|0)|0)==(-1|0)){Aa(o|0)|0;z=0;break f}else{z=u+z|0;break}while(0);if((p|0)==(-1|0))z=0;else{q=z;o=201;break d}}while(0);c[1317]=c[1317]|4;o=198}else{z=0;o=198}while(0);if((((o|0)==198?v>>>0<2147483647:0)?(t=Aa(v|0)|0,s=Aa(0)|0,(t|0)!=(-1|0)&(s|0)!=(-1|0)&t>>>0<s>>>0):0)?(r=s-t|0,q=r>>>0>(a+40|0)>>>0,q):0){p=t;q=q?r:z;o=201}if((o|0)==201){r=(c[1314]|0)+q|0;c[1314]=r;if(r>>>0>(c[1315]|0)>>>0)c[1315]=r;r=c[1212]|0;g:do if(r){t=5272|0;while(1){s=c[t>>2]|0;v=t+4|0;w=c[v>>2]|0;if((p|0)==(s+w|0)){o=213;break}u=c[t+8>>2]|0;if(!u)break;else t=u}if(((o|0)==213?(c[t+12>>2]&8|0)==0:0)?r>>>0>=s>>>0&r>>>0<p>>>0:0){c[v>>2]=w+q;d=(c[1209]|0)+q|0;e=r+8|0;if(!(e&7))e=0;else e=0-e&7;H=d-e|0;c[1212]=r+e;c[1209]=H;c[r+(e+4)>>2]=H|1;c[r+(d+4)>>2]=40;c[1213]=c[1328];break}s=c[1210]|0;if(p>>>0<s>>>0){c[1210]=p;s=p}v=p+q|0;t=5272|0;while(1){if((c[t>>2]|0)==(v|0)){o=223;break}u=c[t+8>>2]|0;if(!u)break;else t=u}if((o|0)==223?(c[t+12>>2]&8|0)==0:0){c[t>>2]=p;h=t+4|0;c[h>>2]=(c[h>>2]|0)+q;h=p+8|0;if(!(h&7))h=0;else h=0-h&7;j=p+(q+8)|0;if(!(j&7))n=0;else n=0-j&7;o=p+(n+q)|0;k=h+a|0;j=p+k|0;m=o-(p+h)-a|0;c[p+(h+4)>>2]=a|3;h:do if((o|0)!=(r|0)){if((o|0)==(c[1211]|0)){H=(c[1208]|0)+m|0;c[1208]=H;c[1211]=j;c[p+(k+4)>>2]=H|1;c[p+(H+k)>>2]=H;break}r=q+4|0;u=c[p+(r+n)>>2]|0;if((u&3|0)==1){a=u&-8;t=u>>>3;i:do if(u>>>0>=256){l=c[p+((n|24)+q)>>2]|0;t=c[p+(q+12+n)>>2]|0;do if((t|0)==(o|0)){v=n|16;u=p+(r+v)|0;t=c[u>>2]|0;if(!t){u=p+(v+q)|0;t=c[u>>2]|0;if(!t){g=0;break}}while(1){w=t+20|0;v=c[w>>2]|0;if(v){t=v;u=w;continue}w=t+16|0;v=c[w>>2]|0;if(!v)break;else{t=v;u=w}}if(u>>>0<s>>>0)Wa();else{c[u>>2]=0;g=t;break}}else{u=c[p+((n|8)+q)>>2]|0;if(u>>>0<s>>>0)Wa();v=u+12|0;if((c[v>>2]|0)!=(o|0))Wa();s=t+8|0;if((c[s>>2]|0)==(o|0)){c[v>>2]=t;c[s>>2]=u;g=t;break}else Wa()}while(0);if(!l)break;s=c[p+(q+28+n)>>2]|0;t=5128+(s<<2)|0;do if((o|0)!=(c[t>>2]|0)){if(l>>>0<(c[1210]|0)>>>0)Wa();s=l+16|0;if((c[s>>2]|0)==(o|0))c[s>>2]=g;else c[l+20>>2]=g;if(!g)break i}else{c[t>>2]=g;if(g)break;c[1207]=c[1207]&~(1<<s);break i}while(0);o=c[1210]|0;if(g>>>0<o>>>0)Wa();c[g+24>>2]=l;s=n|16;l=c[p+(s+q)>>2]|0;do if(l)if(l>>>0<o>>>0)Wa();else{c[g+16>>2]=l;c[l+24>>2]=g;break}while(0);l=c[p+(r+s)>>2]|0;if(!l)break;if(l>>>0<(c[1210]|0)>>>0)Wa();else{c[g+20>>2]=l;c[l+24>>2]=g;break}}else{g=c[p+((n|8)+q)>>2]|0;r=c[p+(q+12+n)>>2]|0;u=4864+(t<<1<<2)|0;do if((g|0)!=(u|0)){if(g>>>0<s>>>0)Wa();if((c[g+12>>2]|0)==(o|0))break;Wa()}while(0);if((r|0)==(g|0)){c[1206]=c[1206]&~(1<<t);break}do if((r|0)==(u|0))l=r+8|0;else{if(r>>>0<s>>>0)Wa();s=r+8|0;if((c[s>>2]|0)==(o|0)){l=s;break}Wa()}while(0);c[g+12>>2]=r;c[l>>2]=g}while(0);o=p+((a|n)+q)|0;m=a+m|0}g=o+4|0;c[g>>2]=c[g>>2]&-2;c[p+(k+4)>>2]=m|1;c[p+(m+k)>>2]=m;g=m>>>3;if(m>>>0<256){l=g<<1;d=4864+(l<<2)|0;m=c[1206]|0;g=1<<g;do if(!(m&g)){c[1206]=m|g;f=4864+(l+2<<2)|0;e=d}else{l=4864+(l+2<<2)|0;g=c[l>>2]|0;if(g>>>0>=(c[1210]|0)>>>0){f=l;e=g;break}Wa()}while(0);c[f>>2]=j;c[e+12>>2]=j;c[p+(k+8)>>2]=e;c[p+(k+12)>>2]=d;break}e=m>>>8;do if(!e)e=0;else{if(m>>>0>16777215){e=31;break}G=(e+1048320|0)>>>16&8;H=e<<G;F=(H+520192|0)>>>16&4;H=H<<F;e=(H+245760|0)>>>16&2;e=14-(F|G|e)+(H<<e>>>15)|0;e=m>>>(e+7|0)&1|e<<1}while(0);l=5128+(e<<2)|0;c[p+(k+28)>>2]=e;c[p+(k+20)>>2]=0;c[p+(k+16)>>2]=0;g=c[1207]|0;f=1<<e;if(!(g&f)){c[1207]=g|f;c[l>>2]=j;c[p+(k+24)>>2]=l;c[p+(k+12)>>2]=j;c[p+(k+8)>>2]=j;break}f=c[l>>2]|0;if((e|0)==31)e=0;else e=25-(e>>>1)|0;j:do if((c[f+4>>2]&-8|0)!=(m|0)){e=m<<e;while(1){g=f+(e>>>31<<2)+16|0;l=c[g>>2]|0;if(!l)break;if((c[l+4>>2]&-8|0)==(m|0)){d=l;break j}else{e=e<<1;f=l}}if(g>>>0<(c[1210]|0)>>>0)Wa();else{c[g>>2]=j;c[p+(k+24)>>2]=f;c[p+(k+12)>>2]=j;c[p+(k+8)>>2]=j;break h}}else d=f;while(0);e=d+8|0;f=c[e>>2]|0;H=c[1210]|0;if(d>>>0>=H>>>0&f>>>0>=H>>>0){c[f+12>>2]=j;c[e>>2]=j;c[p+(k+8)>>2]=f;c[p+(k+12)>>2]=d;c[p+(k+24)>>2]=0;break}else Wa()}else{H=(c[1209]|0)+m|0;c[1209]=H;c[1212]=j;c[p+(k+4)>>2]=H|1}while(0);H=p+(h|8)|0;i=b;return H|0}e=5272|0;while(1){d=c[e>>2]|0;if(d>>>0<=r>>>0?(n=c[e+4>>2]|0,m=d+n|0,m>>>0>r>>>0):0)break;e=c[e+8>>2]|0}e=d+(n+ -39)|0;if(!(e&7))e=0;else e=0-e&7;d=d+(n+ -47+e)|0;d=d>>>0<(r+16|0)>>>0?r:d;e=d+8|0;f=p+8|0;if(!(f&7))f=0;else f=0-f&7;H=q+ -40-f|0;c[1212]=p+f;c[1209]=H;c[p+(f+4)>>2]=H|1;c[p+(q+ -36)>>2]=40;c[1213]=c[1328];c[d+4>>2]=27;c[e+0>>2]=c[1318];c[e+4>>2]=c[1319];c[e+8>>2]=c[1320];c[e+12>>2]=c[1321];c[1318]=p;c[1319]=q;c[1321]=0;c[1320]=e;e=d+28|0;c[e>>2]=7;if((d+32|0)>>>0<m>>>0)do{H=e;e=e+4|0;c[e>>2]=7}while((H+8|0)>>>0<m>>>0);if((d|0)!=(r|0)){d=d-r|0;e=r+(d+4)|0;c[e>>2]=c[e>>2]&-2;c[r+4>>2]=d|1;c[r+d>>2]=d;e=d>>>3;if(d>>>0<256){f=e<<1;d=4864+(f<<2)|0;g=c[1206]|0;e=1<<e;do if(!(g&e)){c[1206]=g|e;k=4864+(f+2<<2)|0;j=d}else{f=4864+(f+2<<2)|0;e=c[f>>2]|0;if(e>>>0>=(c[1210]|0)>>>0){k=f;j=e;break}Wa()}while(0);c[k>>2]=r;c[j+12>>2]=r;c[r+8>>2]=j;c[r+12>>2]=d;break}e=d>>>8;if(e)if(d>>>0>16777215)e=31;else{G=(e+1048320|0)>>>16&8;H=e<<G;F=(H+520192|0)>>>16&4;H=H<<F;e=(H+245760|0)>>>16&2;e=14-(F|G|e)+(H<<e>>>15)|0;e=d>>>(e+7|0)&1|e<<1}else e=0;j=5128+(e<<2)|0;c[r+28>>2]=e;c[r+20>>2]=0;c[r+16>>2]=0;f=c[1207]|0;g=1<<e;if(!(f&g)){c[1207]=f|g;c[j>>2]=r;c[r+24>>2]=j;c[r+12>>2]=r;c[r+8>>2]=r;break}f=c[j>>2]|0;if((e|0)==31)e=0;else e=25-(e>>>1)|0;k:do if((c[f+4>>2]&-8|0)!=(d|0)){e=d<<e;j=f;while(1){f=j+(e>>>31<<2)+16|0;g=c[f>>2]|0;if(!g)break;if((c[g+4>>2]&-8|0)==(d|0)){h=g;break k}else{e=e<<1;j=g}}if(f>>>0<(c[1210]|0)>>>0)Wa();else{c[f>>2]=r;c[r+24>>2]=j;c[r+12>>2]=r;c[r+8>>2]=r;break g}}else h=f;while(0);e=h+8|0;d=c[e>>2]|0;H=c[1210]|0;if(h>>>0>=H>>>0&d>>>0>=H>>>0){c[d+12>>2]=r;c[e>>2]=r;c[r+8>>2]=d;c[r+12>>2]=h;c[r+24>>2]=0;break}else Wa()}}else{H=c[1210]|0;if((H|0)==0|p>>>0<H>>>0)c[1210]=p;c[1318]=p;c[1319]=q;c[1321]=0;c[1215]=c[1324];c[1214]=-1;d=0;do{H=d<<1;G=4864+(H<<2)|0;c[4864+(H+3<<2)>>2]=G;c[4864+(H+2<<2)>>2]=G;d=d+1|0}while((d|0)!=32);d=p+8|0;if(!(d&7))d=0;else d=0-d&7;H=q+ -40-d|0;c[1212]=p+d;c[1209]=H;c[p+(d+4)>>2]=H|1;c[p+(q+ -36)>>2]=40;c[1213]=c[1328]}while(0);d=c[1209]|0;if(d>>>0>a>>>0){G=d-a|0;c[1209]=G;H=c[1212]|0;c[1212]=H+a;c[H+(a+4)>>2]=G|1;c[H+4>>2]=a|3;H=H+8|0;i=b;return H|0}}c[(Oa()|0)>>2]=12;H=0;i=b;return H|0}function Td(a){a=a|0;var b=0,d=0,e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0;b=i;if(!a){i=b;return}q=a+ -8|0;r=c[1210]|0;if(q>>>0<r>>>0)Wa();n=c[a+ -4>>2]|0;m=n&3;if((m|0)==1)Wa();j=n&-8;h=a+(j+ -8)|0;do if(!(n&1)){u=c[q>>2]|0;if(!m){i=b;return}q=-8-u|0;n=a+q|0;m=u+j|0;if(n>>>0<r>>>0)Wa();if((n|0)==(c[1211]|0)){e=a+(j+ -4)|0;o=c[e>>2]|0;if((o&3|0)!=3){e=n;o=m;break}c[1208]=m;c[e>>2]=o&-2;c[a+(q+4)>>2]=m|1;c[h>>2]=m;i=b;return}t=u>>>3;if(u>>>0<256){e=c[a+(q+8)>>2]|0;o=c[a+(q+12)>>2]|0;p=4864+(t<<1<<2)|0;if((e|0)!=(p|0)){if(e>>>0<r>>>0)Wa();if((c[e+12>>2]|0)!=(n|0))Wa()}if((o|0)==(e|0)){c[1206]=c[1206]&~(1<<t);e=n;o=m;break}if((o|0)!=(p|0)){if(o>>>0<r>>>0)Wa();p=o+8|0;if((c[p>>2]|0)==(n|0))s=p;else Wa()}else s=o+8|0;c[e+12>>2]=o;c[s>>2]=e;e=n;o=m;break}s=c[a+(q+24)>>2]|0;t=c[a+(q+12)>>2]|0;do if((t|0)==(n|0)){u=a+(q+20)|0;t=c[u>>2]|0;if(!t){u=a+(q+16)|0;t=c[u>>2]|0;if(!t){p=0;break}}while(1){v=t+20|0;w=c[v>>2]|0;if(w){t=w;u=v;continue}v=t+16|0;w=c[v>>2]|0;if(!w)break;else{t=w;u=v}}if(u>>>0<r>>>0)Wa();else{c[u>>2]=0;p=t;break}}else{u=c[a+(q+8)>>2]|0;if(u>>>0<r>>>0)Wa();r=u+12|0;if((c[r>>2]|0)!=(n|0))Wa();v=t+8|0;if((c[v>>2]|0)==(n|0)){c[r>>2]=t;c[v>>2]=u;p=t;break}else Wa()}while(0);if(s){r=c[a+(q+28)>>2]|0;t=5128+(r<<2)|0;if((n|0)==(c[t>>2]|0)){c[t>>2]=p;if(!p){c[1207]=c[1207]&~(1<<r);e=n;o=m;break}}else{if(s>>>0<(c[1210]|0)>>>0)Wa();r=s+16|0;if((c[r>>2]|0)==(n|0))c[r>>2]=p;else c[s+20>>2]=p;if(!p){e=n;o=m;break}}r=c[1210]|0;if(p>>>0<r>>>0)Wa();c[p+24>>2]=s;s=c[a+(q+16)>>2]|0;do if(s)if(s>>>0<r>>>0)Wa();else{c[p+16>>2]=s;c[s+24>>2]=p;break}while(0);q=c[a+(q+20)>>2]|0;if(q)if(q>>>0<(c[1210]|0)>>>0)Wa();else{c[p+20>>2]=q;c[q+24>>2]=p;e=n;o=m;break}else{e=n;o=m}}else{e=n;o=m}}else{e=q;o=j}while(0);if(e>>>0>=h>>>0)Wa();m=a+(j+ -4)|0;n=c[m>>2]|0;if(!(n&1))Wa();if(!(n&2)){if((h|0)==(c[1212]|0)){w=(c[1209]|0)+o|0;c[1209]=w;c[1212]=e;c[e+4>>2]=w|1;if((e|0)!=(c[1211]|0)){i=b;return}c[1211]=0;c[1208]=0;i=b;return}if((h|0)==(c[1211]|0)){w=(c[1208]|0)+o|0;c[1208]=w;c[1211]=e;c[e+4>>2]=w|1;c[e+w>>2]=w;i=b;return}o=(n&-8)+o|0;m=n>>>3;do if(n>>>0>=256){l=c[a+(j+16)>>2]|0;m=c[a+(j|4)>>2]|0;do if((m|0)==(h|0)){n=a+(j+12)|0;m=c[n>>2]|0;if(!m){n=a+(j+8)|0;m=c[n>>2]|0;if(!m){k=0;break}}while(1){q=m+20|0;p=c[q>>2]|0;if(p){m=p;n=q;continue}p=m+16|0;q=c[p>>2]|0;if(!q)break;else{m=q;n=p}}if(n>>>0<(c[1210]|0)>>>0)Wa();else{c[n>>2]=0;k=m;break}}else{n=c[a+j>>2]|0;if(n>>>0<(c[1210]|0)>>>0)Wa();p=n+12|0;if((c[p>>2]|0)!=(h|0))Wa();q=m+8|0;if((c[q>>2]|0)==(h|0)){c[p>>2]=m;c[q>>2]=n;k=m;break}else Wa()}while(0);if(l){m=c[a+(j+20)>>2]|0;n=5128+(m<<2)|0;if((h|0)==(c[n>>2]|0)){c[n>>2]=k;if(!k){c[1207]=c[1207]&~(1<<m);break}}else{if(l>>>0<(c[1210]|0)>>>0)Wa();m=l+16|0;if((c[m>>2]|0)==(h|0))c[m>>2]=k;else c[l+20>>2]=k;if(!k)break}h=c[1210]|0;if(k>>>0<h>>>0)Wa();c[k+24>>2]=l;l=c[a+(j+8)>>2]|0;do if(l)if(l>>>0<h>>>0)Wa();else{c[k+16>>2]=l;c[l+24>>2]=k;break}while(0);h=c[a+(j+12)>>2]|0;if(h)if(h>>>0<(c[1210]|0)>>>0)Wa();else{c[k+20>>2]=h;c[h+24>>2]=k;break}}}else{k=c[a+j>>2]|0;j=c[a+(j|4)>>2]|0;a=4864+(m<<1<<2)|0;if((k|0)!=(a|0)){if(k>>>0<(c[1210]|0)>>>0)Wa();if((c[k+12>>2]|0)!=(h|0))Wa()}if((j|0)==(k|0)){c[1206]=c[1206]&~(1<<m);break}if((j|0)!=(a|0)){if(j>>>0<(c[1210]|0)>>>0)Wa();a=j+8|0;if((c[a>>2]|0)==(h|0))l=a;else Wa()}else l=j+8|0;c[k+12>>2]=j;c[l>>2]=k}while(0);c[e+4>>2]=o|1;c[e+o>>2]=o;if((e|0)==(c[1211]|0)){c[1208]=o;i=b;return}}else{c[m>>2]=n&-2;c[e+4>>2]=o|1;c[e+o>>2]=o}h=o>>>3;if(o>>>0<256){j=h<<1;d=4864+(j<<2)|0;k=c[1206]|0;h=1<<h;if(k&h){j=4864+(j+2<<2)|0;h=c[j>>2]|0;if(h>>>0<(c[1210]|0)>>>0)Wa();else{f=j;g=h}}else{c[1206]=k|h;f=4864+(j+2<<2)|0;g=d}c[f>>2]=e;c[g+12>>2]=e;c[e+8>>2]=g;c[e+12>>2]=d;i=b;return}f=o>>>8;if(f)if(o>>>0>16777215)f=31;else{v=(f+1048320|0)>>>16&8;w=f<<v;u=(w+520192|0)>>>16&4;w=w<<u;f=(w+245760|0)>>>16&2;f=14-(u|v|f)+(w<<f>>>15)|0;f=o>>>(f+7|0)&1|f<<1}else f=0;g=5128+(f<<2)|0;c[e+28>>2]=f;c[e+20>>2]=0;c[e+16>>2]=0;j=c[1207]|0;h=1<<f;a:do if(j&h){g=c[g>>2]|0;if((f|0)==31)f=0;else f=25-(f>>>1)|0;b:do if((c[g+4>>2]&-8|0)!=(o|0)){f=o<<f;while(1){j=g+(f>>>31<<2)+16|0;h=c[j>>2]|0;if(!h)break;if((c[h+4>>2]&-8|0)==(o|0)){d=h;break b}else{f=f<<1;g=h}}if(j>>>0<(c[1210]|0)>>>0)Wa();else{c[j>>2]=e;c[e+24>>2]=g;c[e+12>>2]=e;c[e+8>>2]=e;break a}}else d=g;while(0);g=d+8|0;f=c[g>>2]|0;w=c[1210]|0;if(d>>>0>=w>>>0&f>>>0>=w>>>0){c[f+12>>2]=e;c[g>>2]=e;c[e+8>>2]=f;c[e+12>>2]=d;c[e+24>>2]=0;break}else Wa()}else{c[1207]=j|h;c[g>>2]=e;c[e+24>>2]=g;c[e+12>>2]=e;c[e+8>>2]=e}while(0);w=(c[1214]|0)+ -1|0;c[1214]=w;if(!w)d=5280|0;else{i=b;return}while(1){d=c[d>>2]|0;if(!d)break;else d=d+8|0}c[1214]=-1;i=b;return}function Ud(a,b){a=a|0;b=b|0;var d=0,e=0,f=0;d=i;do if(a){if(b>>>0>4294967231){c[(Oa()|0)>>2]=12;e=0;break}if(b>>>0<11)e=16;else e=b+11&-8;e=fe(a+ -8|0,e)|0;if(e){e=e+8|0;break}e=Sd(b)|0;if(!e)e=0;else{f=c[a+ -4>>2]|0;f=(f&-8)-((f&3|0)==0?8:4)|0;pe(e|0,a|0,(f>>>0<b>>>0?f:b)|0)|0;Td(a)}}else e=Sd(b)|0;while(0);i=d;return e|0}function Vd(a){a=a|0;if((a|0)==32)a=1;else a=(a+ -9|0)>>>0<5;return a&1|0}function Wd(b,e,f,g,h){b=b|0;e=e|0;f=f|0;g=g|0;h=h|0;var j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;j=i;if(e>>>0>36){c[(Oa()|0)>>2]=22;s=0;t=0;F=s;i=j;return t|0}k=b+4|0;l=b+100|0;do{m=c[k>>2]|0;if(m>>>0<(c[l>>2]|0)>>>0){c[k>>2]=m+1;o=d[m>>0]|0}else o=Zd(b)|0}while((Vd(o)|0)!=0);do if((o|0)==43|(o|0)==45){m=((o|0)==45)<<31>>31;n=c[k>>2]|0;if(n>>>0<(c[l>>2]|0)>>>0){c[k>>2]=n+1;o=d[n>>0]|0;break}else{o=Zd(b)|0;break}}else m=0;while(0);n=(e|0)==0;do if((e&-17|0)==0&(o|0)==48){o=c[k>>2]|0;if(o>>>0<(c[l>>2]|0)>>>0){c[k>>2]=o+1;o=d[o>>0]|0}else o=Zd(b)|0;if((o|32|0)!=120)if(n){e=8;f=46;break}else{f=32;break}e=c[k>>2]|0;if(e>>>0<(c[l>>2]|0)>>>0){c[k>>2]=e+1;o=d[e>>0]|0}else o=Zd(b)|0;if((d[o+5321>>0]|0)>15){g=(c[l>>2]|0)==0;if(!g)c[k>>2]=(c[k>>2]|0)+ -1;if(!f){Yd(b,0);s=0;t=0;F=s;i=j;return t|0}if(g){s=0;t=0;F=s;i=j;return t|0}c[k>>2]=(c[k>>2]|0)+ -1;s=0;t=0;F=s;i=j;return t|0}else{e=16;f=46}}else{e=n?10:e;if((d[o+5321>>0]|0)>>>0<e>>>0)f=32;else{if(c[l>>2]|0)c[k>>2]=(c[k>>2]|0)+ -1;Yd(b,0);c[(Oa()|0)>>2]=22;s=0;t=0;F=s;i=j;return t|0}}while(0);if((f|0)==32)if((e|0)==10){e=o+ -48|0;if(e>>>0<10){n=0;do{n=(n*10|0)+e|0;e=c[k>>2]|0;if(e>>>0<(c[l>>2]|0)>>>0){c[k>>2]=e+1;o=d[e>>0]|0}else o=Zd(b)|0;e=o+ -48|0}while(e>>>0<10&n>>>0<429496729);p=0}else{n=0;p=0}e=o+ -48|0;if(e>>>0<10){do{q=we(n|0,p|0,10,0)|0;r=F;s=((e|0)<0)<<31>>31;t=~s;if(r>>>0>t>>>0|(r|0)==(t|0)&q>>>0>~e>>>0)break;n=ne(q|0,r|0,e|0,s|0)|0;p=F;e=c[k>>2]|0;if(e>>>0<(c[l>>2]|0)>>>0){c[k>>2]=e+1;o=d[e>>0]|0}else o=Zd(b)|0;e=o+ -48|0}while(e>>>0<10&(p>>>0<429496729|(p|0)==429496729&n>>>0<2576980378));if(e>>>0<=9){e=10;f=72}}}else f=46;a:do if((f|0)==46){if(!(e+ -1&e)){f=a[5584+((e*23|0)>>>5&7)>>0]|0;r=a[o+5321>>0]|0;n=r&255;if(n>>>0<e>>>0){o=n;n=0;do{n=o|n<<f;o=c[k>>2]|0;if(o>>>0<(c[l>>2]|0)>>>0){c[k>>2]=o+1;s=d[o>>0]|0}else s=Zd(b)|0;r=a[s+5321>>0]|0;o=r&255}while(o>>>0<e>>>0&n>>>0<134217728);p=0}else{p=0;n=0;s=o}o=oe(-1,-1,f|0)|0;q=F;if((r&255)>>>0>=e>>>0|(p>>>0>q>>>0|(p|0)==(q|0)&n>>>0>o>>>0)){o=s;f=72;break}while(1){n=le(n|0,p|0,f|0)|0;p=F;n=r&255|n;r=c[k>>2]|0;if(r>>>0<(c[l>>2]|0)>>>0){c[k>>2]=r+1;s=d[r>>0]|0}else s=Zd(b)|0;r=a[s+5321>>0]|0;if((r&255)>>>0>=e>>>0|(p>>>0>q>>>0|(p|0)==(q|0)&n>>>0>o>>>0)){o=s;f=72;break a}}}r=a[o+5321>>0]|0;f=r&255;if(f>>>0<e>>>0){n=0;do{n=f+(ba(n,e)|0)|0;f=c[k>>2]|0;if(f>>>0<(c[l>>2]|0)>>>0){c[k>>2]=f+1;q=d[f>>0]|0}else q=Zd(b)|0;r=a[q+5321>>0]|0;f=r&255}while(f>>>0<e>>>0&n>>>0<119304647);p=0}else{n=0;p=0;q=o}if((r&255)>>>0<e>>>0){f=xe(-1,-1,e|0,0)|0;o=F;while(1){if(p>>>0>o>>>0|(p|0)==(o|0)&n>>>0>f>>>0){o=q;f=72;break a}s=we(n|0,p|0,e|0,0)|0;t=F;r=r&255;if(t>>>0>4294967295|(t|0)==-1&s>>>0>~r>>>0){o=q;f=72;break a}n=ne(r|0,0,s|0,t|0)|0;p=F;q=c[k>>2]|0;if(q>>>0<(c[l>>2]|0)>>>0){c[k>>2]=q+1;q=d[q>>0]|0}else q=Zd(b)|0;r=a[q+5321>>0]|0;if((r&255)>>>0>=e>>>0){o=q;f=72;break}}}else{o=q;f=72}}while(0);if((f|0)==72)if((d[o+5321>>0]|0)>>>0<e>>>0){do{f=c[k>>2]|0;if(f>>>0<(c[l>>2]|0)>>>0){c[k>>2]=f+1;f=d[f>>0]|0}else f=Zd(b)|0}while((d[f+5321>>0]|0)>>>0<e>>>0);c[(Oa()|0)>>2]=34;p=h;n=g}if(c[l>>2]|0)c[k>>2]=(c[k>>2]|0)+ -1;if(!(p>>>0<h>>>0|(p|0)==(h|0)&n>>>0<g>>>0)){if(!((g&1|0)!=0|0!=0|(m|0)!=0)){c[(Oa()|0)>>2]=34;t=ne(g|0,h|0,-1,-1)|0;s=F;F=s;i=j;return t|0}if(p>>>0>h>>>0|(p|0)==(h|0)&n>>>0>g>>>0){c[(Oa()|0)>>2]=34;s=h;t=g;F=s;i=j;return t|0}}t=((m|0)<0)<<31>>31;t=je(n^m|0,p^t|0,m|0,t|0)|0;s=F;F=s;i=j;return t|0}



function Xd(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0.0,r=0,s=0,t=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,G=0.0,H=0,I=0.0,J=0.0,K=0.0,L=0.0;g=i;i=i+512|0;k=g;if(!e){e=24;j=-149}else if((e|0)==2){e=53;j=-1074}else if((e|0)==1){e=53;j=-1074}else{J=0.0;i=g;return+J}n=b+4|0;o=b+100|0;do{h=c[n>>2]|0;if(h>>>0<(c[o>>2]|0)>>>0){c[n>>2]=h+1;w=d[h>>0]|0}else w=Zd(b)|0}while((Vd(w)|0)!=0);do if((w|0)==43|(w|0)==45){h=1-(((w|0)==45&1)<<1)|0;m=c[n>>2]|0;if(m>>>0<(c[o>>2]|0)>>>0){c[n>>2]=m+1;w=d[m>>0]|0;break}else{w=Zd(b)|0;break}}else h=1;while(0);r=0;do{if((w|32|0)!=(a[5600+r>>0]|0))break;do if(r>>>0<7){m=c[n>>2]|0;if(m>>>0<(c[o>>2]|0)>>>0){c[n>>2]=m+1;w=d[m>>0]|0;break}else{w=Zd(b)|0;break}}while(0);r=r+1|0}while(r>>>0<8);do if((r|0)==3)p=23;else if((r|0)!=8){m=(f|0)!=0;if(r>>>0>3&m)if((r|0)==8)break;else{p=23;break}a:do if(!r){r=0;do{if((w|32|0)!=(a[5616+r>>0]|0))break a;do if(r>>>0<2){s=c[n>>2]|0;if(s>>>0<(c[o>>2]|0)>>>0){c[n>>2]=s+1;w=d[s>>0]|0;break}else{w=Zd(b)|0;break}}while(0);r=r+1|0}while(r>>>0<3)}while(0);if(!r){do if((w|0)==48){m=c[n>>2]|0;if(m>>>0<(c[o>>2]|0)>>>0){c[n>>2]=m+1;m=d[m>>0]|0}else m=Zd(b)|0;if((m|32|0)!=120){if(!(c[o>>2]|0)){w=48;break}c[n>>2]=(c[n>>2]|0)+ -1;w=48;break}k=c[n>>2]|0;if(k>>>0<(c[o>>2]|0)>>>0){c[n>>2]=k+1;z=d[k>>0]|0;x=0}else{z=Zd(b)|0;x=0}while(1){if((z|0)==46){p=70;break}else if((z|0)!=48){k=0;m=0;s=0;r=0;w=0;y=0;G=1.0;t=0;q=0.0;break}k=c[n>>2]|0;if(k>>>0<(c[o>>2]|0)>>>0){c[n>>2]=k+1;z=d[k>>0]|0;x=1;continue}else{z=Zd(b)|0;x=1;continue}}if((p|0)==70){k=c[n>>2]|0;if(k>>>0<(c[o>>2]|0)>>>0){c[n>>2]=k+1;z=d[k>>0]|0}else z=Zd(b)|0;if((z|0)==48){s=0;r=0;do{k=c[n>>2]|0;if(k>>>0<(c[o>>2]|0)>>>0){c[n>>2]=k+1;z=d[k>>0]|0}else z=Zd(b)|0;s=ne(s|0,r|0,-1,-1)|0;r=F}while((z|0)==48);k=0;m=0;x=1;w=1;y=0;G=1.0;t=0;q=0.0}else{k=0;m=0;s=0;r=0;w=1;y=0;G=1.0;t=0;q=0.0}}b:while(1){B=z+ -48|0;do if(B>>>0>=10){A=z|32;C=(z|0)==46;if(!((A+ -97|0)>>>0<6|C))break b;if(C)if(!w){s=m;r=k;w=1;break}else{z=46;break b}else{B=(z|0)>57?A+ -87|0:B;p=83;break}}else p=83;while(0);if((p|0)==83){p=0;do if(!((k|0)<0|(k|0)==0&m>>>0<8)){if((k|0)<0|(k|0)==0&m>>>0<14){J=G*.0625;I=J;q=q+J*+(B|0);break}if((B|0)==0|(y|0)!=0)I=G;else{y=1;I=G;q=q+G*.5}}else{I=G;t=B+(t<<4)|0}while(0);m=ne(m|0,k|0,1,0)|0;k=F;x=1;G=I}z=c[n>>2]|0;if(z>>>0<(c[o>>2]|0)>>>0){c[n>>2]=z+1;z=d[z>>0]|0;continue}else{z=Zd(b)|0;continue}}if(!x){e=(c[o>>2]|0)==0;if(!e)c[n>>2]=(c[n>>2]|0)+ -1;if(f){if(!e?(l=c[n>>2]|0,c[n>>2]=l+ -1,(w|0)!=0):0)c[n>>2]=l+ -2}else Yd(b,0);J=+(h|0)*0.0;i=g;return+J}p=(w|0)==0;l=p?m:s;p=p?k:r;if((k|0)<0|(k|0)==0&m>>>0<8)do{t=t<<4;m=ne(m|0,k|0,1,0)|0;k=F}while((k|0)<0|(k|0)==0&m>>>0<8);do if((z|32|0)==112){m=he(b,f)|0;k=F;if((m|0)==0&(k|0)==-2147483648)if(!f){Yd(b,0);J=0.0;i=g;return+J}else{if(!(c[o>>2]|0)){m=0;k=0;break}c[n>>2]=(c[n>>2]|0)+ -1;m=0;k=0;break}}else if(!(c[o>>2]|0)){m=0;k=0}else{c[n>>2]=(c[n>>2]|0)+ -1;m=0;k=0}while(0);l=le(l|0,p|0,2)|0;l=ne(l|0,F|0,-32,-1)|0;k=ne(l|0,F|0,m|0,k|0)|0;l=F;if(!t){J=+(h|0)*0.0;i=g;return+J}if((l|0)>0|(l|0)==0&k>>>0>(0-j|0)>>>0){c[(Oa()|0)>>2]=34;J=+(h|0)*1.7976931348623157e+308*1.7976931348623157e+308;i=g;return+J}H=j+ -106|0;E=((H|0)<0)<<31>>31;if((l|0)<(E|0)|(l|0)==(E|0)&k>>>0<H>>>0){c[(Oa()|0)>>2]=34;J=+(h|0)*2.2250738585072014e-308*2.2250738585072014e-308;i=g;return+J}if((t|0)>-1)do{t=t<<1;if(!(q>=.5))G=q;else{G=q+-1.0;t=t|1}q=q+G;k=ne(k|0,l|0,-1,-1)|0;l=F}while((t|0)>-1);j=je(32,0,j|0,((j|0)<0)<<31>>31|0)|0;j=ne(k|0,l|0,j|0,F|0)|0;H=F;if(0>(H|0)|0==(H|0)&e>>>0>j>>>0)if((j|0)<0){e=0;p=126}else{e=j;p=124}else p=124;if((p|0)==124)if((e|0)<53)p=126;else{j=e;G=+(h|0);I=0.0}if((p|0)==126){I=+(h|0);j=e;G=I;I=+Va(+(+_d(1.0,84-e|0)),+I)}H=(j|0)<32&q!=0.0&(t&1|0)==0;q=G*(H?0.0:q)+(I+G*+(((H&1)+t|0)>>>0))-I;if(!(q!=0.0))c[(Oa()|0)>>2]=34;J=+$d(q,k);i=g;return+J}while(0);m=j+e|0;l=0-m|0;B=0;while(1){if((w|0)==46){p=137;break}else if((w|0)!=48){D=0;C=0;A=0;break}r=c[n>>2]|0;if(r>>>0<(c[o>>2]|0)>>>0){c[n>>2]=r+1;w=d[r>>0]|0;B=1;continue}else{w=Zd(b)|0;B=1;continue}}if((p|0)==137){p=c[n>>2]|0;if(p>>>0<(c[o>>2]|0)>>>0){c[n>>2]=p+1;w=d[p>>0]|0}else w=Zd(b)|0;if((w|0)==48){D=0;C=0;do{D=ne(D|0,C|0,-1,-1)|0;C=F;p=c[n>>2]|0;if(p>>>0<(c[o>>2]|0)>>>0){c[n>>2]=p+1;w=d[p>>0]|0}else w=Zd(b)|0}while((w|0)==48);B=1;A=1}else{D=0;C=0;A=1}}c[k>>2]=0;z=w+ -48|0;E=(w|0)==46;c:do if(z>>>0<10|E){p=k+496|0;y=0;x=0;t=0;s=0;r=0;d:while(1){do if(E)if(!A){D=y;C=x;A=1}else break d;else{E=ne(y|0,x|0,1,0)|0;x=F;H=(w|0)!=48;if((s|0)>=125){if(!H){y=E;break}c[p>>2]=c[p>>2]|1;y=E;break}y=k+(s<<2)|0;if(t)z=w+ -48+((c[y>>2]|0)*10|0)|0;c[y>>2]=z;t=t+1|0;z=(t|0)==9;y=E;B=1;t=z?0:t;s=(z&1)+s|0;r=H?E:r}while(0);w=c[n>>2]|0;if(w>>>0<(c[o>>2]|0)>>>0){c[n>>2]=w+1;w=d[w>>0]|0}else w=Zd(b)|0;z=w+ -48|0;E=(w|0)==46;if(!(z>>>0<10|E)){p=160;break c}}z=(B|0)!=0;p=168}else{y=0;x=0;t=0;s=0;r=0;p=160}while(0);do if((p|0)==160){z=(A|0)==0;D=z?y:D;C=z?x:C;z=(B|0)!=0;if(!(z&(w|32|0)==101))if((w|0)>-1){p=168;break}else{p=170;break}z=he(b,f)|0;w=F;do if((z|0)==0&(w|0)==-2147483648)if(!f){Yd(b,0);J=0.0;i=g;return+J}else{if(!(c[o>>2]|0)){z=0;w=0;break}c[n>>2]=(c[n>>2]|0)+ -1;z=0;w=0;break}while(0);b=ne(z|0,w|0,D|0,C|0)|0;C=F}while(0);if((p|0)==168)if(c[o>>2]|0){c[n>>2]=(c[n>>2]|0)+ -1;if(z)b=D;else p=171}else p=170;if((p|0)==170)if(z)b=D;else p=171;if((p|0)==171){c[(Oa()|0)>>2]=22;Yd(b,0);J=0.0;i=g;return+J}n=c[k>>2]|0;if(!n){J=+(h|0)*0.0;i=g;return+J}if((b|0)==(y|0)&(C|0)==(x|0)&((x|0)<0|(x|0)==0&y>>>0<10)?e>>>0>30|(n>>>e|0)==0:0){J=+(h|0)*+(n>>>0);i=g;return+J}H=(j|0)/-2|0;E=((H|0)<0)<<31>>31;if((C|0)>(E|0)|(C|0)==(E|0)&b>>>0>H>>>0){c[(Oa()|0)>>2]=34;J=+(h|0)*1.7976931348623157e+308*1.7976931348623157e+308;i=g;return+J}H=j+ -106|0;E=((H|0)<0)<<31>>31;if((C|0)<(E|0)|(C|0)==(E|0)&b>>>0<H>>>0){c[(Oa()|0)>>2]=34;J=+(h|0)*2.2250738585072014e-308*2.2250738585072014e-308;i=g;return+J}if(t){if((t|0)<9){n=k+(s<<2)|0;o=c[n>>2]|0;do{o=o*10|0;t=t+1|0}while((t|0)!=9);c[n>>2]=o}s=s+1|0}if((r|0)<9?(r|0)<=(b|0)&(b|0)<18:0){if((b|0)==9){J=+(h|0)*+((c[k>>2]|0)>>>0);i=g;return+J}if((b|0)<9){J=+(h|0)*+((c[k>>2]|0)>>>0)/+(c[5632+(8-b<<2)>>2]|0);i=g;return+J}H=e+27+(ba(b,-3)|0)|0;n=c[k>>2]|0;if((H|0)>30|(n>>>H|0)==0){J=+(h|0)*+(n>>>0)*+(c[5632+(b+ -10<<2)>>2]|0);i=g;return+J}}n=(b|0)%9|0;if(!n){n=0;o=0}else{f=(b|0)>-1?n:n+9|0;p=c[5632+(8-f<<2)>>2]|0;if(s){r=1e9/(p|0)|0;n=0;o=0;t=0;do{D=k+(t<<2)|0;E=c[D>>2]|0;H=((E>>>0)/(p>>>0)|0)+o|0;c[D>>2]=H;o=ba((E>>>0)%(p>>>0)|0,r)|0;E=t;t=t+1|0;if((E|0)==(n|0)&(H|0)==0){n=t&127;b=b+ -9|0}}while((t|0)!=(s|0));if(o){c[k+(s<<2)>>2]=o;s=s+1|0}}else{n=0;s=0}o=0;b=9-f+b|0}e:while(1){f=k+(n<<2)|0;if((b|0)<18){do{r=0;f=s+127|0;while(1){f=f&127;p=k+(f<<2)|0;t=le(c[p>>2]|0,0,29)|0;t=ne(t|0,F|0,r|0,0)|0;r=F;if(r>>>0>0|(r|0)==0&t>>>0>1e9){H=xe(t|0,r|0,1e9,0)|0;t=ye(t|0,r|0,1e9,0)|0;r=H}else r=0;c[p>>2]=t;p=(f|0)==(n|0);if(!((f|0)!=(s+127&127|0)|p))s=(t|0)==0?f:s;if(p)break;else f=f+ -1|0}o=o+ -29|0}while((r|0)==0)}else{if((b|0)!=18)break;do{if((c[f>>2]|0)>>>0>=9007199){b=18;break e}r=0;p=s+127|0;while(1){p=p&127;t=k+(p<<2)|0;w=le(c[t>>2]|0,0,29)|0;w=ne(w|0,F|0,r|0,0)|0;r=F;if(r>>>0>0|(r|0)==0&w>>>0>1e9){H=xe(w|0,r|0,1e9,0)|0;w=ye(w|0,r|0,1e9,0)|0;r=H}else r=0;c[t>>2]=w;t=(p|0)==(n|0);if(!((p|0)!=(s+127&127|0)|t))s=(w|0)==0?p:s;if(t)break;else p=p+ -1|0}o=o+ -29|0}while((r|0)==0)}n=n+127&127;if((n|0)==(s|0)){H=s+127&127;s=k+((s+126&127)<<2)|0;c[s>>2]=c[s>>2]|c[k+(H<<2)>>2];s=H}c[k+(n<<2)>>2]=r;b=b+9|0}f:while(1){f=s+1&127;p=k+((s+127&127)<<2)|0;while(1){t=(b|0)==18;r=(b|0)>27?9:1;while(1){w=0;while(1){x=w+n&127;if((x|0)==(s|0)){w=2;break}y=c[k+(x<<2)>>2]|0;z=c[5624+(w<<2)>>2]|0;if(y>>>0<z>>>0){w=2;break}x=w+1|0;if(y>>>0>z>>>0)break;if((x|0)<2)w=x;else{w=x;break}}if((w|0)==2&t)break f;o=r+o|0;if((n|0)==(s|0))n=s;else break}t=(1<<r)+ -1|0;w=1e9>>>r;x=n;y=0;do{D=k+(n<<2)|0;E=c[D>>2]|0;H=(E>>>r)+y|0;c[D>>2]=H;y=ba(E&t,w)|0;H=(n|0)==(x|0)&(H|0)==0;n=n+1&127;b=H?b+ -9|0:b;x=H?n:x}while((n|0)!=(s|0));if(!y){n=x;continue}if((f|0)!=(x|0))break;c[p>>2]=c[p>>2]|1;n=x}c[k+(s<<2)>>2]=y;n=x;s=f}b=n&127;if((b|0)==(s|0)){c[k+(f+ -1<<2)>>2]=0;s=f}G=+((c[k+(b<<2)>>2]|0)>>>0);b=n+1&127;if((b|0)==(s|0)){s=s+1&127;c[k+(s+ -1<<2)>>2]=0}q=+(h|0);I=q*(G*1.0e9+ +((c[k+(b<<2)>>2]|0)>>>0));h=o+53|0;j=h-j|0;if((j|0)<(e|0))if((j|0)<0){e=0;b=1;p=244}else{e=j;b=1;p=243}else{b=0;p=243}if((p|0)==243)if((e|0)<53)p=244;else{G=0.0;J=0.0}if((p|0)==244){L=+Va(+(+_d(1.0,105-e|0)),+I);K=+cb(+I,+(+_d(1.0,53-e|0)));G=L;J=K;I=L+(I-K)}f=n+2&127;do if((f|0)!=(s|0)){k=c[k+(f<<2)>>2]|0;do if(k>>>0>=5e8){if(k>>>0>5e8){J=q*.75+J;break}if((n+3&127|0)==(s|0)){J=q*.5+J;break}else{J=q*.75+J;break}}else{if((k|0)==0?(n+3&127|0)==(s|0):0)break;J=q*.25+J}while(0);if((53-e|0)<=1)break;if(+cb(+J,1.0)!=0.0)break;J=J+1.0}while(0);q=I+J-G;do if((h&2147483647|0)>(-2-m|0)){if(+Q(+q)>=9007199254740992.0){b=(b|0)!=0&(e|0)==(j|0)?0:b;o=o+1|0;q=q*.5}if((o+50|0)<=(l|0)?!((b|0)!=0&J!=0.0):0)break;c[(Oa()|0)>>2]=34}while(0);L=+$d(q,o);i=g;return+L}else if((r|0)==3){e=c[n>>2]|0;if(e>>>0<(c[o>>2]|0)>>>0){c[n>>2]=e+1;e=d[e>>0]|0}else e=Zd(b)|0;if((e|0)==40)e=1;else{if(!(c[o>>2]|0)){L=u;i=g;return+L}c[n>>2]=(c[n>>2]|0)+ -1;L=u;i=g;return+L}while(1){h=c[n>>2]|0;if(h>>>0<(c[o>>2]|0)>>>0){c[n>>2]=h+1;h=d[h>>0]|0}else h=Zd(b)|0;if(!((h+ -48|0)>>>0<10|(h+ -65|0)>>>0<26)?!((h+ -97|0)>>>0<26|(h|0)==95):0)break;e=e+1|0}if((h|0)==41){L=u;i=g;return+L}h=(c[o>>2]|0)==0;if(!h)c[n>>2]=(c[n>>2]|0)+ -1;if(!m){c[(Oa()|0)>>2]=22;Yd(b,0);L=0.0;i=g;return+L}if((e|0)==0|h){L=u;i=g;return+L}do{e=e+ -1|0;c[n>>2]=(c[n>>2]|0)+ -1}while((e|0)!=0);q=u;i=g;return+q}else{if(c[o>>2]|0)c[n>>2]=(c[n>>2]|0)+ -1;c[(Oa()|0)>>2]=22;Yd(b,0);L=0.0;i=g;return+L}}while(0);if((p|0)==23){e=(c[o>>2]|0)==0;if(!e)c[n>>2]=(c[n>>2]|0)+ -1;if(!(r>>>0<4|(f|0)==0|e))do{c[n>>2]=(c[n>>2]|0)+ -1;r=r+ -1|0}while(r>>>0>3)}L=+(h|0)*v;i=g;return+L}function Yd(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0;d=i;c[a+104>>2]=b;f=c[a+8>>2]|0;e=c[a+4>>2]|0;g=f-e|0;c[a+108>>2]=g;if((b|0)!=0&(g|0)>(b|0)){c[a+100>>2]=e+b;i=d;return}else{c[a+100>>2]=f;i=d;return}}function Zd(b){b=b|0;var e=0,f=0,g=0,h=0,j=0,k=0,l=0;f=i;j=b+104|0;l=c[j>>2]|0;if(!((l|0)!=0?(c[b+108>>2]|0)>=(l|0):0))k=3;if((k|0)==3?(e=be(b)|0,(e|0)>=0):0){k=c[j>>2]|0;j=c[b+8>>2]|0;if((k|0)!=0?(g=c[b+4>>2]|0,h=k-(c[b+108>>2]|0)+ -1|0,(j-g|0)>(h|0)):0)c[b+100>>2]=g+h;else c[b+100>>2]=j;g=c[b+4>>2]|0;if(j){l=b+108|0;c[l>>2]=j+1-g+(c[l>>2]|0)}b=g+ -1|0;if((d[b>>0]|0|0)==(e|0)){l=e;i=f;return l|0}a[b>>0]=e;l=e;i=f;return l|0}c[b+100>>2]=0;l=-1;i=f;return l|0}function _d(a,b){a=+a;b=b|0;var d=0,e=0;d=i;if((b|0)>1023){a=a*8.98846567431158e+307;e=b+ -1023|0;if((e|0)>1023){b=b+ -2046|0;b=(b|0)>1023?1023:b;a=a*8.98846567431158e+307}else b=e}else if((b|0)<-1022){a=a*2.2250738585072014e-308;e=b+1022|0;if((e|0)<-1022){b=b+2044|0;b=(b|0)<-1022?-1022:b;a=a*2.2250738585072014e-308}else b=e}b=le(b+1023|0,0,52)|0;e=F;c[k>>2]=b;c[k+4>>2]=e;a=a*+h[k>>3];i=d;return+a}function $d(a,b){a=+a;b=b|0;var c=0;c=i;a=+_d(a,b);i=c;return+a}function ae(b){b=b|0;var d=0,e=0,f=0;e=i;f=b+74|0;d=a[f>>0]|0;a[f>>0]=d+255|d;f=b+20|0;d=b+44|0;if((c[f>>2]|0)>>>0>(c[d>>2]|0)>>>0)eb[c[b+36>>2]&1](b,0,0)|0;c[b+16>>2]=0;c[b+28>>2]=0;c[f>>2]=0;f=c[b>>2]|0;if(!(f&20)){f=c[d>>2]|0;c[b+8>>2]=f;c[b+4>>2]=f;f=0;i=e;return f|0}if(!(f&4)){f=-1;i=e;return f|0}c[b>>2]=f|32;f=-1;i=e;return f|0}function be(a){a=a|0;var b=0,e=0;b=i;i=i+16|0;e=b;if((c[a+8>>2]|0)==0?(ae(a)|0)!=0:0)a=-1;else if((eb[c[a+32>>2]&1](a,e,1)|0)==1)a=d[e>>0]|0;else a=-1;i=b;return a|0}function ce(a,b){a=a|0;b=b|0;var d=0,e=0,f=0.0,g=0,h=0;d=i;i=i+112|0;e=d;h=e+0|0;g=h+112|0;do{c[h>>2]=0;h=h+4|0}while((h|0)<(g|0));g=e+4|0;c[g>>2]=a;h=e+8|0;c[h>>2]=-1;c[e+44>>2]=a;c[e+76>>2]=-1;Yd(e,0);f=+Xd(e,1,1);e=(c[g>>2]|0)-(c[h>>2]|0)+(c[e+108>>2]|0)|0;if(!b){i=d;return+f}if(e)a=a+e|0;c[b>>2]=a;i=d;return+f}function de(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0;e=i;i=i+112|0;g=e;c[g>>2]=0;f=g+4|0;c[f>>2]=a;c[g+44>>2]=a;if((a|0)<0)c[g+8>>2]=-1;else c[g+8>>2]=a+2147483647;c[g+76>>2]=-1;Yd(g,0);d=Wd(g,d,1,-2147483648,0)|0;if(!b){i=e;return d|0}c[b>>2]=a+((c[f>>2]|0)+(c[g+108>>2]|0)-(c[g+8>>2]|0));i=e;return d|0}function ee(b,c){b=b|0;c=c|0;var d=0,e=0,f=0;d=i;f=a[b>>0]|0;e=a[c>>0]|0;if(f<<24>>24==0?1:f<<24>>24!=e<<24>>24)c=f;else{do{b=b+1|0;c=c+1|0;f=a[b>>0]|0;e=a[c>>0]|0}while(!(f<<24>>24==0?1:f<<24>>24!=e<<24>>24));c=f}i=d;return(c&255)-(e&255)|0}function fe(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;d=i;f=a+4|0;e=c[f>>2]|0;l=e&-8;j=a+l|0;m=c[1210]|0;h=e&3;if(!((h|0)!=1&a>>>0>=m>>>0&a>>>0<j>>>0))Wa();g=a+(l|4)|0;p=c[g>>2]|0;if(!(p&1))Wa();if(!h){if(b>>>0<256){r=0;i=d;return r|0}if(l>>>0>=(b+4|0)>>>0?(l-b|0)>>>0<=c[1326]<<1>>>0:0){r=a;i=d;return r|0}r=0;i=d;return r|0}if(l>>>0>=b>>>0){h=l-b|0;if(h>>>0<=15){r=a;i=d;return r|0}c[f>>2]=e&1|b|2;c[a+(b+4)>>2]=h|3;c[g>>2]=c[g>>2]|1;ge(a+b|0,h);r=a;i=d;return r|0}if((j|0)==(c[1212]|0)){g=(c[1209]|0)+l|0;if(g>>>0<=b>>>0){r=0;i=d;return r|0}r=g-b|0;c[f>>2]=e&1|b|2;c[a+(b+4)>>2]=r|1;c[1212]=a+b;c[1209]=r;r=a;i=d;return r|0}if((j|0)==(c[1211]|0)){h=(c[1208]|0)+l|0;if(h>>>0<b>>>0){r=0;i=d;return r|0}g=h-b|0;if(g>>>0>15){c[f>>2]=e&1|b|2;c[a+(b+4)>>2]=g|1;c[a+h>>2]=g;e=a+(h+4)|0;c[e>>2]=c[e>>2]&-2;e=a+b|0}else{c[f>>2]=e&1|h|2;e=a+(h+4)|0;c[e>>2]=c[e>>2]|1;e=0;g=0}c[1208]=g;c[1211]=e;r=a;i=d;return r|0}if(p&2){r=0;i=d;return r|0}g=(p&-8)+l|0;if(g>>>0<b>>>0){r=0;i=d;return r|0}h=g-b|0;o=p>>>3;do if(p>>>0>=256){n=c[a+(l+24)>>2]|0;o=c[a+(l+12)>>2]|0;do if((o|0)==(j|0)){p=a+(l+20)|0;o=c[p>>2]|0;if(!o){p=a+(l+16)|0;o=c[p>>2]|0;if(!o){k=0;break}}while(1){r=o+20|0;q=c[r>>2]|0;if(q){o=q;p=r;continue}q=o+16|0;r=c[q>>2]|0;if(!r)break;else{o=r;p=q}}if(p>>>0<m>>>0)Wa();else{c[p>>2]=0;k=o;break}}else{p=c[a+(l+8)>>2]|0;if(p>>>0<m>>>0)Wa();m=p+12|0;if((c[m>>2]|0)!=(j|0))Wa();q=o+8|0;if((c[q>>2]|0)==(j|0)){c[m>>2]=o;c[q>>2]=p;k=o;break}else Wa()}while(0);if(n){m=c[a+(l+28)>>2]|0;o=5128+(m<<2)|0;if((j|0)==(c[o>>2]|0)){c[o>>2]=k;if(!k){c[1207]=c[1207]&~(1<<m);break}}else{if(n>>>0<(c[1210]|0)>>>0)Wa();m=n+16|0;if((c[m>>2]|0)==(j|0))c[m>>2]=k;else c[n+20>>2]=k;if(!k)break}j=c[1210]|0;if(k>>>0<j>>>0)Wa();c[k+24>>2]=n;m=c[a+(l+16)>>2]|0;do if(m)if(m>>>0<j>>>0)Wa();else{c[k+16>>2]=m;c[m+24>>2]=k;break}while(0);j=c[a+(l+20)>>2]|0;if(j)if(j>>>0<(c[1210]|0)>>>0)Wa();else{c[k+20>>2]=j;c[j+24>>2]=k;break}}}else{k=c[a+(l+8)>>2]|0;l=c[a+(l+12)>>2]|0;p=4864+(o<<1<<2)|0;if((k|0)!=(p|0)){if(k>>>0<m>>>0)Wa();if((c[k+12>>2]|0)!=(j|0))Wa()}if((l|0)==(k|0)){c[1206]=c[1206]&~(1<<o);break}if((l|0)!=(p|0)){if(l>>>0<m>>>0)Wa();m=l+8|0;if((c[m>>2]|0)==(j|0))n=m;else Wa()}else n=l+8|0;c[k+12>>2]=l;c[n>>2]=k}while(0);if(h>>>0<16){c[f>>2]=g|e&1|2;r=a+(g|4)|0;c[r>>2]=c[r>>2]|1;r=a;i=d;return r|0}else{c[f>>2]=e&1|b|2;c[a+(b+4)>>2]=h|3;r=a+(g|4)|0;c[r>>2]=c[r>>2]|1;ge(a+b|0,h);r=a;i=d;return r|0}return 0}function ge(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;d=i;h=a+b|0;l=c[a+4>>2]|0;do if(!(l&1)){p=c[a>>2]|0;if(!(l&3)){i=d;return}l=a+(0-p)|0;m=p+b|0;r=c[1210]|0;if(l>>>0<r>>>0)Wa();if((l|0)==(c[1211]|0)){e=a+(b+4)|0;n=c[e>>2]|0;if((n&3|0)!=3){e=l;n=m;break}c[1208]=m;c[e>>2]=n&-2;c[a+(4-p)>>2]=m|1;c[h>>2]=m;i=d;return}s=p>>>3;if(p>>>0<256){e=c[a+(8-p)>>2]|0;n=c[a+(12-p)>>2]|0;o=4864+(s<<1<<2)|0;if((e|0)!=(o|0)){if(e>>>0<r>>>0)Wa();if((c[e+12>>2]|0)!=(l|0))Wa()}if((n|0)==(e|0)){c[1206]=c[1206]&~(1<<s);e=l;n=m;break}if((n|0)!=(o|0)){if(n>>>0<r>>>0)Wa();o=n+8|0;if((c[o>>2]|0)==(l|0))q=o;else Wa()}else q=n+8|0;c[e+12>>2]=n;c[q>>2]=e;e=l;n=m;break}q=c[a+(24-p)>>2]|0;s=c[a+(12-p)>>2]|0;do if((s|0)==(l|0)){u=16-p|0;t=a+(u+4)|0;s=c[t>>2]|0;if(!s){t=a+u|0;s=c[t>>2]|0;if(!s){o=0;break}}while(1){v=s+20|0;u=c[v>>2]|0;if(u){s=u;t=v;continue}u=s+16|0;v=c[u>>2]|0;if(!v)break;else{s=v;t=u}}if(t>>>0<r>>>0)Wa();else{c[t>>2]=0;o=s;break}}else{t=c[a+(8-p)>>2]|0;if(t>>>0<r>>>0)Wa();r=t+12|0;if((c[r>>2]|0)!=(l|0))Wa();u=s+8|0;if((c[u>>2]|0)==(l|0)){c[r>>2]=s;c[u>>2]=t;o=s;break}else Wa()}while(0);if(q){s=c[a+(28-p)>>2]|0;r=5128+(s<<2)|0;if((l|0)==(c[r>>2]|0)){c[r>>2]=o;if(!o){c[1207]=c[1207]&~(1<<s);e=l;n=m;break}}else{if(q>>>0<(c[1210]|0)>>>0)Wa();r=q+16|0;if((c[r>>2]|0)==(l|0))c[r>>2]=o;else c[q+20>>2]=o;if(!o){e=l;n=m;break}}r=c[1210]|0;if(o>>>0<r>>>0)Wa();c[o+24>>2]=q;p=16-p|0;q=c[a+p>>2]|0;do if(q)if(q>>>0<r>>>0)Wa();else{c[o+16>>2]=q;c[q+24>>2]=o;break}while(0);p=c[a+(p+4)>>2]|0;if(p)if(p>>>0<(c[1210]|0)>>>0)Wa();else{c[o+20>>2]=p;c[p+24>>2]=o;e=l;n=m;break}else{e=l;n=m}}else{e=l;n=m}}else{e=a;n=b}while(0);l=c[1210]|0;if(h>>>0<l>>>0)Wa();m=a+(b+4)|0;o=c[m>>2]|0;if(!(o&2)){if((h|0)==(c[1212]|0)){v=(c[1209]|0)+n|0;c[1209]=v;c[1212]=e;c[e+4>>2]=v|1;if((e|0)!=(c[1211]|0)){i=d;return}c[1211]=0;c[1208]=0;i=d;return}if((h|0)==(c[1211]|0)){v=(c[1208]|0)+n|0;c[1208]=v;c[1211]=e;c[e+4>>2]=v|1;c[e+v>>2]=v;i=d;return}n=(o&-8)+n|0;m=o>>>3;do if(o>>>0>=256){k=c[a+(b+24)>>2]|0;o=c[a+(b+12)>>2]|0;do if((o|0)==(h|0)){o=a+(b+20)|0;m=c[o>>2]|0;if(!m){o=a+(b+16)|0;m=c[o>>2]|0;if(!m){j=0;break}}while(1){p=m+20|0;q=c[p>>2]|0;if(q){m=q;o=p;continue}q=m+16|0;p=c[q>>2]|0;if(!p)break;else{m=p;o=q}}if(o>>>0<l>>>0)Wa();else{c[o>>2]=0;j=m;break}}else{m=c[a+(b+8)>>2]|0;if(m>>>0<l>>>0)Wa();p=m+12|0;if((c[p>>2]|0)!=(h|0))Wa();l=o+8|0;if((c[l>>2]|0)==(h|0)){c[p>>2]=o;c[l>>2]=m;j=o;break}else Wa()}while(0);if(k){m=c[a+(b+28)>>2]|0;l=5128+(m<<2)|0;if((h|0)==(c[l>>2]|0)){c[l>>2]=j;if(!j){c[1207]=c[1207]&~(1<<m);break}}else{if(k>>>0<(c[1210]|0)>>>0)Wa();l=k+16|0;if((c[l>>2]|0)==(h|0))c[l>>2]=j;else c[k+20>>2]=j;if(!j)break}h=c[1210]|0;if(j>>>0<h>>>0)Wa();c[j+24>>2]=k;k=c[a+(b+16)>>2]|0;do if(k)if(k>>>0<h>>>0)Wa();else{c[j+16>>2]=k;c[k+24>>2]=j;break}while(0);h=c[a+(b+20)>>2]|0;if(h)if(h>>>0<(c[1210]|0)>>>0)Wa();else{c[j+20>>2]=h;c[h+24>>2]=j;break}}}else{j=c[a+(b+8)>>2]|0;a=c[a+(b+12)>>2]|0;b=4864+(m<<1<<2)|0;if((j|0)!=(b|0)){if(j>>>0<l>>>0)Wa();if((c[j+12>>2]|0)!=(h|0))Wa()}if((a|0)==(j|0)){c[1206]=c[1206]&~(1<<m);break}if((a|0)!=(b|0)){if(a>>>0<l>>>0)Wa();b=a+8|0;if((c[b>>2]|0)==(h|0))k=b;else Wa()}else k=a+8|0;c[j+12>>2]=a;c[k>>2]=j}while(0);c[e+4>>2]=n|1;c[e+n>>2]=n;if((e|0)==(c[1211]|0)){c[1208]=n;i=d;return}}else{c[m>>2]=o&-2;c[e+4>>2]=n|1;c[e+n>>2]=n}b=n>>>3;if(n>>>0<256){a=b<<1;h=4864+(a<<2)|0;j=c[1206]|0;b=1<<b;if(j&b){a=4864+(a+2<<2)|0;j=c[a>>2]|0;if(j>>>0<(c[1210]|0)>>>0)Wa();else{g=a;f=j}}else{c[1206]=j|b;g=4864+(a+2<<2)|0;f=h}c[g>>2]=e;c[f+12>>2]=e;c[e+8>>2]=f;c[e+12>>2]=h;i=d;return}f=n>>>8;if(f)if(n>>>0>16777215)f=31;else{u=(f+1048320|0)>>>16&8;v=f<<u;t=(v+520192|0)>>>16&4;v=v<<t;f=(v+245760|0)>>>16&2;f=14-(t|u|f)+(v<<f>>>15)|0;f=n>>>(f+7|0)&1|f<<1}else f=0;g=5128+(f<<2)|0;c[e+28>>2]=f;c[e+20>>2]=0;c[e+16>>2]=0;a=c[1207]|0;h=1<<f;if(!(a&h)){c[1207]=a|h;c[g>>2]=e;c[e+24>>2]=g;c[e+12>>2]=e;c[e+8>>2]=e;i=d;return}g=c[g>>2]|0;if((f|0)==31)f=0;else f=25-(f>>>1)|0;a:do if((c[g+4>>2]&-8|0)!=(n|0)){f=n<<f;a=g;while(1){h=a+(f>>>31<<2)+16|0;g=c[h>>2]|0;if(!g)break;if((c[g+4>>2]&-8|0)==(n|0))break a;else{f=f<<1;a=g}}if(h>>>0<(c[1210]|0)>>>0)Wa();c[h>>2]=e;c[e+24>>2]=a;c[e+12>>2]=e;c[e+8>>2]=e;i=d;return}while(0);f=g+8|0;h=c[f>>2]|0;v=c[1210]|0;if(!(g>>>0>=v>>>0&h>>>0>=v>>>0))Wa();c[h+12>>2]=e;c[f>>2]=e;c[e+8>>2]=h;c[e+12>>2]=g;c[e+24>>2]=0;i=d;return}function he(a,b){a=a|0;b=b|0;var e=0,f=0,g=0,h=0,j=0,k=0;e=i;g=a+4|0;h=c[g>>2]|0;f=a+100|0;if(h>>>0<(c[f>>2]|0)>>>0){c[g>>2]=h+1;j=d[h>>0]|0}else j=Zd(a)|0;if((j|0)==43|(j|0)==45){k=c[g>>2]|0;h=(j|0)==45&1;if(k>>>0<(c[f>>2]|0)>>>0){c[g>>2]=k+1;j=d[k>>0]|0}else j=Zd(a)|0;if((j+ -48|0)>>>0>9&(b|0)!=0?(c[f>>2]|0)!=0:0)c[g>>2]=(c[g>>2]|0)+ -1}else h=0;if((j+ -48|0)>>>0>9){if(!(c[f>>2]|0)){j=-2147483648;k=0;F=j;i=e;return k|0}c[g>>2]=(c[g>>2]|0)+ -1;j=-2147483648;k=0;F=j;i=e;return k|0}else b=0;do{b=j+ -48+(b*10|0)|0;j=c[g>>2]|0;if(j>>>0<(c[f>>2]|0)>>>0){c[g>>2]=j+1;j=d[j>>0]|0}else j=Zd(a)|0}while((j+ -48|0)>>>0<10&(b|0)<214748364);k=((b|0)<0)<<31>>31;if((j+ -48|0)>>>0<10)do{k=we(b|0,k|0,10,0)|0;b=F;j=ne(j|0,((j|0)<0)<<31>>31|0,-48,-1)|0;b=ne(j|0,F|0,k|0,b|0)|0;k=F;j=c[g>>2]|0;if(j>>>0<(c[f>>2]|0)>>>0){c[g>>2]=j+1;j=d[j>>0]|0}else j=Zd(a)|0}while((j+ -48|0)>>>0<10&((k|0)<21474836|(k|0)==21474836&b>>>0<2061584302));if((j+ -48|0)>>>0<10)do{j=c[g>>2]|0;if(j>>>0<(c[f>>2]|0)>>>0){c[g>>2]=j+1;j=d[j>>0]|0}else j=Zd(a)|0}while((j+ -48|0)>>>0<10);if(c[f>>2]|0)c[g>>2]=(c[g>>2]|0)+ -1;g=(h|0)!=0;h=je(0,0,b|0,k|0)|0;j=g?F:k;k=g?h:b;F=j;i=e;return k|0}function ie(){}function je(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;b=b-d-(c>>>0>a>>>0|0)>>>0;return(F=b,a-c>>>0|0)|0}function ke(b,d,e){b=b|0;d=d|0;e=e|0;var f=0,g=0,h=0,i=0;f=b+e|0;if((e|0)>=20){d=d&255;i=b&3;h=d|d<<8|d<<16|d<<24;g=f&~3;if(i){i=b+4-i|0;while((b|0)<(i|0)){a[b>>0]=d;b=b+1|0}}while((b|0)<(g|0)){c[b>>2]=h;b=b+4|0}}while((b|0)<(f|0)){a[b>>0]=d;b=b+1|0}return b-e|0}function le(a,b,c){a=a|0;b=b|0;c=c|0;if((c|0)<32){F=b<<c|(a&(1<<c)-1<<32-c)>>>32-c;return a<<c}F=a<<c-32;return 0}function me(b){b=b|0;var c=0;c=b;while(a[c>>0]|0)c=c+1|0;return c-b|0}function ne(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;c=a+c>>>0;return(F=b+d+(c>>>0<a>>>0|0)>>>0,c|0)|0}function oe(a,b,c){a=a|0;b=b|0;c=c|0;if((c|0)<32){F=b>>>c;return a>>>c|(b&(1<<c)-1)<<32-c}F=0;return b>>>c-32|0}function pe(b,d,e){b=b|0;d=d|0;e=e|0;var f=0;if((e|0)>=4096)return Ca(b|0,d|0,e|0)|0;f=b|0;if((b&3)==(d&3)){while(b&3){if(!e)return f|0;a[b>>0]=a[d>>0]|0;b=b+1|0;d=d+1|0;e=e-1|0}while((e|0)>=4){c[b>>2]=c[d>>2];b=b+4|0;d=d+4|0;e=e-4|0}}while((e|0)>0){a[b>>0]=a[d>>0]|0;b=b+1|0;d=d+1|0;e=e-1|0}return f|0}function qe(a,b,c){a=a|0;b=b|0;c=c|0;if((c|0)<32){F=b>>c;return a>>>c|(b&(1<<c)-1)<<32-c}F=(b|0)<0?-1:0;return b>>c-32|0}function re(b){b=b|0;var c=0;c=a[n+(b>>>24)>>0]|0;if((c|0)<8)return c|0;c=a[n+(b>>16&255)>>0]|0;if((c|0)<8)return c+8|0;c=a[n+(b>>8&255)>>0]|0;if((c|0)<8)return c+16|0;return(a[n+(b&255)>>0]|0)+24|0}function se(b){b=b|0;var c=0;c=a[m+(b&255)>>0]|0;if((c|0)<8)return c|0;c=a[m+(b>>8&255)>>0]|0;if((c|0)<8)return c+8|0;c=a[m+(b>>16&255)>>0]|0;if((c|0)<8)return c+16|0;return(a[m+(b>>>24)>>0]|0)+24|0}function te(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0;f=a&65535;d=b&65535;c=ba(d,f)|0;e=a>>>16;d=(c>>>16)+(ba(d,e)|0)|0;b=b>>>16;a=ba(b,f)|0;return(F=(d>>>16)+(ba(b,e)|0)+(((d&65535)+a|0)>>>16)|0,d+a<<16|c&65535|0)|0}function ue(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0;j=b>>31|((b|0)<0?-1:0)<<1;i=((b|0)<0?-1:0)>>31|((b|0)<0?-1:0)<<1;f=d>>31|((d|0)<0?-1:0)<<1;e=((d|0)<0?-1:0)>>31|((d|0)<0?-1:0)<<1;h=je(j^a,i^b,j,i)|0;g=F;b=f^j;a=e^i;a=je((ze(h,g,je(f^c,e^d,f,e)|0,F,0)|0)^b,F^a,b,a)|0;return a|0}function ve(a,b,d,e){a=a|0;b=b|0;d=d|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0;f=i;i=i+8|0;j=f|0;h=b>>31|((b|0)<0?-1:0)<<1;g=((b|0)<0?-1:0)>>31|((b|0)<0?-1:0)<<1;l=e>>31|((e|0)<0?-1:0)<<1;k=((e|0)<0?-1:0)>>31|((e|0)<0?-1:0)<<1;b=je(h^a,g^b,h,g)|0;a=F;ze(b,a,je(l^d,k^e,l,k)|0,F,j)|0;a=je(c[j>>2]^h,c[j+4>>2]^g,h,g)|0;b=F;i=f;return(F=b,a)|0}function we(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0;e=a;f=c;a=te(e,f)|0;c=F;return(F=(ba(b,f)|0)+(ba(d,e)|0)+c|c&0,a|0|0)|0}function xe(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;a=ze(a,b,c,d,0)|0;return a|0}function ye(a,b,d,e){a=a|0;b=b|0;d=d|0;e=e|0;var f=0,g=0;g=i;i=i+8|0;f=g|0;ze(a,b,d,e,f)|0;i=g;return(F=c[f+4>>2]|0,c[f>>2]|0)|0}function ze(a,b,d,e,f){a=a|0;b=b|0;d=d|0;e=e|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;h=a;j=b;i=j;l=d;g=e;k=g;if(!i){g=(f|0)!=0;if(!k){if(g){c[f>>2]=(h>>>0)%(l>>>0);c[f+4>>2]=0}k=0;m=(h>>>0)/(l>>>0)>>>0;return(F=k,m)|0}else{if(!g){l=0;m=0;return(F=l,m)|0}c[f>>2]=a|0;c[f+4>>2]=b&0;l=0;m=0;return(F=l,m)|0}}m=(k|0)==0;do if(l){if(!m){k=(re(k|0)|0)-(re(i|0)|0)|0;if(k>>>0<=31){m=k+1|0;l=31-k|0;a=k-31>>31;j=m;b=h>>>(m>>>0)&a|i<<l;a=i>>>(m>>>0)&a;k=0;l=h<<l;break}if(!f){l=0;m=0;return(F=l,m)|0}c[f>>2]=a|0;c[f+4>>2]=j|b&0;l=0;m=0;return(F=l,m)|0}k=l-1|0;if(k&l){l=(re(l|0)|0)+33-(re(i|0)|0)|0;p=64-l|0;m=32-l|0;n=m>>31;o=l-32|0;a=o>>31;j=l;b=m-1>>31&i>>>(o>>>0)|(i<<m|h>>>(l>>>0))&a;a=a&i>>>(l>>>0);k=h<<p&n;l=(i<<p|h>>>(o>>>0))&n|h<<m&l-33>>31;break}if(f){c[f>>2]=k&h;c[f+4>>2]=0}if((l|0)==1){o=j|b&0;p=a|0|0;return(F=o,p)|0}else{p=se(l|0)|0;o=i>>>(p>>>0)|0;p=i<<32-p|h>>>(p>>>0)|0;return(F=o,p)|0}}else{if(m){if(f){c[f>>2]=(i>>>0)%(l>>>0);c[f+4>>2]=0}o=0;p=(i>>>0)/(l>>>0)>>>0;return(F=o,p)|0}if(!h){if(f){c[f>>2]=0;c[f+4>>2]=(i>>>0)%(k>>>0)}o=0;p=(i>>>0)/(k>>>0)>>>0;return(F=o,p)|0}l=k-1|0;if(!(l&k)){if(f){c[f>>2]=a|0;c[f+4>>2]=l&i|b&0}o=0;p=i>>>((se(k|0)|0)>>>0);return(F=o,p)|0}k=(re(k|0)|0)-(re(i|0)|0)|0;if(k>>>0<=30){a=k+1|0;l=31-k|0;j=a;b=i<<l|h>>>(a>>>0);a=i>>>(a>>>0);k=0;l=h<<l;break}if(!f){o=0;p=0;return(F=o,p)|0}c[f>>2]=a|0;c[f+4>>2]=j|b&0;o=0;p=0;return(F=o,p)|0}while(0);if(!j){g=l;e=0;i=0}else{h=d|0|0;g=g|e&0;e=ne(h,g,-1,-1)|0;d=F;i=0;do{m=l;l=k>>>31|l<<1;k=i|k<<1;m=b<<1|m>>>31|0;n=b>>>31|a<<1|0;je(e,d,m,n)|0;p=F;o=p>>31|((p|0)<0?-1:0)<<1;i=o&1;b=je(m,n,o&h,(((p|0)<0?-1:0)>>31|((p|0)<0?-1:0)<<1)&g)|0;a=F;j=j-1|0}while((j|0)!=0);g=l;e=0}h=0;if(f){c[f>>2]=b;c[f+4>>2]=a}o=(k|0)>>>31|(g|h)<<1|(h<<1|k>>>31)&0|e;p=(k<<1|0>>>31)&-2|i;return(F=o,p)|0}function Ae(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;return eb[a&1](b|0,c|0,d|0)|0}function Be(a,b,c,d,e,f){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;fb[a&3](b|0,c|0,d|0,e|0,f|0)}function Ce(a,b){a=a|0;b=b|0;gb[a&31](b|0)}function De(a,b,c){a=a|0;b=b|0;c=c|0;hb[a&3](b|0,c|0)}function Ee(a,b){a=a|0;b=b|0;return ib[a&1](b|0)|0}function Fe(a){a=a|0;jb[a&3]()}function Ge(a,b,c,d,e,f,g){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;kb[a&3](b|0,c|0,d|0,e|0,f|0,g|0)}function He(a,b,c){a=a|0;b=b|0;c=c|0;return lb[a&3](b|0,c|0)|0}function Ie(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;mb[a&3](b|0,c|0,d|0,e|0)}function Je(a,b,c){a=a|0;b=b|0;c=c|0;ca(0);return 0}function Ke(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;ca(1)}function Le(a){a=a|0;ca(2)}function Me(a,b){a=a|0;b=b|0;ca(3)}function Ne(a){a=a|0;ca(4);return 0}function Oe(){ca(5)}function Pe(){bb()}function Qe(a,b,c,d,e,f){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;ca(6)}function Re(a,b){a=a|0;b=b|0;ca(7);return 0}function Se(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;ca(8)}




// EMSCRIPTEN_END_FUNCS
// (start of meteor/midamble.js)
// This "midamble" is hacked into the output JS in a place
// where it has access to the inner function generated
// by Emscripten, the one that starts with "use asm".
// NOTE: This doesn't work with minification on!
/////setInnerMalloc = function (hookedMalloc) {
/////  _malloc = hookedMalloc;
/////};
/////setInnerFree = function (hookedFree) {
/////  _free = hookedFree;
/////};
// (end of meteor/midamble.js)
var eb=[Je,Dd];var fb=[Ke,Kd,Jd,Ke];var gb=[Le,wb,yb,Ab,Db,Ib,Hb,bc,dc,zc,yc,Oc,rd,qd,yd,Bd,zd,Ad,Cd,zb,Rd,Le,Le,Le,Le,Le,Le,Le,Le,Le,Le,Le];var hb=[Me,Cb,Fb,fc];var ib=[Ne,sd];var jb=[Oe,Pe,Pd,Qd];var kb=[Qe,Md,Ld,Qe];var lb=[Re,Bb,Eb,ec];var mb=[Se,Fd,Gd,Se];return{_yo:$c,_strlen:me,_retireVar:id,_bitshift64Lshr:oe,_unyo:ad,_solve:ed,_bitshift64Shl:le,_getSolution:fd,___cxa_is_pointer_type:Od,_memset:ke,_getNumVars:gd,_memcpy:pe,_getConflictClauseSize:jd,_addClause:dd,_i64Subtract:je,_createTheSolver:bd,_realloc:Ud,_i64Add:ne,_solveAssuming:hd,___cxa_can_catch:Nd,_ensureVar:cd,_getConflictClause:kd,_free:Td,_malloc:Sd,__GLOBAL__I_a:cc,__GLOBAL__I_a127:Pc,runPostSets:ie,stackAlloc:nb,stackSave:ob,stackRestore:pb,setThrew:qb,setTempRet0:tb,getTempRet0:ub,dynCall_iiii:Ae,dynCall_viiiii:Be,dynCall_vi:Ce,dynCall_vii:De,dynCall_ii:Ee,dynCall_v:Fe,dynCall_viiiiii:Ge,dynCall_iii:He,dynCall_viiii:Ie}})


// EMSCRIPTEN_END_ASM
(Module.asmGlobalArg,Module.asmLibraryArg,buffer);var _yo=Module["_yo"]=asm["_yo"];var _strlen=Module["_strlen"]=asm["_strlen"];var _retireVar=Module["_retireVar"]=asm["_retireVar"];var _bitshift64Lshr=Module["_bitshift64Lshr"]=asm["_bitshift64Lshr"];var _unyo=Module["_unyo"]=asm["_unyo"];var _solve=Module["_solve"]=asm["_solve"];var _bitshift64Shl=Module["_bitshift64Shl"]=asm["_bitshift64Shl"];var _getSolution=Module["_getSolution"]=asm["_getSolution"];var ___cxa_is_pointer_type=Module["___cxa_is_pointer_type"]=asm["___cxa_is_pointer_type"];var _memset=Module["_memset"]=asm["_memset"];var _getNumVars=Module["_getNumVars"]=asm["_getNumVars"];var _memcpy=Module["_memcpy"]=asm["_memcpy"];var _getConflictClauseSize=Module["_getConflictClauseSize"]=asm["_getConflictClauseSize"];var _addClause=Module["_addClause"]=asm["_addClause"];var _i64Subtract=Module["_i64Subtract"]=asm["_i64Subtract"];var _createTheSolver=Module["_createTheSolver"]=asm["_createTheSolver"];var _realloc=Module["_realloc"]=asm["_realloc"];var _i64Add=Module["_i64Add"]=asm["_i64Add"];var _solveAssuming=Module["_solveAssuming"]=asm["_solveAssuming"];var ___cxa_can_catch=Module["___cxa_can_catch"]=asm["___cxa_can_catch"];var _ensureVar=Module["_ensureVar"]=asm["_ensureVar"];var _getConflictClause=Module["_getConflictClause"]=asm["_getConflictClause"];var _free=Module["_free"]=asm["_free"];var _malloc=Module["_malloc"]=asm["_malloc"];var __GLOBAL__I_a=Module["__GLOBAL__I_a"]=asm["__GLOBAL__I_a"];var __GLOBAL__I_a127=Module["__GLOBAL__I_a127"]=asm["__GLOBAL__I_a127"];var runPostSets=Module["runPostSets"]=asm["runPostSets"];var dynCall_iiii=Module["dynCall_iiii"]=asm["dynCall_iiii"];var dynCall_viiiii=Module["dynCall_viiiii"]=asm["dynCall_viiiii"];var dynCall_vi=Module["dynCall_vi"]=asm["dynCall_vi"];var dynCall_vii=Module["dynCall_vii"]=asm["dynCall_vii"];var dynCall_ii=Module["dynCall_ii"]=asm["dynCall_ii"];var dynCall_v=Module["dynCall_v"]=asm["dynCall_v"];var dynCall_viiiiii=Module["dynCall_viiiiii"]=asm["dynCall_viiiiii"];var dynCall_iii=Module["dynCall_iii"]=asm["dynCall_iii"];var dynCall_viiii=Module["dynCall_viiii"]=asm["dynCall_viiii"];Runtime.stackAlloc=asm["stackAlloc"];Runtime.stackSave=asm["stackSave"];Runtime.stackRestore=asm["stackRestore"];Runtime.setTempRet0=asm["setTempRet0"];Runtime.getTempRet0=asm["getTempRet0"];var i64Math=(function(){var goog={math:{}};goog.math.Long=(function(low,high){this.low_=low|0;this.high_=high|0});goog.math.Long.IntCache_={};goog.math.Long.fromInt=(function(value){if(-128<=value&&value<128){var cachedObj=goog.math.Long.IntCache_[value];if(cachedObj){return cachedObj}}var obj=new goog.math.Long(value|0,value<0?-1:0);if(-128<=value&&value<128){goog.math.Long.IntCache_[value]=obj}return obj});goog.math.Long.fromNumber=(function(value){if(isNaN(value)||!isFinite(value)){return goog.math.Long.ZERO}else if(value<=-goog.math.Long.TWO_PWR_63_DBL_){return goog.math.Long.MIN_VALUE}else if(value+1>=goog.math.Long.TWO_PWR_63_DBL_){return goog.math.Long.MAX_VALUE}else if(value<0){return goog.math.Long.fromNumber(-value).negate()}else{return new goog.math.Long(value%goog.math.Long.TWO_PWR_32_DBL_|0,value/goog.math.Long.TWO_PWR_32_DBL_|0)}});goog.math.Long.fromBits=(function(lowBits,highBits){return new goog.math.Long(lowBits,highBits)});goog.math.Long.fromString=(function(str,opt_radix){if(str.length==0){throw Error("number format error: empty string")}var radix=opt_radix||10;if(radix<2||36<radix){throw Error("radix out of range: "+radix)}if(str.charAt(0)=="-"){return goog.math.Long.fromString(str.substring(1),radix).negate()}else if(str.indexOf("-")>=0){throw Error('number format error: interior "-" character: '+str)}var radixToPower=goog.math.Long.fromNumber(Math.pow(radix,8));var result=goog.math.Long.ZERO;for(var i=0;i<str.length;i+=8){var size=Math.min(8,str.length-i);var value=parseInt(str.substring(i,i+size),radix);if(size<8){var power=goog.math.Long.fromNumber(Math.pow(radix,size));result=result.multiply(power).add(goog.math.Long.fromNumber(value))}else{result=result.multiply(radixToPower);result=result.add(goog.math.Long.fromNumber(value))}}return result});goog.math.Long.TWO_PWR_16_DBL_=1<<16;goog.math.Long.TWO_PWR_24_DBL_=1<<24;goog.math.Long.TWO_PWR_32_DBL_=goog.math.Long.TWO_PWR_16_DBL_*goog.math.Long.TWO_PWR_16_DBL_;goog.math.Long.TWO_PWR_31_DBL_=goog.math.Long.TWO_PWR_32_DBL_/2;goog.math.Long.TWO_PWR_48_DBL_=goog.math.Long.TWO_PWR_32_DBL_*goog.math.Long.TWO_PWR_16_DBL_;goog.math.Long.TWO_PWR_64_DBL_=goog.math.Long.TWO_PWR_32_DBL_*goog.math.Long.TWO_PWR_32_DBL_;goog.math.Long.TWO_PWR_63_DBL_=goog.math.Long.TWO_PWR_64_DBL_/2;goog.math.Long.ZERO=goog.math.Long.fromInt(0);goog.math.Long.ONE=goog.math.Long.fromInt(1);goog.math.Long.NEG_ONE=goog.math.Long.fromInt(-1);goog.math.Long.MAX_VALUE=goog.math.Long.fromBits(4294967295|0,2147483647|0);goog.math.Long.MIN_VALUE=goog.math.Long.fromBits(0,2147483648|0);goog.math.Long.TWO_PWR_24_=goog.math.Long.fromInt(1<<24);goog.math.Long.prototype.toInt=(function(){return this.low_});goog.math.Long.prototype.toNumber=(function(){return this.high_*goog.math.Long.TWO_PWR_32_DBL_+this.getLowBitsUnsigned()});goog.math.Long.prototype.toString=(function(opt_radix){var radix=opt_radix||10;if(radix<2||36<radix){throw Error("radix out of range: "+radix)}if(this.isZero()){return"0"}if(this.isNegative()){if(this.equals(goog.math.Long.MIN_VALUE)){var radixLong=goog.math.Long.fromNumber(radix);var div=this.div(radixLong);var rem=div.multiply(radixLong).subtract(this);return div.toString(radix)+rem.toInt().toString(radix)}else{return"-"+this.negate().toString(radix)}}var radixToPower=goog.math.Long.fromNumber(Math.pow(radix,6));var rem=this;var result="";while(true){var remDiv=rem.div(radixToPower);var intval=rem.subtract(remDiv.multiply(radixToPower)).toInt();var digits=intval.toString(radix);rem=remDiv;if(rem.isZero()){return digits+result}else{while(digits.length<6){digits="0"+digits}result=""+digits+result}}});goog.math.Long.prototype.getHighBits=(function(){return this.high_});goog.math.Long.prototype.getLowBits=(function(){return this.low_});goog.math.Long.prototype.getLowBitsUnsigned=(function(){return this.low_>=0?this.low_:goog.math.Long.TWO_PWR_32_DBL_+this.low_});goog.math.Long.prototype.getNumBitsAbs=(function(){if(this.isNegative()){if(this.equals(goog.math.Long.MIN_VALUE)){return 64}else{return this.negate().getNumBitsAbs()}}else{var val=this.high_!=0?this.high_:this.low_;for(var bit=31;bit>0;bit--){if((val&1<<bit)!=0){break}}return this.high_!=0?bit+33:bit+1}});goog.math.Long.prototype.isZero=(function(){return this.high_==0&&this.low_==0});goog.math.Long.prototype.isNegative=(function(){return this.high_<0});goog.math.Long.prototype.isOdd=(function(){return(this.low_&1)==1});goog.math.Long.prototype.equals=(function(other){return this.high_==other.high_&&this.low_==other.low_});goog.math.Long.prototype.notEquals=(function(other){return this.high_!=other.high_||this.low_!=other.low_});goog.math.Long.prototype.lessThan=(function(other){return this.compare(other)<0});goog.math.Long.prototype.lessThanOrEqual=(function(other){return this.compare(other)<=0});goog.math.Long.prototype.greaterThan=(function(other){return this.compare(other)>0});goog.math.Long.prototype.greaterThanOrEqual=(function(other){return this.compare(other)>=0});goog.math.Long.prototype.compare=(function(other){if(this.equals(other)){return 0}var thisNeg=this.isNegative();var otherNeg=other.isNegative();if(thisNeg&&!otherNeg){return-1}if(!thisNeg&&otherNeg){return 1}if(this.subtract(other).isNegative()){return-1}else{return 1}});goog.math.Long.prototype.negate=(function(){if(this.equals(goog.math.Long.MIN_VALUE)){return goog.math.Long.MIN_VALUE}else{return this.not().add(goog.math.Long.ONE)}});goog.math.Long.prototype.add=(function(other){var a48=this.high_>>>16;var a32=this.high_&65535;var a16=this.low_>>>16;var a00=this.low_&65535;var b48=other.high_>>>16;var b32=other.high_&65535;var b16=other.low_>>>16;var b00=other.low_&65535;var c48=0,c32=0,c16=0,c00=0;c00+=a00+b00;c16+=c00>>>16;c00&=65535;c16+=a16+b16;c32+=c16>>>16;c16&=65535;c32+=a32+b32;c48+=c32>>>16;c32&=65535;c48+=a48+b48;c48&=65535;return goog.math.Long.fromBits(c16<<16|c00,c48<<16|c32)});goog.math.Long.prototype.subtract=(function(other){return this.add(other.negate())});goog.math.Long.prototype.multiply=(function(other){if(this.isZero()){return goog.math.Long.ZERO}else if(other.isZero()){return goog.math.Long.ZERO}if(this.equals(goog.math.Long.MIN_VALUE)){return other.isOdd()?goog.math.Long.MIN_VALUE:goog.math.Long.ZERO}else if(other.equals(goog.math.Long.MIN_VALUE)){return this.isOdd()?goog.math.Long.MIN_VALUE:goog.math.Long.ZERO}if(this.isNegative()){if(other.isNegative()){return this.negate().multiply(other.negate())}else{return this.negate().multiply(other).negate()}}else if(other.isNegative()){return this.multiply(other.negate()).negate()}if(this.lessThan(goog.math.Long.TWO_PWR_24_)&&other.lessThan(goog.math.Long.TWO_PWR_24_)){return goog.math.Long.fromNumber(this.toNumber()*other.toNumber())}var a48=this.high_>>>16;var a32=this.high_&65535;var a16=this.low_>>>16;var a00=this.low_&65535;var b48=other.high_>>>16;var b32=other.high_&65535;var b16=other.low_>>>16;var b00=other.low_&65535;var c48=0,c32=0,c16=0,c00=0;c00+=a00*b00;c16+=c00>>>16;c00&=65535;c16+=a16*b00;c32+=c16>>>16;c16&=65535;c16+=a00*b16;c32+=c16>>>16;c16&=65535;c32+=a32*b00;c48+=c32>>>16;c32&=65535;c32+=a16*b16;c48+=c32>>>16;c32&=65535;c32+=a00*b32;c48+=c32>>>16;c32&=65535;c48+=a48*b00+a32*b16+a16*b32+a00*b48;c48&=65535;return goog.math.Long.fromBits(c16<<16|c00,c48<<16|c32)});goog.math.Long.prototype.div=(function(other){if(other.isZero()){throw Error("division by zero")}else if(this.isZero()){return goog.math.Long.ZERO}if(this.equals(goog.math.Long.MIN_VALUE)){if(other.equals(goog.math.Long.ONE)||other.equals(goog.math.Long.NEG_ONE)){return goog.math.Long.MIN_VALUE}else if(other.equals(goog.math.Long.MIN_VALUE)){return goog.math.Long.ONE}else{var halfThis=this.shiftRight(1);var approx=halfThis.div(other).shiftLeft(1);if(approx.equals(goog.math.Long.ZERO)){return other.isNegative()?goog.math.Long.ONE:goog.math.Long.NEG_ONE}else{var rem=this.subtract(other.multiply(approx));var result=approx.add(rem.div(other));return result}}}else if(other.equals(goog.math.Long.MIN_VALUE)){return goog.math.Long.ZERO}if(this.isNegative()){if(other.isNegative()){return this.negate().div(other.negate())}else{return this.negate().div(other).negate()}}else if(other.isNegative()){return this.div(other.negate()).negate()}var res=goog.math.Long.ZERO;var rem=this;while(rem.greaterThanOrEqual(other)){var approx=Math.max(1,Math.floor(rem.toNumber()/other.toNumber()));var log2=Math.ceil(Math.log(approx)/Math.LN2);var delta=log2<=48?1:Math.pow(2,log2-48);var approxRes=goog.math.Long.fromNumber(approx);var approxRem=approxRes.multiply(other);while(approxRem.isNegative()||approxRem.greaterThan(rem)){approx-=delta;approxRes=goog.math.Long.fromNumber(approx);approxRem=approxRes.multiply(other)}if(approxRes.isZero()){approxRes=goog.math.Long.ONE}res=res.add(approxRes);rem=rem.subtract(approxRem)}return res});goog.math.Long.prototype.modulo=(function(other){return this.subtract(this.div(other).multiply(other))});goog.math.Long.prototype.not=(function(){return goog.math.Long.fromBits(~this.low_,~this.high_)});goog.math.Long.prototype.and=(function(other){return goog.math.Long.fromBits(this.low_&other.low_,this.high_&other.high_)});goog.math.Long.prototype.or=(function(other){return goog.math.Long.fromBits(this.low_|other.low_,this.high_|other.high_)});goog.math.Long.prototype.xor=(function(other){return goog.math.Long.fromBits(this.low_^other.low_,this.high_^other.high_)});goog.math.Long.prototype.shiftLeft=(function(numBits){numBits&=63;if(numBits==0){return this}else{var low=this.low_;if(numBits<32){var high=this.high_;return goog.math.Long.fromBits(low<<numBits,high<<numBits|low>>>32-numBits)}else{return goog.math.Long.fromBits(0,low<<numBits-32)}}});goog.math.Long.prototype.shiftRight=(function(numBits){numBits&=63;if(numBits==0){return this}else{var high=this.high_;if(numBits<32){var low=this.low_;return goog.math.Long.fromBits(low>>>numBits|high<<32-numBits,high>>numBits)}else{return goog.math.Long.fromBits(high>>numBits-32,high>=0?0:-1)}}});goog.math.Long.prototype.shiftRightUnsigned=(function(numBits){numBits&=63;if(numBits==0){return this}else{var high=this.high_;if(numBits<32){var low=this.low_;return goog.math.Long.fromBits(low>>>numBits|high<<32-numBits,high>>>numBits)}else if(numBits==32){return goog.math.Long.fromBits(high,0)}else{return goog.math.Long.fromBits(high>>>numBits-32,0)}}});var navigator={appName:"Modern Browser"};var dbits;var canary=0xdeadbeefcafe;var j_lm=(canary&16777215)==15715070;function BigInteger(a,b,c){if(a!=null)if("number"==typeof a)this.fromNumber(a,b,c);else if(b==null&&"string"!=typeof a)this.fromString(a,256);else this.fromString(a,b)}function nbi(){return new BigInteger(null)}function am1(i,x,w,j,c,n){while(--n>=0){var v=x*this[i++]+w[j]+c;c=Math.floor(v/67108864);w[j++]=v&67108863}return c}function am2(i,x,w,j,c,n){var xl=x&32767,xh=x>>15;while(--n>=0){var l=this[i]&32767;var h=this[i++]>>15;var m=xh*l+h*xl;l=xl*l+((m&32767)<<15)+w[j]+(c&1073741823);c=(l>>>30)+(m>>>15)+xh*h+(c>>>30);w[j++]=l&1073741823}return c}function am3(i,x,w,j,c,n){var xl=x&16383,xh=x>>14;while(--n>=0){var l=this[i]&16383;var h=this[i++]>>14;var m=xh*l+h*xl;l=xl*l+((m&16383)<<14)+w[j]+c;c=(l>>28)+(m>>14)+xh*h;w[j++]=l&268435455}return c}if(j_lm&&navigator.appName=="Microsoft Internet Explorer"){BigInteger.prototype.am=am2;dbits=30}else if(j_lm&&navigator.appName!="Netscape"){BigInteger.prototype.am=am1;dbits=26}else{BigInteger.prototype.am=am3;dbits=28}BigInteger.prototype.DB=dbits;BigInteger.prototype.DM=(1<<dbits)-1;BigInteger.prototype.DV=1<<dbits;var BI_FP=52;BigInteger.prototype.FV=Math.pow(2,BI_FP);BigInteger.prototype.F1=BI_FP-dbits;BigInteger.prototype.F2=2*dbits-BI_FP;var BI_RM="0123456789abcdefghijklmnopqrstuvwxyz";var BI_RC=new Array;var rr,vv;rr="0".charCodeAt(0);for(vv=0;vv<=9;++vv)BI_RC[rr++]=vv;rr="a".charCodeAt(0);for(vv=10;vv<36;++vv)BI_RC[rr++]=vv;rr="A".charCodeAt(0);for(vv=10;vv<36;++vv)BI_RC[rr++]=vv;function int2char(n){return BI_RM.charAt(n)}function intAt(s,i){var c=BI_RC[s.charCodeAt(i)];return c==null?-1:c}function bnpCopyTo(r){for(var i=this.t-1;i>=0;--i)r[i]=this[i];r.t=this.t;r.s=this.s}function bnpFromInt(x){this.t=1;this.s=x<0?-1:0;if(x>0)this[0]=x;else if(x<-1)this[0]=x+DV;else this.t=0}function nbv(i){var r=nbi();r.fromInt(i);return r}function bnpFromString(s,b){var k;if(b==16)k=4;else if(b==8)k=3;else if(b==256)k=8;else if(b==2)k=1;else if(b==32)k=5;else if(b==4)k=2;else{this.fromRadix(s,b);return}this.t=0;this.s=0;var i=s.length,mi=false,sh=0;while(--i>=0){var x=k==8?s[i]&255:intAt(s,i);if(x<0){if(s.charAt(i)=="-")mi=true;continue}mi=false;if(sh==0)this[this.t++]=x;else if(sh+k>this.DB){this[this.t-1]|=(x&(1<<this.DB-sh)-1)<<sh;this[this.t++]=x>>this.DB-sh}else this[this.t-1]|=x<<sh;sh+=k;if(sh>=this.DB)sh-=this.DB}if(k==8&&(s[0]&128)!=0){this.s=-1;if(sh>0)this[this.t-1]|=(1<<this.DB-sh)-1<<sh}this.clamp();if(mi)BigInteger.ZERO.subTo(this,this)}function bnpClamp(){var c=this.s&this.DM;while(this.t>0&&this[this.t-1]==c)--this.t}function bnToString(b){if(this.s<0)return"-"+this.negate().toString(b);var k;if(b==16)k=4;else if(b==8)k=3;else if(b==2)k=1;else if(b==32)k=5;else if(b==4)k=2;else return this.toRadix(b);var km=(1<<k)-1,d,m=false,r="",i=this.t;var p=this.DB-i*this.DB%k;if(i-->0){if(p<this.DB&&(d=this[i]>>p)>0){m=true;r=int2char(d)}while(i>=0){if(p<k){d=(this[i]&(1<<p)-1)<<k-p;d|=this[--i]>>(p+=this.DB-k)}else{d=this[i]>>(p-=k)&km;if(p<=0){p+=this.DB;--i}}if(d>0)m=true;if(m)r+=int2char(d)}}return m?r:"0"}function bnNegate(){var r=nbi();BigInteger.ZERO.subTo(this,r);return r}function bnAbs(){return this.s<0?this.negate():this}function bnCompareTo(a){var r=this.s-a.s;if(r!=0)return r;var i=this.t;r=i-a.t;if(r!=0)return this.s<0?-r:r;while(--i>=0)if((r=this[i]-a[i])!=0)return r;return 0}function nbits(x){var r=1,t;if((t=x>>>16)!=0){x=t;r+=16}if((t=x>>8)!=0){x=t;r+=8}if((t=x>>4)!=0){x=t;r+=4}if((t=x>>2)!=0){x=t;r+=2}if((t=x>>1)!=0){x=t;r+=1}return r}function bnBitLength(){if(this.t<=0)return 0;return this.DB*(this.t-1)+nbits(this[this.t-1]^this.s&this.DM)}function bnpDLShiftTo(n,r){var i;for(i=this.t-1;i>=0;--i)r[i+n]=this[i];for(i=n-1;i>=0;--i)r[i]=0;r.t=this.t+n;r.s=this.s}function bnpDRShiftTo(n,r){for(var i=n;i<this.t;++i)r[i-n]=this[i];r.t=Math.max(this.t-n,0);r.s=this.s}function bnpLShiftTo(n,r){var bs=n%this.DB;var cbs=this.DB-bs;var bm=(1<<cbs)-1;var ds=Math.floor(n/this.DB),c=this.s<<bs&this.DM,i;for(i=this.t-1;i>=0;--i){r[i+ds+1]=this[i]>>cbs|c;c=(this[i]&bm)<<bs}for(i=ds-1;i>=0;--i)r[i]=0;r[ds]=c;r.t=this.t+ds+1;r.s=this.s;r.clamp()}function bnpRShiftTo(n,r){r.s=this.s;var ds=Math.floor(n/this.DB);if(ds>=this.t){r.t=0;return}var bs=n%this.DB;var cbs=this.DB-bs;var bm=(1<<bs)-1;r[0]=this[ds]>>bs;for(var i=ds+1;i<this.t;++i){r[i-ds-1]|=(this[i]&bm)<<cbs;r[i-ds]=this[i]>>bs}if(bs>0)r[this.t-ds-1]|=(this.s&bm)<<cbs;r.t=this.t-ds;r.clamp()}function bnpSubTo(a,r){var i=0,c=0,m=Math.min(a.t,this.t);while(i<m){c+=this[i]-a[i];r[i++]=c&this.DM;c>>=this.DB}if(a.t<this.t){c-=a.s;while(i<this.t){c+=this[i];r[i++]=c&this.DM;c>>=this.DB}c+=this.s}else{c+=this.s;while(i<a.t){c-=a[i];r[i++]=c&this.DM;c>>=this.DB}c-=a.s}r.s=c<0?-1:0;if(c<-1)r[i++]=this.DV+c;else if(c>0)r[i++]=c;r.t=i;r.clamp()}function bnpMultiplyTo(a,r){var x=this.abs(),y=a.abs();var i=x.t;r.t=i+y.t;while(--i>=0)r[i]=0;for(i=0;i<y.t;++i)r[i+x.t]=x.am(0,y[i],r,i,0,x.t);r.s=0;r.clamp();if(this.s!=a.s)BigInteger.ZERO.subTo(r,r)}function bnpSquareTo(r){var x=this.abs();var i=r.t=2*x.t;while(--i>=0)r[i]=0;for(i=0;i<x.t-1;++i){var c=x.am(i,x[i],r,2*i,0,1);if((r[i+x.t]+=x.am(i+1,2*x[i],r,2*i+1,c,x.t-i-1))>=x.DV){r[i+x.t]-=x.DV;r[i+x.t+1]=1}}if(r.t>0)r[r.t-1]+=x.am(i,x[i],r,2*i,0,1);r.s=0;r.clamp()}function bnpDivRemTo(m,q,r){var pm=m.abs();if(pm.t<=0)return;var pt=this.abs();if(pt.t<pm.t){if(q!=null)q.fromInt(0);if(r!=null)this.copyTo(r);return}if(r==null)r=nbi();var y=nbi(),ts=this.s,ms=m.s;var nsh=this.DB-nbits(pm[pm.t-1]);if(nsh>0){pm.lShiftTo(nsh,y);pt.lShiftTo(nsh,r)}else{pm.copyTo(y);pt.copyTo(r)}var ys=y.t;var y0=y[ys-1];if(y0==0)return;var yt=y0*(1<<this.F1)+(ys>1?y[ys-2]>>this.F2:0);var d1=this.FV/yt,d2=(1<<this.F1)/yt,e=1<<this.F2;var i=r.t,j=i-ys,t=q==null?nbi():q;y.dlShiftTo(j,t);if(r.compareTo(t)>=0){r[r.t++]=1;r.subTo(t,r)}BigInteger.ONE.dlShiftTo(ys,t);t.subTo(y,y);while(y.t<ys)y[y.t++]=0;while(--j>=0){var qd=r[--i]==y0?this.DM:Math.floor(r[i]*d1+(r[i-1]+e)*d2);if((r[i]+=y.am(0,qd,r,j,0,ys))<qd){y.dlShiftTo(j,t);r.subTo(t,r);while(r[i]<--qd)r.subTo(t,r)}}if(q!=null){r.drShiftTo(ys,q);if(ts!=ms)BigInteger.ZERO.subTo(q,q)}r.t=ys;r.clamp();if(nsh>0)r.rShiftTo(nsh,r);if(ts<0)BigInteger.ZERO.subTo(r,r)}function bnMod(a){var r=nbi();this.abs().divRemTo(a,null,r);if(this.s<0&&r.compareTo(BigInteger.ZERO)>0)a.subTo(r,r);return r}function Classic(m){this.m=m}function cConvert(x){if(x.s<0||x.compareTo(this.m)>=0)return x.mod(this.m);else return x}function cRevert(x){return x}function cReduce(x){x.divRemTo(this.m,null,x)}function cMulTo(x,y,r){x.multiplyTo(y,r);this.reduce(r)}function cSqrTo(x,r){x.squareTo(r);this.reduce(r)}Classic.prototype.convert=cConvert;Classic.prototype.revert=cRevert;Classic.prototype.reduce=cReduce;Classic.prototype.mulTo=cMulTo;Classic.prototype.sqrTo=cSqrTo;function bnpInvDigit(){if(this.t<1)return 0;var x=this[0];if((x&1)==0)return 0;var y=x&3;y=y*(2-(x&15)*y)&15;y=y*(2-(x&255)*y)&255;y=y*(2-((x&65535)*y&65535))&65535;y=y*(2-x*y%this.DV)%this.DV;return y>0?this.DV-y:-y}function Montgomery(m){this.m=m;this.mp=m.invDigit();this.mpl=this.mp&32767;this.mph=this.mp>>15;this.um=(1<<m.DB-15)-1;this.mt2=2*m.t}function montConvert(x){var r=nbi();x.abs().dlShiftTo(this.m.t,r);r.divRemTo(this.m,null,r);if(x.s<0&&r.compareTo(BigInteger.ZERO)>0)this.m.subTo(r,r);return r}function montRevert(x){var r=nbi();x.copyTo(r);this.reduce(r);return r}function montReduce(x){while(x.t<=this.mt2)x[x.t++]=0;for(var i=0;i<this.m.t;++i){var j=x[i]&32767;var u0=j*this.mpl+((j*this.mph+(x[i]>>15)*this.mpl&this.um)<<15)&x.DM;j=i+this.m.t;x[j]+=this.m.am(0,u0,x,i,0,this.m.t);while(x[j]>=x.DV){x[j]-=x.DV;x[++j]++}}x.clamp();x.drShiftTo(this.m.t,x);if(x.compareTo(this.m)>=0)x.subTo(this.m,x)}function montSqrTo(x,r){x.squareTo(r);this.reduce(r)}function montMulTo(x,y,r){x.multiplyTo(y,r);this.reduce(r)}Montgomery.prototype.convert=montConvert;Montgomery.prototype.revert=montRevert;Montgomery.prototype.reduce=montReduce;Montgomery.prototype.mulTo=montMulTo;Montgomery.prototype.sqrTo=montSqrTo;function bnpIsEven(){return(this.t>0?this[0]&1:this.s)==0}function bnpExp(e,z){if(e>4294967295||e<1)return BigInteger.ONE;var r=nbi(),r2=nbi(),g=z.convert(this),i=nbits(e)-1;g.copyTo(r);while(--i>=0){z.sqrTo(r,r2);if((e&1<<i)>0)z.mulTo(r2,g,r);else{var t=r;r=r2;r2=t}}return z.revert(r)}function bnModPowInt(e,m){var z;if(e<256||m.isEven())z=new Classic(m);else z=new Montgomery(m);return this.exp(e,z)}BigInteger.prototype.copyTo=bnpCopyTo;BigInteger.prototype.fromInt=bnpFromInt;BigInteger.prototype.fromString=bnpFromString;BigInteger.prototype.clamp=bnpClamp;BigInteger.prototype.dlShiftTo=bnpDLShiftTo;BigInteger.prototype.drShiftTo=bnpDRShiftTo;BigInteger.prototype.lShiftTo=bnpLShiftTo;BigInteger.prototype.rShiftTo=bnpRShiftTo;BigInteger.prototype.subTo=bnpSubTo;BigInteger.prototype.multiplyTo=bnpMultiplyTo;BigInteger.prototype.squareTo=bnpSquareTo;BigInteger.prototype.divRemTo=bnpDivRemTo;BigInteger.prototype.invDigit=bnpInvDigit;BigInteger.prototype.isEven=bnpIsEven;BigInteger.prototype.exp=bnpExp;BigInteger.prototype.toString=bnToString;BigInteger.prototype.negate=bnNegate;BigInteger.prototype.abs=bnAbs;BigInteger.prototype.compareTo=bnCompareTo;BigInteger.prototype.bitLength=bnBitLength;BigInteger.prototype.mod=bnMod;BigInteger.prototype.modPowInt=bnModPowInt;BigInteger.ZERO=nbv(0);BigInteger.ONE=nbv(1);function bnpFromRadix(s,b){this.fromInt(0);if(b==null)b=10;var cs=this.chunkSize(b);var d=Math.pow(b,cs),mi=false,j=0,w=0;for(var i=0;i<s.length;++i){var x=intAt(s,i);if(x<0){if(s.charAt(i)=="-"&&this.signum()==0)mi=true;continue}w=b*w+x;if(++j>=cs){this.dMultiply(d);this.dAddOffset(w,0);j=0;w=0}}if(j>0){this.dMultiply(Math.pow(b,j));this.dAddOffset(w,0)}if(mi)BigInteger.ZERO.subTo(this,this)}function bnpChunkSize(r){return Math.floor(Math.LN2*this.DB/Math.log(r))}function bnSigNum(){if(this.s<0)return-1;else if(this.t<=0||this.t==1&&this[0]<=0)return 0;else return 1}function bnpDMultiply(n){this[this.t]=this.am(0,n-1,this,0,0,this.t);++this.t;this.clamp()}function bnpDAddOffset(n,w){if(n==0)return;while(this.t<=w)this[this.t++]=0;this[w]+=n;while(this[w]>=this.DV){this[w]-=this.DV;if(++w>=this.t)this[this.t++]=0;++this[w]}}function bnpToRadix(b){if(b==null)b=10;if(this.signum()==0||b<2||b>36)return"0";var cs=this.chunkSize(b);var a=Math.pow(b,cs);var d=nbv(a),y=nbi(),z=nbi(),r="";this.divRemTo(d,y,z);while(y.signum()>0){r=(a+z.intValue()).toString(b).substr(1)+r;y.divRemTo(d,y,z)}return z.intValue().toString(b)+r}function bnIntValue(){if(this.s<0){if(this.t==1)return this[0]-this.DV;else if(this.t==0)return-1}else if(this.t==1)return this[0];else if(this.t==0)return 0;return(this[1]&(1<<32-this.DB)-1)<<this.DB|this[0]}function bnpAddTo(a,r){var i=0,c=0,m=Math.min(a.t,this.t);while(i<m){c+=this[i]+a[i];r[i++]=c&this.DM;c>>=this.DB}if(a.t<this.t){c+=a.s;while(i<this.t){c+=this[i];r[i++]=c&this.DM;c>>=this.DB}c+=this.s}else{c+=this.s;while(i<a.t){c+=a[i];r[i++]=c&this.DM;c>>=this.DB}c+=a.s}r.s=c<0?-1:0;if(c>0)r[i++]=c;else if(c<-1)r[i++]=this.DV+c;r.t=i;r.clamp()}BigInteger.prototype.fromRadix=bnpFromRadix;BigInteger.prototype.chunkSize=bnpChunkSize;BigInteger.prototype.signum=bnSigNum;BigInteger.prototype.dMultiply=bnpDMultiply;BigInteger.prototype.dAddOffset=bnpDAddOffset;BigInteger.prototype.toRadix=bnpToRadix;BigInteger.prototype.intValue=bnIntValue;BigInteger.prototype.addTo=bnpAddTo;var Wrapper={abs:(function(l,h){var x=new goog.math.Long(l,h);var ret;if(x.isNegative()){ret=x.negate()}else{ret=x}HEAP32[tempDoublePtr>>2]=ret.low_;HEAP32[tempDoublePtr+4>>2]=ret.high_}),ensureTemps:(function(){if(Wrapper.ensuredTemps)return;Wrapper.ensuredTemps=true;Wrapper.two32=new BigInteger;Wrapper.two32.fromString("4294967296",10);Wrapper.two64=new BigInteger;Wrapper.two64.fromString("18446744073709551616",10);Wrapper.temp1=new BigInteger;Wrapper.temp2=new BigInteger}),lh2bignum:(function(l,h){var a=new BigInteger;a.fromString(h.toString(),10);var b=new BigInteger;a.multiplyTo(Wrapper.two32,b);var c=new BigInteger;c.fromString(l.toString(),10);var d=new BigInteger;c.addTo(b,d);return d}),stringify:(function(l,h,unsigned){var ret=(new goog.math.Long(l,h)).toString();if(unsigned&&ret[0]=="-"){Wrapper.ensureTemps();var bignum=new BigInteger;bignum.fromString(ret,10);ret=new BigInteger;Wrapper.two64.addTo(bignum,ret);ret=ret.toString(10)}return ret}),fromString:(function(str,base,min,max,unsigned){Wrapper.ensureTemps();var bignum=new BigInteger;bignum.fromString(str,base);var bigmin=new BigInteger;bigmin.fromString(min,10);var bigmax=new BigInteger;bigmax.fromString(max,10);if(unsigned&&bignum.compareTo(BigInteger.ZERO)<0){var temp=new BigInteger;bignum.addTo(Wrapper.two64,temp);bignum=temp}var error=false;if(bignum.compareTo(bigmin)<0){bignum=bigmin;error=true}else if(bignum.compareTo(bigmax)>0){bignum=bigmax;error=true}var ret=goog.math.Long.fromString(bignum.toString());HEAP32[tempDoublePtr>>2]=ret.low_;HEAP32[tempDoublePtr+4>>2]=ret.high_;if(error)throw"range error"})};return Wrapper})();if(memoryInitializer){if(typeof Module["locateFile"]==="function"){memoryInitializer=Module["locateFile"](memoryInitializer)}else if(Module["memoryInitializerPrefixURL"]){memoryInitializer=Module["memoryInitializerPrefixURL"]+memoryInitializer}if(ENVIRONMENT_IS_NODE||ENVIRONMENT_IS_SHELL){var data=Module["readBinary"](memoryInitializer);HEAPU8.set(data,STATIC_BASE)}else{addRunDependency("memory initializer");Browser.asyncLoad(memoryInitializer,(function(data){HEAPU8.set(data,STATIC_BASE);removeRunDependency("memory initializer")}),(function(data){throw"could not load memory initializer "+memoryInitializer}))}}function ExitStatus(status){this.name="ExitStatus";this.message="Program terminated with exit("+status+")";this.status=status}ExitStatus.prototype=new Error;ExitStatus.prototype.constructor=ExitStatus;var initialStackTop;var preloadStartTime=null;var calledMain=false;dependenciesFulfilled=function runCaller(){if(!Module["calledRun"]&&shouldRunNow)run();if(!Module["calledRun"])dependenciesFulfilled=runCaller};Module["callMain"]=Module.callMain=function callMain(args){assert(runDependencies==0,"cannot call main when async dependencies remain! (listen on __ATMAIN__)");assert(__ATPRERUN__.length==0,"cannot call main when preRun functions remain to be called");args=args||[];ensureInitRuntime();var argc=args.length+1;function pad(){for(var i=0;i<4-1;i++){argv.push(0)}}var argv=[allocate(intArrayFromString(Module["thisProgram"]),"i8",ALLOC_NORMAL)];pad();for(var i=0;i<argc-1;i=i+1){argv.push(allocate(intArrayFromString(args[i]),"i8",ALLOC_NORMAL));pad()}argv.push(0);argv=allocate(argv,"i32",ALLOC_NORMAL);initialStackTop=STACKTOP;try{var ret=Module["_main"](argc,argv,0);exit(ret)}catch(e){if(e instanceof ExitStatus){return}else if(e=="SimulateInfiniteLoop"){Module["noExitRuntime"]=true;return}else{if(e&&typeof e==="object"&&e.stack)Module.printErr("exception thrown: "+[e,e.stack]);throw e}}finally{calledMain=true}};function run(args){args=args||Module["arguments"];if(preloadStartTime===null)preloadStartTime=Date.now();if(runDependencies>0){return}preRun();if(runDependencies>0)return;if(Module["calledRun"])return;function doRun(){if(Module["calledRun"])return;Module["calledRun"]=true;if(ABORT)return;ensureInitRuntime();preMain();if(ENVIRONMENT_IS_WEB&&preloadStartTime!==null){Module.printErr("pre-main prep time: "+(Date.now()-preloadStartTime)+" ms")}if(Module["_main"]&&shouldRunNow){Module["callMain"](args)}postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout((function(){setTimeout((function(){Module["setStatus"]("")}),1);doRun()}),1)}else{doRun()}}Module["run"]=Module.run=run;function exit(status){if(Module["noExitRuntime"]){return}ABORT=true;EXITSTATUS=status;STACKTOP=initialStackTop;exitRuntime();if(ENVIRONMENT_IS_NODE){process["stdout"]["once"]("drain",(function(){process["exit"](status)}));console.log(" ");setTimeout((function(){process["exit"](status)}),500)}else if(ENVIRONMENT_IS_SHELL&&typeof quit==="function"){quit(status)}throw new ExitStatus(status)}Module["exit"]=Module.exit=exit;function abort(text){if(text){Module.print(text);Module.printErr(text)}ABORT=true;EXITSTATUS=1;var extra="\nIf this abort() is unexpected, build with -s ASSERTIONS=1 which can give more information.";throw"abort() at "+stackTrace()+extra}Module["abort"]=Module.abort=abort;if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}var shouldRunNow=true;if(Module["noInitialRun"]){shouldRunNow=false}run();var origMalloc=Module._malloc;var origFree=Module._free;var MEMSTATS={totalMemory:Module.HEAPU8.length,heapUsed:0};var MEMSTATS_DATA={pointerToSizeMap:{},getSizeOfPointer:(function(ptr){return MEMSTATS_DATA.pointerToSizeMap[ptr]})};Module.MEMSTATS=MEMSTATS;Module.MEMSTATS_DATA=MEMSTATS_DATA;var hookedMalloc=(function(size){var ptr=origMalloc(size);if(!ptr){return 0}MEMSTATS.heapUsed+=size;MEMSTATS_DATA.pointerToSizeMap[ptr]=size;return ptr});var hookedFree=(function(ptr){if(ptr){MEMSTATS.heapUsed-=MEMSTATS_DATA.getSizeOfPointer(ptr)||0;delete MEMSTATS_DATA.pointerToSizeMap[ptr]}return origFree(ptr)});Module._malloc=hookedMalloc;Module._free=hookedFree;_malloc=hookedMalloc;_free=hookedFree;var setInnerMalloc,setInnerFree;if(setInnerMalloc){setInnerMalloc(hookedMalloc);setInnerFree(hookedFree)}return module.exports});if(typeof module!=="undefined"){module.exports=C_MINISAT}




};
BundleModuleCode['ext/underscore']=function (module,exports){
//     Underscore.js 1.9.0
//     http://underscorejs.org
//     (c) 2009-2018 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
//     Underscore may be freely distributed under the MIT license.

(function() {

  // Baseline setup
  // --------------

  // Establish the root object, `window` (`self`) in the browser, `global`
  // on the server, or `this` in some virtual machines. We use `self`
  // instead of `window` for `WebWorker` support.
  var root = typeof self == 'object' && self.self === self && self ||
            typeof global == 'object' && global.global === global && global ||
            this ||
            {};

  // Save the previous value of the `_` variable.
  var previousUnderscore = root._;

  // Save bytes in the minified (but not gzipped) version:
  var ArrayProto = Array.prototype, ObjProto = Object.prototype;
  var SymbolProto = typeof Symbol !== 'undefined' ? Symbol.prototype : null;

  // Create quick reference variables for speed access to core prototypes.
  var push = ArrayProto.push,
      slice = ArrayProto.slice,
      toString = ObjProto.toString,
      hasOwnProperty = ObjProto.hasOwnProperty;

  // All **ECMAScript 5** native function implementations that we hope to use
  // are declared here.
  var nativeIsArray = Array.isArray,
      nativeKeys = Object.keys,
      nativeCreate = Object.create;

  // Naked function reference for surrogate-prototype-swapping.
  var Ctor = function(){};

  // Create a safe reference to the Underscore object for use below.
  var _ = function(obj) {
    if (obj instanceof _) return obj;
    if (!(this instanceof _)) return new _(obj);
    this._wrapped = obj;
  };

  // Export the Underscore object for **Node.js**, with
  // backwards-compatibility for their old module API. If we're in
  // the browser, add `_` as a global object.
  // (`nodeType` is checked to ensure that `module`
  // and `exports` are not HTML elements.)
  if (typeof exports != 'undefined' && !exports.nodeType) {
    if (typeof module != 'undefined' && !module.nodeType && module.exports) {
      exports = module.exports = _;
    }
    exports._ = _;
  } else {
    root._ = _;
  }

  // Current version.
  _.VERSION = '1.9.0';

  // Internal function that returns an efficient (for current engines) version
  // of the passed-in callback, to be repeatedly applied in other Underscore
  // functions.
  var optimizeCb = function(func, context, argCount) {
    if (context === void 0) return func;
    switch (argCount == null ? 3 : argCount) {
      case 1: return function(value) {
        return func.call(context, value);
      };
      // The 2-argument case is omitted because we’re not using it.
      case 3: return function(value, index, collection) {
        return func.call(context, value, index, collection);
      };
      case 4: return function(accumulator, value, index, collection) {
        return func.call(context, accumulator, value, index, collection);
      };
    }
    return function() {
      return func.apply(context, arguments);
    };
  };

  var builtinIteratee;

  // An internal function to generate callbacks that can be applied to each
  // element in a collection, returning the desired result — either `identity`,
  // an arbitrary callback, a property matcher, or a property accessor.
  var cb = function(value, context, argCount) {
    if (_.iteratee !== builtinIteratee) return _.iteratee(value, context);
    if (value == null) return _.identity;
    if (_.isFunction(value)) return optimizeCb(value, context, argCount);
    if (_.isObject(value) && !_.isArray(value)) return _.matcher(value);
    return _.property(value);
  };

  // External wrapper for our callback generator. Users may customize
  // `_.iteratee` if they want additional predicate/iteratee shorthand styles.
  // This abstraction hides the internal-only argCount argument.
  _.iteratee = builtinIteratee = function(value, context) {
    return cb(value, context, Infinity);
  };

  // Some functions take a variable number of arguments, or a few expected
  // arguments at the beginning and then a variable number of values to operate
  // on. This helper accumulates all remaining arguments past the function’s
  // argument length (or an explicit `startIndex`), into an array that becomes
  // the last argument. Similar to ES6’s "rest parameter".
  var restArguments = function(func, startIndex) {
    startIndex = startIndex == null ? func.length - 1 : +startIndex;
    return function() {
      var length = Math.max(arguments.length - startIndex, 0),
          rest = Array(length),
          index = 0;
      for (; index < length; index++) {
        rest[index] = arguments[index + startIndex];
      }
      switch (startIndex) {
        case 0: return func.call(this, rest);
        case 1: return func.call(this, arguments[0], rest);
        case 2: return func.call(this, arguments[0], arguments[1], rest);
      }
      var args = Array(startIndex + 1);
      for (index = 0; index < startIndex; index++) {
        args[index] = arguments[index];
      }
      args[startIndex] = rest;
      return func.apply(this, args);
    };
  };

  // An internal function for creating a new object that inherits from another.
  var baseCreate = function(prototype) {
    if (!_.isObject(prototype)) return {};
    if (nativeCreate) return nativeCreate(prototype);
    Ctor.prototype = prototype;
    var result = new Ctor;
    Ctor.prototype = null;
    return result;
  };

  var shallowProperty = function(key) {
    return function(obj) {
      return obj == null ? void 0 : obj[key];
    };
  };

  var has = function(obj, path) {
    return obj != null && hasOwnProperty.call(obj, path);
  }

  var deepGet = function(obj, path) {
    var length = path.length;
    for (var i = 0; i < length; i++) {
      if (obj == null) return void 0;
      obj = obj[path[i]];
    }
    return length ? obj : void 0;
  };

  // Helper for collection methods to determine whether a collection
  // should be iterated as an array or as an object.
  // Related: http://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength
  // Avoids a very nasty iOS 8 JIT bug on ARM-64. #2094
  var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
  var getLength = shallowProperty('length');
  var isArrayLike = function(collection) {
    var length = getLength(collection);
    return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
  };

  // Collection Functions
  // --------------------

  // The cornerstone, an `each` implementation, aka `forEach`.
  // Handles raw objects in addition to array-likes. Treats all
  // sparse array-likes as if they were dense.
  _.each = _.forEach = function(obj, iteratee, context) {
    iteratee = optimizeCb(iteratee, context);
    var i, length;
    if (isArrayLike(obj)) {
      for (i = 0, length = obj.length; i < length; i++) {
        iteratee(obj[i], i, obj);
      }
    } else {
      var keys = _.keys(obj);
      for (i = 0, length = keys.length; i < length; i++) {
        iteratee(obj[keys[i]], keys[i], obj);
      }
    }
    return obj;
  };

  // Return the results of applying the iteratee to each element.
  _.map = _.collect = function(obj, iteratee, context) {
    iteratee = cb(iteratee, context);
    var keys = !isArrayLike(obj) && _.keys(obj),
        length = (keys || obj).length,
        results = Array(length);
    for (var index = 0; index < length; index++) {
      var currentKey = keys ? keys[index] : index;
      results[index] = iteratee(obj[currentKey], currentKey, obj);
    }
    return results;
  };

  // Create a reducing function iterating left or right.
  var createReduce = function(dir) {
    // Wrap code that reassigns argument variables in a separate function than
    // the one that accesses `arguments.length` to avoid a perf hit. (#1991)
    var reducer = function(obj, iteratee, memo, initial) {
      var keys = !isArrayLike(obj) && _.keys(obj),
          length = (keys || obj).length,
          index = dir > 0 ? 0 : length - 1;
      if (!initial) {
        memo = obj[keys ? keys[index] : index];
        index += dir;
      }
      for (; index >= 0 && index < length; index += dir) {
        var currentKey = keys ? keys[index] : index;
        memo = iteratee(memo, obj[currentKey], currentKey, obj);
      }
      return memo;
    };

    return function(obj, iteratee, memo, context) {
      var initial = arguments.length >= 3;
      return reducer(obj, optimizeCb(iteratee, context, 4), memo, initial);
    };
  };

  // **Reduce** builds up a single result from a list of values, aka `inject`,
  // or `foldl`.
  _.reduce = _.foldl = _.inject = createReduce(1);

  // The right-associative version of reduce, also known as `foldr`.
  _.reduceRight = _.foldr = createReduce(-1);

  // Return the first value which passes a truth test. Aliased as `detect`.
  _.find = _.detect = function(obj, predicate, context) {
    var keyFinder = isArrayLike(obj) ? _.findIndex : _.findKey;
    var key = keyFinder(obj, predicate, context);
    if (key !== void 0 && key !== -1) return obj[key];
  };

  // Return all the elements that pass a truth test.
  // Aliased as `select`.
  _.filter = _.select = function(obj, predicate, context) {
    var results = [];
    predicate = cb(predicate, context);
    _.each(obj, function(value, index, list) {
      if (predicate(value, index, list)) results.push(value);
    });
    return results;
  };

  // Return all the elements for which a truth test fails.
  _.reject = function(obj, predicate, context) {
    return _.filter(obj, _.negate(cb(predicate)), context);
  };

  // Determine whether all of the elements match a truth test.
  // Aliased as `all`.
  _.every = _.all = function(obj, predicate, context) {
    predicate = cb(predicate, context);
    var keys = !isArrayLike(obj) && _.keys(obj),
        length = (keys || obj).length;
    for (var index = 0; index < length; index++) {
      var currentKey = keys ? keys[index] : index;
      if (!predicate(obj[currentKey], currentKey, obj)) return false;
    }
    return true;
  };

  // Determine if at least one element in the object matches a truth test.
  // Aliased as `any`.
  _.some = _.any = function(obj, predicate, context) {
    predicate = cb(predicate, context);
    var keys = !isArrayLike(obj) && _.keys(obj),
        length = (keys || obj).length;
    for (var index = 0; index < length; index++) {
      var currentKey = keys ? keys[index] : index;
      if (predicate(obj[currentKey], currentKey, obj)) return true;
    }
    return false;
  };

  // Determine if the array or object contains a given item (using `===`).
  // Aliased as `includes` and `include`.
  _.contains = _.includes = _.include = function(obj, item, fromIndex, guard) {
    if (!isArrayLike(obj)) obj = _.values(obj);
    if (typeof fromIndex != 'number' || guard) fromIndex = 0;
    return _.indexOf(obj, item, fromIndex) >= 0;
  };

  // Invoke a method (with arguments) on every item in a collection.
  _.invoke = restArguments(function(obj, path, args) {
    var contextPath, func;
    if (_.isFunction(path)) {
      func = path;
    } else if (_.isArray(path)) {
      contextPath = path.slice(0, -1);
      path = path[path.length - 1];
    }
    return _.map(obj, function(context) {
      var method = func;
      if (!method) {
        if (contextPath && contextPath.length) {
          context = deepGet(context, contextPath);
        }
        if (context == null) return void 0;
        method = context[path];
      }
      return method == null ? method : method.apply(context, args);
    });
  });

  // Convenience version of a common use case of `map`: fetching a property.
  _.pluck = function(obj, key) {
    return _.map(obj, _.property(key));
  };

  // Convenience version of a common use case of `filter`: selecting only objects
  // containing specific `key:value` pairs.
  _.where = function(obj, attrs) {
    return _.filter(obj, _.matcher(attrs));
  };

  // Convenience version of a common use case of `find`: getting the first object
  // containing specific `key:value` pairs.
  _.findWhere = function(obj, attrs) {
    return _.find(obj, _.matcher(attrs));
  };

  // Return the maximum element (or element-based computation).
  _.max = function(obj, iteratee, context) {
    var result = -Infinity, lastComputed = -Infinity,
        value, computed;
    if (iteratee == null || typeof iteratee == 'number' && typeof obj[0] != 'object' && obj != null) {
      obj = isArrayLike(obj) ? obj : _.values(obj);
      for (var i = 0, length = obj.length; i < length; i++) {
        value = obj[i];
        if (value != null && value > result) {
          result = value;
        }
      }
    } else {
      iteratee = cb(iteratee, context);
      _.each(obj, function(v, index, list) {
        computed = iteratee(v, index, list);
        if (computed > lastComputed || computed === -Infinity && result === -Infinity) {
          result = v;
          lastComputed = computed;
        }
      });
    }
    return result;
  };

  // Return the minimum element (or element-based computation).
  _.min = function(obj, iteratee, context) {
    var result = Infinity, lastComputed = Infinity,
        value, computed;
    if (iteratee == null || typeof iteratee == 'number' && typeof obj[0] != 'object' && obj != null) {
      obj = isArrayLike(obj) ? obj : _.values(obj);
      for (var i = 0, length = obj.length; i < length; i++) {
        value = obj[i];
        if (value != null && value < result) {
          result = value;
        }
      }
    } else {
      iteratee = cb(iteratee, context);
      _.each(obj, function(v, index, list) {
        computed = iteratee(v, index, list);
        if (computed < lastComputed || computed === Infinity && result === Infinity) {
          result = v;
          lastComputed = computed;
        }
      });
    }
    return result;
  };

  // Shuffle a collection.
  _.shuffle = function(obj) {
    return _.sample(obj, Infinity);
  };

  // Sample **n** random values from a collection using the modern version of the
  // [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle).
  // If **n** is not specified, returns a single random element.
  // The internal `guard` argument allows it to work with `map`.
  _.sample = function(obj, n, guard) {
    if (n == null || guard) {
      if (!isArrayLike(obj)) obj = _.values(obj);
      return obj[_.random(obj.length - 1)];
    }
    var sample = isArrayLike(obj) ? _.clone(obj) : _.values(obj);
    var length = getLength(sample);
    n = Math.max(Math.min(n, length), 0);
    var last = length - 1;
    for (var index = 0; index < n; index++) {
      var rand = _.random(index, last);
      var temp = sample[index];
      sample[index] = sample[rand];
      sample[rand] = temp;
    }
    return sample.slice(0, n);
  };

  // Sort the object's values by a criterion produced by an iteratee.
  _.sortBy = function(obj, iteratee, context) {
    var index = 0;
    iteratee = cb(iteratee, context);
    return _.pluck(_.map(obj, function(value, key, list) {
      return {
        value: value,
        index: index++,
        criteria: iteratee(value, key, list)
      };
    }).sort(function(left, right) {
      var a = left.criteria;
      var b = right.criteria;
      if (a !== b) {
        if (a > b || a === void 0) return 1;
        if (a < b || b === void 0) return -1;
      }
      return left.index - right.index;
    }), 'value');
  };

  // An internal function used for aggregate "group by" operations.
  var group = function(behavior, partition) {
    return function(obj, iteratee, context) {
      var result = partition ? [[], []] : {};
      iteratee = cb(iteratee, context);
      _.each(obj, function(value, index) {
        var key = iteratee(value, index, obj);
        behavior(result, value, key);
      });
      return result;
    };
  };

  // Groups the object's values by a criterion. Pass either a string attribute
  // to group by, or a function that returns the criterion.
  _.groupBy = group(function(result, value, key) {
    if (has(result, key)) result[key].push(value); else result[key] = [value];
  });

  // Indexes the object's values by a criterion, similar to `groupBy`, but for
  // when you know that your index values will be unique.
  _.indexBy = group(function(result, value, key) {
    result[key] = value;
  });

  // Counts instances of an object that group by a certain criterion. Pass
  // either a string attribute to count by, or a function that returns the
  // criterion.
  _.countBy = group(function(result, value, key) {
    if (has(result, key)) result[key]++; else result[key] = 1;
  });

  var reStrSymbol = /[^\ud800-\udfff]|[\ud800-\udbff][\udc00-\udfff]|[\ud800-\udfff]/g;
  // Safely create a real, live array from anything iterable.
  _.toArray = function(obj) {
    if (!obj) return [];
    if (_.isArray(obj)) return slice.call(obj);
    if (_.isString(obj)) {
      // Keep surrogate pair characters together
      return obj.match(reStrSymbol);
    }
    if (isArrayLike(obj)) return _.map(obj, _.identity);
    return _.values(obj);
  };

  // Return the number of elements in an object.
  _.size = function(obj) {
    if (obj == null) return 0;
    return isArrayLike(obj) ? obj.length : _.keys(obj).length;
  };

  // Split a collection into two arrays: one whose elements all satisfy the given
  // predicate, and one whose elements all do not satisfy the predicate.
  _.partition = group(function(result, value, pass) {
    result[pass ? 0 : 1].push(value);
  }, true);

  // Array Functions
  // ---------------

  // Get the first element of an array. Passing **n** will return the first N
  // values in the array. Aliased as `head` and `take`. The **guard** check
  // allows it to work with `_.map`.
  _.first = _.head = _.take = function(array, n, guard) {
    if (array == null || array.length < 1) return void 0;
    if (n == null || guard) return array[0];
    return _.initial(array, array.length - n);
  };

  // Returns everything but the last entry of the array. Especially useful on
  // the arguments object. Passing **n** will return all the values in
  // the array, excluding the last N.
  _.initial = function(array, n, guard) {
    return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n)));
  };

  // Get the last element of an array. Passing **n** will return the last N
  // values in the array.
  _.last = function(array, n, guard) {
    if (array == null || array.length < 1) return void 0;
    if (n == null || guard) return array[array.length - 1];
    return _.rest(array, Math.max(0, array.length - n));
  };

  // Returns everything but the first entry of the array. Aliased as `tail` and `drop`.
  // Especially useful on the arguments object. Passing an **n** will return
  // the rest N values in the array.
  _.rest = _.tail = _.drop = function(array, n, guard) {
    return slice.call(array, n == null || guard ? 1 : n);
  };

  // Trim out all falsy values from an array.
  _.compact = function(array) {
    return _.filter(array, Boolean);
  };

  // Internal implementation of a recursive `flatten` function.
  var flatten = function(input, shallow, strict, output) {
    output = output || [];
    var idx = output.length;
    for (var i = 0, length = getLength(input); i < length; i++) {
      var value = input[i];
      if (isArrayLike(value) && (_.isArray(value) || _.isArguments(value))) {
        // Flatten current level of array or arguments object.
        if (shallow) {
          var j = 0, len = value.length;
          while (j < len) output[idx++] = value[j++];
        } else {
          flatten(value, shallow, strict, output);
          idx = output.length;
        }
      } else if (!strict) {
        output[idx++] = value;
      }
    }
    return output;
  };

  // Flatten out an array, either recursively (by default), or just one level.
  _.flatten = function(array, shallow) {
    return flatten(array, shallow, false);
  };

  // Return a version of the array that does not contain the specified value(s).
  _.without = restArguments(function(array, otherArrays) {
    return _.difference(array, otherArrays);
  });

  // Produce a duplicate-free version of the array. If the array has already
  // been sorted, you have the option of using a faster algorithm.
  // The faster algorithm will not work with an iteratee if the iteratee
  // is not a one-to-one function, so providing an iteratee will disable
  // the faster algorithm.
  // Aliased as `unique`.
  _.uniq = _.unique = function(array, isSorted, iteratee, context) {
    if (!_.isBoolean(isSorted)) {
      context = iteratee;
      iteratee = isSorted;
      isSorted = false;
    }
    if (iteratee != null) iteratee = cb(iteratee, context);
    var result = [];
    var seen = [];
    for (var i = 0, length = getLength(array); i < length; i++) {
      var value = array[i],
          computed = iteratee ? iteratee(value, i, array) : value;
      if (isSorted && !iteratee) {
        if (!i || seen !== computed) result.push(value);
        seen = computed;
      } else if (iteratee) {
        if (!_.contains(seen, computed)) {
          seen.push(computed);
          result.push(value);
        }
      } else if (!_.contains(result, value)) {
        result.push(value);
      }
    }
    return result;
  };

  // Produce an array that contains the union: each distinct element from all of
  // the passed-in arrays.
  _.union = restArguments(function(arrays) {
    return _.uniq(flatten(arrays, true, true));
  });

  // Produce an array that contains every item shared between all the
  // passed-in arrays.
  _.intersection = function(array) {
    var result = [];
    var argsLength = arguments.length;
    for (var i = 0, length = getLength(array); i < length; i++) {
      var item = array[i];
      if (_.contains(result, item)) continue;
      var j;
      for (j = 1; j < argsLength; j++) {
        if (!_.contains(arguments[j], item)) break;
      }
      if (j === argsLength) result.push(item);
    }
    return result;
  };

  // Take the difference between one array and a number of other arrays.
  // Only the elements present in just the first array will remain.
  _.difference = restArguments(function(array, rest) {
    rest = flatten(rest, true, true);
    return _.filter(array, function(value){
      return !_.contains(rest, value);
    });
  });

  // Complement of _.zip. Unzip accepts an array of arrays and groups
  // each array's elements on shared indices.
  _.unzip = function(array) {
    var length = array && _.max(array, getLength).length || 0;
    var result = Array(length);

    for (var index = 0; index < length; index++) {
      result[index] = _.pluck(array, index);
    }
    return result;
  };

  // Zip together multiple lists into a single array -- elements that share
  // an index go together.
  _.zip = restArguments(_.unzip);

  // Converts lists into objects. Pass either a single array of `[key, value]`
  // pairs, or two parallel arrays of the same length -- one of keys, and one of
  // the corresponding values. Passing by pairs is the reverse of _.pairs.
  _.object = function(list, values) {
    var result = {};
    for (var i = 0, length = getLength(list); i < length; i++) {
      if (values) {
        result[list[i]] = values[i];
      } else {
        result[list[i][0]] = list[i][1];
      }
    }
    return result;
  };

  // Generator function to create the findIndex and findLastIndex functions.
  var createPredicateIndexFinder = function(dir) {
    return function(array, predicate, context) {
      predicate = cb(predicate, context);
      var length = getLength(array);
      var index = dir > 0 ? 0 : length - 1;
      for (; index >= 0 && index < length; index += dir) {
        if (predicate(array[index], index, array)) return index;
      }
      return -1;
    };
  };

  // Returns the first index on an array-like that passes a predicate test.
  _.findIndex = createPredicateIndexFinder(1);
  _.findLastIndex = createPredicateIndexFinder(-1);

  // Use a comparator function to figure out the smallest index at which
  // an object should be inserted so as to maintain order. Uses binary search.
  _.sortedIndex = function(array, obj, iteratee, context) {
    iteratee = cb(iteratee, context, 1);
    var value = iteratee(obj);
    var low = 0, high = getLength(array);
    while (low < high) {
      var mid = Math.floor((low + high) / 2);
      if (iteratee(array[mid]) < value) low = mid + 1; else high = mid;
    }
    return low;
  };

  // Generator function to create the indexOf and lastIndexOf functions.
  var createIndexFinder = function(dir, predicateFind, sortedIndex) {
    return function(array, item, idx) {
      var i = 0, length = getLength(array);
      if (typeof idx == 'number') {
        if (dir > 0) {
          i = idx >= 0 ? idx : Math.max(idx + length, i);
        } else {
          length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1;
        }
      } else if (sortedIndex && idx && length) {
        idx = sortedIndex(array, item);
        return array[idx] === item ? idx : -1;
      }
      if (item !== item) {
        idx = predicateFind(slice.call(array, i, length), _.isNaN);
        return idx >= 0 ? idx + i : -1;
      }
      for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) {
        if (array[idx] === item) return idx;
      }
      return -1;
    };
  };

  // Return the position of the first occurrence of an item in an array,
  // or -1 if the item is not included in the array.
  // If the array is large and already in sort order, pass `true`
  // for **isSorted** to use binary search.
  _.indexOf = createIndexFinder(1, _.findIndex, _.sortedIndex);
  _.lastIndexOf = createIndexFinder(-1, _.findLastIndex);

  // Generate an integer Array containing an arithmetic progression. A port of
  // the native Python `range()` function. See
  // [the Python documentation](http://docs.python.org/library/functions.html#range).
  _.range = function(start, stop, step) {
    if (stop == null) {
      stop = start || 0;
      start = 0;
    }
    if (!step) {
      step = stop < start ? -1 : 1;
    }

    var length = Math.max(Math.ceil((stop - start) / step), 0);
    var range = Array(length);

    for (var idx = 0; idx < length; idx++, start += step) {
      range[idx] = start;
    }

    return range;
  };

  // Chunk a single array into multiple arrays, each containing `count` or fewer
  // items.
  _.chunk = function(array, count) {
    if (count == null || count < 1) return [];
    var result = [];
    var i = 0, length = array.length;
    while (i < length) {
      result.push(slice.call(array, i, i += count));
    }
    return result;
  };

  // Function (ahem) Functions
  // ------------------

  // Determines whether to execute a function as a constructor
  // or a normal function with the provided arguments.
  var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) {
    if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args);
    var self = baseCreate(sourceFunc.prototype);
    var result = sourceFunc.apply(self, args);
    if (_.isObject(result)) return result;
    return self;
  };

  // Create a function bound to a given object (assigning `this`, and arguments,
  // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if
  // available.
  _.bind = restArguments(function(func, context, args) {
    if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function');
    var bound = restArguments(function(callArgs) {
      return executeBound(func, bound, context, this, args.concat(callArgs));
    });
    return bound;
  });

  // Partially apply a function by creating a version that has had some of its
  // arguments pre-filled, without changing its dynamic `this` context. _ acts
  // as a placeholder by default, allowing any combination of arguments to be
  // pre-filled. Set `_.partial.placeholder` for a custom placeholder argument.
  _.partial = restArguments(function(func, boundArgs) {
    var placeholder = _.partial.placeholder;
    var bound = function() {
      var position = 0, length = boundArgs.length;
      var args = Array(length);
      for (var i = 0; i < length; i++) {
        args[i] = boundArgs[i] === placeholder ? arguments[position++] : boundArgs[i];
      }
      while (position < arguments.length) args.push(arguments[position++]);
      return executeBound(func, bound, this, this, args);
    };
    return bound;
  });

  _.partial.placeholder = _;

  // Bind a number of an object's methods to that object. Remaining arguments
  // are the method names to be bound. Useful for ensuring that all callbacks
  // defined on an object belong to it.
  _.bindAll = restArguments(function(obj, keys) {
    keys = flatten(keys, false, false);
    var index = keys.length;
    if (index < 1) throw new Error('bindAll must be passed function names');
    while (index--) {
      var key = keys[index];
      obj[key] = _.bind(obj[key], obj);
    }
  });

  // Memoize an expensive function by storing its results.
  _.memoize = function(func, hasher) {
    var memoize = function(key) {
      var cache = memoize.cache;
      var address = '' + (hasher ? hasher.apply(this, arguments) : key);
      if (!has(cache, address)) cache[address] = func.apply(this, arguments);
      return cache[address];
    };
    memoize.cache = {};
    return memoize;
  };

  // Delays a function for the given number of milliseconds, and then calls
  // it with the arguments supplied.
  _.delay = restArguments(function(func, wait, args) {
    return setTimeout(function() {
      return func.apply(null, args);
    }, wait);
  });

  // Defers a function, scheduling it to run after the current call stack has
  // cleared.
  _.defer = _.partial(_.delay, _, 1);

  // Returns a function, that, when invoked, will only be triggered at most once
  // during a given window of time. Normally, the throttled function will run
  // as much as it can, without ever going more than once per `wait` duration;
  // but if you'd like to disable the execution on the leading edge, pass
  // `{leading: false}`. To disable execution on the trailing edge, ditto.
  _.throttle = function(func, wait, options) {
    var timeout, context, args, result;
    var previous = 0;
    if (!options) options = {};

    var later = function() {
      previous = options.leading === false ? 0 : _.now();
      timeout = null;
      result = func.apply(context, args);
      if (!timeout) context = args = null;
    };

    var throttled = function() {
      var now = _.now();
      if (!previous && options.leading === false) previous = now;
      var remaining = wait - (now - previous);
      context = this;
      args = arguments;
      if (remaining <= 0 || remaining > wait) {
        if (timeout) {
          clearTimeout(timeout);
          timeout = null;
        }
        previous = now;
        result = func.apply(context, args);
        if (!timeout) context = args = null;
      } else if (!timeout && options.trailing !== false) {
        timeout = setTimeout(later, remaining);
      }
      return result;
    };

    throttled.cancel = function() {
      clearTimeout(timeout);
      previous = 0;
      timeout = context = args = null;
    };

    return throttled;
  };

  // Returns a function, that, as long as it continues to be invoked, will not
  // be triggered. The function will be called after it stops being called for
  // N milliseconds. If `immediate` is passed, trigger the function on the
  // leading edge, instead of the trailing.
  _.debounce = function(func, wait, immediate) {
    var timeout, result;

    var later = function(context, args) {
      timeout = null;
      if (args) result = func.apply(context, args);
    };

    var debounced = restArguments(function(args) {
      if (timeout) clearTimeout(timeout);
      if (immediate) {
        var callNow = !timeout;
        timeout = setTimeout(later, wait);
        if (callNow) result = func.apply(this, args);
      } else {
        timeout = _.delay(later, wait, this, args);
      }

      return result;
    });

    debounced.cancel = function() {
      clearTimeout(timeout);
      timeout = null;
    };

    return debounced;
  };

  // Returns the first function passed as an argument to the second,
  // allowing you to adjust arguments, run code before and after, and
  // conditionally execute the original function.
  _.wrap = function(func, wrapper) {
    return _.partial(wrapper, func);
  };

  // Returns a negated version of the passed-in predicate.
  _.negate = function(predicate) {
    return function() {
      return !predicate.apply(this, arguments);
    };
  };

  // Returns a function that is the composition of a list of functions, each
  // consuming the return value of the function that follows.
  _.compose = function() {
    var args = arguments;
    var start = args.length - 1;
    return function() {
      var i = start;
      var result = args[start].apply(this, arguments);
      while (i--) result = args[i].call(this, result);
      return result;
    };
  };

  // Returns a function that will only be executed on and after the Nth call.
  _.after = function(times, func) {
    return function() {
      if (--times < 1) {
        return func.apply(this, arguments);
      }
    };
  };

  // Returns a function that will only be executed up to (but not including) the Nth call.
  _.before = function(times, func) {
    var memo;
    return function() {
      if (--times > 0) {
        memo = func.apply(this, arguments);
      }
      if (times <= 1) func = null;
      return memo;
    };
  };

  // Returns a function that will be executed at most one time, no matter how
  // often you call it. Useful for lazy initialization.
  _.once = _.partial(_.before, 2);

  _.restArguments = restArguments;

  // Object Functions
  // ----------------

  // Keys in IE < 9 that won't be iterated by `for key in ...` and thus missed.
  var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString');
  var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString',
    'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString'];

  var collectNonEnumProps = function(obj, keys) {
    var nonEnumIdx = nonEnumerableProps.length;
    var constructor = obj.constructor;
    var proto = _.isFunction(constructor) && constructor.prototype || ObjProto;

    // Constructor is a special case.
    var prop = 'constructor';
    if (has(obj, prop) && !_.contains(keys, prop)) keys.push(prop);

    while (nonEnumIdx--) {
      prop = nonEnumerableProps[nonEnumIdx];
      if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)) {
        keys.push(prop);
      }
    }
  };

  // Retrieve the names of an object's own properties.
  // Delegates to **ECMAScript 5**'s native `Object.keys`.
  _.keys = function(obj) {
    if (!_.isObject(obj)) return [];
    if (nativeKeys) return nativeKeys(obj);
    var keys = [];
    for (var key in obj) if (has(obj, key)) keys.push(key);
    // Ahem, IE < 9.
    if (hasEnumBug) collectNonEnumProps(obj, keys);
    return keys;
  };

  // Retrieve all the property names of an object.
  _.allKeys = function(obj) {
    if (!_.isObject(obj)) return [];
    var keys = [];
    for (var key in obj) keys.push(key);
    // Ahem, IE < 9.
    if (hasEnumBug) collectNonEnumProps(obj, keys);
    return keys;
  };

  // Retrieve the values of an object's properties.
  _.values = function(obj) {
    var keys = _.keys(obj);
    var length = keys.length;
    var values = Array(length);
    for (var i = 0; i < length; i++) {
      values[i] = obj[keys[i]];
    }
    return values;
  };

  // Returns the results of applying the iteratee to each element of the object.
  // In contrast to _.map it returns an object.
  _.mapObject = function(obj, iteratee, context) {
    iteratee = cb(iteratee, context);
    var keys = _.keys(obj),
        length = keys.length,
        results = {};
    for (var index = 0; index < length; index++) {
      var currentKey = keys[index];
      results[currentKey] = iteratee(obj[currentKey], currentKey, obj);
    }
    return results;
  };

  // Convert an object into a list of `[key, value]` pairs.
  // The opposite of _.object.
  _.pairs = function(obj) {
    var keys = _.keys(obj);
    var length = keys.length;
    var pairs = Array(length);
    for (var i = 0; i < length; i++) {
      pairs[i] = [keys[i], obj[keys[i]]];
    }
    return pairs;
  };

  // Invert the keys and values of an object. The values must be serializable.
  _.invert = function(obj) {
    var result = {};
    var keys = _.keys(obj);
    for (var i = 0, length = keys.length; i < length; i++) {
      result[obj[keys[i]]] = keys[i];
    }
    return result;
  };

  // Return a sorted list of the function names available on the object.
  // Aliased as `methods`.
  _.functions = _.methods = function(obj) {
    var names = [];
    for (var key in obj) {
      if (_.isFunction(obj[key])) names.push(key);
    }
    return names.sort();
  };

  // An internal function for creating assigner functions.
  var createAssigner = function(keysFunc, defaults) {
    return function(obj) {
      var length = arguments.length;
      if (defaults) obj = Object(obj);
      if (length < 2 || obj == null) return obj;
      for (var index = 1; index < length; index++) {
        var source = arguments[index],
            keys = keysFunc(source),
            l = keys.length;
        for (var i = 0; i < l; i++) {
          var key = keys[i];
          if (!defaults || obj[key] === void 0) obj[key] = source[key];
        }
      }
      return obj;
    };
  };

  // Extend a given object with all the properties in passed-in object(s).
  _.extend = createAssigner(_.allKeys);

  // Assigns a given object with all the own properties in the passed-in object(s).
  // (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)
  _.extendOwn = _.assign = createAssigner(_.keys);

  // Returns the first key on an object that passes a predicate test.
  _.findKey = function(obj, predicate, context) {
    predicate = cb(predicate, context);
    var keys = _.keys(obj), key;
    for (var i = 0, length = keys.length; i < length; i++) {
      key = keys[i];
      if (predicate(obj[key], key, obj)) return key;
    }
  };

  // Internal pick helper function to determine if `obj` has key `key`.
  var keyInObj = function(value, key, obj) {
    return key in obj;
  };

  // Return a copy of the object only containing the whitelisted properties.
  _.pick = restArguments(function(obj, keys) {
    var result = {}, iteratee = keys[0];
    if (obj == null) return result;
    if (_.isFunction(iteratee)) {
      if (keys.length > 1) iteratee = optimizeCb(iteratee, keys[1]);
      keys = _.allKeys(obj);
    } else {
      iteratee = keyInObj;
      keys = flatten(keys, false, false);
      obj = Object(obj);
    }
    for (var i = 0, length = keys.length; i < length; i++) {
      var key = keys[i];
      var value = obj[key];
      if (iteratee(value, key, obj)) result[key] = value;
    }
    return result;
  });

  // Return a copy of the object without the blacklisted properties.
  _.omit = restArguments(function(obj, keys) {
    var iteratee = keys[0], context;
    if (_.isFunction(iteratee)) {
      iteratee = _.negate(iteratee);
      if (keys.length > 1) context = keys[1];
    } else {
      keys = _.map(flatten(keys, false, false), String);
      iteratee = function(value, key) {
        return !_.contains(keys, key);
      };
    }
    return _.pick(obj, iteratee, context);
  });

  // Fill in a given object with default properties.
  _.defaults = createAssigner(_.allKeys, true);

  // Creates an object that inherits from the given prototype object.
  // If additional properties are provided then they will be added to the
  // created object.
  _.create = function(prototype, props) {
    var result = baseCreate(prototype);
    if (props) _.extendOwn(result, props);
    return result;
  };

  // Create a (shallow-cloned) duplicate of an object.
  _.clone = function(obj) {
    if (!_.isObject(obj)) return obj;
    return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
  };

  // Invokes interceptor with the obj, and then returns obj.
  // The primary purpose of this method is to "tap into" a method chain, in
  // order to perform operations on intermediate results within the chain.
  _.tap = function(obj, interceptor) {
    interceptor(obj);
    return obj;
  };

  // Returns whether an object has a given set of `key:value` pairs.
  _.isMatch = function(object, attrs) {
    var keys = _.keys(attrs), length = keys.length;
    if (object == null) return !length;
    var obj = Object(object);
    for (var i = 0; i < length; i++) {
      var key = keys[i];
      if (attrs[key] !== obj[key] || !(key in obj)) return false;
    }
    return true;
  };


  // Internal recursive comparison function for `isEqual`.
  var eq, deepEq;
  eq = function(a, b, aStack, bStack) {
    // Identical objects are equal. `0 === -0`, but they aren't identical.
    // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
    if (a === b) return a !== 0 || 1 / a === 1 / b;
    // `null` or `undefined` only equal to itself (strict comparison).
    if (a == null || b == null) return false;
    // `NaN`s are equivalent, but non-reflexive.
    if (a !== a) return b !== b;
    // Exhaust primitive checks
    var type = typeof a;
    if (type !== 'function' && type !== 'object' && typeof b != 'object') return false;
    return deepEq(a, b, aStack, bStack);
  };

  // Internal recursive comparison function for `isEqual`.
  deepEq = function(a, b, aStack, bStack) {
    // Unwrap any wrapped objects.
    if (a instanceof _) a = a._wrapped;
    if (b instanceof _) b = b._wrapped;
    // Compare `[[Class]]` names.
    var className = toString.call(a);
    if (className !== toString.call(b)) return false;
    switch (className) {
      // Strings, numbers, regular expressions, dates, and booleans are compared by value.
      case '[object RegExp]':
      // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i')
      case '[object String]':
        // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
        // equivalent to `new String("5")`.
        return '' + a === '' + b;
      case '[object Number]':
        // `NaN`s are equivalent, but non-reflexive.
        // Object(NaN) is equivalent to NaN.
        if (+a !== +a) return +b !== +b;
        // An `egal` comparison is performed for other numeric values.
        return +a === 0 ? 1 / +a === 1 / b : +a === +b;
      case '[object Date]':
      case '[object Boolean]':
        // Coerce dates and booleans to numeric primitive values. Dates are compared by their
        // millisecond representations. Note that invalid dates with millisecond representations
        // of `NaN` are not equivalent.
        return +a === +b;
      case '[object Symbol]':
        return SymbolProto.valueOf.call(a) === SymbolProto.valueOf.call(b);
    }

    var areArrays = className === '[object Array]';
    if (!areArrays) {
      if (typeof a != 'object' || typeof b != 'object') return false;

      // Objects with different constructors are not equivalent, but `Object`s or `Array`s
      // from different frames are.
      var aCtor = a.constructor, bCtor = b.constructor;
      if (aCtor !== bCtor && !(_.isFunction(aCtor) && aCtor instanceof aCtor &&
                               _.isFunction(bCtor) && bCtor instanceof bCtor)
                          && ('constructor' in a && 'constructor' in b)) {
        return false;
      }
    }
    // Assume equality for cyclic structures. The algorithm for detecting cyclic
    // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.

    // Initializing stack of traversed objects.
    // It's done here since we only need them for objects and arrays comparison.
    aStack = aStack || [];
    bStack = bStack || [];
    var length = aStack.length;
    while (length--) {
      // Linear search. Performance is inversely proportional to the number of
      // unique nested structures.
      if (aStack[length] === a) return bStack[length] === b;
    }

    // Add the first object to the stack of traversed objects.
    aStack.push(a);
    bStack.push(b);

    // Recursively compare objects and arrays.
    if (areArrays) {
      // Compare array lengths to determine if a deep comparison is necessary.
      length = a.length;
      if (length !== b.length) return false;
      // Deep compare the contents, ignoring non-numeric properties.
      while (length--) {
        if (!eq(a[length], b[length], aStack, bStack)) return false;
      }
    } else {
      // Deep compare objects.
      var keys = _.keys(a), key;
      length = keys.length;
      // Ensure that both objects contain the same number of properties before comparing deep equality.
      if (_.keys(b).length !== length) return false;
      while (length--) {
        // Deep compare each member
        key = keys[length];
        if (!(has(b, key) && eq(a[key], b[key], aStack, bStack))) return false;
      }
    }
    // Remove the first object from the stack of traversed objects.
    aStack.pop();
    bStack.pop();
    return true;
  };

  // Perform a deep comparison to check if two objects are equal.
  _.isEqual = function(a, b) {
    return eq(a, b);
  };

  // Is a given array, string, or object empty?
  // An "empty" object has no enumerable own-properties.
  _.isEmpty = function(obj) {
    if (obj == null) return true;
    if (isArrayLike(obj) && (_.isArray(obj) || _.isString(obj) || _.isArguments(obj))) return obj.length === 0;
    return _.keys(obj).length === 0;
  };

  // Is a given value a DOM element?
  _.isElement = function(obj) {
    return !!(obj && obj.nodeType === 1);
  };

  // Is a given value an array?
  // Delegates to ECMA5's native Array.isArray
  _.isArray = nativeIsArray || function(obj) {
    return toString.call(obj) === '[object Array]';
  };

  // Is a given variable an object?
  _.isObject = function(obj) {
    var type = typeof obj;
    return type === 'function' || type === 'object' && !!obj;
  };

  // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp, isError, isMap, isWeakMap, isSet, isWeakSet.
  _.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp', 'Error', 'Symbol', 'Map', 'WeakMap', 'Set', 'WeakSet'], function(name) {
    _['is' + name] = function(obj) {
      return toString.call(obj) === '[object ' + name + ']';
    };
  });

  // Define a fallback version of the method in browsers (ahem, IE < 9), where
  // there isn't any inspectable "Arguments" type.
  if (!_.isArguments(arguments)) {
    _.isArguments = function(obj) {
      return has(obj, 'callee');
    };
  }

  // Optimize `isFunction` if appropriate. Work around some typeof bugs in old v8,
  // IE 11 (#1621), Safari 8 (#1929), and PhantomJS (#2236).
  var nodelist = root.document && root.document.childNodes;
  if (typeof /./ != 'function' && typeof Int8Array != 'object' && typeof nodelist != 'function') {
    _.isFunction = function(obj) {
      return typeof obj == 'function' || false;
    };
  }

  // Is a given object a finite number?
  _.isFinite = function(obj) {
    return !_.isSymbol(obj) && isFinite(obj) && !isNaN(parseFloat(obj));
  };

  // Is the given value `NaN`?
  _.isNaN = function(obj) {
    return _.isNumber(obj) && isNaN(obj);
  };

  // Is a given value a boolean?
  _.isBoolean = function(obj) {
    return obj === true || obj === false || toString.call(obj) === '[object Boolean]';
  };

  // Is a given value equal to null?
  _.isNull = function(obj) {
    return obj === null;
  };

  // Is a given variable undefined?
  _.isUndefined = function(obj) {
    return obj === void 0;
  };

  // Shortcut function for checking if an object has a given property directly
  // on itself (in other words, not on a prototype).
  _.has = function(obj, path) {
    if (!_.isArray(path)) {
      return has(obj, path);
    }
    var length = path.length;
    for (var i = 0; i < length; i++) {
      var key = path[i];
      if (obj == null || !hasOwnProperty.call(obj, key)) {
        return false;
      }
      obj = obj[key];
    }
    return !!length;
  };

  // Utility Functions
  // -----------------

  // Run Underscore.js in *noConflict* mode, returning the `_` variable to its
  // previous owner. Returns a reference to the Underscore object.
  _.noConflict = function() {
    root._ = previousUnderscore;
    return this;
  };

  // Keep the identity function around for default iteratees.
  _.identity = function(value) {
    return value;
  };

  // Predicate-generating functions. Often useful outside of Underscore.
  _.constant = function(value) {
    return function() {
      return value;
    };
  };

  _.noop = function(){};

  // Creates a function that, when passed an object, will traverse that object’s
  // properties down the given `path`, specified as an array of keys or indexes.
  _.property = function(path) {
    if (!_.isArray(path)) {
      return shallowProperty(path);
    }
    return function(obj) {
      return deepGet(obj, path);
    };
  };

  // Generates a function for a given object that returns a given property.
  _.propertyOf = function(obj) {
    if (obj == null) {
      return function(){};
    }
    return function(path) {
      return !_.isArray(path) ? obj[path] : deepGet(obj, path);
    };
  };

  // Returns a predicate for checking whether an object has a given set of
  // `key:value` pairs.
  _.matcher = _.matches = function(attrs) {
    attrs = _.extendOwn({}, attrs);
    return function(obj) {
      return _.isMatch(obj, attrs);
    };
  };

  // Run a function **n** times.
  _.times = function(n, iteratee, context) {
    var accum = Array(Math.max(0, n));
    iteratee = optimizeCb(iteratee, context, 1);
    for (var i = 0; i < n; i++) accum[i] = iteratee(i);
    return accum;
  };

  // Return a random integer between min and max (inclusive).
  _.random = function(min, max) {
    if (max == null) {
      max = min;
      min = 0;
    }
    return min + Math.floor(Math.random() * (max - min + 1));
  };

  // A (possibly faster) way to get the current timestamp as an integer.
  _.now = Date.now || function() {
    return new Date().getTime();
  };

  // List of HTML entities for escaping.
  var escapeMap = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#x27;',
    '`': '&#x60;'
  };
  var unescapeMap = _.invert(escapeMap);

  // Functions for escaping and unescaping strings to/from HTML interpolation.
  var createEscaper = function(map) {
    var escaper = function(match) {
      return map[match];
    };
    // Regexes for identifying a key that needs to be escaped.
    var source = '(?:' + _.keys(map).join('|') + ')';
    var testRegexp = RegExp(source);
    var replaceRegexp = RegExp(source, 'g');
    return function(string) {
      string = string == null ? '' : '' + string;
      return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string;
    };
  };
  _.escape = createEscaper(escapeMap);
  _.unescape = createEscaper(unescapeMap);

  // Traverses the children of `obj` along `path`. If a child is a function, it
  // is invoked with its parent as context. Returns the value of the final
  // child, or `fallback` if any child is undefined.
  _.result = function(obj, path, fallback) {
    if (!_.isArray(path)) path = [path];
    var length = path.length;
    if (!length) {
      return _.isFunction(fallback) ? fallback.call(obj) : fallback;
    }
    for (var i = 0; i < length; i++) {
      var prop = obj == null ? void 0 : obj[path[i]];
      if (prop === void 0) {
        prop = fallback;
        i = length; // Ensure we don't continue iterating.
      }
      obj = _.isFunction(prop) ? prop.call(obj) : prop;
    }
    return obj;
  };

  // Generate a unique integer id (unique within the entire client session).
  // Useful for temporary DOM ids.
  var idCounter = 0;
  _.uniqueId = function(prefix) {
    var id = ++idCounter + '';
    return prefix ? prefix + id : id;
  };

  // By default, Underscore uses ERB-style template delimiters, change the
  // following template settings to use alternative delimiters.
  _.templateSettings = {
    evaluate: /<%([\s\S]+?)%>/g,
    interpolate: /<%=([\s\S]+?)%>/g,
    escape: /<%-([\s\S]+?)%>/g
  };

  // When customizing `templateSettings`, if you don't want to define an
  // interpolation, evaluation or escaping regex, we need one that is
  // guaranteed not to match.
  var noMatch = /(.)^/;

  // Certain characters need to be escaped so that they can be put into a
  // string literal.
  var escapes = {
    "'": "'",
    '\\': '\\',
    '\r': 'r',
    '\n': 'n',
    '\u2028': 'u2028',
    '\u2029': 'u2029'
  };

  var escapeRegExp = /\\|'|\r|\n|\u2028|\u2029/g;

  var escapeChar = function(match) {
    return '\\' + escapes[match];
  };

  // JavaScript micro-templating, similar to John Resig's implementation.
  // Underscore templating handles arbitrary delimiters, preserves whitespace,
  // and correctly escapes quotes within interpolated code.
  // NB: `oldSettings` only exists for backwards compatibility.
  _.template = function(text, settings, oldSettings) {
    if (!settings && oldSettings) settings = oldSettings;
    settings = _.defaults({}, settings, _.templateSettings);

    // Combine delimiters into one regular expression via alternation.
    var matcher = RegExp([
      (settings.escape || noMatch).source,
      (settings.interpolate || noMatch).source,
      (settings.evaluate || noMatch).source
    ].join('|') + '|$', 'g');

    // Compile the template source, escaping string literals appropriately.
    var index = 0;
    var source = "__p+='";
    text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
      source += text.slice(index, offset).replace(escapeRegExp, escapeChar);
      index = offset + match.length;

      if (escape) {
        source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
      } else if (interpolate) {
        source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
      } else if (evaluate) {
        source += "';\n" + evaluate + "\n__p+='";
      }

      // Adobe VMs need the match returned to produce the correct offset.
      return match;
    });
    source += "';\n";

    // If a variable is not specified, place data values in local scope.
    if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';

    source = "var __t,__p='',__j=Array.prototype.join," +
      "print=function(){__p+=__j.call(arguments,'');};\n" +
      source + 'return __p;\n';

    var render;
    try {
      render = new Function(settings.variable || 'obj', '_', source);
    } catch (e) {
      e.source = source;
      throw e;
    }

    var template = function(data) {
      return render.call(this, data, _);
    };

    // Provide the compiled source as a convenience for precompilation.
    var argument = settings.variable || 'obj';
    template.source = 'function(' + argument + '){\n' + source + '}';

    return template;
  };

  // Add a "chain" function. Start chaining a wrapped Underscore object.
  _.chain = function(obj) {
    var instance = _(obj);
    instance._chain = true;
    return instance;
  };

  // OOP
  // ---------------
  // If Underscore is called as a function, it returns a wrapped object that
  // can be used OO-style. This wrapper holds altered versions of all the
  // underscore functions. Wrapped objects may be chained.

  // Helper function to continue chaining intermediate results.
  var chainResult = function(instance, obj) {
    return instance._chain ? _(obj).chain() : obj;
  };

  // Add your own custom functions to the Underscore object.
  _.mixin = function(obj) {
    _.each(_.functions(obj), function(name) {
      var func = _[name] = obj[name];
      _.prototype[name] = function() {
        var args = [this._wrapped];
        push.apply(args, arguments);
        return chainResult(this, func.apply(_, args));
      };
    });
    return _;
  };

  // Add all of the Underscore functions to the wrapper object.
  _.mixin(_);

  // Add all mutator Array functions to the wrapper.
  _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
    var method = ArrayProto[name];
    _.prototype[name] = function() {
      var obj = this._wrapped;
      method.apply(obj, arguments);
      if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0];
      return chainResult(this, obj);
    };
  });

  // Add all accessor Array functions to the wrapper.
  _.each(['concat', 'join', 'slice'], function(name) {
    var method = ArrayProto[name];
    _.prototype[name] = function() {
      return chainResult(this, method.apply(this._wrapped, arguments));
    };
  });

  // Extracts the result from a wrapped and chained object.
  _.prototype.value = function() {
    return this._wrapped;
  };

  // Provide unwrapping proxy for some methods used in engine operations
  // such as arithmetic and JSON stringification.
  _.prototype.valueOf = _.prototype.toJSON = _.prototype.value;

  _.prototype.toString = function() {
    return String(this._wrapped);
  };

  // AMD registration happens at the end for compatibility with AMD loaders
  // that may not enforce next-turn semantics on modules. Even though general
  // practice for AMD registration is to be anonymous, underscore registers
  // as a named module because, like jQuery, it is a base library that is
  // popular enough to be bundled in a third party lib, but not be part of
  // an AMD load request. Those cases could generate an error when an
  // anonymous define() is called outside of a loader request.
  if (typeof define == 'function' && define.amd) {
    define('underscore', [], function() {
      return _;
    });
  }
}());
};
BundleModuleCode['ext/jsep']=function (module,exports){
//     JavaScript Expression Parser (JSEP) <%= version %>
//     JSEP may be freely distributed under the MIT License
//     http://jsep.from.so/

/*global module: true, exports: true, console: true */
(function (root) {
	'use strict';
	// Node Types
	// ----------

	// This is the full set of types that any JSEP node can be.
	// Store them here to save space when minified
	var COMPOUND = 'Compound',
		IDENTIFIER = 'Identifier',
		MEMBER_EXP = 'MemberExpression',
		LITERAL = 'Literal',
		THIS_EXP = 'ThisExpression',
		CALL_EXP = 'CallExpression',
		UNARY_EXP = 'UnaryExpression',
		BINARY_EXP = 'BinaryExpression',
		LOGICAL_EXP = 'LogicalExpression',
		CONDITIONAL_EXP = 'ConditionalExpression',
		ARRAY_EXP = 'ArrayExpression',

		PERIOD_CODE = 46, // '.'
		COMMA_CODE  = 44, // ','
		SQUOTE_CODE = 39, // single quote
		DQUOTE_CODE = 34, // double quotes
		OPAREN_CODE = 40, // (
		CPAREN_CODE = 41, // )
		OBRACK_CODE = 91, // [
		CBRACK_CODE = 93, // ]
		QUMARK_CODE = 63, // ?
		SEMCOL_CODE = 59, // ;
		COLON_CODE  = 58, // :

		throwError = function(message, index) {
			var error = new Error(message + ' at character ' + index);
			error.index = index;
			error.description = message;
			throw error;
		},

	// Operations
	// ----------

	// Set `t` to `true` to save space (when minified, not gzipped)
		t = true,
	// Use a quickly-accessible map to store all of the unary operators
	// Values are set to `true` (it really doesn't matter)
		unary_ops = {'-': t, '!': t, '~': t, '+': t},
	// Also use a map for the binary operations but set their values to their
	// binary precedence for quick reference:
	// see [Order of operations](http://en.wikipedia.org/wiki/Order_of_operations#Programming_language)
		binary_ops = {
			'||': 1, '&&': 2, '|': 3, /* or */ 
            '^': 4 /*xor*/,  '^1': 4 /*exactlyonce*/,  '^0': 4 /*atmostonce*/,
            '&': 5, /* and */ '->':5, /* implies */
			'==': 6, '!=': 6, '===': 6, '!==': 6, '==~':6,
			'<': 7,  '>': 7,  '<=': 7,  '>=': 7,
			'<<':8,  '>>': 8, '>>>': 8,
			'+': 9, '-': 9 /* not */, 
			'*': 10, '/': 10, '%': 10,
		},
	// Get return the longest key length of any object
		getMaxKeyLen = function(obj) {
			var max_len = 0, len;
			for(var key in obj) {
				if((len = key.length) > max_len && obj.hasOwnProperty(key)) {
					max_len = len;
				}
			}
			return max_len;
		},
		max_unop_len = getMaxKeyLen(unary_ops),
		max_binop_len = getMaxKeyLen(binary_ops),
	// Literals
	// ----------
	// Store the values to return for the various literals we may encounter
		literals = {
			'true': true,
			'false': false,
			'null': null
		},
	// Except for `this`, which is special. This could be changed to something like `'self'` as well
		this_str = 'this',
	// Returns the precedence of a binary operator or `0` if it isn't a binary operator
		binaryPrecedence = function(op_val) {
			return binary_ops[op_val] || 0;
		},
	// Utility function (gets called from multiple places)
	// Also note that `a && b` and `a || b` are *logical* expressions, not binary expressions
		createBinaryExpression = function (operator, left, right) {
			var type = (operator === '||' || operator === '&&') ? LOGICAL_EXP : BINARY_EXP;
			return {
				type: type,
				operator: operator,
				left: left,
				right: right
			};
		},
		// `ch` is a character code in the next three functions
		isDecimalDigit = function(ch) {
			return (ch >= 48 && ch <= 57); // 0...9
		},
		isIdentifierStart = function(ch) {
			return (ch === 36) || (ch === 95) || // `$` and `_`
					(ch >= 65 && ch <= 90) || // A...Z
					(ch >= 97 && ch <= 122) || // a...z
                    (ch >= 128 && !binary_ops[String.fromCharCode(ch)]); // any non-ASCII that is not an operator
		},
		isIdentifierPart = function(ch) {
			return (ch === 36) || (ch === 95) || // `$` and `_`
					(ch >= 65 && ch <= 90) || // A...Z
					(ch >= 97 && ch <= 122) || // a...z
					(ch >= 48 && ch <= 57) || // 0...9
                    (ch >= 128 && !binary_ops[String.fromCharCode(ch)]); // any non-ASCII that is not an operator
		},

		// Parsing
		// -------
		// `expr` is a string with the passed in expression
		jsep = function(expr) {
			// `index` stores the character number we are currently at while `length` is a constant
			// All of the gobbles below will modify `index` as we move along
			var index = 0,
				charAtFunc = expr.charAt,
				charCodeAtFunc = expr.charCodeAt,
				exprI = function(i) { return charAtFunc.call(expr, i); },
				exprICode = function(i) { return charCodeAtFunc.call(expr, i); },
				length = expr.length,

				// Push `index` up to the next non-space character
				gobbleSpaces = function() {
					var ch = exprICode(index);
					// space or tab
					while(ch === 32 || ch === 9 || ch === 10 || ch === 13) {
						ch = exprICode(++index);
					}
				},

				// The main parsing function. Much of this code is dedicated to ternary expressions
				gobbleExpression = function() {
					var test = gobbleBinaryExpression(),
						consequent, alternate;
					gobbleSpaces();
					if(exprICode(index) === QUMARK_CODE) {
						// Ternary expression: test ? consequent : alternate
						index++;
						consequent = gobbleExpression();
						if(!consequent) {
							throwError('Expected expression', index);
						}
						gobbleSpaces();
						if(exprICode(index) === COLON_CODE) {
							index++;
							alternate = gobbleExpression();
							if(!alternate) {
								throwError('Expected expression', index);
							}
							return {
								type: CONDITIONAL_EXP,
								test: test,
								consequent: consequent,
								alternate: alternate
							};
						} else {
							throwError('Expected :', index);
						}
					} else {
						return test;
					}
				},

				// Search for the operation portion of the string (e.g. `+`, `===`)
				// Start by taking the longest possible binary operations (3 characters: `===`, `!==`, `>>>`)
				// and move down from 3 to 2 to 1 character until a matching binary operation is found
				// then, return that binary operation
				gobbleBinaryOp = function() {
					gobbleSpaces();
					var biop, to_check = expr.substr(index, max_binop_len), tc_len = to_check.length;
					while(tc_len > 0) {
						// Don't accept a binary op when it is an identifier.
						// Binary ops that start with a identifier-valid character must be followed
						// by a non identifier-part valid character
						if(binary_ops.hasOwnProperty(to_check) && (
							!isIdentifierStart(exprICode(index)) ||
							(index+to_check.length< expr.length && !isIdentifierPart(exprICode(index+to_check.length)))
						)) {
							index += tc_len;
							return to_check;
						}
						to_check = to_check.substr(0, --tc_len);
					}
					return false;
				},

				// This function is responsible for gobbling an individual expression,
				// e.g. `1`, `1+2`, `a+(b*2)-Math.sqrt(2)`
				gobbleBinaryExpression = function() {
					var ch_i, node, biop, prec, stack, biop_info, left, right, i, cur_biop;

					// First, try to get the leftmost thing
					// Then, check to see if there's a binary operator operating on that leftmost thing
					left = gobbleToken();
					biop = gobbleBinaryOp();

					// If there wasn't a binary operator, just return the leftmost node
					if(!biop) {
						return left;
					}

					// Otherwise, we need to start a stack to properly place the binary operations in their
					// precedence structure
					biop_info = { value: biop, prec: binaryPrecedence(biop)};

					right = gobbleToken();
					if(!right) {
						throwError("Expected expression after " + biop, index);
					}
					stack = [left, biop_info, right];

					// Properly deal with precedence using [recursive descent](http://www.engr.mun.ca/~theo/Misc/exp_parsing.htm)
					while((biop = gobbleBinaryOp())) {
						prec = binaryPrecedence(biop);

						if(prec === 0) {
							break;
						}
						biop_info = { value: biop, prec: prec };

						cur_biop = biop;
						// Reduce: make a binary expression from the three topmost entries.
						while ((stack.length > 2) && (prec <= stack[stack.length - 2].prec)) {
							right = stack.pop();
							biop = stack.pop().value;
							left = stack.pop();
							node = createBinaryExpression(biop, left, right);
							stack.push(node);
						}

						node = gobbleToken();
						if(!node) {
							throwError("Expected expression after " + cur_biop, index);
						}
						stack.push(biop_info, node);
					}

					i = stack.length - 1;
					node = stack[i];
					while(i > 1) {
						node = createBinaryExpression(stack[i - 1].value, stack[i - 2], node);
						i -= 2;
					}
					return node;
				},

				// An individual part of a binary expression:
				// e.g. `foo.bar(baz)`, `1`, `"abc"`, `(a % 2)` (because it's in parenthesis)
				gobbleToken = function() {
					var ch, to_check, tc_len;

					gobbleSpaces();
					ch = exprICode(index);

					if(isDecimalDigit(ch) || ch === PERIOD_CODE) {
						// Char code 46 is a dot `.` which can start off a numeric literal
						return gobbleNumericLiteral();
					} else if(ch === SQUOTE_CODE || ch === DQUOTE_CODE) {
						// Single or double quotes
						return gobbleStringLiteral();
					} else if (ch === OBRACK_CODE) {
						return gobbleArray();
					} else {
						to_check = expr.substr(index, max_unop_len);
						tc_len = to_check.length;
						while(tc_len > 0) {
						// Don't accept an unary op when it is an identifier.
						// Unary ops that start with a identifier-valid character must be followed
						// by a non identifier-part valid character
							if(unary_ops.hasOwnProperty(to_check) && (
								!isIdentifierStart(exprICode(index)) ||
								(index+to_check.length < expr.length && !isIdentifierPart(exprICode(index+to_check.length)))
							)) {
								index += tc_len;
								return {
									type: UNARY_EXP,
									operator: to_check,
									argument: gobbleToken(),
									prefix: true
								};
							}
							to_check = to_check.substr(0, --tc_len);
						}

						if (isIdentifierStart(ch) || ch === OPAREN_CODE) { // open parenthesis
							// `foo`, `bar.baz`
							return gobbleVariable();
						}
					}

					return false;
				},
				// Parse simple numeric literals: `12`, `3.4`, `.5`. Do this by using a string to
				// keep track of everything in the numeric literal and then calling `parseFloat` on that string
				gobbleNumericLiteral = function() {
					var number = '', ch, chCode;
					while(isDecimalDigit(exprICode(index))) {
						number += exprI(index++);
					}

					if(exprICode(index) === PERIOD_CODE) { // can start with a decimal marker
						number += exprI(index++);

						while(isDecimalDigit(exprICode(index))) {
							number += exprI(index++);
						}
					}

					ch = exprI(index);
					if(ch === 'e' || ch === 'E') { // exponent marker
						number += exprI(index++);
						ch = exprI(index);
						if(ch === '+' || ch === '-') { // exponent sign
							number += exprI(index++);
						}
						while(isDecimalDigit(exprICode(index))) { //exponent itself
							number += exprI(index++);
						}
						if(!isDecimalDigit(exprICode(index-1)) ) {
							throwError('Expected exponent (' + number + exprI(index) + ')', index);
						}
					}


					chCode = exprICode(index);
					// Check to make sure this isn't a variable name that start with a number (123abc)
					if(isIdentifierStart(chCode)) {
						throwError('Variable names cannot start with a number (' +
									number + exprI(index) + ')', index);
					} else if(chCode === PERIOD_CODE) {
						throwError('Unexpected period', index);
					}

					return {
						type: LITERAL,
						value: parseFloat(number),
						raw: number
					};
				},

				// Parses a string literal, staring with single or double quotes with basic support for escape codes
				// e.g. `"hello world"`, `'this is\nJSEP'`
				gobbleStringLiteral = function() {
					var str = '', quote = exprI(index++), closed = false, ch;

					while(index < length) {
						ch = exprI(index++);
						if(ch === quote) {
							closed = true;
							break;
						} else if(ch === '\\') {
							// Check for all of the common escape codes
							ch = exprI(index++);
							switch(ch) {
								case 'n': str += '\n'; break;
								case 'r': str += '\r'; break;
								case 't': str += '\t'; break;
								case 'b': str += '\b'; break;
								case 'f': str += '\f'; break;
								case 'v': str += '\x0B'; break;
								default : str += ch;
							}
						} else {
							str += ch;
						}
					}

					if(!closed) {
						throwError('Unclosed quote after "'+str+'"', index);
					}

					return {
						type: LITERAL,
						value: str,
						raw: quote + str + quote
					};
				},

				// Gobbles only identifiers
				// e.g.: `foo`, `_value`, `$x1`
				// Also, this function checks if that identifier is a literal:
				// (e.g. `true`, `false`, `null`) or `this`
				gobbleIdentifier = function() {
					var ch = exprICode(index), start = index, identifier;

					if(isIdentifierStart(ch)) {
						index++;
					} else {
						throwError('Unexpected ' + exprI(index), index);
					}

					while(index < length) {
						ch = exprICode(index);
						if(isIdentifierPart(ch)) {
							index++;
						} else {
							break;
						}
					}
					identifier = expr.slice(start, index);

					if(literals.hasOwnProperty(identifier)) {
						return {
							type: LITERAL,
							value: literals[identifier],
							raw: identifier
						};
					} else if(identifier === this_str) {
						return { type: THIS_EXP };
					} else {
						return {
							type: IDENTIFIER,
							name: identifier
						};
					}
				},

				// Gobbles a list of arguments within the context of a function call
				// or array literal. This function also assumes that the opening character
				// `(` or `[` has already been gobbled, and gobbles expressions and commas
				// until the terminator character `)` or `]` is encountered.
				// e.g. `foo(bar, baz)`, `my_func()`, or `[bar, baz]`
				gobbleArguments = function(termination) {
					var ch_i, args = [], node, closed = false;
					while(index < length) {
						gobbleSpaces();
						ch_i = exprICode(index);
						if(ch_i === termination) { // done parsing
							closed = true;
							index++;
							break;
						} else if (ch_i === COMMA_CODE) { // between expressions
							index++;
						} else {
							node = gobbleExpression();
							if(!node || node.type === COMPOUND) {
								throwError('Expected comma', index);
							}
							args.push(node);
						}
					}
					if (!closed) {
						throwError('Expected ' + String.fromCharCode(termination), index);
					}
					return args;
				},

				// Gobble a non-literal variable name. This variable name may include properties
				// e.g. `foo`, `bar.baz`, `foo['bar'].baz`
				// It also gobbles function calls:
				// e.g. `Math.acos(obj.angle)`
				gobbleVariable = function() {
					var ch_i, node;
					ch_i = exprICode(index);

					if(ch_i === OPAREN_CODE) {
						node = gobbleGroup();
					} else {
						node = gobbleIdentifier();
					}
					gobbleSpaces();
					ch_i = exprICode(index);
					while(ch_i === PERIOD_CODE || ch_i === OBRACK_CODE || ch_i === OPAREN_CODE) {
						index++;
						if(ch_i === PERIOD_CODE) {
							gobbleSpaces();
							node = {
								type: MEMBER_EXP,
								computed: false,
								object: node,
								property: gobbleIdentifier()
							};
						} else if(ch_i === OBRACK_CODE) {
							node = {
								type: MEMBER_EXP,
								computed: true,
								object: node,
								property: gobbleExpression()
							};
							gobbleSpaces();
							ch_i = exprICode(index);
							if(ch_i !== CBRACK_CODE) {
								throwError('Unclosed [', index);
							}
							index++;
						} else if(ch_i === OPAREN_CODE) {
							// A function call is being made; gobble all the arguments
							node = {
								type: CALL_EXP,
								'arguments': gobbleArguments(CPAREN_CODE),
								callee: node
							};
						}
						gobbleSpaces();
						ch_i = exprICode(index);
					}
					return node;
				},

				// Responsible for parsing a group of things within parentheses `()`
				// This function assumes that it needs to gobble the opening parenthesis
				// and then tries to gobble everything within that parenthesis, assuming
				// that the next thing it should see is the close parenthesis. If not,
				// then the expression probably doesn't have a `)`
				gobbleGroup = function() {
					index++;
					var node = gobbleExpression();
					gobbleSpaces();
					if(exprICode(index) === CPAREN_CODE) {
						index++;
						return node;
					} else {
						throwError('Unclosed (', index);
					}
				},

				// Responsible for parsing Array literals `[1, 2, 3]`
				// This function assumes that it needs to gobble the opening bracket
				// and then tries to gobble the expressions as arguments.
				gobbleArray = function() {
					index++;
					return {
						type: ARRAY_EXP,
						elements: gobbleArguments(CBRACK_CODE)
					};
				},

				nodes = [], ch_i, node;

			while(index < length) {
				ch_i = exprICode(index);

				// Expressions can be separated by semicolons, commas, or just inferred without any
				// separators
				if(ch_i === SEMCOL_CODE || ch_i === COMMA_CODE) {
					index++; // ignore separators
				} else {
					// Try to gobble each expression individually
					if((node = gobbleExpression())) {
						nodes.push(node);
					// If we weren't able to find a binary expression and are out of room, then
					// the expression passed in probably has too much
					} else if(index < length) {
						throwError('Unexpected "' + exprI(index) + '"', index);
					}
				}
			}

			// If there's only one expression just try returning the expression
			if(nodes.length === 1) {
				return nodes[0];
			} else {
				return {
					type: COMPOUND,
					body: nodes
				};
			}
		};

	// To be filled in by the template
	jsep.version = '<%= version %>';
	jsep.toString = function() { return 'JavaScript Expression Parser (JSEP) v' + jsep.version; };

	/**
	 * @method jsep.addUnaryOp
	 * @param {string} op_name The name of the unary op to add
	 * @return jsep
	 */
	jsep.addUnaryOp = function(op_name) {
		max_unop_len = Math.max(op_name.length, max_unop_len);
		unary_ops[op_name] = t; return this;
	};

	/**
	 * @method jsep.addBinaryOp
	 * @param {string} op_name The name of the binary op to add
	 * @param {number} precedence The precedence of the binary op (can be a float)
	 * @return jsep
	 */
	jsep.addBinaryOp = function(op_name, precedence) {
		max_binop_len = Math.max(op_name.length, max_binop_len);
		binary_ops[op_name] = precedence;
		return this;
	};

	/**
	 * @method jsep.addLiteral
	 * @param {string} literal_name The name of the literal to add
	 * @param {*} literal_value The value of the literal
	 * @return jsep
	 */
	jsep.addLiteral = function(literal_name, literal_value) {
		literals[literal_name] = literal_value;
		return this;
	};

	/**
	 * @method jsep.removeUnaryOp
	 * @param {string} op_name The name of the unary op to remove
	 * @return jsep
	 */
	jsep.removeUnaryOp = function(op_name) {
		delete unary_ops[op_name];
		if(op_name.length === max_unop_len) {
			max_unop_len = getMaxKeyLen(unary_ops);
		}
		return this;
	};

	/**
	 * @method jsep.removeAllUnaryOps
	 * @return jsep
	 */
	jsep.removeAllUnaryOps = function() {
		unary_ops = {};
		max_unop_len = 0;

		return this;
	};

	/**
	 * @method jsep.removeBinaryOp
	 * @param {string} op_name The name of the binary op to remove
	 * @return jsep
	 */
	jsep.removeBinaryOp = function(op_name) {
		delete binary_ops[op_name];
		if(op_name.length === max_binop_len) {
			max_binop_len = getMaxKeyLen(binary_ops);
		}
		return this;
	};

	/**
	 * @method jsep.removeAllBinaryOps
	 * @return jsep
	 */
	jsep.removeAllBinaryOps = function() {
		binary_ops = {};
		max_binop_len = 0;

		return this;
	};

	/**
	 * @method jsep.removeLiteral
	 * @param {string} literal_name The name of the literal to remove
	 * @return jsep
	 */
	jsep.removeLiteral = function(literal_name) {
		delete literals[literal_name];
		return this;
	};

	/**
	 * @method jsep.removeAllLiterals
	 * @return jsep
	 */
	jsep.removeAllLiterals = function() {
		literals = {};

		return this;
	};

	// In desktop environments, have a way to restore the old value for `jsep`
	if (typeof exports === 'undefined') {
		var old_jsep = root.jsep;
		// The star of the show! It's a function!
		root.jsep = jsep;
		// And a courteous function willing to move out of the way for other similarly-named objects!
		jsep.noConflict = function() {
			if(root.jsep === jsep) {
				root.jsep = old_jsep;
			}
			return jsep;
		};
	} else {
		// In Node.JS environments
		if (typeof module !== 'undefined' && module.exports) {
			exports = module.exports = jsep;
		} else {
			exports.parse = jsep;
		}
	}
}(this));
};
BundleModuleCode['logic/prolog']=function (module,exports){
/**
  * 
  * Tau Prolog. A Prolog interpreter in JavaScript.
  *
  * http://tau-prolog.org/documentation
  * 
  * Copyright (C) 2017 - 2019 José Antonio Riaza Valverde
  * 
  * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
  * 
  * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  * 
  * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
  * 
  * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
  * 
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  * 
  **/

var prolog;

(function() {
	
	// VERSION
	var version = { major: 0, minor: 2, patch: 87, status: "beta" };



	// IO FILE SYSTEM

	function cd(working_directory, path) {
        if(path[0] === "/")
            working_directory = path;
        else
            working_directory += working_directory[working_directory.length-1] === "/" ? path : "/" + path;
        working_directory = working_directory.replace(/\/\.\//g, "/");
        var dirs = working_directory.split("/");
        var dirs2 = [];
        for(var i = 0; i < dirs.length; i++) {
            if(dirs[i] !== "..") {
                dirs2.push(dirs[i]);
            } else {
                if(dirs2.length !== 0)
                	dirs2.pop();
            }
        }
        return dirs2.join("/").replace(/\/\.$/, "/");
	}
	
	// Virtual file
	function TauFile(name, type, parent, text) {
		text = text === undefined ? "" : text;
		this.name = name;
		this.type = type;
		this.parent = parent;
		this.text = text;
		this.created = Date.now() / 1000;
		this.modified = this.created;
	}

	TauFile.prototype.get = function(length, position) {
		if(position === this.text.length) {
			return "end_of_file";
		} else if(position > this.text.length) {
			return "end_of_file";
		} else {
			return this.text.substring(position, position+length);
		}
	};

	TauFile.prototype.put = function(text, position) {
		if(position === "end_of_file") {
			this.text += text;
			return true;
		} else if(position === "past_end_of_file") {
			return null;
		} else {
			this.text = this.text.substring(0, position) + text + this.text.substring(position+text.length);
			return true;
		}
	};

	TauFile.prototype.get_byte = function(position) {
		if(position === "end_of_stream")
			return -1;
		var index = Math.floor(position/2);
		if(this.text.length <= index)
			return -1;
		var code = codePointAt(this.text[Math.floor(position/2)], 0);
		if(position % 2 === 0)
			return code & 0xff;
		else
			return code / 256 >>> 0;
	};

	TauFile.prototype.put_byte = function(byte, position) {
		var index = position === "end_of_stream" ? this.text.length : Math.floor(position/2);
		if(this.text.length < index)
			return null;
		var code = this.text.length === index ? -1 : codePointAt(this.text[Math.floor(position/2)], 0);
		if(position % 2 === 0) {
			code = code / 256 >>> 0;
			code = ((code & 0xff) << 8) | (byte & 0xff);
		} else {
			code = code & 0xff;
			code = ((byte & 0xff) << 8) | (code & 0xff);
		}
		if(this.text.length === index)
			this.text += fromCodePoint(code);
		else 
			this.text = this.text.substring(0, index) + fromCodePoint(code) + this.text.substring(index+1);
		return true;
	};

	TauFile.prototype.flush = function() {
		return true;
	};

	TauFile.prototype.close = function() {
		this.modified = Date.now() / 1000;
		return true;
	};

	TauFile.prototype.size = function() {
		return this.text.length;
	};

	// Virtual directory
	function TauDirectory(name, parent) {
		this.name = name;
		this.parent = parent;
		this.files = {};
		this.length = 0;
		this.created = Date.now() / 1000;
		this.modified = this.created;
	}

	TauDirectory.prototype.lookup = function(file) {
		if(this.files.hasOwnProperty(file))
			return this.files[file];
		return null;
	};

	TauDirectory.prototype.push = function(name, file) {
		if(!this.files.hasOwnProperty(name))
			this.length++;
		this.files[name] = file;
		this.modified = Date.now() / 1000;
	};

	TauDirectory.prototype.remove = function(name) {
		if(this.files.hasOwnProperty(name)) {
			this.length--;
			delete this.files[name];
			this.modified = Date.now() / 1000;
		}
	};

	TauDirectory.prototype.empty = function() {
		return this.length === 0;
	};

	TauDirectory.prototype.size = function() {
		return 4096;
	};

	// Virtual file system for browser
	tau_file_system = {
		// Current files
		files: new TauDirectory("/", "/", null),
		// Open file
		open: function(path, type, mode) {
			var dirs = path.replace(/\/$/, "").split("/");
			var dir = tau_file_system.files;
			var name = dirs[dirs.length-1];
			for(var i = 1; i < dirs.length-1; i++) {
				dir = dir.lookup(dirs[i]);
				if(!pl.type.is_directory(dir))
					return null;
			}
			var file = dir.lookup(name);
			if(file === null) {
				if(mode === "read")
					return null;
				file = new TauFile(name, type, dir);
				dir.push(name, file);
			} else if(!pl.type.is_file(file)) {
				return null;
			}
			if(mode === "write")
				file.text = "";
			return file;
		},
		// Get item
		get: function(path) {
			var dirs = path.replace(/\/$/, "").split("/");
			var file = tau_file_system.files;
			for(var i = 1; i < dirs.length; i++)
				if(pl.type.is_directory(file))
					file = file.lookup(dirs[i]);
				else
					return null;
			return file;
		}
	};

	// User input for browser
	tau_user_input = {
		buffer: "",
		get: function( length, _ ) {
			var text;
			while( tau_user_input.buffer.length < length ) {
				text = window.prompt();
				if( text ) {
					tau_user_input.buffer += text;
				}
			}
			text = tau_user_input.buffer.substr( 0, length );
			tau_user_input.buffer = tau_user_input.buffer.substr( length );
			return text;
		}
	};

	// User output for browser
	tau_user_output = {
		put: function( text, _ ) {
			console.log( text );
			return true;
		},
		flush: function() {
			return true;
		} 
	};

	// Virtual file system for Node.js
	nodejs_file_system = {
		// Open file
		open: function( path, type, mode ) {
			var fs = require('fs');
			var fd = fs.openSync( path, mode[0] );
			if( mode === "read" && !fs.existsSync( path ) )
				return null;
			return {
				get: function( length, position ) {
					var buffer = new Buffer( length );
					fs.readSync( fd, buffer, 0, length, position );
					return buffer.toString();
				},
				put: function( text, position ) {
					var buffer = Buffer.from( text );
					if( position === "end_of_file" )
						fs.writeSync( fd, buffer );
					else if( position === "past_end_of_file" )
						return null;
					else
						fs.writeSync( fd, buffer, 0, buffer.length, position );
					return true;
				},
				get_byte: function( position ) {
					return null;
				},
				put_byte: function( byte, position ) {
					return null;
				},
				flush: function() {
					return true;
				},
				close: function() {
					fs.closeSync( fd );
					return true;
				}
			};
		}
	};

	// User input for Node.js
	nodejs_user_input = {
		buffer: "",
		get: function( length, _ ) {
			var text;
			var readlineSync = require('readline-sync');
			while( nodejs_user_input.buffer.length < length )
				nodejs_user_input.buffer += readlineSync.question();
			text = nodejs_user_input.buffer.substr( 0, length );
			nodejs_user_input.buffer = nodejs_user_input.buffer.substr( length );
			return text;
		}
	};

	// User output for Node.js
	nodejs_user_output = {
		put: function( text, _ ) {
			process.stdout.write( text );
			return true;
		},
		flush: function() {
			return true;
		}
	};
	
	
	
	// PARSER
	
	var indexOf;
	if(!Array.prototype.indexOf) {
		indexOf = function(array, elem) {
			var len = array.length;
			for(var i = 0; i < len; i++) {
				if(elem === array[i]) return i;
			}
			return -1;
		};
	} else {
		indexOf = function(array, elem) {
			return array.indexOf(elem);
		};
	}

	var reduce = function(array, fn) {
		if(array.length === 0) return undefined;
		var elem = array[0];
		var len = array.length;
		for(var i = 1; i < len; i++) {
			elem = fn(elem, array[i]);
		}
		return elem;
	};

	var map;
	if(!Array.prototype.map) {
		map = function(array, fn) {
			var a = [];
			var len = array.length;
			for(var i = 0; i < len; i++) {
				a.push( fn(array[i]) );
			}
			return a;
		};
	} else {
		map = function(array, fn) {
			return array.map(fn);
		};
	}
	
	var filter;
	if(!Array.prototype.filter) {
		filter = function(array, fn) {
			var a = [];
			var len = array.length;
			for(var i = 0; i < len; i++) {
				if(fn(array[i]))
					a.push( array[i] );
			}
			return a;
		};
	} else {
		filter = function(array, fn) {
			return array.filter(fn);
		};
	}
	
	var codePointAt;
	if(!String.prototype.codePointAt) {
		codePointAt = function(str, i) {
			return str.charCodeAt(i);
		};
	} else {
		codePointAt = function(str, i) {
			return str.codePointAt(i);
		};
	}
	
	var fromCodePoint;
	if(!String.fromCodePoint) {
		fromCodePoint = function() {
			return String.fromCharCode.apply(null, arguments);
		};
	} else {
		fromCodePoint = function() {
			return String.fromCodePoint.apply(null, arguments);
		};
	}

	var stringLength;
	var regexAstralSymbols = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g;
	if(Array.from)
		stringLength = function(str) {
			return Array.from(str).length;
		};
	else
		stringLength = function(str) {
			return str.replace(regexAstralSymbols, '_').length;
		};



	var ERROR = 0;
	var SUCCESS = 1;

	var regex_escape = /(\\a)|(\\b)|(\\f)|(\\n)|(\\r)|(\\t)|(\\v)|\\x([0-9a-fA-F]+)\\|\\([0-7]+)\\|(\\\\)|(\\')|('')|(\\")|(\\`)|(\\.)|(.)/g;
	var escape_map = {"\\a": 7, "\\b": 8, "\\f": 12, "\\n": 10, "\\r": 13, "\\t": 9, "\\v": 11};
	function escape(str) {
		var s = [];
		var _error = false;
		str.replace(regex_escape, function(match, a, b, f, n, r, t, v, hex, octal, back, single, dsingle, double, backquote, error, char) {
			switch(true) {
				case hex !== undefined:
					s.push( parseInt(hex, 16) );
					return "";
				case octal !== undefined:
					s.push( parseInt(octal, 8) );
					return "";
				case back !== undefined:
				case single !== undefined:
				case dsingle !== undefined:
				case double !== undefined:
				case backquote !== undefined:
					s.push( codePointAt(match.substr(1),0) );
					return "";
				case char !== undefined:
					s.push( codePointAt(char,0) );
					return "";
				case error !== undefined:
					_error = true;
				default:
					s.push(escape_map[match]);
					return "";
			}
		});
		if(_error)
			return null;
		return s;
	}

	// Escape atoms
	function escapeAtom(str, quote) {
		var atom = '';
		if( str === "\\" ) return null;
		if( str.length < 2 ) return str;
		try {
			str = str.replace(/\\([0-7]+)\\/g, function(match, g1) {
				return fromCodePoint(parseInt(g1, 8));
			});
			str = str.replace(/\\x([0-9a-fA-F]+)\\/g, function(match, g1) {
				return fromCodePoint(parseInt(g1, 16));
			});
		} catch(error) {
			return null;
		}
		for( var i = 0; i < str.length; i++) {
			var a = str.charAt(i);
			var b = str.charAt(i+1);
			if( a === quote && b === quote ) {
				i++;
				atom += quote;
			} else if( a === '\\' ) {
				if( ['a','b','f','n','r','t','v',"'",'"','\\','\a','\b','\f','\n','\r','\t','\v'].indexOf(b) !== -1 ) {
					i += 1;
					switch( b ) {
						case 'a': atom += '\a'; break;
						case 'b': atom += '\b'; break;
						case 'f': atom += '\f'; break;
						case 'n': atom += '\n'; break;
						case 'r': atom += '\r'; break;
						case 't': atom += '\t'; break;
						case 'v': atom += '\v'; break;
						case "'": atom += "'"; break;
						case '"': atom += '"'; break;
						case '\\': atom += '\\'; break;
					}
				} else {
					return null;
				}
			} else {
				atom += a;
			}
		}
		return atom;
	}
	
	// Redo escape
	function redoEscape(str) {
		var atom = '';
		for( var i = 0; i < str.length; i++) {
			switch( str.charAt(i) ) {
				case "'": atom += "\\'"; break;
				case '\\': atom += '\\\\'; break;
				//case '\a': atom += '\\a'; break;
				case '\b': atom += '\\b'; break;
				case '\f': atom += '\\f'; break;
				case '\n': atom += '\\n'; break;
				case '\r': atom += '\\r'; break;
				case '\t': atom += '\\t'; break;
				case '\v': atom += '\\v'; break;
				default: atom += str.charAt(i); break;
			}
		}
		return atom;
	}

	// String to num
	function convertNum(num) {
		var n = num.substr(2);
		switch(num.substr(0,2).toLowerCase()) {
			case "0x":
				return parseInt(n, 16);
			case "0b":
				return parseInt(n, 2);
			case "0o":
				return parseInt(n, 8);
			case "0'":
				return escape(n)[0];
			default:
				return parseFloat(num);
		}
	}

	// Regular expressions for tokens
	var rules = {
		whitespace: /^\s*(?:(?:%.*)|(?:\/\*(?:\n|\r|.)*?\*\/)|(?:\s+))\s*/,
		variable: /^(?:[A-Z_][a-zA-Z0-9_]*)/,
		atom: /^(\!|,|;|[a-z][0-9a-zA-Z_]*|[#\$\&\*\+\-\.\/\:\<\=\>\?\@\^\~\\]+|'(?:(?:'')|(?:\\')|[^'])*')/,
		number: /^(?:0o[0-7]+|0x[0-9a-fA-F]+|0b[01]+|0'(?:''|\\[abfnrtv\\'"`]|\\x?\d+\\|[^\\])|\d+(?:\.\d+(?:[eE][+-]?\d+)?)?)/,
		string: /^(?:"([^"]|""|\\")*"|`([^`]|``|\\`)*`)/,
		l_brace: /^(?:\[)/,
		r_brace: /^(?:\])/,
		l_bracket: /^(?:\{)/,
		r_bracket: /^(?:\})/,
		bar: /^(?:\|)/,
		l_paren: /^(?:\()/,
		r_paren: /^(?:\))/
	};

	// Replace chars of char_conversion session
	function replace( thread, text ) {
		if( thread.get_flag( "char_conversion" ).id === "on" ) {
			return text.replace(/./g, function(char) {
				return thread.get_char_conversion( char );
			});
		}
		return text;
	}

	// Tokenize strings
	function Tokenizer(thread) {
		this.thread = thread;
		this.text = ""; // Current text to be analized
		this.tokens = []; // Consumed tokens
	}

	Tokenizer.prototype.set_last_tokens = function(tokens) {
		return this.tokens = tokens;
	};

	Tokenizer.prototype.new_text = function(text) {
		this.text = text;
		this.tokens = [];
	};

	Tokenizer.prototype.get_tokens = function(init) {
		var text;
		var len = 0; // Total length respect to text
		var line = 0;
		var start = 0;
		var tokens = [];
		var last_in_blank = false;

		if(init) {
			var token = this.tokens[init-1];
			len = token.len;
			text = replace( this.thread, this.text.substr(token.len) );
			line = token.line;
			start = token.start;
		}
		else
			text = this.text;


		// If there is nothing to be analized, return null
		if(/^\s*$/.test(text))
			return null;

		while(text !== "") {
			var matches = [];
			var last_is_blank = false;

			if(/^\n/.exec(text) !== null) {
				line++;
				start = 0;
				len++;
				text = text.replace(/\n/, "");
				last_in_blank = true;
				continue;
			}

			for(var rule in rules) {
				if(rules.hasOwnProperty(rule)) {
					var matchs = rules[rule].exec( text );
					if(matchs) {
						matches.push({
							value: matchs[0],
							name: rule,
							matches: matchs
						});
					}
				}
			}

			// Lexical error
			if(!matches.length)
				return this.set_last_tokens( [{ value: text, matches: [], name: "lexical", line: line, start: start }] );

			var token = reduce( matches, function(a, b) {
				return a.value.length >= b.value.length ? a : b;
			} );

			token.start = start;
			token.line = line;

			text = text.replace(token.value, "");
			start += token.value.length;
			len += token.value.length;

			switch(token.name) {
				case "atom":
					token.raw = token.value;
					if(token.value.charAt(0) === "'") {
						token.value = escapeAtom( token.value.substr(1, token.value.length - 2), "'" );
						if( token.value === null ) {
							token.name = "lexical";
							token.value = "unknown escape sequence";
						}
					}
					break;
				case "number":
					token.float = token.value.substring(0,2) !== "0x" && token.value.match(/[.eE]/) !== null && token.value !== "0'.";
					token.value = convertNum( token.value );
					token.blank = last_is_blank;
					break;
				case "string":
					var del = token.value.charAt(0);
					token.value = escapeAtom( token.value.substr(1, token.value.length - 2), del );
					if( token.value === null ) {
						token.name = "lexical";
						token.value = "unknown escape sequence";
					}
					break;
				case "whitespace":
					var last = tokens[tokens.length-1];
					if(last) last.space = true;
					last_is_blank = true;
					continue;
				case "r_bracket":
					if( tokens.length > 0 && tokens[tokens.length-1].name === "l_bracket" ) {
						token = tokens.pop();
						token.name = "atom";
						token.value = "{}";
						token.raw = "{}";
						token.space = false;
					}
					break;
				case "r_brace":
					if( tokens.length > 0 && tokens[tokens.length-1].name === "l_brace" ) {
						token = tokens.pop();
						token.name = "atom";
						token.value = "[]";
						token.raw = "[]";
						token.space = false;
					}
					break;
			}
			token.len = len;
			tokens.push( token );
			last_is_blank = false;
		}

		var t = this.set_last_tokens( tokens );
		return t.length === 0 ? null : t;
	};

	// Parse an expression
	function parseExpr(thread, tokens, start, priority, toplevel) {
		if(!tokens[start]) return {type: ERROR, value: pl.error.syntax(tokens[start-1], "expression expected", true)};
		var error;

		if(priority === "0") {
			var token = tokens[start];
			switch(token.name) {
				case "number":
					return {type: SUCCESS, len: start+1, value: new pl.type.Num(token.value, token.float)};
				case "variable":
					return {type: SUCCESS, len: start+1, value: new pl.type.Var(token.value)};
				case "string":
					var str;
					switch( thread.get_flag( "double_quotes" ).id ) {
						case "atom":;
							str = new Term( token.value, [] );
							break;
						case "codes":
							str = new Term( "[]", [] );
							for(var i = token.value.length-1; i >= 0; i-- )
								str = new Term( ".", [new pl.type.Num( codePointAt(token.value,i), false ), str] );
							break;
						case "chars":
							str = new Term( "[]", [] );
							for(var i = token.value.length-1; i >= 0; i-- )
								str = new Term( ".", [new pl.type.Term( token.value.charAt(i), [] ), str] );
							break;
					}
					return {type: SUCCESS, len: start+1, value: str};
				case "l_paren":
					var expr = parseExpr(thread, tokens, start+1, thread.__get_max_priority(), true);
					if(expr.type !== SUCCESS) return expr;
					if(tokens[expr.len] && tokens[expr.len].name === "r_paren") {
						expr.len++;
						return expr;
					}
					return {type: ERROR, derived: true, value: pl.error.syntax(tokens[expr.len] ? tokens[expr.len] : tokens[expr.len-1], ") or operator expected", !tokens[expr.len])}
				case "l_bracket":
					var expr = parseExpr(thread, tokens, start+1, thread.__get_max_priority(), true);
					if(expr.type !== SUCCESS) return expr;
					if(tokens[expr.len] && tokens[expr.len].name === "r_bracket") {
						expr.len++;
						expr.value = new Term( "{}", [expr.value] );
						return expr;
					}
					return {type: ERROR, derived: true, value: pl.error.syntax(tokens[expr.len] ? tokens[expr.len] : tokens[expr.len-1], "} or operator expected", !tokens[expr.len])}
			}
			// Compound term
			var result = parseTerm(thread, tokens, start, toplevel);
			if(result.type === SUCCESS || result.derived)
				return result;
			// List
			result = parseList(thread, tokens, start);
			if(result.type === SUCCESS || result.derived)
				return result;
			// Unexpected
			return {type: ERROR, derived: false, value: pl.error.syntax(tokens[start], "unexpected token")};
		}

		var max_priority = thread.__get_max_priority();
		var next_priority = thread.__get_next_priority(priority);
		var aux_start = start;
		
		// Prefix operators
		if(tokens[start].name === "atom" && tokens[start+1] && (tokens[start].space || tokens[start+1].name !== "l_paren")) {
			var token = tokens[start++];
			var classes = thread.__lookup_operator_classes(priority, token.value);
			
			// Associative prefix operator
			if(classes && classes.indexOf("fy") > -1) {
				var expr = parseExpr(thread, tokens, start, priority, toplevel);
				if(expr.type !== ERROR) {
					if( token.value === "-" && !token.space && pl.type.is_number( expr.value ) ) {
						return {
							value: new pl.type.Num(-expr.value.value, expr.value.is_float),
							len: expr.len,
							type: SUCCESS
						};
					} else {
						return {
							value: new pl.type.Term(token.value, [expr.value]),
							len: expr.len,
							type: SUCCESS
						};
					}
				} else {
					error = expr;
				}
			// Non-associative prefix operator
			} else if(classes && classes.indexOf("fx") > -1) {
				var expr = parseExpr(thread, tokens, start, next_priority, toplevel);
				if(expr.type !== ERROR) {
					return {
						value: new pl.type.Term(token.value, [expr.value]),
						len: expr.len,
						type: SUCCESS
					};
				} else {
					error = expr;
				}
			}
		}

		start = aux_start;
		var expr = parseExpr(thread, tokens, start, next_priority, toplevel);
		if(expr.type === SUCCESS) {
			start = expr.len;
			var token = tokens[start];
			if(tokens[start] && (
				tokens[start].name === "atom" && thread.__lookup_operator_classes(priority, token.value) ||
				tokens[start].name === "bar" && thread.__lookup_operator_classes(priority, "|")
			) ) {
				var next_priority_lt = next_priority;
				var next_priority_eq = priority;
				var classes = thread.__lookup_operator_classes(priority, token.value);

				if(classes.indexOf("xf") > -1) {
					return {
						value: new pl.type.Term(token.value, [expr.value]),
						len: ++expr.len,
						type: SUCCESS
					};
				} else if(classes.indexOf("xfx") > -1) {
					var expr2 = parseExpr(thread, tokens, start + 1, next_priority_lt, toplevel);
					if(expr2.type === SUCCESS) {
						return {
							value: new pl.type.Term(token.value, [expr.value, expr2.value]),
							len: expr2.len,
							type: SUCCESS
						};
					} else {
						expr2.derived = true;
						return expr2;
					}
				} else if(classes.indexOf("xfy") > -1) {
					var expr2 = parseExpr(thread, tokens, start + 1, next_priority_eq, toplevel);
					if(expr2.type === SUCCESS) {
						return {
							value: new pl.type.Term(token.value, [expr.value, expr2.value]),
							len: expr2.len,
							type: SUCCESS
						};
					} else {
						expr2.derived = true;
						return expr2;
					}
				} else if(expr.type !== ERROR) {
					while(true) {
						start = expr.len;
						var token = tokens[start];
						if(token && token.name === "atom" && thread.__lookup_operator_classes(priority, token.value)) {
							var classes = thread.__lookup_operator_classes(priority, token.value);
							if( classes.indexOf("yf") > -1 ) {
								expr = {
									value: new pl.type.Term(token.value, [expr.value]),
									len: ++start,
									type: SUCCESS
								};
							} else if( classes.indexOf("yfx") > -1 ) {
								var expr2 = parseExpr(thread, tokens, ++start, next_priority_lt, toplevel);
								if(expr2.type === ERROR) {
									expr2.derived = true;
									return expr2;
								}
								start = expr2.len;
								expr = {
									value: new pl.type.Term(token.value, [expr.value, expr2.value]),
									len: start,
									type: SUCCESS
								};
							} else { break; }
						} else { break; }
					}
				}
			} else {
				error = {type: ERROR, value: pl.error.syntax(tokens[expr.len-1], "operator expected")};
			}
			return expr;
		}
		return expr;
	}

	// Parse a compound term
	function parseTerm(thread, tokens, start, toplevel) {
		if(!tokens[start] || (tokens[start].name === "atom" && tokens[start].raw === "." && !toplevel && (tokens[start].space || !tokens[start+1] || tokens[start+1].name !== "l_paren")))
			return {type: ERROR, derived: false, value: pl.error.syntax(tokens[start-1], "unfounded token")};
		var atom = tokens[start];
		var exprs = [];
		if(tokens[start].name === "atom" && tokens[start].raw !== ",") {
			start++;
			if(tokens[start-1].space) return {type: SUCCESS, len: start, value: new pl.type.Term(atom.value, exprs)};
			if(tokens[start] && tokens[start].name === "l_paren") {
				if(tokens[start+1] && tokens[start+1].name === "r_paren") 
					return {type: ERROR, derived: true, value: pl.error.syntax(tokens[start+1], "argument expected")};
				var expr = parseExpr(thread, tokens, ++start, "999", true);
				if(expr.type === ERROR) {
					if( expr.derived )
						return expr;
					else
						return {type: ERROR, derived: true, value: pl.error.syntax(tokens[start] ? tokens[start] : tokens[start-1], "argument expected", !tokens[start])};
				}
				exprs.push(expr.value);
				start = expr.len;
				while(tokens[start] && tokens[start].name === "atom" && tokens[start].value === ",") {
					expr = parseExpr(thread, tokens, start+1, "999", true);
					if(expr.type === ERROR) {
						if( expr.derived )
							return expr;
						else
							return {type: ERROR, derived: true, value: pl.error.syntax(tokens[start+1] ? tokens[start+1] : tokens[start], "argument expected", !tokens[start+1])};
					}
					exprs.push(expr.value);
					start = expr.len;
				}
				if(tokens[start] && tokens[start].name === "r_paren") start++;
				else return {type: ERROR, derived: true, value: pl.error.syntax(tokens[start] ? tokens[start] : tokens[start-1], ", or ) expected", !tokens[start])};
			}
			return {type: SUCCESS, len: start, value: new pl.type.Term(atom.value, exprs)};
		}
		return {type: ERROR, derived: false, value: pl.error.syntax(tokens[start], "term expected")};
	}

	// Parse a list
	function parseList(thread, tokens, start) {
		if(!tokens[start]) 
			return {type: ERROR, derived: false, value: pl.error.syntax(tokens[start-1], "[ expected")};
		if(tokens[start] && tokens[start].name === "l_brace") {
			var expr = parseExpr(thread, tokens, ++start, "999", true);
			var exprs = [expr.value];
			var cons = undefined;

			if(expr.type === ERROR) {
				if(tokens[start] && tokens[start].name === "r_brace") {
					return {type: SUCCESS, len: start+1, value: new pl.type.Term("[]", [])};
				}
				return {type: ERROR, derived: true, value: pl.error.syntax(tokens[start], "] expected")};
			}
			
			start = expr.len;

			while(tokens[start] && tokens[start].name === "atom" && tokens[start].value === ",") {
				expr = parseExpr(thread, tokens, start+1, "999", true);
				if(expr.type === ERROR) {
					if( expr.derived )
						return expr;
					else
						return {type: ERROR, derived: true, value: pl.error.syntax(tokens[start+1] ? tokens[start+1] : tokens[start], "argument expected", !tokens[start+1])};
				}
				exprs.push(expr.value);
				start = expr.len;
			}
			var bar = false
			if(tokens[start] && tokens[start].name === "bar") {
				bar = true;
				expr = parseExpr(thread, tokens, start+1, "999", true);
				if(expr.type === ERROR) {
					if( expr.derived )
						return expr;
					else
						return {type: ERROR, derived: true, value: pl.error.syntax(tokens[start+1] ? tokens[start+1] : tokens[start], "argument expected", !tokens[start+1])};
				}
				cons = expr.value;
				start = expr.len;
			}
			if(tokens[start] && tokens[start].name === "r_brace")
				return {type: SUCCESS, len: start+1, value: arrayToList(exprs, cons) };
			else
				return {type: ERROR, derived: true, value: pl.error.syntax(tokens[start] ? tokens[start] : tokens[start-1], bar ? "] expected" : ", or | or ] expected", !tokens[start])};
		}
		return {type: ERROR, derived: false, value: pl.error.syntax(tokens[start], "list expected")};
	}

	// Parse a rule
	function parseRule(thread, tokens, start) {
		var line = tokens[start].line;
		var expr = parseExpr(thread, tokens, start, thread.__get_max_priority(), false);
		var rule = null;
		var obj;
		if(expr.type !== ERROR) {
			start = expr.len;
			if(tokens[start] && tokens[start].name === "atom" && tokens[start].raw === ".") {
				start++;
				if( pl.type.is_term(expr.value) ) {
					if(expr.value.indicator === ":-/2") {
						rule = new pl.type.Rule(expr.value.args[0], body_conversion(expr.value.args[1]))
						obj = {
							value: rule,
							len: start,
							type: SUCCESS
						};
					} else if(expr.value.indicator === "-->/2") {
						rule = rule_to_dcg(new pl.type.Rule(expr.value.args[0], expr.value.args[1]), thread);
						if(!pl.type.is_rule(rule))
							return {
								value: rule,
								len: start,
								type: ERROR
							};
						rule.body = body_conversion( rule.body );
						obj = {
							value: rule,
							len: start,
							type: pl.type.is_rule( rule ) ? SUCCESS : ERROR
						};
					} else {
						rule = new pl.type.Rule(expr.value, null);
						obj = {
							value: rule,
							len: start,
							type: SUCCESS
						};
					}
					if( rule ) {
						var singleton = rule.singleton_variables();
						if( singleton.length > 0 )
							thread.throw_warning( pl.warning.singleton( singleton, rule.head.indicator, line ) );
					}
					return obj;
				} else {
					return { type: ERROR, value: pl.error.syntax(tokens[start], "callable expected") };
				}
			} else {
				return { type: ERROR, value: pl.error.syntax(tokens[start] ? tokens[start] : tokens[start-1], ". or operator expected") };
			}
		}
		return expr;
	}

	// Parse a program
	function parseProgram(thread, string, options) {
		options = options ? options : {};
		options.from = options.from ? options.from : "$tau-js";
		options.reconsult = options.reconsult !== undefined ? options.reconsult : true;
		var tokenizer = new Tokenizer( thread );
		var reconsulted = {};
		var indicator;
		tokenizer.new_text( string );
		var n = 0;
		var tokens = tokenizer.get_tokens( n );
		while( tokens !== null && tokens[n] ) {
			var expr = parseRule(thread, tokens, n);
			if( expr.type === ERROR ) {
				return new Term("throw", [expr.value]);
			} else {
				// Term expansion
				var term_expansion = thread.session.rules["term_expansion/2"];
				if(term_expansion && term_expansion.length > 0) {
					var n_thread = new Thread( thread.session );
					var term = expr.value.body ? new Term(":-", [expr.value.head, expr.value.body]) : expr.value.head;
					term = term.rename( thread.session );
					n_thread.query("term_expansion(" + term.toString() + ", X).");
					n_thread.answer(function(answer) {
						if(answer && !pl.type.is_error(answer) && pl.type.is_term(answer.links['X'])) {
							var term = answer.links['X'];
							var rule = term.indicator === ":-/2" ? new Rule(term.args[0], term.args[1]) : new Rule( term, null ) ;
							parseProgramExpansion(thread, options, reconsulted, {value: rule, len: expr.len, type: expr.type});
						} else {
							parseProgramExpansion(thread, options, reconsulted, expr);
						}
					});
				} else {
					parseProgramExpansion(thread, options, reconsulted, expr);
				}
				n = expr.len;
				if(expr.value.body === null && expr.value.head.indicator === ":-/1" && 
				   expr.value.head.args[0].indicator === "char_conversion/2") {
					tokens = tokenizer.get_tokens( n );
					n = 0;
				}
			}
		}
		return true;
	}

	function parseGoalExpansion(thread, head, term, set, origin) {
		var n_thread = new Thread( thread.session );
		n_thread.__goal_expansion = true;
		var varterm = thread.next_free_variable();
		var varhead = thread.next_free_variable();
		var goal = varhead + " = " + head + ", goal_expansion(" + term + ", " + varterm + ").";
		n_thread.query(goal);
		n_thread.answer(function(answer) {
			if(answer && !pl.type.is_error(answer) && answer.links[varterm]) {
				set(answer.links[varhead], body_conversion(answer.links[varterm]));
				parseGoalExpansion(thread, origin.head(), origin.term(), origin.set, origin);
			}
		});
	}

	function parseQueryExpansion(thread, term) {
		var n_thread = new Thread( thread.session );
		n_thread.__goal_expansion = true;
		var varterm = thread.next_free_variable();
		var goal = "goal_expansion(" + term + ", " + varterm + ").";
		n_thread.query(goal);
		var variables = n_thread.head_point().substitution.domain();
		n_thread.answer(function(answer) {
			if(answer && !pl.type.is_error(answer) && answer.links[varterm]) {
				for(var i = 0; i < variables.length; i++) {
					if(variables[i] !== varterm.id && answer.links[variables[i]]) {
						var subs = new Substitution();
						subs.links[answer.links[variables[i]]] = variables[i];
						answer.links[varterm] = answer.links[varterm].apply( subs );
					}
				}
				parseQueryExpansion(thread, body_conversion(answer.links[varterm]));
			} else {
				thread.add_goal(term);
			}
		});
	}

	function parseProgramExpansion(thread, options, reconsulted, expr) {
		var exprs = [];
		if(pl.type.is_instantiated_list(expr.value.head) && expr.value.body === null) {
			var pointer = expr.value.head;
			while(pointer.indicator === "./2") {
				var rule = pointer.args[0];
				if(rule.indicator === ":-/2")
					exprs.push(new Rule(rule.args[0], rule.args[1]));
				else
					exprs.push(new Rule(rule, null));
				pointer = pointer.args[1];
			}
		} else {
			exprs.push(expr.value);
		}
		for(var i = 0; i < exprs.length; i++) {
			expr.value = exprs[i];
			if(expr.value.body === null && expr.value.head.indicator === "?-/1") {
				var n_thread = new Thread( thread.session );
				n_thread.add_goal( expr.value.head.args[0] );
				n_thread.answer( function( answer ) {
					if( pl.type.is_error( answer ) ) {
						thread.throw_warning( answer.args[0] );
					} else if( answer === false || answer === null ) {
						thread.throw_warning( pl.warning.failed_goal( expr.value.head.args[0], expr.len ) );
					}
				} );
			} else if(expr.value.body === null && expr.value.head.indicator === ":-/1") {
				thread.run_directive(expr.value.head.args[0]);
			} else {
				indicator = expr.value.head.indicator;
				if( options.reconsult !== false && reconsulted[indicator] !== true && !thread.is_multifile_predicate( indicator ) ) {
					thread.session.rules[indicator] = filter( thread.session.rules[indicator] || [], function( rule ) { return rule.dynamic; } );
					reconsulted[indicator] = true;
				}
				var goal_expansion = thread.session.rules["goal_expansion/2"];
				if(expr.value.body !== null && goal_expansion && goal_expansion.length > 0) {
					thread.renamed_variables = {};
					var origin = {
						head: function() { return expr.value.head; },
						term: function() { return expr.value.body; },
						set: function(h, p){
							expr.value.head = h;
							expr.value.body = p;
						}
					};
					parseGoalExpansion(thread, expr.value.head, body_conversion(expr.value.body), origin.set, origin);
				}
				thread.add_rule(expr.value, options);
			}
		}
	}
	
	// Parse a query
	function parseQuery(thread, string) {
		var tokenizer = new Tokenizer( thread );
		tokenizer.new_text( string );
		var n = 0;
		do {
			var tokens = tokenizer.get_tokens( n );
			if( tokens === null ) break;
			var expr = parseExpr(thread, tokens, 0, thread.__get_max_priority(), false);
			if(expr.type !== ERROR) {
				var expr_position = expr.len;
				var tokens_pos = expr_position;
				if(tokens[expr_position] && tokens[expr_position].name === "atom" && tokens[expr_position].raw === ".") {
					expr.value = body_conversion(expr.value);
					// Goal expansion
					var goal_expansion = thread.session.rules["goal_expansion/2"];
					if(!thread.__goal_expansion && goal_expansion && goal_expansion.length > 0) {
						parseQueryExpansion(thread, expr.value);
					} else {
						thread.add_goal( expr.value );
					}
				} else {
					var token = tokens[expr_position];
					return new Term("throw", [pl.error.syntax(token ? token : tokens[expr_position-1], ". or operator expected", !token)] );
				}
				
				n = expr.len + 1;
			} else {
				return new Term("throw", [expr.value]);
			}
		} while( true );
		return true;
	}


	
	// UTILS

	// Rule to DCG
	function rule_to_dcg(rule, thread) {
		rule = rule.rename( thread );
		var begin = thread.next_free_variable();
		var dcg = body_to_dcg( rule.body, begin, thread );
		if( dcg.error )
			return dcg.value;
		rule.body = dcg.value;
		// push-back lists
		if(rule.head.indicator === ",/2") {
			var terminals = rule.head.args[1];
			rule.head = rule.head.args[0];
			var last = thread.next_free_variable();
			var pointer = terminals;
			if(!pl.type.is_list(pointer)) {
				return pl.error.type("list", pointer, "DCG/0");
			}
			if(pointer.indicator === "[]/0") {
				terminals = dcg.variable;
			} else {
				while(pointer.indicator === "./2" && pl.type.is_list(pointer) && pointer.args[1].indicator !== "[]/0") {
					pointer = pointer.args[1];
				}
				if(pl.type.is_variable(pointer))
					return pl.error.instantiation("DCG/0");
				else if(!pl.type.is_list(pointer))
					return pl.error.type("list", terminals, "DCG/0");
				pointer.args[1] = dcg.variable;
			}
			rule.body = new Term(",", [rule.body, new Term("=", [last, terminals])]);
			rule.head = new Term(rule.head.id, rule.head.args.concat([begin, last]));
		} else {
			// replace first assignment
			var first_assign = rule.body;
			if(pl.type.is_term(first_assign) && first_assign.indicator === ",/2")
				first_assign = first_assign.args[0];
			if(pl.type.is_term(first_assign) && first_assign.indicator === "=/2" &&
			   pl.type.is_variable(first_assign.args[0]) && first_assign.args[0] === begin) {
				begin = first_assign.args[1];
				rule.body = rule.body.replace(null);
			}
			// add last variable
			rule.head = new Term(rule.head.id, rule.head.args.concat([begin, dcg.variable]));
		}
		return rule;
	}

	// Body to DCG
	function body_to_dcg(expr, last, thread) {
		var free;
		if( pl.type.is_term( expr ) && expr.indicator === "!/0" ) {
			free = thread.next_free_variable();
			return {
				value: new Term(",", [expr, new Term("=", [last, free])]),
				variable: free,
				error: false
			};
		} else if( pl.type.is_term( expr ) && expr.indicator === "\\+/1" ) {
			var left = body_to_dcg(expr.args[0], last, thread);
			if( left.error ) return left;
			return {
				value: new Term(expr.id, [left.value]),
				variable: last,
				error: false
			};
		} else if( pl.type.is_term( expr ) && (expr.indicator === ",/2" || expr.indicator === "->/2") ) {
			var left = body_to_dcg(expr.args[0], last, thread);
			if( left.error ) return left;
			var right = body_to_dcg(expr.args[1], left.variable, thread);
			if( right.error ) return right;
			return {
				value: new Term(expr.id, [left.value, right.value]),
				variable: right.variable,
				error: false
			};
		} else if( pl.type.is_term( expr ) && expr.indicator === ";/2" ) {
			var left = body_to_dcg(expr.args[0], last, thread);
			if( left.error ) return left;
			var right = body_to_dcg(expr.args[1], last, thread);
			if( right.error ) return right;
			return {
				value: new Term(",", [new Term(";", [left.value, right.value]), new Term("=", [left.variable, right.variable])]),
				variable: right.variable,
				error: false
			};
		} else if( pl.type.is_term( expr ) && expr.indicator === "{}/1" ) {
			free = thread.next_free_variable();
			return {
				value: new Term(",", [expr.args[0], new Term("=", [last, free])]),
				variable: free,
				error: false
			};
		} else if( pl.type.is_empty_list( expr ) ) {
			return {
				value: new Term("true", []),
				variable: last,
				error: false
			};
		} else if( pl.type.is_list( expr ) ) {
			free = thread.next_free_variable();
			var pointer = expr;
			var prev;
			while( pointer.indicator === "./2" ) {
				prev = pointer;
				pointer = pointer.args[1];
			}
			if( pl.type.is_variable( pointer ) ) {
				return {
					value: pl.error.instantiation("DCG/0"),
					variable: last,
					error: true
				};
			} else if( !pl.type.is_empty_list( pointer ) ) {
				return {
					value: pl.error.type("list", expr, "DCG/0"),
					variable: last,
					error: true
				};
			} else {
				prev.args[1] = free;
				return {
					value: new Term("=", [last, expr]),
					variable: free,
					error: false
				};
			}
		} else if( pl.type.is_callable( expr ) ) {
			free = thread.next_free_variable();
			expr = new Term( expr.id, expr.args.concat([last,free]) );
			return {
				value: expr,
				variable: free,
				error: false
			};
		} else {
			return {
				value: pl.error.type( "callable", expr, "DCG/0" ),
				variable: last,
				error: true
			};
		}
	}
	
	// Body conversion
	function body_conversion( expr ) {
		if( pl.type.is_variable( expr ) )
			return new Term( "call", [expr] );
		else if( pl.type.is_term( expr ) && [",/2", ";/2", "->/2"].indexOf(expr.indicator) !== -1 )
			return new Term( expr.id, [body_conversion( expr.args[0] ), body_conversion( expr.args[1] )] );
		return expr;
	}
	
	// List to Prolog list
	function arrayToList( array, cons ) {
		var list = cons ? cons : new Term( "[]", [] );
		for(var i = array.length-1; i >= 0; i-- )
			list = new Term( ".", [array[i], list] );
		return list;
	}
	
	// Remove element from array
	function remove( array, element ) {
		for( var i = array.length - 1; i >= 0; i-- ) {
			if( array[i] === element ) {
				array.splice(i, 1);
			}
		}
	}
	
	// Remove duplicate elements
	function nub( array ) {
		var seen = {};
		var unique = [];
		for( var i = 0; i < array.length; i++ ) {
			if( !(array[i] in seen) ) {
				unique.push( array[i] );
				seen[array[i]] = true;
			}
		}
		return unique;
	}
	
	// Retract a rule
	function retract( thread, point, indicator, rule ) {
		if( thread.session.rules[indicator] !== null ) {
			for( var i = 0; i < thread.session.rules[indicator].length; i++ ) {
				if( thread.session.rules[indicator][i] === rule ) {
					thread.session.rules[indicator].splice( i, 1 );
					thread.success( point );
					break;
				}
			}
		}
	}
	
	// call/n
	function callN( n ) {
		return function ( thread, point, atom ) {
			var closure = atom.args[0], args = atom.args.slice(1, n);
			if( pl.type.is_variable( closure ) ) {
				thread.throw_error( pl.error.instantiation( thread.level ) );
			} else if( !pl.type.is_callable( closure ) ) {
				thread.throw_error( pl.error.type( "callable", closure, thread.level ) );
			} else {
				var goal = new Term( closure.id, closure.args.concat( args ) );
				thread.prepend( [new State( point.goal.replace( goal ), point.substitution, point )] );
			}
		};
	}
	
	// String to indicator
	function str_indicator( str ) {
		for( var i = str.length - 1; i >= 0; i-- )
			if( str.charAt(i) === "/" )
				return new Term( "/", [new Term( str.substring(0, i) ), new Num( parseInt(str.substring(i+1)), false )] );
	}
	
	

	// PROLOG OBJECTS
	
	// Variables
	function Var( id ) {
		this.id = id;
	}
	
	// Numbers
	function Num( value, is_float ) {
		this.is_float = is_float !== undefined ? is_float : parseInt( value ) !== value;
		this.value = this.is_float ? value : parseInt( value );
	}
	
	// Terms
	var term_ref = 0;
	function Term( id, args, ref ) {
		term_ref++;
		this.ref = ref || term_ref;
		this.id = id;
		this.args = args || [];
		this.indicator = id + "/" + this.args.length;
	}

	// Streams
	var stream_ref = 0;
	function Stream( stream, mode, alias, type, reposition, eof_action ) {
		this.id = stream_ref++;
		this.stream = stream;
		this.mode = mode; // "read" or "write" or "append"
		this.alias = alias;
		this.type = type !== undefined ? type : "text"; // "text" or "binary"
		this.reposition = reposition !== undefined ? reposition : true; // true or false
		this.eof_action = eof_action !== undefined ? eof_action : "eof_code"; // "error" or "eof_code" or "reset"
		this.position = this.mode === "append" ? "end_of_stream" : 0;
		this.output = this.mode === "write" || this.mode === "append";
		this.input = this.mode === "read";
	}
	
	// Substitutions
	function Substitution( links, attrs ) {
		links = links || {};
		attrs = attrs || {};
		this.links = links;
		this.attrs = attrs;
	}
	
	// States
	function State( goal, subs, parent ) {
		subs = subs || new Substitution();
		parent = parent || null;
		this.goal = goal;
		this.substitution = subs;
		this.parent = parent;
	}
	
	// Rules
	function Rule( head, body, dynamic ) {
		this.head = head;
		this.body = body;
		this.dynamic = dynamic ? dynamic : false;
	}
 
	// Session
    // output: user text output function 
	function Session( limit, output ) {
        var self=this;
		limit = limit === undefined || limit <= 0 ? 1000 : limit;
		this.rules = {};
		this.src_predicates = {};
		this.rename = 0;
		this.modules = [];
		this.total_threads = 1;
		this.renamed_variables = {};
		this.public_predicates = {};
		this.multifile_predicates = {};
		this.limit = limit;
		this.streams = {
			"user_input": new Stream(
				nodejs_flag ? nodejs_user_input : tau_user_input,
				"read", "user_input", "text", false, "reset" ),
			"user_output": new Stream(
				nodejs_flag ? nodejs_user_output : output?{
                  put: function (text, arg) { output(text); return true; },
                  flush : function () { return true },
                 }: tau_user_output,
				"write", "user_output", "text", false, "eof_code" )
		};
		this.file_system = nodejs_flag ? nodejs_file_system : tau_file_system;
		this.standard_input = this.streams["user_input"];
		this.standard_output = this.streams["user_output"];
		this.current_input = this.streams["user_input"];
		this.current_output = this.streams["user_output"];
		this.working_directory = "/"; // only for browser
		this.format_success = function( state ) { return state.substitution; };
		this.format_error = function( state ) { return state.goal; };
		this.flag = {	
			bounded: pl.flag.bounded.value,
			max_integer: pl.flag.max_integer.value,
			min_integer: pl.flag.min_integer.value,
			integer_rounding_function: pl.flag.integer_rounding_function.value,
			char_conversion: pl.flag.char_conversion.value,
			debug: pl.flag.debug.value,
			max_arity: pl.flag.max_arity.value,
			unknown: pl.flag.unknown.value,
			double_quotes: pl.flag.double_quotes.value,
			occurs_check: pl.flag.occurs_check.value,
			dialect: pl.flag.dialect.value,
			version_data: pl.flag.version_data.value,
			nodejs: pl.flag.nodejs.value,
			argv: pl.flag.argv.value
		};
		this.__loaded_modules = [];
		this.__char_conversion = {};
		this.__operators = {
			1200: { ":-": ["fx", "xfx"],  "-->": ["xfx"], "?-": ["fx"] },
			1100: { ";": ["xfy"] },
			1050: { "->": ["xfy"] },
			1000: { ",": ["xfy"] },
			900: { "\\+": ["fy"] },
			700: {
				"=": ["xfx"], "\\=": ["xfx"], "==": ["xfx"], "\\==": ["xfx"],
				"@<": ["xfx"], "@=<": ["xfx"], "@>": ["xfx"], "@>=": ["xfx"],
				"=..": ["xfx"], "is": ["xfx"], "=:=": ["xfx"], "=\\=": ["xfx"],
				"<": ["xfx"], "=<": ["xfx"], ">": ["xfx"], ">=": ["xfx"]
			},
			600: { ":": ["xfy"] },
			500: { "+": ["yfx"], "-": ["yfx"], "/\\": ["yfx"], "\\/": ["yfx"] },
			400: {
				"*": ["yfx"], "/": ["yfx"], "//": ["yfx"], "rem": ["yfx"],
				"mod": ["yfx"], "<<": ["yfx"], ">>": ["yfx"], "div": ["yfx"]
			},
			200: { "**": ["xfx"], "^": ["xfy"], "-": ["fy"], "+": ["fy"], "\\": ["fy"] }
		};
		this.thread = new Thread( this );
	}
	
	// Threads
	function Thread( session ) {
		this.epoch = Date.now();
		this.session = session;
		this.session.total_threads++;
		this.format_success = session.format_success;
		this.format_error = session.format_error;
		this.total_steps = 0;
		this.cpu_time = 0;
		this.cpu_time_last = 0;
		this.points = [];
		this.debugger = false;
		this.debugger_states = [];
		this.level = "top_level/0";
		this.current_limit = this.session.limit;
		this.warnings = [];
		this.__calls = [];
		this.__goal_expansion = false;
	}
	
	// Modules
	function Module( id, rules, exports ) {
		this.id = id;
		this.rules = rules;
		this.exports = exports;
		pl.module[id] = this;
	}
	
	Module.prototype.exports_predicate = function( indicator ) {
		return this.exports.indexOf( indicator ) !== -1;
	};



	// UNIFY PROLOG OBJECTS
	
	// Variables
	Var.prototype.unify = function( obj, occurs_check ) {
		if( occurs_check && indexOf( obj.variables(), this.id ) !== -1 && !pl.type.is_variable( obj ) ) {
			return null;
		}
		var links = {};
		links[this.id] = obj;
		return new Substitution( links );
	};
	
	// Numbers
	Num.prototype.unify = function( obj, _ ) {
		if( pl.type.is_number( obj ) && this.value === obj.value && this.is_float === obj.is_float ) {
			return new Substitution();
		}
		return null;
	};
	
	// Terms
	Term.prototype.unify = function( obj, occurs_check ) {
		if( pl.type.is_term( obj ) && this.indicator === obj.indicator ) {
			var subs = new Substitution();
			for( var i = 0; i < this.args.length; i++ ) {
				var mgu = pl.unify( this.args[i].apply( subs ), obj.args[i].apply( subs ), occurs_check );
				if( mgu === null )
					return null;
				for( var x in mgu.links )
					subs.links[x] = mgu.links[x];
				subs = subs.apply( mgu );
			}
			return subs;
		}
		return null;
	};

	// Streams
	Stream.prototype.unify = function( obj, occurs_check ) {
		if( pl.type.is_stream( obj ) && this.id === obj.id ) {
			return new Substitution();
		}
		return null;
	};
	
	

	// PROLOG OBJECTS TO STRING
	
	// Variables
	Var.prototype.toString = function( _ ) {
		return this.id;
	};
	
	// Numbers
	Num.prototype.toString = function( _ ) {
		var str = this.value.toString();
		var e = str.indexOf("e");
		if(e !== -1) {
			if(str.indexOf(".") !== -1)
				return str
			else
				return str.replace("e", ".0e");
		}
		return this.is_float && indexOf(str, ".") === -1 ? this.value + ".0" : str;
	};
	
	// Terms
	Term.prototype.toString = function( options, priority, from ) {
		options = !options ? {} : options;
		options.quoted = options.quoted === undefined ? true: options.quoted;
		options.ignore_ops = options.ignore_ops === undefined ? false : options.ignore_ops;
		options.numbervars = options.numbervars === undefined ? false : options.numbervars;
		priority = priority === undefined ? {priority: 999, class: "", indicator: ""} : priority;
		from = from === undefined ? "" : from;
		if( options.numbervars && this.indicator === "$VAR/1" && pl.type.is_integer( this.args[0] ) && this.args[0].value >= 0 ) {
			var i = this.args[0].value;
			var number = Math.floor( i/26 );
			var letter =  i % 26;
			return "ABCDEFGHIJKLMNOPQRSTUVWXYZ"[letter] + (number !== 0 ? number : "");
		}
		switch( this.indicator ){
			case "[]/0":
			case "{}/0":
			case "!/0":
				return this.id;
			case "{}/1":
				return "{" + this.args[0].toString( options ) + "}";
			case "./2":
				if( options.ignore_ops === false ) {
					var list = "[" + this.args[0].toString( options );
					var pointer = this.args[1];
					while( pointer.indicator === "./2" ) {
						list += ", " + pointer.args[0].toString( options );
						pointer = pointer.args[1];
					}
					if( pointer.indicator !== "[]/0" ) {
						list += "|" + pointer.toString( options );
					}
					list += "]";
					return list;
				}
			default:
				var id = this.id;
				var operator = options.session ? options.session.lookup_operator( this.id, this.args.length ) : null;
				if( options.session === undefined || options.ignore_ops || operator === null ) {
					if( options.quoted && ! /^(!|;|[a-z][0-9a-zA-Z_]*|[#\$\&\*\+\-\.\/\:\<\=\>\?\@\^\~\\]+)$/.test( id ) && id !== "{}" && id !== "[]" )
						id = "'" + redoEscape(id) + "'";
					return id + (this.args.length ? "(" + map( this.args,
						function(x) { return x.toString( options); }
					).join(", ") + ")" : "");
				} else {
					var priority_op = parseInt(operator.priority);
					var priority_arg = parseInt(priority.priority);
					var cond = priority_op > priority_arg || priority_op === priority_arg && (
						operator.class === "xfx" ||
						operator.class === "xfy" && this.indicator !== priority.indicator ||
						operator.class === "yfx" && this.indicator !== priority.indicator ||
						this.indicator === priority.indicator && operator.class === "yfx" && from === "right" ||
						this.indicator === priority.indicator && operator.class === "xfy" && from === "left");
					operator.indicator = this.indicator;
					var lpar = cond ? "(" : "";
					var rpar = cond ? ")" : "";
					var space = /^[a-z][0-9a-zA-Z_]*$/.test( id ) ? " " : "";
					if( this.args.length === 0 ) {
						return "(" + this.id + ")";
					} else if( ["fy","fx"].indexOf( operator.class) !== -1 ) {
						return lpar + id + space + this.args[0].toString( options, operator ) + rpar;
					} else if( ["yf","xf"].indexOf( operator.class) !== -1 ) {
						return lpar + this.args[0].toString( options, operator ) + space + id + rpar;
					} else {
						return lpar + this.args[0].toString( options, operator, "left" ) + space + this.id + space + this.args[1].toString( options, operator, "right" ) +  rpar;
					}
				}
		}
	};

	// Streams
	Stream.prototype.toString = function( _ ) {
		return "<stream>(" + this.id + ")";
	};
	
	// Substitutions
	Substitution.prototype.toString = function( options ) {
		var str = "{";
		for( var link in this.links ) {
			if(!this.links.hasOwnProperty(link)) continue;
			if( str !== "{" ) {
				str += ", ";
			}
			str += link + "/" + this.links[link].toString( options );
		}
		str += "}";
		return str;
	};
	
	// States
	State.prototype.toString = function( options ) {
		if( this.goal === null ) {
			return "<" + this.substitution.toString( options ) + ">";
		} else {
			return "<" + this.goal.toString( options ) + ", " + this.substitution.toString( options ) + ">";
		}
	};
	
	// Rules
	Rule.prototype.toString = function( options ) {
		if( !this.body ) {
			return this.head.toString( options ) + ".";
		} else {
			return this.head.toString( options, 1200, "left" ) + " :- " + this.body.toString( options, 1200, "right" ) + ".";
		}
	};
	
	// Session
	Session.prototype.toString = function( options ) {
		var str = "";
		for(var i = 0; i < this.modules.length; i++) {
			str += ":- use_module(library(" + this.modules[i] + ")).\n";
		}
		str += "\n";
		for(var key in this.rules) {
			if(!this.rules.hasOwnProperty(key)) continue;
			for(i = 0; i < this.rules[key].length; i++) {
				str += this.rules[key][i].toString( options );
				str += "\n";
			}
		}
		return str;
	};
	
	
	
	// CLONE PROLOG OBJECTS
	
	// Variables
	Var.prototype.clone = function() {
		return new Var( this.id );
	};
	
	// Numbers
	Num.prototype.clone = function() {
		return new Num( this.value, this.is_float );
	};
	
	// Terms
	Term.prototype.clone = function() {
		return new Term( this.id, map( this.args, function( arg ) {
			return arg.clone();
		} ) );
	};

	// Streams
	Stream.prototype.clone = function() {
		return new Stram( this.stream, this.mode, this.alias, this.type, this.reposition, this.eof_action );
	};
	
	// Substitutions
	Substitution.prototype.clone = function() {
		var links = {};
		var attrs = {};
		for( var link in this.links ) {
			if(!this.links.hasOwnProperty(link)) continue;
			links[link] = this.links[link].clone();
		}
		for( var attr in this.attrs ) {
			if(!this.attrs.hasOwnProperty(attrs)) continue;
			attrs[attr] = {};
			for( var m in this.attrs[attr] ) {
				if(!this.attrs[attr].hasOwnProperty(m)) continue;
				attrs[attr][m] = this.attrs[attr][m].clone();
			}
		}
		return new Substitution( links, attrs );
	};
	
	// States
	State.prototype.clone = function() {
		return new State( this.goal.clone(), this.substitution.clone(), this.parent );
	};
	
	// Rules
	Rule.prototype.clone = function() {
		return new Rule( this.head.clone(), this.body !== null ? this.body.clone() : null );
	};
	
	
	
	// COMPARE PROLOG OBJECTS
	
	// Variables
	Var.prototype.equals = function( obj ) {
		return pl.type.is_variable( obj ) && this.id === obj.id;
	};
	
	// Numbers
	Num.prototype.equals = function( obj ) {
		return pl.type.is_number( obj ) && this.value === obj.value && this.is_float === obj.is_float;
	};
	
	// Terms
	Term.prototype.equals = function( obj ) {
		if( !pl.type.is_term( obj ) || this.indicator !== obj.indicator ) {
			return false;
		}
		for( var i = 0; i < this.args.length; i++ ) {
			if( !this.args[i].equals( obj.args[i] ) ) {
				return false;
			}
		}
		return true;
	};

	// Streams
	Stream.prototype.equals = function( obj ) {
		return pl.type.is_stream( obj ) && this.id === obj.id;
	};
	
	// Substitutions
	Substitution.prototype.equals = function( obj ) {
	var link;
		if( !pl.type.is_substitution( obj ) ) {
			return false;
		}
		for( link in this.links ) {
			if(!this.links.hasOwnProperty(link)) continue;
			if( !obj.links[link] || !this.links[link].equals( obj.links[link] ) ) {
				return false;
			}
		}
		for( link in obj.links ) {
			if(!obj.links.hasOwnProperty(link)) continue;
			if( !this.links[link] ) {
				return false;
			}
		}
		return true;
	};
	
	// States
	State.prototype.equals = function( obj ) {
		return pl.type.is_state( obj ) && this.goal.equals( obj.goal ) && this.substitution.equals( obj.substitution ) && this.parent === obj.parent;
	};
	
	// Rules
	Rule.prototype.equals = function( obj ) {
		return pl.type.is_rule( obj ) && this.head.equals( obj.head ) && (this.body === null && obj.body === null || this.body !== null && this.body.equals( obj.body ));
	};
	
	
	
	// RENAME VARIABLES OF PROLOG OBJECTS
	
	// Variables
	Var.prototype.rename = function( thread ) {
		return thread.get_free_variable( this );
	};
	
	// Numbers
	Num.prototype.rename = function( _ ) {
		return this;
	};
	
	// Terms
	Term.prototype.rename = function( thread ) {
		// atom
		/*if(this.args.length === 0)
			return this;*/
		// list
		if( this.indicator === "./2" ) {
			var arr = [], pointer = this;
			var last_neq = -1, pointer_neq = null, i = 0;
			while( pointer.indicator === "./2" ) {
				var app = pointer.args[0].rename(thread);
				var cmp = app == pointer.args[0];
				arr.push(app);
				pointer = pointer.args[1];
				if(!cmp) {
					last_neq = i;
					pointer_neq = pointer;
				}
				i++;
			}
			var list = pointer.rename(thread);
			var cmp = list == pointer;
			if(last_neq === -1 && cmp)
				return this;
			var start = cmp ? last_neq : arr.length-1;
			var list = cmp ? pointer_neq : list;
			for(var i = start; i >= 0; i--) {
				list = new Term( ".", [arr[i], list] );
			}
			return list;
		}
		// compound term
		var eq = true;
		var args = [];
		for(var i = 0; i < this.args.length; i++) {
			var app = this.args[i].rename(thread);
			eq = eq && this.args[i] == app;
			args.push(app);
		}
		/*if(eq)
			return this;*/
		return new Term(this.id, args);
	};

	// Streams
	Stream.prototype.rename = function( thread ) {
		return this;
	};
	
	// Rules
	Rule.prototype.rename = function( thread ) {
		return new Rule( this.head.rename( thread ), this.body !== null ? this.body.rename( thread ) : null );
	};
	
	
	
	// GET ID OF VARIABLES FROM PROLOG OBJECTS
	
	// Variables
	Var.prototype.variables = function() {
		return [this.id];
	};
	
	// Numbers
	Num.prototype.variables = function() {
		return [];
	};
	
	// Terms
	Term.prototype.variables = function() {
		return [].concat.apply( [], map( this.args, function( arg ) {
			return arg.variables();
		} ) );
	};

	// Streams
	Stream.prototype.variables = function() {
		return [];
	};
	
	// Rules
	Rule.prototype.variables = function() {
		if( this.body === null ) {
			return this.head.variables();
		} else {
			return this.head.variables().concat( this.body.variables() );
		}
	};
	
	
	
	// APPLY SUBSTITUTIONS TO PROLOG OBJECTS
	
	// Variables
	Var.prototype.apply = function( subs ) {
		if( subs.lookup( this.id ) ) {
			return subs.lookup( this.id );
		}
		return this;
	};
	
	// Numbers
	Num.prototype.apply = function( _ ) {
		return this;
	};
	
	// Terms
	Term.prototype.apply = function( subs ) {
		// atom
		if(this.args.length === 0)
			return this;
		// list
		if( this.indicator === "./2" ) {
			var arr = [], pointer = this;
			var last_neq = -1, pointer_neq = null, i = 0;
			while( pointer.indicator === "./2" ) {
				var app = pointer.args[0].apply(subs);
				var cmp = app == pointer.args[0];
				arr.push(app);
				pointer = pointer.args[1];
				if(!cmp) {
					last_neq = i;
					pointer_neq = pointer;
				}
				i++;
			}
			var list = pointer.apply(subs);
			var cmp = list == pointer;
			if(last_neq === -1 && cmp)
				return this;
			var start = cmp ? last_neq : arr.length-1;
			var list = cmp ? pointer_neq : list;
			for(var i = start; i >= 0; i--) {
				list = new Term( ".", [arr[i], list] );
			}
			return list;
		}
		// compound term
		var eq = true;
		var args = [];
		for(var i = 0; i < this.args.length; i++) {
			var app = this.args[i].apply(subs);
			eq = eq && this.args[i] == app;
			args.push(app);
		}
		if(eq)
			return this;
		return new Term(this.id, args, this.ref);
	};

	// Streams
	Stream.prototype.apply = function( _ ) {
		return this;
	};
	
	// Rules
	Rule.prototype.apply = function( subs ) {
		return new Rule( this.head.apply( subs ), this.body !== null ? this.body.apply( subs ) : null );
	};
	
	// Substitutions
	Substitution.prototype.apply = function( subs ) {
		var link, links = {}, attr, attrs = {}, m;
		for( link in this.links ) {
			if(!this.links.hasOwnProperty(link)) continue;
			links[link] = this.links[link].apply(subs);
		}
		for( attr in this.attrs ) {
			if(!this.attrs.hasOwnProperty(attr)) continue;
			attrs[attr] = {};
			for( m in this.attrs[attr] ) {
				if(!this.attrs[attr].hasOwnProperty(m)) continue;
				attrs[attr][m] = this.attrs[attr][m].apply(subs);
			}
		}
		return new Substitution( links, attrs );
	};
	
	
	
	// SELECTION FUNCTION
	
	// Select term
	Term.prototype.select = function() {
		var pointer = this;
		while( pointer.indicator === ",/2" )
			pointer = pointer.args[0];
		return pointer;
	};
	
	// Replace term
	Term.prototype.replace = function( expr ) {
		if( this.indicator === ",/2" ) {
			if( this.args[0].indicator === ",/2" ) {
				return new Term( ",", [this.args[0].replace( expr ), this.args[1]] );
			} else {
				return expr === null ? this.args[1] : new Term( ",", [expr, this.args[1]] );
			}
		} else {
			return expr;
		}
	};

	// Search term
	Term.prototype.search = function( expr ) {
		if(this == expr || this.ref === expr.ref)
			return true;
		for( var i = 0; i < this.args.length; i++ )
			if( pl.type.is_term( this.args[i] ) && this.args[i].search( expr ) )
				return true;
		return false;
	};
	
	
	
	// PROLOG SESSIONS AND THREADS

	// Get current input
	Session.prototype.get_current_input = function() {
		return this.current_input;
	};
	Thread.prototype.get_current_input = function() {
		return this.session.get_current_input();
	};

	// Get current output
	Session.prototype.get_current_output = function() {
		return this.current_output;
	};
	Thread.prototype.get_current_output = function() {
		return this.session.get_current_output();
	};

	// Set current input
	Session.prototype.set_current_input = function( input ) {
		this.current_input = input;
	};
	Thread.prototype.set_current_input = function( input ) {
		return this.session.set_current_input( input );
	};

	// Set current output
	Session.prototype.set_current_output = function( output ) {
		this.current_output = output;
	};
	Thread.prototype.set_current_output = function( output ) {
		return this.session.set_current_output( output);
	};

	// Get stream by alias
	Session.prototype.get_stream_by_alias = function( alias ) {
		return this.streams[alias];
	};
	Thread.prototype.get_stream_by_alias = function( alias ) {
		return this.session.get_stream_by_alias( alias );
	};

	// Open file
	Session.prototype.file_system_open = function( path, type, mode ) {
		if(this.get_flag("nodejs").indicator === "false/0")
			path = cd(this.working_directory, path);
		return this.file_system.open( path, type, mode );
	};
	Thread.prototype.file_system_open = function( path, type, mode ) {
		return this.session.file_system_open( path, type, mode );
	};

	// Get conversion of the char
	Session.prototype.get_char_conversion = function( char ) {
		return this.__char_conversion[char] || char;
	};
	Thread.prototype.get_char_conversion = function( char ) {
		return this.session.get_char_conversion( char );
	};
	
	// Parse an expression
	Session.prototype.parse = function( string ) {
		return this.thread.parse( string );
	};
	Thread.prototype.parse = function( string ) {
		var tokenizer = new Tokenizer( this );
		tokenizer.new_text( string );
		var tokens = tokenizer.get_tokens();
		if( tokens === null )
			return false;
		var expr = parseExpr(this, tokens, 0, this.__get_max_priority(), false);
		if( expr.len !== tokens.length )
			return false;
		return { value: expr.value, expr: expr, tokens: tokens };
	};
	
	// Get flag value
	Session.prototype.get_flag = function( flag ) {
		return this.flag[flag];
	};
	Thread.prototype.get_flag = function( flag ) {
		return this.session.get_flag( flag );
	};

	// Add a rule
	Session.prototype.add_rule = function( rule, options ) {
		options = options ? options : {};
		options.from = options.from ? options.from : "$tau-js";
		this.src_predicates[rule.head.indicator] = options.from;
		if(!this.rules[rule.head.indicator]) {
			this.rules[rule.head.indicator] = [];
		}
		this.rules[rule.head.indicator].push(rule);
		if( !this.public_predicates.hasOwnProperty( rule.head.indicator ) )
			this.public_predicates[rule.head.indicator] = false;
		return true;
	};
	Thread.prototype.add_rule = function( rule, options ) {
		return this.session.add_rule( rule, options );
	};

	// Run a directive
	Session.prototype.run_directive = function( directive ) {
		this.thread.run_directive( directive );
	};
	Thread.prototype.run_directive = function( directive ) {
		if( pl.type.is_directive( directive ) ) {
			if(pl.directive[directive.indicator])
				pl.directive[directive.indicator]( this, directive );
			else
				pl.directive[directive.id + "/*"]( this, directive );
			return true;
		}
		return false;
	};
	
	// Get maximum priority of the operators
	Session.prototype.__get_max_priority = function() {
		return "1200";
	};
	Thread.prototype.__get_max_priority = function() {
		return this.session.__get_max_priority();
	};
	
	// Get next priority of the operators
	Session.prototype.__get_next_priority = function( priority ) {
		var max = 0;
		priority = parseInt( priority );
		for( var key in this.__operators ) {
			if( !this.__operators.hasOwnProperty(key) ) continue;
			var n = parseInt(key);
			if( n > max && n < priority ) max = n;
		}
		return max.toString();
	};
	Thread.prototype.__get_next_priority = function( priority ) {
		return this.session.__get_next_priority( priority );
	};
	
	// Get classes of an operator
	Session.prototype.__lookup_operator_classes = function( priority, operator ) {
		if( this.__operators.hasOwnProperty( priority ) && this.__operators[priority][operator] instanceof Array ) {
			return this.__operators[priority][operator]  || false;
		}
		return false;
	};
	Thread.prototype.__lookup_operator_classes = function( priority, operator ) {
		return this.session.__lookup_operator_classes( priority, operator );
	};

	// Get operator
	Session.prototype.lookup_operator = function( name, arity ) {
		for(var p in this.__operators)
			if(this.__operators[p][name])
				for(var i = 0; i < this.__operators[p][name].length; i++)
					if( arity === 0 || this.__operators[p][name][i].length === arity+1 )
						return {priority: p, class: this.__operators[p][name][i]};
		return null;
	};
	Thread.prototype.lookup_operator = function( name, arity ) {
		return this.session.lookup_operator( name, arity );
	};
	
	// Throw a warning
	Session.prototype.throw_warning = function( warning ) {
		this.thread.throw_warning( warning );
	};
	Thread.prototype.throw_warning = function( warning ) {
		this.warnings.push( warning );
	};
	
	// Get warnings
	Session.prototype.get_warnings = function() {
		return this.thread.get_warnings();
	};
	Thread.prototype.get_warnings = function() {
		return this.warnings;
	};

	// Add a goal
	Session.prototype.add_goal = function( goal, unique ) {
		this.thread.add_goal( goal, unique );
	};
	Thread.prototype.add_goal = function( goal, unique, parent ) {
		parent = parent ? parent : null;
		if( unique === true )
			this.points = [];
		var vars = goal.variables();
		var links = {};
		for( var i = 0; i < vars.length; i++ )
			links[vars[i]] = new Var(vars[i]);
		this.points.push( new State( goal, new Substitution(links), parent ) );
	};

	// Consult a program from a string
	Session.prototype.consult = function( program, options ) {
		return this.thread.consult( program, options );
	};
	Thread.prototype.consult = function( program, options ) {
		var string = "";
		// string
		if( typeof program === "string" ) {
			string = program;
			var len = string.length;
			// script id
			if( this.get_flag("nodejs").indicator === "false/0" && program != "" && document.getElementById( string ) ) {
				var script = document.getElementById( string );
				var type = script.getAttribute( "type" );
				if( type !== null && type.replace( / /g, "" ).toLowerCase() === "text/prolog" ) {
					string = script.text;
				}
			// file (node.js)
			} else if( this.get_flag("nodejs").indicator === "true/0" ) {
				var fs = require("fs");
				const isFile = fs.existsSync(program);
				if(isFile) string = fs.readFileSync( program ).toString();
				else string = program;
			// http request
			} else if( program != "" && !(/\s/.test(program)) ) {
				try {
					var xhttp = new XMLHttpRequest();
					xhttp.onreadystatechange = function() {
						if( this.readyState == 4 && this.status == 200 )
							string = xhttp.responseText;
					}
					xhttp.open("GET", program, false);
					xhttp.send();
				} catch(ex) {}
			}
		// html
		} else if( program.nodeName ) {
			switch( program.nodeName.toLowerCase() ) {
				case "input":
				case "textarea":
					string = program.value;
					break;
				default:
					string = program.innerHTML;
					break;
			}
		} else {
			return false;
		}
		this.warnings = [];
		return parseProgram( this, string, options );
	};

	// Query goal from a string (without ?-)
	Session.prototype.query = function( string ) {
		return this.thread.query( string );
	};
	Thread.prototype.query = function( string ) {
		this.points = [];
		this.debugger_states = [];
		return parseQuery( this, string );
	};
	
	// Get first choice point
	Session.prototype.head_point = function() {
		return this.thread.head_point();
	};
	Thread.prototype.head_point = function() {
		return this.points[this.points.length-1];
	};
	
	// Get free variable
	Session.prototype.get_free_variable = function( variable ) {
		return this.thread.get_free_variable( variable );
	};
	Thread.prototype.get_free_variable = function( variable ) {
		var variables = [];
		if( variable.id === "_" || this.session.renamed_variables[variable.id] === undefined ) {
			this.session.rename++;
			if( this.current_point )
				variables = this.current_point.substitution.domain();
			while( indexOf( variables, pl.format_variable( this.session.rename ) ) !== -1 ) {
				this.session.rename++;
			}
			if( variable.id === "_" ) {
				return new Var( pl.format_variable( this.session.rename ) );
			} else {
				this.session.renamed_variables[variable.id] = pl.format_variable( this.session.rename );
			}
		}
		return new Var( this.session.renamed_variables[variable.id] );
	};
	
	// Get next free variable
	Session.prototype.next_free_variable = function() {
		return this.thread.next_free_variable();
	};
	Thread.prototype.next_free_variable = function() {
		this.session.rename++;
		var variables = [];
		if( this.current_point )
			variables = this.current_point.substitution.domain();
		while( indexOf( variables, pl.format_variable( this.session.rename ) ) !== -1 ) {
			this.session.rename++;
		}
		return new Var( pl.format_variable( this.session.rename ) );
	};
	
	// Check if a predicate is public
	Session.prototype.is_public_predicate = function( indicator ) {
		return !this.public_predicates.hasOwnProperty( indicator ) || this.public_predicates[indicator] === true;
	};
	Thread.prototype.is_public_predicate = function( indicator ) {
		return this.session.is_public_predicate( indicator );
	};
	
	// Check if a predicate is multifile
	Session.prototype.is_multifile_predicate = function( indicator ) {
		return this.multifile_predicates.hasOwnProperty( indicator ) && this.multifile_predicates[indicator] === true;
	};
	Thread.prototype.is_multifile_predicate = function( indicator ) {
		return this.session.is_multifile_predicate( indicator );
	};
	
	// Insert states at the beginning
	Session.prototype.prepend = function( states ) {
		return this.thread.prepend( states );
	};
	Thread.prototype.prepend = function( states ) {
		for(var i = states.length-1; i >= 0; i--)
			this.points.push( states[i] );
	};
	
	// Remove the selected term and prepend the current state
	Session.prototype.success = function( point, parent ) {
		return this.thread.success( point, parent );
	}
	Thread.prototype.success = function( point, parent ) {
		var parent = typeof parent === "undefined" ? point : parent;
		this.prepend( [new State( point.goal.replace( null ), point.substitution, parent ) ] );
	};
	
	// Throw error
	Session.prototype.throw_error = function( error ) {
		return this.thread.throw_error( error );
	};
	Thread.prototype.throw_error = function( error ) {
		this.prepend( [new State( new Term( "throw", [error] ), new Substitution(), null, null )] );
	};
	
	// Selection rule
	Session.prototype.step_rule = function( mod, atom ) {
		return this.thread.step_rule( mod, atom );
	}
	Thread.prototype.step_rule = function( mod, atom ) {
		var name = atom.indicator;
		if( mod === "user" )
			mod = null;
		if( mod === null && this.session.rules.hasOwnProperty(name) )
			return this.session.rules[name];
		var modules = mod === null ? this.session.modules : (indexOf(this.session.modules, mod) === -1 ? [] : [mod]);
		for( var i = 0; i < modules.length; i++ ) {
			var module = pl.module[modules[i]];
			if( module.rules.hasOwnProperty(name) && (module.rules.hasOwnProperty(this.level) || module.exports_predicate(name)) )
				return pl.module[modules[i]].rules[name];
		}
		return null;
	};
	
	// Resolution step
	Session.prototype.step = function() {
		return this.thread.step();
	}
	Thread.prototype.step = function() {
		if( this.points.length === 0 ) {
			return;
		}
		var asyn = false;
		var point = this.points.pop();
		this.current_point = point;
		if( this.debugger )
			this.debugger_states.push( point );
		
		if( pl.type.is_term( point.goal ) ) {
			
			var atom = point.goal.select();
			var mod = null;
			var states = [];
			if( atom !== null ) {

				this.total_steps++;
				var level = point;
				while( level.parent !== null && level.parent.goal.search( atom ) )
					level = level.parent;
				this.level = level.parent === null ? "top_level/0" : level.parent.goal.select().indicator;
				
				if( pl.type.is_term( atom ) && atom.indicator === ":/2" ) {
					mod = atom.args[0].id;
					atom = atom.args[1];
					atom.from_module = mod;
				}

				if(
					(mod === null || atom.indicator === "listing/0" || atom.indicator === "listing/1")
					&& pl.type.is_builtin( atom )
				) {
					this.__call_indicator = atom.indicator;
					asyn = pl.predicate[atom.indicator]( this, point, atom );
				} else {
					var srule = this.step_rule(mod, atom);
					if( srule === null ) {
						if( !this.session.rules.hasOwnProperty( atom.indicator ) ) {
							if( this.get_flag( "unknown" ).id === "error" ) {
								this.throw_error( pl.error.existence( "procedure", atom.indicator, this.level ) );
							} else if( this.get_flag( "unknown" ).id === "warning" ) {
								this.throw_warning( "unknown procedure " + atom.indicator + " (from " + this.level + ")" );
							}
						}
					} else if( srule instanceof Function ) {
						asyn = srule( this, point, atom );
					} else {
						// Goal expansion
						if( this.__goal_expansion && atom.indicator === "goal_expansion/2" )
							srule = srule.concat(pl.predicate["goal_expansion/2"]);
						for( var _rule in srule ) {
							if(!srule.hasOwnProperty(_rule)) continue;
							var rule = srule[_rule];
							this.session.renamed_variables = {};
							rule = rule.rename( this );
							var occurs_check = this.get_flag( "occurs_check" ).indicator === "true/0";
							var state = new State();
							var mgu = pl.unify( atom, rule.head, occurs_check );
							if( mgu !== null ) {
								state.goal = point.goal.replace( rule.body );
								if( state.goal !== null ) {
									state.goal = state.goal.apply( mgu );
								}
								state.substitution = point.substitution.apply( mgu );
								state.parent = point;
								states.push( state );
							}
						}
						this.prepend( states );
					}
				}
			}
		} else if( pl.type.is_variable( point.goal ) ) {
			this.throw_error( pl.error.instantiation( this.level ) );
		} else {
			this.throw_error( pl.error.type( "callable", point.goal, this.level ) );
		}
		return asyn;
	};
	
	// Find next computed answer
	Session.prototype.answer = function( success ) {
		return this.thread.answer( success );
	}
	Thread.prototype.answer = function( success ) {
		success = success || function( _ ) { };
		this.__calls.push( success );
		if( this.__calls.length > 1 ) {
			return;
		}
		this.again();
	};
	
	// Find all computed answers
	Session.prototype.answers = function( callback, max, after ) {
		return this.thread.answers( callback, max, after );
	}
	Thread.prototype.answers = function( callback, max, after ) {
		var answers = max || 1000;
		var thread = this;
		if( max <= 0 ) {
			if(after)
				after();
			return;
		}
		this.answer( function( answer ) {
			callback( answer );
			if( answer !== false ) {
				setTimeout( function() {
					thread.answers( callback, max-1, after );
				}, 1 );
			} else if(after) {
				after();
			}
		} );
	};

	// Again finding next computed answer
	Session.prototype.again = function( reset_limit ) {
		return this.thread.again( reset_limit );
	};
	Thread.prototype.again = function( reset_limit ) {
		var answer;
		var t0 = Date.now();
		while( this.__calls.length > 0 ) {
			this.warnings = [];
			if( reset_limit !== false )
				this.current_limit = this.session.limit;
			while( this.current_limit > 0 && this.points.length > 0 && this.head_point().goal !== null && !pl.type.is_error( this.head_point().goal ) ) {
				this.current_limit--;
				if( this.step() === true ) {
					return;
				}
			}
			var t1 = Date.now();
			this.cpu_time_last = t1-t0;
			this.cpu_time += this.cpu_time_last;
			var success = this.__calls.shift();
			if( this.current_limit <= 0 ) {
				success( null );
			} else if( this.points.length === 0 ) {
				success( false );
			} else if( pl.type.is_error( this.head_point().goal ) ) {
				answer = this.format_error( this.points.pop() );
				this.points = [];
				success( answer );
			} else {
				if( this.debugger )
					this.debugger_states.push( this.head_point() );
				answer = this.format_success( this.points.pop() );
				success( answer );
			}
		}
	};
	
	// Unfolding transformation
	Session.prototype.unfold = function( rule ) {
		if(rule.body === null)
			return false;
		var head = rule.head;
		var body = rule.body;
		var atom = body.select();
		var thread = new Thread( this );
		var unfolded = [];
		thread.add_goal( atom );
		thread.step();
		for( var i = thread.points.length-1; i >= 0; i-- ) {
			var point = thread.points[i];
			var head2 = head.apply( point.substitution );
			var body2 = body.replace( point.goal );
			if( body2 !== null )
				body2 = body2.apply( point.substitution );
			unfolded.push( new Rule( head2, body2 ) );
		}
		var rules = this.rules[head.indicator];
		var index = indexOf( rules, rule );
		if( unfolded.length > 0 && index !== -1 ) {
			rules.splice.apply( rules, [index, 1].concat(unfolded) );
			return true;
		}
		return false;
	};
	Thread.prototype.unfold = function(rule) {
		return this.session.unfold(rule);
	};

	
	
	// INTERPRET EXPRESSIONS
	
	// Variables
	Var.prototype.interpret = function( thread ) {
		return pl.error.instantiation( thread.level );
	};
	
	// Numbers
	Num.prototype.interpret = function( thread ) {
		return this;
	};
	
	// Terms
	Term.prototype.interpret = function( thread ) {
		if( pl.type.is_unitary_list( this ) ) {
			return this.args[0].interpret( thread );
		} else {
			return pl.operate( thread, this );
		}
	};
	
	
	
	// COMPARE PROLOG OBJECTS
	
	// Variables
	Var.prototype.compare = function( obj ) {
		if( this.id < obj.id ) {
			return -1;
		} else if( this.id > obj.id ) {
			return 1;
		} else {
			return 0;
		}
	};
	
	// Numbers
	Num.prototype.compare = function( obj ) {
		if( this.value === obj.value && this.is_float === obj.is_float ) {
			return 0;
		} else if( this.value < obj.value || this.value === obj.value && this.is_float && !obj.is_float ) {
			return -1;
		} else if( this.value > obj.value ) {
			return 1;
		}
	};
	
	// Terms
	Term.prototype.compare = function( obj ) {
		if( this.args.length < obj.args.length || this.args.length === obj.args.length && this.id < obj.id ) {
			return -1;
		} else if( this.args.length > obj.args.length || this.args.length === obj.args.length && this.id > obj.id ) {
			return 1;
		} else {
			for( var i = 0; i < this.args.length; i++ ) {
				var arg = pl.compare( this.args[i], obj.args[i] );
				if( arg !== 0 ) {
					return arg;
				}
			}
			return 0;
		}
	};
	

	
	// SUBSTITUTIONS
	
	// Lookup variable
	Substitution.prototype.lookup = function( variable ) {
		if( this.links[variable] ) {
			return this.links[variable];
		} else {
			return null;
		}
	};
	
	// Filter variables
	Substitution.prototype.filter = function( predicate ) {
		var links = {};
		for( var id in this.links ) {
			if(!this.links.hasOwnProperty(id)) continue;
			var value = this.links[id];
			if( predicate( id, value ) ) {
				links[id] = value;
			}
		}
		return new Substitution( links, this.attrs );
	};
	
	// Exclude variables
	Substitution.prototype.exclude = function( variables ) {
		var links = {};
		for( var variable in this.links ) {
			if(!this.links.hasOwnProperty(variable)) continue;
			if( indexOf( variables, variable ) === -1 ) {
				links[variable] = this.links[variable];
			}
		}
		return new Substitution( links, this.attrs );
	};
	
	// Add link
	Substitution.prototype.add = function( variable, value ) {
		this.links[variable] = value;
	};
	
	// Get domain
	Substitution.prototype.domain = function( plain ) {
		var f = plain === true ? function(x){return x;} : function(x){return new Var(x);};
		var vars = [];
		for( var x in this.links )
			vars.push( f(x) );
		return vars;
	};

	// Get an attribute
	Substitution.prototype.get_attribute = function( variable, module ) {
		if( this.attrs[variable] )
			return this.attrs[variable][module];
	}

	// Set an attribute (in a new substitution)
	Substitution.prototype.set_attribute = function( variable, module, value ) {
		var subs = new Substitution( this.links );
		for( var v in this.attrs ) {
			if( v === variable ) {
				subs.attrs[v] = {};
				for( var m in this.attrs[v] ) {
					subs.attrs[v][m] = this.attrs[v][m];
				}
			} else {
				subs.attrs[v] = this.attrs[v];
			}
		}
		if( !subs.attrs[variable] ) {
			subs.attrs[variable] = {};
		}
		subs.attrs[variable][module] = value;
		return subs;
	}

	// Check if a variables has attributes
	Substitution.prototype.has_attributes = function( variable ) {
		return this.attrs[variable] && this.attrs[variable] !== {};
	}
	
	
	
	// GENERATE JAVASCRIPT CODE FROM PROLOG OBJECTS
	
	// Variables
	Var.prototype.compile = function() {
		return 'new pl.type.Var("' + this.id.toString() + '")';
	};
	
	// Numbers
	Num.prototype.compile = function() {
		return 'new pl.type.Num(' + this.value.toString() + ', ' + this.is_float.toString() + ')';
	};
	
	// Terms
	Term.prototype.compile = function() {
		return 'new pl.type.Term("' + this.id.replace(/"/g, '\\"') + '", [' + map( this.args, function( arg ) {
			return arg.compile();
		} ) + '])';
	};
	
	// Rules
	Rule.prototype.compile = function() {
		return 'new pl.type.Rule(' + this.head.compile() + ', ' + (this.body === null ? 'null' : this.body.compile()) + ')';
	};
	
	// Sessions
	Session.prototype.compile = function() {
		var str, obj = [], rules;
		for( var _indicator in this.rules ) {
			if(!this.rules.hasOwnProperty(_indicator)) continue;
			var indicator = this.rules[_indicator];
			rules = [];
			str = "\"" + _indicator + "\": [";
			for( var i = 0; i < indicator.length; i++ ) {
				rules.push( indicator[i].compile() );
			}
			str += rules.join();
			str += "]";
			obj.push( str );
		}
		return "{" + obj.join() + "};";
	};
	
	
	
	// PROLOG TO JAVASCRIPT
	Var.prototype.toJavaScript = function() {
		return this.toString();
	};
	
	// Numbers
	Num.prototype.toJavaScript = function() {
		return this.value;
	};
	
	// Terms
	Term.prototype.toJavaScript = function() {
		// Atom => String
		if( this.args.length === 0 && this.indicator !== "[]/0" ) {
			return this.toString();
		} else if( pl.type.is_list( this ) ) {
			// List => Array
			var all_obj = true;
			var arr = [];
			var obj = {};
			var pointer = this;
			var value;
			while( pointer.indicator === "./2" ) {
				value = pointer.args[0].toJavaScript();
				arr.push( value );
				all_obj = all_obj && pl.type.is_term(pointer.args[0]) && pointer.args[0].indicator === "-/2" && pl.type.is_atom(pointer.args[0].args[0]);
				if(all_obj)
					obj[pointer.args[0].args[0].id] = pointer.args[0].args[1].toJavaScript();
				pointer = pointer.args[1];
			}
			if( pointer.indicator === "[]/0" )
				return all_obj && arr.length > 0 ? obj : arr;

		}
		return this.toString();
	};
	
	
	
	// RULES
	
	// Return singleton variables in the session
	Rule.prototype.singleton_variables = function() {
		var variables = this.head.variables();
		var count = {};
		var singleton = [];
		if( this.body !== null )
			variables = variables.concat( this.body.variables() );
		for( var i = 0; i < variables.length; i++ ) {
			if( count[variables[i]] === undefined )
				count[variables[i]] = 0;
			count[variables[i]]++;
		}
		for( var key in count )
			if( key !== "_" && count[key] === 1 )
				singleton.push( key );
		return singleton;
	};



	// NODEJS

	var nodejs_flag = typeof process !== 'undefined' && !process.browser

	var nodejs_arguments = nodejs_flag ?
		arrayToList( map(process.argv.slice(1), function(arg) { return new Term( arg ); })) :
		new Term("[]", []);
	
	
	
	// PROLOG

	var pl = {
		
		// Environment
		__env: nodejs_flag ? global : window,
		
		// Modules
		module: {},
		
		// Version
		version: version,
		
		// Parser
		parser: {
			tokenizer: Tokenizer,
			expression: parseExpr
		},
		
		// Utils
		utils: {
			
			// String to indicator
			str_indicator: str_indicator,
			// Code point at
			codePointAt: codePointAt,
			// From code point
			fromCodePoint: fromCodePoint,
			// Current directory
			cd: cd
			
		},
		
		// Statistics
		statistics: {
			
			// Number of created terms
			getCountTerms: function() {
				return term_ref;
			}
			
		},
		
		// JavaScript to Prolog
		fromJavaScript: {
			
			// Type testing
			test: {
				
				// Boolean
				boolean: function( obj, tobj ) {
					return obj === true || obj === false;
				},
				
				// Number
				number: function( obj, tobj ) {
					return typeof obj === "number";
				},
				
				// String
				string: function( obj, tobj ) {
					return typeof obj === "string";
				},
				
				// List
				list: function( obj, tobj ) {
					return obj instanceof Array;
				},
				
				// Variable
				variable: function( obj, tobj ) {
					return obj === undefined;
				},

				// Object
				object: function( obj, tobj ) {
					tobj = tobj === undefined ? false : tobj;
					return tobj && !(obj instanceof Array) && typeof obj === "object";
				},
				
				// Any
				any: function( _, tobj ) {
					return true;
				}
				
			},
			
			// Function conversion
			conversion: {
				
				// Bolean
				boolean: function( obj, tobj ) {
					return new Term( obj ? "true" : "false", [] );
				},
				
				// Number
				number: function( obj, tobj ) {
					return new Num( obj, obj % 1 !== 0 );
				},
				
				// String
				string: function( obj, tobj ) {
					return new Term( obj, [] );
				},
				
				// List
				list: function( obj, tobj ) {
					tobj = tobj === undefined ? false : tobj;
					var arr = [];
					var elem;
					for( var i = 0; i < obj.length; i++ ) {
						elem = pl.fromJavaScript.apply( obj[i], tobj );
						if( elem === undefined )
							return undefined;
						arr.push( elem );
					}
					return arrayToList( arr );
				},
				
				// Variable
				variable: function( obj, tobj ) {
					return new Var( "_" );
				},

				// Object
				object: function( obj, tobj ) {
					tobj = tobj === undefined ? false : tobj;
					var list = new Term("[]", []);
					var arr = [];
					for(var prop in obj) {
						if(!obj.hasOwnProperty(prop)) continue;
						arr.push(new Term("-", [
							pl.fromJavaScript.apply(prop, tobj),
							pl.fromJavaScript.apply(obj[prop], tobj)
						]));
					}
					return arrayToList(arr);
				},
				
				// Any
				any: function( obj, tobj ) {
					return undefined;
				}
				
			},
			
			// Transform object
			apply: function( obj, tobj ) {
				tobj = tobj === undefined ? false : tobj;
				for( var i in pl.fromJavaScript.test )
					if( i !== "any" && pl.fromJavaScript.test[i]( obj, tobj ) )
						return pl.fromJavaScript.conversion[i]( obj, tobj );
				return pl.fromJavaScript.conversion.any( obj, tobj );
			}
		},
		
		// Types
		type: {
			
			// Objects
			Var: Var,
			Num: Num,
			Term: Term,
			Rule: Rule,
			State: State,
			Stream: Stream,
			Module: Module,
			Thread: Thread,
			Session: Session,
			Substitution: Substitution,
			File: TauFile,
			Directory: TauDirectory,
			
			// Order
			order: [Var, Num, Term, Stream],
			
			// Compare types
			compare: function( x, y ) {
				var ord_x = indexOf( pl.type.order, x.constructor );
				var ord_y = indexOf( pl.type.order, y.constructor );
				if( ord_x < ord_y ) {
					return -1;
				} else if( ord_x > ord_y ) {
					return 1;
				} else {
					if( x.constructor === Num )
						if( x.is_float && y.is_float )
							return 0;
						else if( x.is_float )
							return -1;
						else if( y.is_float )
							return 1;
					return 0;
				}
			},
			
			// Is a substitution
			is_substitution: function( obj ) {
				return obj instanceof Substitution;
			},
			
			// Is a state
			is_state: function( obj ) {
				return obj instanceof State;
			},
			
			// Is a rule
			is_rule: function( obj ) {
				return obj instanceof Rule;
			},
			
			// Is a variable
			is_variable: function( obj ) {
				return obj instanceof Var;
			},

			// Is a stream
			is_stream: function( obj ) {
				return obj instanceof Stream;
			},
			
			// Is an anonymous variable
			is_anonymous_var: function( obj ) {
				return obj instanceof Var && obj.id === "_";
			},
			
			// Is a callable term
			is_callable: function( obj ) {
				return obj instanceof Term;
			},
			
			// Is a number
			is_number: function( obj ) {
				return obj instanceof Num;
			},
			
			// Is an integer
			is_integer: function( obj ) {
				return obj instanceof Num && !obj.is_float;
			},
			
			// Is a float
			is_float: function( obj ) {
				return obj instanceof Num && obj.is_float;
			},
			
			// Is a term
			is_term: function( obj ) {
				return obj instanceof Term;
			},
			
			// Is an atom
			is_atom: function( obj ) {
				return obj instanceof Term && obj.args.length === 0;
			},
			
			// Is a ground term
			is_ground: function( obj ) {
				if( obj instanceof Var ) return false;
				if( obj instanceof Term )
					for( var i = 0; i < obj.args.length; i++ )
						if( !pl.type.is_ground( obj.args[i] ) )
							return false;
				return true;
			},
			
			// Is atomic
			is_atomic: function( obj ) {
				return obj instanceof Term && obj.args.length === 0 || obj instanceof Num;
			},
			
			// Is compound
			is_compound: function( obj ) {
				return obj instanceof Term && obj.args.length > 0;
			},
			
			// Is a list
			is_list: function( obj ) {
				return obj instanceof Term && (obj.indicator === "[]/0" || obj.indicator === "./2");
			},
			
			// Is an empty list
			is_empty_list: function( obj ) {
				return obj instanceof Term && obj.indicator === "[]/0";
			},
			
			// Is a non empty list
			is_non_empty_list: function( obj ) {
				return obj instanceof Term && obj.indicator === "./2";
			},
			
			// Is a fully list
			is_fully_list: function( obj ) {
				while( obj instanceof Term && obj.indicator === "./2" ) {
					obj = obj.args[1];
				}
				return obj instanceof Var || obj instanceof Term && obj.indicator === "[]/0";
			},
			
			// Is a instantiated list
			is_instantiated_list: function( obj ) {
				while( obj instanceof Term && obj.indicator === "./2" ) {
					obj = obj.args[1];
				}
				return obj instanceof Term && obj.indicator === "[]/0";
			},
			
			// Is an unitary list
			is_unitary_list: function( obj ) {
				return obj instanceof Term && obj.indicator === "./2" && obj.args[1] instanceof Term && obj.args[1].indicator === "[]/0";
			},
			
			// Is a character
			is_character: function( obj ) {
				return obj instanceof Term && (obj.id.length === 1 || obj.id.length > 0 && obj.id.length <= 2 && codePointAt( obj.id, 0 ) >= 65536);
			},
			
			// Is a character
			is_character_code: function( obj ) {
				return obj instanceof Num && !obj.is_float && obj.value >= 0 && obj.value <= 1114111;
			},

			// Is a byte
			is_byte: function( obj ) {
				return obj instanceof Num && !obj.is_float && obj.value >= 0 && obj.value <= 255;
			},
			
			// Is an operator
			is_operator: function( obj ) {
				return obj instanceof Term && pl.arithmetic.evaluation[obj.indicator];
			},
			
			// Is a directive
			is_directive: function( obj ) {
				return obj instanceof Term && (pl.directive[obj.indicator] !== undefined || pl.directive[obj.id + "/*"] !== undefined);
			},
			
			// Is a built-in predicate
			is_builtin: function( obj ) {
				return obj instanceof Term && pl.predicate[obj.indicator] !== undefined && obj.indicator !== "goal_expansion/2";
			},
			
			// Is an error
			is_error: function( obj ) {
				return obj instanceof Term && obj.indicator === "throw/1";
			},
			
			// Is a predicate indicator
			is_predicate_indicator: function( obj ) {
				return obj instanceof Term && obj.indicator === "//2" && obj.args[0] instanceof Term && obj.args[0].args.length === 0 && obj.args[1] instanceof Num && obj.args[1].is_float === false;
			},
			
			// Is a flag
			is_flag: function( obj ) {
				return obj instanceof Term && obj.args.length === 0 && pl.flag[obj.id] !== undefined;
			},
			
			// Is a valid value for a flag
			is_value_flag: function( flag, obj ) {
				if( !pl.type.is_flag( flag ) ) return false;
				for( var value in pl.flag[flag.id].allowed ) {
					if(!pl.flag[flag.id].allowed.hasOwnProperty(value)) continue;
					if( pl.flag[flag.id].allowed[value].equals( obj ) ) return true;
				}
				return false;
			},

			// Is a io mode
			is_io_mode: function( obj ) {
				return pl.type.is_atom( obj ) && ["read","write","append"].indexOf( obj.id ) !== -1;
			},

			// Is a stream option
			is_stream_option: function( obj ) {
				return pl.type.is_term( obj ) && (
					obj.indicator === "alias/1" && pl.type.is_atom(obj.args[0]) ||
					obj.indicator === "reposition/1" && pl.type.is_atom(obj.args[0]) && (obj.args[0].id === "true" || obj.args[0].id === "false") ||
					obj.indicator === "type/1" && pl.type.is_atom(obj.args[0]) && (obj.args[0].id === "text" || obj.args[0].id === "binary") ||
					obj.indicator === "eof_action/1" && pl.type.is_atom(obj.args[0]) && (obj.args[0].id === "error" || obj.args[0].id === "eof_code" || obj.args[0].id === "reset")
				);
			},

			// Is a stream position
			is_stream_position: function( obj ) {
				return pl.type.is_integer( obj ) && obj.value >= 0 || pl.type.is_atom( obj ) && (obj.id === "end_of_stream" || obj.id === "past_end_of_stream");
			},

			// Is a stream property
			is_stream_property: function( obj ) {
				return pl.type.is_term( obj ) && (
					obj.indicator === "input/0" || 
					obj.indicator === "output/0" || 
					obj.indicator === "alias/1" && (pl.type.is_variable( obj.args[0] ) || pl.type.is_atom( obj.args[0] )) ||
					obj.indicator === "file_name/1" && (pl.type.is_variable( obj.args[0] ) || pl.type.is_atom( obj.args[0] )) ||
					obj.indicator === "position/1" && (pl.type.is_variable( obj.args[0] ) || pl.type.is_stream_position( obj.args[0] )) ||
					obj.indicator === "reposition/1" && (pl.type.is_variable( obj.args[0] ) || pl.type.is_atom(obj.args[0]) && (obj.args[0].id === "true" || obj.args[0].id === "false")) ||
					obj.indicator === "type/1" && (pl.type.is_variable( obj.args[0] ) || pl.type.is_atom(obj.args[0]) && (obj.args[0].id === "text" || obj.args[0].id === "binary")) ||
					obj.indicator === "mode/1" && (pl.type.is_variable( obj.args[0] ) || pl.type.is_atom(obj.args[0]) && (obj.args[0].id === "read" || obj.args[0].id === "write" || obj.args[0].id === "append")) ||
					obj.indicator === "eof_action/1" && (pl.type.is_variable( obj.args[0] ) || pl.type.is_atom(obj.args[0]) && (obj.args[0].id === "error" || obj.args[0].id === "eof_code" || obj.args[0].id === "reset")) ||
					obj.indicator === "end_of_stream/1" && (pl.type.is_variable( obj.args[0] ) || pl.type.is_atom(obj.args[0]) && (obj.args[0].id === "at" || obj.args[0].id === "past" || obj.args[0].id === "not"))
				);
			},

			// Is a streamable term
			is_streamable: function( obj ) {
				return obj.__proto__.stream !== undefined;
			},

			// Is a read option
			is_read_option: function( obj ) {
				return pl.type.is_term( obj ) && ["variables/1","variable_names/1","singletons/1"].indexOf( obj.indicator ) !== -1;
			},

			// Is a write option
			is_write_option: function( obj ) {
				return pl.type.is_term( obj ) && (
					obj.indicator === "quoted/1" && pl.type.is_atom(obj.args[0]) && (obj.args[0].id === "true" || obj.args[0].id === "false") ||
					obj.indicator === "ignore_ops/1" && pl.type.is_atom(obj.args[0]) && (obj.args[0].id === "true" || obj.args[0].id === "false") ||
					obj.indicator === "numbervars/1" && pl.type.is_atom(obj.args[0]) && (obj.args[0].id === "true" || obj.args[0].id === "false")
				);
			},

			// Is a close option
			is_close_option: function( obj ) {
				return pl.type.is_term( obj ) &&
					obj.indicator === "force/1" &&
					pl.type.is_atom(obj.args[0]) &&
					(obj.args[0].id === "true" || obj.args[0].id === "false");
			},
			
			// Is a modifiable flag
			is_modifiable_flag: function( obj ) {
				return pl.type.is_flag( obj ) && pl.flag[obj.id].changeable;
			},
			
			// Is an existing module
			is_module: function( obj ) {
				return obj instanceof Term && obj.indicator === "library/1" && obj.args[0] instanceof Term && obj.args[0].args.length === 0 && pl.module[obj.args[0].id] !== undefined;
			},

			// Is a virtual file
			is_file: function( obj ) {
				return obj instanceof TauFile;
			},

			// Is a virtual directory
			is_directory: function( obj ) {
				return obj instanceof TauDirectory;
			}
			
		},

		// Arithmetic functions
		arithmetic: {
			
			// Evaluation
			evaluation: {
				"e/0": {
					type_args: null,
					type_result: true,
					fn: function( _ ) { return Math.E; }
				},
				"pi/0": {
					type_args: null,
					type_result: true,
					fn: function( _ ) { return Math.PI; }
				},
				"tau/0": {
					type_args: null,
					type_result: true,
					fn: function( _ ) { return 2*Math.PI; }
				},
				"epsilon/0": {
					type_args: null,
					type_result: true,
					fn: function( _ ) { return Number.EPSILON; }
				},
				"+/1": {
					type_args: null,
					type_result: null,
					fn: function( x, _ ) { return x; }
				},
				"-/1": {
					type_args: null,
					type_result: null,
					fn: function( x, _ ) { return -x; }
				},
				"\\/1": {
					type_args: false,
					type_result: false,
					fn: function( x, _ ) { return ~x; }
				},
				"abs/1": {
					type_args: null,
					type_result: null,
					fn: function( x, _ ) { return Math.abs( x ); }
				},
				"sign/1": {
					type_args: null,
					type_result: null,
					fn: function( x, _ ) { return Math.sign( x ); }
				},
				"float_integer_part/1": {
					type_args: true,
					type_result: false,
					fn: function( x, _ ) { return parseInt( x ); }
				},
				"float_fractional_part/1": {
					type_args: true,
					type_result: true,
					fn: function( x, _ ) { return x - parseInt( x ); }
				},
				"float/1": {
					type_args: null,
					type_result: true,
					fn: function( x, _ ) { return parseFloat( x ); }
				},
				"floor/1": {
					type_args: true,
					type_result: false,
					fn: function( x, _ ) { return Math.floor( x ); }
				},
				"truncate/1": {
					type_args: true,
					type_result: false,
					fn: function( x, _ ) { return parseInt( x ); }
				},
				"round/1": {
					type_args: true,
					type_result: false,
					fn: function( x, _ ) { return Math.round( x ); }
				},
				"ceiling/1": {
					type_args: true,
					type_result: false,
					fn: function( x, _ ) { return Math.ceil( x ); }
				},
				"sin/1": {
					type_args: null,
					type_result: true,
					fn: function( x, _ ) { return Math.sin( x ); }
				},
				"cos/1": {
					type_args: null,
					type_result: true,
					fn: function( x, _ ) { return Math.cos( x ); }
				},
				"tan/1": {
					type_args: null,
					type_result: true,
					fn: function( x, _ ) { return Math.tan( x ); }
				},
				"asin/1": {
					type_args: null,
					type_result: true,
					fn: function( x, _ ) { return Math.asin( x ); }
				},
				"acos/1": {
					type_args: null,
					type_result: true,
					fn: function( x, _ ) { return Math.acos( x ); }
				},
				"atan/1": {
					type_args: null,
					type_result: true,
					fn: function( x, _ ) { return Math.atan( x ); }
				},
				"atan2/2": {
					type_args: null,
					type_result: true,
					fn: function( x, y, _ ) { return Math.atan2( x, y ); }
				},
				"exp/1": {
					type_args: null,
					type_result: true,
					fn: function( x, _ ) { return Math.exp( x ); }
				},
				"sqrt/1": {
					type_args: null,
					type_result: true,
					fn: function( x, _ ) { return Math.sqrt( x ); }
				},
				"log/1": {
					type_args: null,
					type_result: true,
					fn: function( x, thread ) { return x > 0 ? Math.log( x ) : pl.error.evaluation( "undefined", thread.__call_indicator ); }
				},
				"+/2": {
					type_args: null,
					type_result: null,
					fn: function( x, y, _ ) { return x + y; }
				},
				"-/2": {
					type_args: null,
					type_result: null,
					fn:  function( x, y, _ ) { return x - y; }
				},
				"*/2": {
					type_args: null,
					type_result: null,
					fn: function( x, y, _ ) { return x * y; }
				},
				"//2": {
					type_args: null,
					type_result: true,
					fn: function( x, y, thread ) { return y ? x / y : pl.error.evaluation( "zero_division", thread.__call_indicator ); }
				},
				"///2": {
					type_args: false,
					type_result: false,
					fn: function( x, y, thread ) { return y ? Math.trunc( x / y ) : pl.error.evaluation( "zero_division", thread.__call_indicator ); }
				},
				"div/2": {
					type_args: false,
					type_result: false,
					fn: function( x, y, thread ) { return y ? Math.floor( x / y ) : pl.error.evaluation( "zero_division", thread.__call_indicator ); }
				},
				"**/2": {
					type_args: null,
					type_result: true,
					fn: function( x, y, _ ) { return Math.pow(x, y); }
				},
				"^/2": {
					type_args: null,
					type_result: null,
					fn: function( x, y, _ ) { return Math.pow(x, y); }
				},
				"<</2": {
					type_args: false,
					type_result: false,
					fn: function( x, y, _ ) { return x << y; }
				},
				">>/2": {
					type_args: false,
					type_result: false,
					fn: function( x, y, _ ) { return x >> y; }
				},
				"/\\/2": {
					type_args: false,
					type_result: false,
					fn: function( x, y, _ ) { return x & y; }
				},
				"\\//2": {
					type_args: false,
					type_result: false,
					fn: function( x, y, _ ) { return x | y; }
				},
				"xor/2": {
					type_args: false,
					type_result: false,
					fn: function( x, y, _ ) { return x ^ y; }
				},
				"rem/2": {
					type_args: false,
					type_result: false,
					fn: function( x, y, thread ) { return y ? x % y : pl.error.evaluation( "zero_division", thread.__call_indicator ); }
				},
				"mod/2": {
					type_args: false,
					type_result: false,
					fn: function( x, y, thread ) { return y ? x - Math.floor( x / y ) * y : pl.error.evaluation( "zero_division", thread.__call_indicator ); }
				},
				"max/2": {
					type_args: null,
					type_result: null,
					fn: function( x, y, _ ) { return Math.max( x, y ); }
				},
				"min/2": {
					type_args: null,
					type_result: null,
					fn: function( x, y, _ ) { return Math.min( x, y ); }
				}
				
			}
			
		},
		
		// Directives
		directive: {
			
			// dynamic/1
			"dynamic/1": function( thread, atom ) {
				var indicators = atom.args[0];
				if(!pl.type.is_list(indicators))
					indicators = arrayToList([indicators]);
				var pointer = indicators;
				while(pl.type.is_term(pointer) && pointer.indicator === "./2") {
					indicator = pointer.args[0];
					if( pl.type.is_variable( indicator ) ) {
						thread.throw_error( pl.error.instantiation( atom.indicator ) );
					} else if( !pl.type.is_compound( indicator ) || indicator.indicator !== "//2" ) {
						thread.throw_error( pl.error.type( "predicate_indicator", indicator, atom.indicator ) );
					} else if( pl.type.is_variable( indicator.args[0] ) || pl.type.is_variable( indicator.args[1] ) ) {
						thread.throw_error( pl.error.instantiation( atom.indicator ) );
					} else if( !pl.type.is_atom( indicator.args[0] ) ) {
						thread.throw_error( pl.error.type( "atom", indicator.args[0], atom.indicator ) );
					} else if( !pl.type.is_integer( indicator.args[1] ) ) {
						thread.throw_error( pl.error.type( "integer", indicator.args[1], atom.indicator ) );
					} else {
						var key = indicator.args[0].id + "/" + indicator.args[1].value;
						thread.session.public_predicates[key] = true;
						if( !thread.session.rules[key] )
							thread.session.rules[key] = [];
					}
					pointer = pointer.args[1];
				}
				if(pl.type.is_variable(pointer)) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if(!pl.type.is_term(pointer) || pointer.indicator !== "[]/0") {
					thread.throw_error( pl.error.type( "predicate_indicator", indicator, atom.indicator ) );
				}
			},

			// dynamic/[2..]
			"dynamic/*": function( thread, atom ) {
				for(var i = 0; i < atom.args.length; i++) {
					pl.directive["dynamic/1"](thread, new Term("dynamic", [atom.args[i]]));
				}
			},
			
			// multifile/1
			"multifile/1": function( thread, atom ) {
				var indicator = atom.args[0];
				if( pl.type.is_variable( indicator ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_compound( indicator ) || indicator.indicator !== "//2" ) {
					thread.throw_error( pl.error.type( "predicate_indicator", indicator, atom.indicator ) );
				} else if( pl.type.is_variable( indicator.args[0] ) || pl.type.is_variable( indicator.args[1] ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_atom( indicator.args[0] ) ) {
					thread.throw_error( pl.error.type( "atom", indicator.args[0], atom.indicator ) );
				} else if( !pl.type.is_integer( indicator.args[1] ) ) {
					thread.throw_error( pl.error.type( "integer", indicator.args[1], atom.indicator ) );
				} else {
					thread.session.multifile_predicates[atom.args[0].args[0].id + "/" + atom.args[0].args[1].value] = true;
				}
			},
			
			// set_prolog_flag
			"set_prolog_flag/2": function( thread, atom ) {
				var flag = atom.args[0], value = atom.args[1];
				if( pl.type.is_variable( flag ) || pl.type.is_variable( value ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_atom( flag ) ) {
					thread.throw_error( pl.error.type( "atom", flag, atom.indicator ) );
				} else if( !pl.type.is_flag( flag ) ) {
					thread.throw_error( pl.error.domain( "prolog_flag", flag, atom.indicator ) );
				} else if( !pl.type.is_value_flag( flag, value ) ) {
					thread.throw_error( pl.error.domain( "flag_value", new Term( "+", [flag, value] ), atom.indicator ) );
				} else if( !pl.type.is_modifiable_flag( flag ) ) {
					thread.throw_error( pl.error.permission( "modify", "flag", flag, atom.indicator ) );
				} else {
					thread.session.flag[flag.id] = value;
				}
			},
			
			// use_module/1
			"use_module/1": function( thread, atom ) {
				var module = atom.args[0];
				if( pl.type.is_variable( module ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_term( module ) ) {
					thread.throw_error( pl.error.type( "term", module, atom.indicator ) );
				} else {
					if( pl.type.is_module( module ) ) {
						var name = module.args[0].id;
						if( indexOf(thread.session.modules, name) === -1 )
							thread.session.modules.push( name );
					} else {
						// TODO
						// error no existe modulo
					}
				}
			},
			
			// char_conversion/2
			"char_conversion/2": function( thread, atom ) {
				var inchar = atom.args[0], outchar = atom.args[1];
				if( pl.type.is_variable( inchar ) || pl.type.is_variable( outchar ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_character( inchar ) ) {
					thread.throw_error( pl.error.type( "character", inchar, atom.indicator ) );
				} else if( !pl.type.is_character( outchar ) ) {
					thread.throw_error( pl.error.type( "character", outchar, atom.indicator ) );
				} else {
					if( inchar.id === outchar.id ) {
						delete thread.session.__char_conversion[inchar.id];
					} else {
						thread.session.__char_conversion[inchar.id] = outchar.id;
					}
				}
			},
			
			// op/3
			"op/3": function( thread, atom ) {
				var priority = atom.args[0], type = atom.args[1], operator = atom.args[2];
				if( pl.type.is_variable( priority ) || pl.type.is_variable( type ) || pl.type.is_variable( operator ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_integer( priority ) ) {
					thread.throw_error( pl.error.type( "integer", priority, atom.indicator ) );
				} else if( !pl.type.is_atom( type ) ) {
					thread.throw_error( pl.error.type( "atom", type, atom.indicator ) );
				} else if( !pl.type.is_atom( operator ) ) {
					thread.throw_error( pl.error.type( "atom", operator, atom.indicator ) );
				} else if( priority.value < 0 || priority.value > 1200 ) {
					thread.throw_error( pl.error.domain( "operator_priority", priority, atom.indicator ) );
				} else if( operator.id === "," ) {
					thread.throw_error( pl.error.permission( "modify", "operator", operator, atom.indicator ) );
				} else if( operator.id === "|" && (priority.value < 1001 || type.id.length !== 3 ) ) {
					thread.throw_error( pl.error.permission( "modify", "operator", operator, atom.indicator ) );
				} else if( ["fy", "fx", "yf", "xf", "xfx", "yfx", "xfy"].indexOf( type.id ) === -1 ) {
					thread.throw_error( pl.error.domain( "operator_specifier", type, atom.indicator ) );
				} else {
					var fix = { prefix: null, infix: null, postfix: null };
					for( var p in thread.session.__operators ) {
						if(!thread.session.__operators.hasOwnProperty(p)) continue;
						var classes = thread.session.__operators[p][operator.id];
						if( classes ) {
							if( indexOf( classes, "fx" ) !== -1 ) { fix.prefix = { priority: p, type: "fx" }; }
							if( indexOf( classes, "fy" ) !== -1 ) { fix.prefix = { priority: p, type: "fy" }; }
							if( indexOf( classes, "xf" ) !== -1 ) { fix.postfix = { priority: p, type: "xf" }; }
							if( indexOf( classes, "yf" ) !== -1 ) { fix.postfix = { priority: p, type: "yf" }; }
							if( indexOf( classes, "xfx" ) !== -1 ) { fix.infix = { priority: p, type: "xfx" }; }
							if( indexOf( classes, "xfy" ) !== -1 ) { fix.infix = { priority: p, type: "xfy" }; }
							if( indexOf( classes, "yfx" ) !== -1 ) { fix.infix = { priority: p, type: "yfx" }; }
						}
					}
					var current_class;
					switch( type.id ) {
						case "fy": case "fx": current_class = "prefix"; break;
						case "yf": case "xf": current_class = "postfix"; break;
						default: current_class = "infix"; break;
					}
					if( ((fix.prefix && current_class === "prefix" || fix.postfix && current_class === "postfix" || fix.infix && current_class === "infix")
						&& fix[current_class].type !== type.id || fix.infix && current_class === "postfix" || fix.postfix && current_class === "infix") && priority.value !== 0 ) {
						thread.throw_error( pl.error.permission( "create", "operator", operator, atom.indicator ) );
					} else {
						if( fix[current_class] ) {
							remove( thread.session.__operators[fix[current_class].priority][operator.id], type.id );
							if( thread.session.__operators[fix[current_class].priority][operator.id].length === 0 ) {
								delete thread.session.__operators[fix[current_class].priority][operator.id];
							}
						}
						if( priority.value > 0 ) {
							if( !thread.session.__operators[priority.value] ) thread.session.__operators[priority.value.toString()] = {};
							if( !thread.session.__operators[priority.value][operator.id] ) thread.session.__operators[priority.value][operator.id] = [];
							thread.session.__operators[priority.value][operator.id].push( type.id );
						}
						return true;
					}
				}
			}
			
		},
		
		// Built-in predicates
		predicate: {

			// TERM AND GOAL EXPANSION

			"goal_expansion/2": [
				new Rule(new Term("goal_expansion", [new Term(",", [new Var("X"),new Var("Y")]),new Term(",", [new Var("X_"),new Var("Y_")])]), new Term(";", [new Term(",", [new Term("goal_expansion", [new Var("X"),new Var("X_")]),new Term(";", [new Term("goal_expansion", [new Var("Y"),new Var("Y_")]),new Term("=", [new Var("Y_"),new Var("Y")])])]),new Term(",", [new Term("=", [new Var("X"),new Var("X_")]),new Term("goal_expansion", [new Var("Y"),new Var("Y_")])])])),
				new Rule(new Term("goal_expansion", [new Term(";", [new Var("X"),new Var("Y")]),new Term(";", [new Var("X_"),new Var("Y_")])]), new Term(";", [new Term(",", [new Term("goal_expansion", [new Var("X"),new Var("X_")]),new Term(";", [new Term("goal_expansion", [new Var("Y"),new Var("Y_")]),new Term("=", [new Var("Y_"),new Var("Y")])])]),new Term(",", [new Term("=", [new Var("X"),new Var("X_")]),new Term("goal_expansion", [new Var("Y"),new Var("Y_")])])])),
				new Rule(new Term("goal_expansion", [new Term("->", [new Var("X"),new Var("Y")]),new Term("->", [new Var("X_"),new Var("Y_")])]), new Term(";", [new Term(",", [new Term("goal_expansion", [new Var("X"),new Var("X_")]),new Term(";", [new Term("goal_expansion", [new Var("Y"),new Var("Y_")]),new Term("=", [new Var("Y_"),new Var("Y")])])]),new Term(",", [new Term("=", [new Var("X"),new Var("X_")]),new Term("goal_expansion", [new Var("Y"),new Var("Y_")])])])),
				new Rule(new Term("goal_expansion", [new Term("catch", [new Var("X"),new Var("Y"),new Var("Z")]),new Term("catch", [new Var("X_"),new Var("Y"),new Var("Z_")])]), new Term(";", [new Term(",", [new Term("goal_expansion", [new Var("X"),new Var("X_")]),new Term(";", [new Term("goal_expansion", [new Var("Z"),new Var("Z_")]),new Term("=", [new Var("Z_"),new Var("Z")])])]),new Term(",", [new Term("=", [new Var("X_"),new Var("X")]),new Term("goal_expansion", [new Var("Z"),new Var("Z_")])])])),
				new Rule(new Term("goal_expansion", [new Term("\\+", [new Var("X")]),new Term("\\+", [new Var("X_")])]), new Term(",", [new Term("nonvar", [new Var("X")]),new Term("goal_expansion", [new Var("X"),new Var("X_")])])),
				new Rule(new Term("goal_expansion", [new Term("once", [new Var("X")]),new Term("once", [new Var("X_")])]), new Term(",", [new Term("nonvar", [new Var("X")]),new Term("goal_expansion", [new Var("X"),new Var("X_")])])),
				new Rule(new Term("goal_expansion", [new Term("findall", [new Var("X"),new Var("Y"),new Var("Z")]),new Term("findall", [new Var("X"),new Var("Y_"),new Var("Z")])]), new Term("goal_expansion", [new Var("Y"),new Var("Y_")])),
				new Rule(new Term("goal_expansion", [new Term("setof", [new Var("X"),new Var("Y"),new Var("Z")]),new Term("findall", [new Var("X"),new Var("Y_"),new Var("Z")])]), new Term("goal_expansion", [new Var("Y"),new Var("Y_")])),
				new Rule(new Term("goal_expansion", [new Term("bagof", [new Var("X"),new Var("Y"),new Var("Z")]),new Term("findall", [new Var("X"),new Var("Y_"),new Var("Z")])]), new Term("goal_expansion", [new Var("Y"),new Var("Y_")])),
				new Rule(new Term("goal_expansion", [new Term("call", [new Var("X")]),new Term("call", [new Var("X_")])]), new Term(",", [new Term("nonvar", [new Var("X")]),new Term("goal_expansion", [new Var("X"),new Var("X_")])])),
				new Rule(new Term("goal_expansion", [new Term("call", [new Var("X"),new Var("A1")]),new Term("call", [new Var("F_")])]), new Term(",", [new Term("=..", [new Var("F"),new Term(".", [new Var("X"),new Term(".", [new Var("A1"),new Term("[]", [])])])]),new Term("goal_expansion", [new Var("F"),new Var("F_")])])),
				new Rule(new Term("goal_expansion", [new Term("call", [new Var("X"),new Var("A1"),new Var("A2")]),new Term("call", [new Var("F_")])]), new Term(",", [new Term("=..", [new Var("F"),new Term(".", [new Var("X"),new Term(".", [new Var("A1"),new Term(".", [new Var("A2"),new Term("[]", [])])])])]),new Term("goal_expansion", [new Var("F"),new Var("F_")])])),
				new Rule(new Term("goal_expansion", [new Term("call", [new Var("X"),new Var("A1"),new Var("A2"),new Var("A3")]),new Term("call", [new Var("F_")])]), new Term(",", [new Term("=..", [new Var("F"),new Term(".", [new Var("X"),new Term(".", [new Var("A1"),new Term(".", [new Var("A2"),new Term(".", [new Var("A3"),new Term("[]", [])])])])])]),new Term("goal_expansion", [new Var("F"),new Var("F_")])])),
				new Rule(new Term("goal_expansion", [new Term("call", [new Var("X"),new Var("A1"),new Var("A2"),new Var("A3"),new Var("A4")]),new Term("call", [new Var("F_")])]), new Term(",", [new Term("=..", [new Var("F"),new Term(".", [new Var("X"),new Term(".", [new Var("A1"),new Term(".", [new Var("A2"),new Term(".", [new Var("A3"),new Term(".", [new Var("A4"),new Term("[]", [])])])])])])]),new Term("goal_expansion", [new Var("F"),new Var("F_")])])),
				new Rule(new Term("goal_expansion", [new Term("call", [new Var("X"),new Var("A1"),new Var("A2"),new Var("A3"),new Var("A4"),new Var("A5")]),new Term("call", [new Var("F_")])]), new Term(",", [new Term("=..", [new Var("F"),new Term(".", [new Var("X"),new Term(".", [new Var("A1"),new Term(".", [new Var("A2"),new Term(".", [new Var("A3"),new Term(".", [new Var("A4"),new Term(".", [new Var("A5"),new Term("[]", [])])])])])])])]),new Term("goal_expansion", [new Var("F"),new Var("F_")])])),
				new Rule(new Term("goal_expansion", [new Term("call", [new Var("X"),new Var("A1"),new Var("A2"),new Var("A3"),new Var("A4"),new Var("A5"),new Var("A6")]),new Term("call", [new Var("F_")])]), new Term(",", [new Term("=..", [new Var("F"),new Term(".", [new Var("X"),new Term(".", [new Var("A1"),new Term(".", [new Var("A2"),new Term(".", [new Var("A3"),new Term(".", [new Var("A4"),new Term(".", [new Var("A5"),new Term(".", [new Var("A6"),new Term("[]", [])])])])])])])])]),new Term("goal_expansion", [new Var("F"),new Var("F_")])])),
				new Rule(new Term("goal_expansion", [new Term("call", [new Var("X"),new Var("A1"),new Var("A2"),new Var("A3"),new Var("A4"),new Var("A5"),new Var("A6"),new Var("A7")]),new Term("call", [new Var("F_")])]), new Term(",", [new Term("=..", [new Var("F"),new Term(".", [new Var("X"),new Term(".", [new Var("A1"),new Term(".", [new Var("A2"),new Term(".", [new Var("A3"),new Term(".", [new Var("A4"),new Term(".", [new Var("A5"),new Term(".", [new Var("A6"),new Term(".", [new Var("A7"),new Term("[]", [])])])])])])])])])]),new Term("goal_expansion", [new Var("F"),new Var("F_")])]))
			],



			// ATTRIBUTED VARIABLES
			
			//put_attr/3
			"put_attr/3": function( thread, point, atom ) {
				var variable = atom.args[0], module = atom.args[1], value = atom.args[2];
				if( !pl.type.is_variable(variable) ) {
					thread.throw_error( pl.error.type( "variable", variable, atom.indicator ) );
				} else if( !pl.type.is_atom(module) ) {
					thread.throw_error( pl.error.type( "atom", module, atom.indicator ) );
				} else {
					var subs = point.substitution.set_attribute( variable.id, module, value );
					thread.prepend( [new State( point.goal.replace(null), subs, point )] );
				}
			},

			// get_attr/3
			"get_attr/3": function( thread, point, atom ) {
				var variable = atom.args[0], module = atom.args[1], value = atom.args[2];
				if( !pl.type.is_variable(variable) ) {
					thread.throw_error( pl.error.type( "variable", variable, atom.indicator ) );
				} else if( !pl.type.is_atom(module) ) {
					thread.throw_error( pl.error.type( "atom", module, atom.indicator ) );
				} else {
					var attr = point.substitution.get_attribute( variable.id, module );
					if( attr ) {
						thread.prepend( [new State(
							point.goal.replace( new Term("=", [value, attr]) ),
							point.substitution,
							point
						)] );
					}
				}
			},


			
			// INPUT AND OUTPUT
			
			// op/3
			"op/3": function( thread, point, atom ) {
				if( pl.directive["op/3"]( thread, atom ) )
					thread.success( point );
			},
			
			// current_op/3
			"current_op/3": function( thread, point, atom ) {
				var priority = atom.args[0], specifier = atom.args[1], operator = atom.args[2];
				var points = [];
				for( var p in thread.session.__operators )
					for( var o in thread.session.__operators[p] )
						for( var i = 0; i < thread.session.__operators[p][o].length; i++ )
							points.push( new State(
								point.goal.replace(
									new Term( ",", [
										new Term( "=", [new Num( p, false ), priority] ),
										new Term( ",", [
											new Term( "=", [new Term( thread.session.__operators[p][o][i], [] ), specifier] ),
											new Term( "=", [new Term( o, [] ), operator] )
										] )
									] )
								),
								point.substitution,
								point
							) );
				thread.prepend( points );
			},
		


			// LOGIC AND CONTROL STRUCTURES
		
			// ;/2 (disjunction)
			";/2": function( thread, point, atom ) {
				var left = atom.args[0], right = atom.args[1];
				if( pl.type.is_term( left ) && left.indicator === "->/2" ) {
					var cond = left.args[0], then = left.args[1], otherwise = right;
					var goal_fst = point.goal.replace( new Term( ",", [cond, new Term( ",", [new Term( "!" ), then] )] ) );
					var goal_snd = point.goal.replace( new Term( ",", [new Term( "!" ), otherwise] ) );
					thread.prepend( [
						new State( goal_fst, point.substitution, point ),
						new State( goal_snd, point.substitution, point )
					] );
				} else {
					thread.prepend([
						new State( point.goal.replace( left ), point.substitution, point ),
						new State( point.goal.replace( right ), point.substitution, point )
					]);
				}
			},
			
			// !/0 (cut)
			"!/0": function( thread, point, atom ) {
				var parent_cut, last_cut, states = [];
				parent_cut = point;
				last_cut = null;
				while( parent_cut.parent !== null && parent_cut.parent.goal.search( atom ) ) {
					last_cut = parent_cut;
					parent_cut = parent_cut.parent;
					if(parent_cut.goal !== null) {
						var selected = parent_cut.goal.select();
						if( selected && selected.id === "call" && selected.search(atom) ) {
							parent_cut = last_cut;
							break;
						}
					}
				}
				for( var i = thread.points.length-1; i >= 0; i-- ) {
					var state = thread.points[i];
					var node = state.parent;
					while( node !== null && node !== parent_cut.parent ) {
						node = node.parent;
					}
					if( node === null && node !== parent_cut.parent )
						states.push( state );
				}
				thread.points = states.reverse();
				thread.success( point );
			},
			
			// \+ (negation)
			"\\+/1": function( thread, point, atom ) {
				var goal = atom.args[0];
				if( pl.type.is_variable( goal ) ) {
					thread.throw_error( pl.error.instantiation( thread.level ) );
				} else if( !pl.type.is_callable( goal ) ) {
					thread.throw_error( pl.error.type( "callable", goal, thread.level ) );
				} else {
					// TRANSPARENT VERSION OF THE NEGATION
					/*var neg_thread;
					if(point.negation_thread) {
						neg_thread = point.negation_thread;
					} else {
						neg_thread = new Thread( thread.session );
						neg_thread.add_goal( goal );
						point.negation_thread = neg_thread;
					}
					neg_thread.answer( function( answer ) {
						if(answer === false) {
							thread.success( point );
						} else if(pl.type.is_error( answer )) {
							thread.throw_error( answer.args[0] );
						} else if(answer === null) {
							thread.prepend( [point] );
							thread.current_limit = 0;
						}
						thread.again( answer !== null );
					} );
					return true;*/
					
					// '\+'(X) :- call(X), !, fail.
					// '\+'(_).
					thread.prepend( [
						new State( point.goal.replace( new Term( ",", [new Term( ",", [ new Term( "call", [goal] ), new Term( "!", [] ) ] ), new Term( "fail", [] ) ] ) ), point.substitution, point ),
						new State( point.goal.replace( null ), point.substitution, point )
					] );
				}
			},
			
			// ->/2 (implication)
			"->/2": function( thread, point, atom ) {
				var cond = atom.args[0], then = atom.args[1];
				var goal = point.goal.replace( new Term( ",", [cond, new Term( ",", [new Term( "!" ), then] )] ) );
				thread.prepend( [new State( goal, point.substitution, point )] );
			},
			
			// fail/0
			"fail/0": function( _1, _2, _3 ) {},
			
			// false/0
			"false/0": function( _1, _2, _3 ) {},
			
			// true/0
			"true/0": function( thread, point, _ ) {
				thread.success( point );
			},
			
			// call/1..8
			"call/1": callN(1),
			"call/2": callN(2),
			"call/3": callN(3),
			"call/4": callN(4),
			"call/5": callN(5),
			"call/6": callN(6),
			"call/7": callN(7),
			"call/8": callN(8),
			
			// once/1
			"once/1": function( thread, point, atom ) {
				var goal = atom.args[0];
				thread.prepend( [new State( point.goal.replace( new Term( ",", [new Term( "call", [goal] ), new Term( "!", [] )] ) ), point.substitution, point )] );
			},
			
			// forall/2
			"forall/2": function( thread, point, atom ) {
				var generate = atom.args[0], test = atom.args[1];
				thread.prepend( [new State( point.goal.replace( new Term( "\\+", [new Term( ",", [new Term( "call", [generate] ), new Term( "\\+", [new Term( "call", [test] )] )] )] ) ), point.substitution, point )] );
			},
			
			// repeat/0
			"repeat/0": function( thread, point, _ ) {
				thread.prepend( [new State( point.goal.replace( null ), point.substitution, point ), point] );
			},
			
			// EXCEPTIONS
			
			// throw/1
			"throw/1": function( thread, point, atom ) {
				if( pl.type.is_variable( atom.args[0] ) ) {
					thread.throw_error( pl.error.instantiation( thread.level ) );
				} else {
					thread.throw_error( atom.args[0] );
				}
			},
			
			// catch/3
			"catch/3": function( thread, point, atom ) {
				var points = thread.points;
				thread.points = [];
				thread.prepend( [new State( atom.args[0], point.substitution, point )] );
				var format_success = thread.session.format_success;
				var format_error = thread.session.format_error;
				thread.session.format_success = function(x) { return x.substitution; };
				thread.session.format_error = function(x) { return x.goal; };
				var callback = function( answer ) {
					var call_points = thread.points;
					thread.points = points;
					thread.session.format_success = format_success;
					thread.session.format_error = format_error;
					if( pl.type.is_error( answer ) ) {
						var states = [];
						for( var i = thread.points.length-1 ; i >= 0; i-- ) {
							var state = thread.points[i];
							var node = state.parent;
							while( node !== null && node !== point.parent ) {
								node = node.parent;
							}
							if( node === null && node !== point.parent )
								states.push( state );
						}
						thread.points = states;
						var occurs_check = thread.get_flag( "occurs_check" ).indicator === "true/0";
						var state = new State();
						var mgu = pl.unify( answer.args[0], atom.args[1], occurs_check );
						if( mgu !== null ) {
							state.substitution = point.substitution.apply( mgu );
							state.goal = point.goal.replace( atom.args[2] ).apply( mgu );
							state.parent = point;
							thread.prepend( [state] );
						} else {
							thread.throw_error( answer.args[0] );
						}
					} else if( answer !== false ) {
						var answer_state = answer === null ? [] : [new State(
							point.goal.apply( answer ).replace( null ),
							point.substitution.apply( answer ),
							point
						)];
						var filter_points = [];
						for( var i = call_points.length-1; i >= 0; i-- ) {
							filter_points.push( call_points[i] );
							var selected = call_points[i].goal !== null ? call_points[i].goal.select() : null;
							if( pl.type.is_term( selected ) && selected.indicator === "!/0" )
								break;
						}
						var catch_points = map( filter_points, function( state ) {
							if( state.goal === null )
								state.goal = new Term( "true", [] );
							state = new State(
								point.goal.replace( new Term( "catch", [state.goal, atom.args[1], atom.args[2]] ) ),
								point.substitution.apply( state.substitution ),
								state.parent
							);
							state.exclude = atom.args[0].variables();
							return state;
						} ).reverse();
						thread.prepend( catch_points );
						thread.prepend( answer_state );
						if( answer === null ) {
							this.current_limit = 0;
							thread.__calls.shift()( null );
						}
					}
				};
				thread.__calls.unshift( callback );
			},
			
			// UNIFICATION
			
			// =/2 (unification)
			"=/2": function( thread, point, atom ) {
				var occurs_check = thread.get_flag( "occurs_check" ).indicator === "true/0";
				var state = new State();
				var mgu = pl.unify( atom.args[0], atom.args[1], occurs_check );
				if( mgu !== null ) {
					state.goal = point.goal.apply( mgu ).replace( null );
					state.substitution = point.substitution.apply( mgu );
					state.parent = point;
					thread.prepend( [state] );
				}
			},
			
			// unify_with_occurs_check/2
			"unify_with_occurs_check/2": function( thread, point, atom ) {
				var state = new State();
				var mgu = pl.unify( atom.args[0], atom.args[1], true );
				if( mgu !== null ) {
					state.goal = point.goal.apply( mgu ).replace( null );
					state.substitution = point.substitution.apply( mgu );
					state.parent = point;
					thread.prepend( [state] );
				}
			},
			
			// \=/2
			"\\=/2": function( thread, point, atom ) {
				var occurs_check = thread.get_flag( "occurs_check" ).indicator === "true/0";
				var mgu = pl.unify( atom.args[0], atom.args[1], occurs_check );
				if( mgu === null ) {
					thread.success( point );
				}
			},
			
			// subsumes_term/2
			"subsumes_term/2": function( thread, point, atom ) {
				var occurs_check = thread.get_flag( "occurs_check" ).indicator === "true/0";
				var mgu = pl.unify( atom.args[1], atom.args[0], occurs_check );
				if( mgu !== null && atom.args[1].apply( mgu ).equals( atom.args[1] ) ) {
					thread.success( point );
				}
			},
			
			// ALL SOLUTIONS

			// findall/3
			"findall/3": function( thread, point, atom ) {
				var template = atom.args[0], goal = atom.args[1], instances = atom.args[2];
				if( pl.type.is_variable( goal ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_callable( goal ) ) {
					thread.throw_error( pl.error.type( "callable", goal, atom.indicator ) );
				} else if( !pl.type.is_variable( instances ) && !pl.type.is_list( instances ) ) {
					thread.throw_error( pl.error.type( "list", instances, atom.indicator ) );
				} else {
					var variable = thread.next_free_variable();
					var newGoal = new Term( ",", [goal, new Term( "=", [variable, template] )] );
					var nthread = new Thread(thread.session);
					nthread.debugger = thread.debugger;
					nthread.format_success = function(state) { return state.substitution; };
					nthread.format_error = function(state) { return state.goal; };
					nthread.add_goal( newGoal, true, point );
					nthread.head_point().parent = point;
					var answers = [];
					var callback = function( answer ) {
						if( answer !== false && answer !== null && !pl.type.is_error( answer ) ) {
							answers.push( answer.links[variable.id] );
							nthread.answer(callback);
						} else {
							var reset_limit = true;
							if( pl.type.is_error( answer ) ) {
								thread.throw_error( answer.args[0] );
							} else if( nthread.current_limit > 0 ) {
								var list = arrayToList(answers);
								thread.prepend( [new State(
									point.goal.replace( new Term( "=", [instances, list] ) ),
									point.substitution,
									point
								)] );
							} else {
								thread.prepend( [point] );
								thread.current_limit = 0;
								reset_limit = false;
							}
							if(reset_limit && nthread.debugger)
								thread.debugger_states = thread.debugger_states.concat(nthread.debugger_states);
							thread.again(reset_limit);
						}
					};
					nthread.answer(callback);
					return true;
				}
			},
			
			// bagof/3
			"bagof/3": function( thread, point, atom ) {
				var answer, template = atom.args[0], goal = atom.args[1], instances = atom.args[2];
				if( pl.type.is_variable( goal ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_callable( goal ) ) {
					thread.throw_error( pl.error.type( "callable", goal, atom.indicator ) );
				} else if( !pl.type.is_variable( instances ) && !pl.type.is_list( instances ) ) {
					thread.throw_error( pl.error.type( "list", instances, atom.indicator ) );
				} else {
					var variable = thread.next_free_variable();
					var template_vars;
					if( goal.indicator === "^/2" ) {
						template_vars = goal.args[0].variables();
						goal = goal.args[1];
					} else {
						template_vars = [];
					}
					template_vars = template_vars.concat( template.variables() );
					var free_vars = goal.variables().filter( function( v ){
						return indexOf( template_vars, v ) === -1;
					} );
					var list_vars = new Term( "[]" );
					for( var i = free_vars.length - 1; i >= 0; i-- ) {
						list_vars = new Term( ".", [ new Var( free_vars[i] ), list_vars ] );
					}
					var newGoal = new Term( ",", [goal, new Term( "=", [variable, new Term( ",", [list_vars, template] )] )] );
					var nthread = new Thread(thread.session);
					nthread.debugger = thread.debugger;
					nthread.format_success = function(state) { return state.substitution; };
					nthread.format_error = function(state) { return state.goal; };
					nthread.add_goal( newGoal, true, point );
					nthread.head_point().parent = point;
					var answers = [];
					var callback = function( answer ) {
						if( answer !== false && answer !== null && !pl.type.is_error( answer ) ) {
							var match = false;
							var arg_vars = answer.links[variable.id].args[0];
							var arg_template = answer.links[variable.id].args[1];
							for( var _elem in answers ) {
								if(!answers.hasOwnProperty(_elem)) continue;
								var elem = answers[_elem];
								if( elem.variables.equals( arg_vars ) ) {
									elem.answers.push( arg_template );
									match = true;
									break;
								}
							}
							if( !match )
								answers.push( {variables: arg_vars, answers: [arg_template]} );
							nthread.answer(callback);
						} else {
							reset_limit = true;
							if( pl.type.is_error( answer ) ) {
								thread.throw_error( answer.args[0] );
							} else if( thread.current_limit > 0 ) {
								var states = [];
								for( var i = 0; i < answers.length; i++ ) {
									answer = answers[i].answers;
									var list = arrayToList(answer);
									states.push( new State(
										point.goal.replace( new Term( ",", [new Term( "=", [list_vars, answers[i].variables] ), new Term( "=", [instances, list] )] ) ),
										point.substitution,
										point
									) );
								}
								thread.prepend( states );
							} else {
								thread.prepend( [point] );
								thread.current_limit = 0;
								reset_limit = false;
							}
							if(reset_limit && nthread.debugger)
								thread.debugger_states = thread.debugger_states.concat(nthread.debugger_states);
							thread.again(reset_limit);
						}
					};
					nthread.answer(callback);
					return true;
				}
			},

			// setof/3
			"setof/3": function( thread, point, atom ) {
				var answer, template = atom.args[0], goal = atom.args[1], instances = atom.args[2];
				if( pl.type.is_variable( goal ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_callable( goal ) ) {
					thread.throw_error( pl.error.type( "callable", goal, atom.indicator ) );
				} else if( !pl.type.is_variable( instances ) && !pl.type.is_list( instances ) ) {
					thread.throw_error( pl.error.type( "list", instances, atom.indicator ) );
				} else {
					var variable = thread.next_free_variable();
					var template_vars;
					if( goal.indicator === "^/2" ) {
						template_vars = goal.args[0].variables();
						goal = goal.args[1];
					} else {
						template_vars = [];
					}
					template_vars = template_vars.concat( template.variables() );
					var free_vars = goal.variables().filter( function( v ){
						return indexOf( template_vars, v ) === -1;
					} );
					var list_vars = new Term( "[]" );
					for( var i = free_vars.length - 1; i >= 0; i-- ) {
						list_vars = new Term( ".", [ new Var( free_vars[i] ), list_vars ] );
					}
					var newGoal = new Term( ",", [goal, new Term( "=", [variable, new Term( ",", [list_vars, template] )] )] );
					var nthread = new Thread(thread.session);
					nthread.debugger = thread.debugger;
					nthread.format_success = function(state) { return state.substitution; };
					nthread.format_error = function(state) { return state.goal; };
					nthread.add_goal( newGoal, true, point );
					nthread.head_point().parent = point;
					var answers = [];
					var callback = function( answer ) {
						if( answer !== false && answer !== null && !pl.type.is_error( answer ) ) {
							var match = false;
							var arg_vars = answer.links[variable.id].args[0];
							var arg_template = answer.links[variable.id].args[1];
							for( var _elem in answers ) {
								if(!answers.hasOwnProperty(_elem)) continue;
								var elem = answers[_elem];
								if( elem.variables.equals( arg_vars ) ) {
									elem.answers.push( arg_template );
									match = true;
									break;
								}
							}
							if( !match )
								answers.push( {variables: arg_vars, answers: [arg_template]} );
							nthread.answer(callback);
						} else {
							reset_limit = true;
							if( pl.type.is_error( answer ) ) {
								thread.throw_error( answer.args[0] );
							} else if( thread.current_limit > 0 ) {
								var states = [];
								for( var i = 0; i < answers.length; i++ ) {
									answer = answers[i].answers.sort( pl.compare );
									var list = arrayToList(answer);
									states.push( new State(
										point.goal.replace( new Term( ",", [new Term( "=", [list_vars, answers[i].variables] ), new Term( "=", [instances, list] )] ) ),
										point.substitution,
										point
									) );
								}
								thread.prepend( states );
							} else {
								thread.prepend( [point] );
								thread.current_limit = 0;
								reset_limit = false;
							}
							if(reset_limit && nthread.debugger)
								thread.debugger_states = thread.debugger_states.concat(nthread.debugger_states);
							thread.again(reset_limit);
						}
					};
					nthread.answer(callback);
					return true;
				}
			},
			
			// TERM CREATION AND DECOMPOSITION
			
			// functor/3
			"functor/3": function( thread, point, atom ) {
				var subs;
				var term = atom.args[0], name = atom.args[1], arity = atom.args[2];
				if( pl.type.is_variable( term ) && (pl.type.is_variable( name ) || pl.type.is_variable( arity )) ) {
					thread.throw_error( pl.error.instantiation( "functor/3" ) );
				} else if( !pl.type.is_variable( arity ) && !pl.type.is_integer( arity ) ) {
					thread.throw_error( pl.error.type( "integer", atom.args[2], "functor/3" ) );
				} else if( !pl.type.is_variable( name ) && !pl.type.is_atomic( name ) ) {
					thread.throw_error( pl.error.type( "atomic", atom.args[1], "functor/3" ) );
				} else if( pl.type.is_integer( name ) && pl.type.is_integer( arity ) && arity.value !== 0 ) {
					thread.throw_error( pl.error.type( "atom", atom.args[1], "functor/3" ) );
				} else if( pl.type.is_variable( term ) ) {
					if( atom.args[2].value >= 0 ) {
						var args = [];
						for( var i = 0; i < arity.value; i++ )
							args.push( thread.next_free_variable() );
						var functor = pl.type.is_integer( name ) ? name : new Term( name.id, args );
						thread.prepend( [new State( point.goal.replace( new Term( "=", [term, functor] ) ), point.substitution, point )] );
					}
				} else {
					var id = pl.type.is_integer( term ) ? term : new Term( term.id, [] );
					var length = pl.type.is_integer( term ) ? new Num( 0, false ) : new Num( term.args.length, false );
					var goal = new Term( ",", [new Term( "=", [id, name] ), new Term( "=", [length, arity] )] );
					thread.prepend( [new State( point.goal.replace( goal ), point.substitution, point )] );
				}
			},
			
			// arg/3
			"arg/3": function( thread, point, atom ) {
				if( pl.type.is_variable( atom.args[0] ) || pl.type.is_variable( atom.args[1] ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( atom.args[0].value < 0 ) {
					thread.throw_error( pl.error.domain( "not_less_than_zero", atom.args[0], atom.indicator ) );
				} else if( !pl.type.is_compound( atom.args[1] ) ) {
					thread.throw_error( pl.error.type( "compound", atom.args[1], atom.indicator ) );
				} else {
					var n = atom.args[0].value;
					if( n > 0 && n <= atom.args[1].args.length ) {
						var goal = new Term( "=", [atom.args[1].args[n-1], atom.args[2]] );
						thread.prepend( [new State( point.goal.replace( goal ), point.substitution, point )] );
					}
				}
			},
			
			// =../2 (univ)
			"=../2": function( thread, point, atom ) {
				var list;
				if( pl.type.is_variable( atom.args[0] ) && (pl.type.is_variable( atom.args[1] )
				|| pl.type.is_non_empty_list( atom.args[1] ) && pl.type.is_variable( atom.args[1].args[0] )) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_fully_list( atom.args[1] ) ) {
					thread.throw_error( pl.error.type( "list", atom.args[1], atom.indicator ) );
				} else if( !pl.type.is_variable( atom.args[0] ) ) {
					if( pl.type.is_atomic( atom.args[0] ) ) {
						list = new Term( ".", [atom.args[0], new Term( "[]" )] );
					} else {
						list = new Term( "[]" );
						for( var i = atom.args[0].args.length - 1; i >= 0; i-- ) {
							list = new Term( ".", [atom.args[0].args[i], list] );
						}
						list = new Term( ".", [new Term( atom.args[0].id ), list] );
					}
					thread.prepend( [new State( point.goal.replace( new Term( "=", [list, atom.args[1]] ) ), point.substitution, point )] );
				} else if( !pl.type.is_variable( atom.args[1] ) ) {
					var args = [];
					list = atom.args[1].args[1];
					while( list.indicator === "./2" ) {
						args.push( list.args[0] );
						list = list.args[1];
					}
					if( pl.type.is_variable( atom.args[0] ) && pl.type.is_variable( list ) ) {
						thread.throw_error( pl.error.instantiation( atom.indicator ) );
					} else if( args.length === 0 && pl.type.is_compound( atom.args[1].args[0] ) ) {
						thread.throw_error( pl.error.type( "atomic", atom.args[1].args[0], atom.indicator ) );
					} else if( args.length > 0 && (pl.type.is_compound( atom.args[1].args[0] ) || pl.type.is_number( atom.args[1].args[0] )) ) {
						thread.throw_error( pl.error.type( "atom", atom.args[1].args[0], atom.indicator ) );
					} else {
						if( args.length === 0 ) {
							thread.prepend( [new State( point.goal.replace( new Term( "=", [atom.args[1].args[0], atom.args[0]], point ) ), point.substitution, point )] );
						} else {
							thread.prepend( [new State( point.goal.replace( new Term( "=", [new Term( atom.args[1].args[0].id, args ), atom.args[0]] ) ), point.substitution, point )] );
						}
					}
				}
			},
			
			// copy_term/2
			"copy_term/2": function( thread, point, atom ) {
				var renamed = atom.args[0].rename( thread );
				thread.prepend( [new State( point.goal.replace( new Term( "=", [renamed, atom.args[1]] ) ), point.substitution, point.parent )] );
			},
			
			// term_variables/2
			"term_variables/2": function( thread, point, atom ) {
				var term = atom.args[0], vars = atom.args[1];
				if( !pl.type.is_fully_list( vars ) ) {
					thread.throw_error( pl.error.type( "list", vars, atom.indicator ) );
				} else {
					var list = arrayToList( map( nub( term.variables() ), function(v) {
						return new Var(v);
					} ) );
					thread.prepend( [new State( point.goal.replace( new Term( "=", [vars, list] ) ), point.substitution, point )] );
				}
			},
			
			// CLAUSE RETRIEVAL AND INFORMATION
			
			// clause/2
			"clause/2": function( thread, point, atom ) {
				if( pl.type.is_variable( atom.args[0] ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_callable( atom.args[0] ) ) {
					thread.throw_error( pl.error.type( "callable", atom.args[0], atom.indicator ) );
				} else if( !pl.type.is_variable( atom.args[1] ) && !pl.type.is_callable( atom.args[1] ) ) {
					thread.throw_error( pl.error.type( "callable", atom.args[1], atom.indicator ) );
				} else if( thread.session.rules[atom.args[0].indicator] !== undefined ) {
					if( thread.is_public_predicate( atom.args[0].indicator ) ) {
						var states = [];
						for( var _rule in thread.session.rules[atom.args[0].indicator] ) {
							if(!thread.session.rules[atom.args[0].indicator].hasOwnProperty(_rule)) continue;
							var rule = thread.session.rules[atom.args[0].indicator][_rule];
							thread.session.renamed_variables = {};
							rule = rule.rename( thread );
							if( rule.body === null ) {
								rule.body = new Term( "true" );
							}
							var goal = new Term( ",", [new Term( "=", [rule.head, atom.args[0]] ), new Term( "=", [rule.body, atom.args[1]] )] );
							states.push( new State( point.goal.replace( goal ), point.substitution, point ) );
						}
						thread.prepend( states );
					} else {
						thread.throw_error( pl.error.permission( "access", "private_procedure", atom.args[0].indicator, atom.indicator ) );
					}
				}
			},
			
			// current_predicate/1
			"current_predicate/1": function( thread, point, atom ) {
				var indicator = atom.args[0];
				if( !pl.type.is_variable( indicator ) && (!pl.type.is_compound( indicator ) || indicator.indicator !== "//2") ) {
					thread.throw_error( pl.error.type( "predicate_indicator", indicator, atom.indicator ) );
				} else if( !pl.type.is_variable( indicator ) && !pl.type.is_variable( indicator.args[0] ) && !pl.type.is_atom( indicator.args[0] ) ) {
					thread.throw_error( pl.error.type( "atom", indicator.args[0], atom.indicator ) );
				} else if( !pl.type.is_variable( indicator ) && !pl.type.is_variable( indicator.args[1] ) && !pl.type.is_integer( indicator.args[1] ) ) {
					thread.throw_error( pl.error.type( "integer", indicator.args[1], atom.indicator ) );
				} else {
					var states = [];
					for( var i in thread.session.rules ) {
						if(!thread.session.rules.hasOwnProperty(i)) continue;
						var index = i.lastIndexOf( "/" );
						var name = i.substr( 0, index );
						var arity = parseInt( i.substr( index+1, i.length-(index+1) ) );
						var predicate = new Term( "/", [new Term( name ), new Num( arity, false )] );
						var goal = new Term( "=", [predicate, indicator] );
						states.push( new State( point.goal.replace( goal ), point.substitution, point ) );
					}
					thread.prepend( states );
				}
			},

			// listing/0
			"listing/0": function( thread, point, atom ) {
				var from_module = atom.from_module ? atom.from_module : "user";
				var rules;
				if(from_module === "user") {
					rules = thread.session.rules;
				} else {
					if(pl.module[from_module])
						rules = pl.module[from_module].rules;
					else
						rules = {};
				}
				var str = "";
				for(var indicator in rules) {
					if(!rules.hasOwnProperty(indicator)) continue;
					var predicate = rules[indicator];
					str += "% " + indicator + "\n";
					if(predicate instanceof Array) {
						for(var i = 0; i < predicate.length; i++)
							str += predicate[i].toString( {session: thread.session} ) + "\n";
					} else {
						str += "/*\n" + predicate.toString() + "\n*/";
					}
					str += "\n";
				}
                // @blab+
                thread.session.result=str;
				if (!thread.session.silent) thread.prepend( [new State(
					point.goal.replace(new Term("write", [new Term(str, [])])),
					point.substitution,
					point
				)] );
			},

			// listing/1
			"listing/1": function( thread, point, atom ) {
				var indicator = atom.args[0];
				if(pl.type.is_variable(indicator)) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if(!pl.type.is_predicate_indicator(indicator)) {
					thread.throw_error( pl.error.type( "predicate_indicator", indicator, atom.indicator ) );
				} else {
					var from_module = atom.from_module ? atom.from_module : "user";
					var rules;
					if(from_module === "user") {
						rules = thread.session.rules;
					} else {
						if(pl.module[from_module])
							rules = pl.module[from_module].rules;
						else
							rules = {};
					}
					var str = "";
					var str_indicator = indicator.args[0].id + "/" + indicator.args[1].value;
					if(rules.hasOwnProperty(str_indicator)) {
						var predicate = rules[str_indicator];
						if(predicate instanceof Array) {
							for(var i = 0; i < predicate.length; i++)
								str += predicate[i].toString( {session: thread.session} ) + "\n";
						} else {
							str += "/*\n" + predicate.toString() + "\n*/";
						}
						str += "\n";
					}

                    // @blab+
                    thread.session.result=str;
					if (!thread.session.silent) thread.prepend( [new State(
						point.goal.replace(new Term("write", [new Term(str, [])])),
						point.substitution,
						point
					)] );
				}
			},			
			// CLAUSE CREATION AND DESTRUCTION
			
			// asserta/1
			"asserta/1": function( thread, point, atom ) {
				if( pl.type.is_variable( atom.args[0] ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_callable( atom.args[0] ) ) {
					thread.throw_error( pl.error.type( "callable", atom.args[0], atom.indicator ) );
				} else {
					var head, body;
					if( atom.args[0].indicator === ":-/2" ) {
						head = atom.args[0].args[0];
						body = body_conversion( atom.args[0].args[1] );
					} else {
						head = atom.args[0];
						body = null;
					}
					if( !pl.type.is_callable( head ) ) {
						thread.throw_error( pl.error.type( "callable", head, atom.indicator ) );
					} else if( body !== null && !pl.type.is_callable( body ) ) {
						thread.throw_error( pl.error.type( "callable", body, atom.indicator ) );
					} else if( thread.is_public_predicate( head.indicator ) ) {
						if( thread.session.rules[head.indicator] === undefined ) {
							thread.session.rules[head.indicator] = [];
						}
						thread.session.public_predicates[head.indicator] = true;
						thread.session.rules[head.indicator] = [new Rule( head, body, true )].concat( thread.session.rules[head.indicator] );
						thread.success( point );
					} else {
						thread.throw_error( pl.error.permission( "modify", "static_procedure", head.indicator, atom.indicator ) );
					}
				}
			},
			
			// assertz/1
			"assertz/1": function( thread, point, atom ) {
				if( pl.type.is_variable( atom.args[0] ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_callable( atom.args[0] ) ) {
					thread.throw_error( pl.error.type( "callable", atom.args[0], atom.indicator ) );
				} else {
					var head, body;
					if( atom.args[0].indicator === ":-/2" ) {
						head = atom.args[0].args[0];
						body = body_conversion( atom.args[0].args[1] );
					} else {
						head = atom.args[0];
						body = null;
					}
					if( !pl.type.is_callable( head ) ) {
						thread.throw_error( pl.error.type( "callable", head, atom.indicator ) );
					} else if( body !== null && !pl.type.is_callable( body ) ) {
						thread.throw_error( pl.error.type( "callable", body, atom.indicator ) );
					} else if( thread.is_public_predicate( head.indicator ) ) {
						if( thread.session.rules[head.indicator] === undefined ) {
							thread.session.rules[head.indicator] = [];
						}
						thread.session.public_predicates[head.indicator] = true;
						thread.session.rules[head.indicator].push( new Rule( head, body, true ) );
						thread.success( point );
					} else {
						thread.throw_error( pl.error.permission( "modify", "static_procedure", head.indicator, atom.indicator ) );
					}
				}
			},
			
			// retract/1
			"retract/1": function( thread, point, atom ) {
				if( pl.type.is_variable( atom.args[0] ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_callable( atom.args[0] ) ) {
					thread.throw_error( pl.error.type( "callable", atom.args[0], atom.indicator ) );
				} else {
					var head, body;
					if( atom.args[0].indicator === ":-/2" ) {
						head = atom.args[0].args[0];
						body = atom.args[0].args[1];
					} else {
						head = atom.args[0];
						body = new Term( "true" );
					}
					if( typeof point.retract === "undefined" ) {
						if( thread.is_public_predicate( head.indicator ) ) {
							if( thread.session.rules[head.indicator] !== undefined ) {
								var states = [];
								for( var i = 0; i < thread.session.rules[head.indicator].length; i++ ) {
									thread.session.renamed_variables = {};
									var orule = thread.session.rules[head.indicator][i];
									var rule = orule.rename( thread );
									if( rule.body === null )
										rule.body = new Term( "true", [] );
									var occurs_check = thread.get_flag( "occurs_check" ).indicator === "true/0";
									var mgu = pl.unify( new Term( ",", [head, body] ), new Term( ",", [rule.head, rule.body] ), occurs_check );
									if( mgu !== null ) {
										var state = new State( point.goal.replace( new Term(",", [
											new Term( "retract", [ new Term( ":-", [head, body] ) ] ),
											new Term( ",", [
												new Term( "=", [head, rule.head] ),
												new Term( "=", [body, rule.body] )
											] )
										] ) ), point.substitution, point );
										state.retract = orule;
										states.push( state );
									}
								}
								thread.prepend( states );
							}
						} else {
							thread.throw_error( pl.error.permission( "modify", "static_procedure", head.indicator, atom.indicator ) );
						}
					} else {
						retract( thread, point, head.indicator, point.retract );
					}
				}
			},
			
			// retractall/1
			"retractall/1": function( thread, point, atom ) {
				var head = atom.args[0];
				if( pl.type.is_variable( head ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_callable( head ) ) {
					thread.throw_error( pl.error.type( "callable", head, atom.indicator ) );
				} else {
				thread.prepend( [
						new State( point.goal.replace( new Term( ",", [
							new Term( "retract", [new pl.type.Term( ":-", [head, new Var( "_" )] )] ),
							new Term( "fail", [] )
						] ) ), point.substitution, point ),
						new State( point.goal.replace( null ), point.substitution, point )
					] );
				}
			},

			// abolish/1
			"abolish/1": function( thread, point, atom ) {
				if( pl.type.is_variable( atom.args[0] ) || pl.type.is_term( atom.args[0] ) && atom.args[0].indicator === "//2"
				&& (pl.type.is_variable( atom.args[0].args[0] ) || pl.type.is_variable( atom.args[0].args[1] )) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_term( atom.args[0] ) || atom.args[0].indicator !== "//2" ) {
					thread.throw_error( pl.error.type( "predicate_indicator", atom.args[0], atom.indicator ) );
				} else if( !pl.type.is_atom( atom.args[0].args[0] ) ) {
					thread.throw_error( pl.error.type( "atom", atom.args[0].args[0], atom.indicator ) );
				} else if( !pl.type.is_integer( atom.args[0].args[1] ) ) {
					thread.throw_error( pl.error.type( "integer", atom.args[0].args[1], atom.indicator ) );
				} else if( atom.args[0].args[1].value < 0 ) {
					thread.throw_error( pl.error.domain( "not_less_than_zero", atom.args[0].args[1], atom.indicator ) );
				} else if( pl.type.is_number(thread.get_flag( "max_arity" )) && atom.args[0].args[1].value > thread.get_flag( "max_arity" ).value ) {
					thread.throw_error( pl.error.representation( "max_arity", atom.indicator ) );
				} else {
					var indicator = atom.args[0].args[0].id + "/" + atom.args[0].args[1].value;
					if( thread.is_public_predicate( indicator ) ) {
						delete thread.session.rules[indicator];
						thread.success( point );
					} else {
						thread.throw_error( pl.error.permission( "modify", "static_procedure", indicator, atom.indicator ) );
					}
				}
			},
			
			// ATOM PROCESSING
			
			// atom_length/2
			"atom_length/2": function( thread, point, atom ) {
				if( pl.type.is_variable( atom.args[0] ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_atom( atom.args[0] ) ) {
					thread.throw_error( pl.error.type( "atom", atom.args[0], atom.indicator ) );
				} else if( !pl.type.is_variable( atom.args[1] ) && !pl.type.is_integer( atom.args[1] ) ) {
					thread.throw_error( pl.error.type( "integer", atom.args[1], atom.indicator ) );
				} else if( pl.type.is_integer( atom.args[1] ) && atom.args[1].value < 0 ) {
					thread.throw_error( pl.error.domain( "not_less_than_zero", atom.args[1], atom.indicator ) );
				} else {
					var length = new Num( stringLength(atom.args[0].id), false );
					thread.prepend( [new State( point.goal.replace( new Term( "=", [length, atom.args[1]] ) ), point.substitution, point )] );
				}
			},
			
			// atom_concat/3
			"atom_concat/3": function( thread, point, atom ) {
				var str, goal, start = atom.args[0], end = atom.args[1], whole = atom.args[2];
				if( pl.type.is_variable( whole ) && (pl.type.is_variable( start ) || pl.type.is_variable( end )) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_variable( start ) && !pl.type.is_atom( start ) ) {
					thread.throw_error( pl.error.type( "atom", start, atom.indicator ) );
				} else if( !pl.type.is_variable( end ) && !pl.type.is_atom( end ) ) {
					thread.throw_error( pl.error.type( "atom", end, atom.indicator ) );
				} else if( !pl.type.is_variable( whole ) && !pl.type.is_atom( whole ) ) {
					thread.throw_error( pl.error.type( "atom", whole, atom.indicator ) );
				} else {
					var v1 = pl.type.is_variable( start );
					var v2 = pl.type.is_variable( end );
					//var v3 = pl.type.is_variable( whole );
					if( !v1 && !v2 ) {
						goal = new Term( "=", [whole, new Term( start.id + end.id )] );
						thread.prepend( [new State( point.goal.replace( goal ), point.substitution, point )] );
					} else if( v1 && !v2 ) {
						str = whole.id.substr( 0, whole.id.length - end.id.length );
						if( str + end.id === whole.id ) {
							goal = new Term( "=", [start, new Term( str )] );
							thread.prepend( [new State( point.goal.replace( goal ), point.substitution, point )] );
						}
					} else if( v2 && !v1 ) {
						str = whole.id.substr( start.id.length );
						if( start.id + str === whole.id ) {
							goal = new Term( "=", [end, new Term( str )] );
							thread.prepend( [new State( point.goal.replace( goal ), point.substitution, point )] );
						}
					} else {
						var states = [];
						for( var i = 0; i <= whole.id.length; i++ ) {
							var atom1 = new Term( whole.id.substr( 0, i ) );
							var atom2 = new Term( whole.id.substr( i ) );
							goal = new Term( ",", [new Term( "=", [atom1, start] ), new Term( "=", [atom2, end] )] );
							states.push( new State( point.goal.replace( goal ), point.substitution, point ) );
						}
						thread.prepend( states );
					}
				}
			},
			
			// sub_atom/5
			"sub_atom/5": function( thread, point, atom ) {
				var i, atom1 = atom.args[0], before = atom.args[1], length = atom.args[2], after = atom.args[3], subatom = atom.args[4];
				if( pl.type.is_variable( atom1 ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_variable( before ) && !pl.type.is_integer( before ) ) {
					thread.throw_error( pl.error.type( "integer", before, atom.indicator ) );
				} else if( !pl.type.is_variable( length ) && !pl.type.is_integer( length ) ) {
					thread.throw_error( pl.error.type( "integer", length, atom.indicator ) );
				} else if( !pl.type.is_variable( after ) && !pl.type.is_integer( after ) ) {
					thread.throw_error( pl.error.type( "integer", after, atom.indicator ) );
				} else if( pl.type.is_integer( before ) && before.value < 0 ) {
					thread.throw_error( pl.error.domain( "not_less_than_zero", before, atom.indicator ) );
				} else if( pl.type.is_integer( length ) && length.value < 0 ) {
					thread.throw_error( pl.error.domain( "not_less_than_zero", length, atom.indicator ) );
				} else if( pl.type.is_integer( after ) && after.value < 0 ) {
					thread.throw_error( pl.error.domain( "not_less_than_zero", after, atom.indicator ) );
				} else {
					var bs = [], ls = [], as = [];
					if( pl.type.is_variable( before ) ) {
						for( i = 0; i <= atom1.id.length; i++ ) {
							bs.push( i );
						}
					} else {
						bs.push( before.value );
					}
					if( pl.type.is_variable( length ) ) {
						for( i = 0; i <= atom1.id.length; i++ ) {
							ls.push( i );
						}
					} else {
						ls.push( length.value );
					}
					if( pl.type.is_variable( after ) ) {
						for( i = 0; i <= atom1.id.length; i++ ) {
							as.push( i );
						}
					} else {
						as.push( after.value );
					}
					var states = [];
					for( var _i in bs ) {
						if(!bs.hasOwnProperty(_i)) continue;
						i = bs[_i];
						for( var _j in ls ) {
							if(!ls.hasOwnProperty(_j)) continue;
							var j = ls[_j];
							var k = atom1.id.length - i - j;
							if( indexOf( as, k ) !== -1 ) {
							if( i+j+k === atom1.id.length ) {
									var str = atom1.id.substr( i, j );
									if( atom1.id === atom1.id.substr( 0, i ) + str + atom1.id.substr( i+j, k ) ) {
										var pl1 = new Term( "=", [new Term( str ), subatom] );
										var pl2 = new Term( "=", [before, new Num( i )] );
										var pl3 = new Term( "=", [length, new Num( j )] );
										var pl4 = new Term( "=", [after, new Num( k )] );
										var goal = new Term( ",", [ new Term( ",", [ new Term( ",", [pl2, pl3] ), pl4] ), pl1] );
										states.push( new State( point.goal.replace( goal ), point.substitution, point ) );
									}
								}
							}
						}
					}
					thread.prepend( states );
				}
			},
			
			// atom_chars/2
			"atom_chars/2": function( thread, point, atom ) {
				var atom1 = atom.args[0], list = atom.args[1];
				if( pl.type.is_variable( atom1 ) && pl.type.is_variable( list ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_variable( atom1 ) && !pl.type.is_atom( atom1 ) ) {
					thread.throw_error( pl.error.type( "atom", atom1, atom.indicator ) );
				} else {
					if( !pl.type.is_variable( atom1 ) ) {
						var list1 = new Term( "[]" );
						var unilen = stringLength(atom1.id);
						for( var i = unilen-1; i >= 0; i-- ) {
							list1 = new Term( ".", [new Term( atom1.id.charAt( i ) ), list1] );
						}
						thread.prepend( [new State( point.goal.replace( new Term( "=", [list, list1] ) ), point.substitution, point )] );
					} else {			
						var pointer = list;
						var v = pl.type.is_variable( atom1 );
						var str = "";
						while( pointer.indicator === "./2" ) {
							if( !pl.type.is_character( pointer.args[0] ) ) {
								if( pl.type.is_variable( pointer.args[0] ) && v ) {
									thread.throw_error( pl.error.instantiation( atom.indicator ) );
									return;
								} else if( !pl.type.is_variable( pointer.args[0] ) ) {
									thread.throw_error( pl.error.type( "character", pointer.args[0], atom.indicator ) );
									return;
								}
							} else {
								str += pointer.args[0].id;
							}
							pointer = pointer.args[1];
						}
						if( pl.type.is_variable( pointer ) && v ) {
							thread.throw_error( pl.error.instantiation( atom.indicator ) );
						} else if( !pl.type.is_empty_list( pointer ) && !pl.type.is_variable( pointer ) ) {
							thread.throw_error( pl.error.type( "list", list, atom.indicator ) );
						} else {
							thread.prepend( [new State( point.goal.replace( new Term( "=", [new Term( str ), atom1] ) ), point.substitution, point )] );
						}
					}
				}
			},
			
			// atom_codes/2
			"atom_codes/2": function( thread, point, atom ) {
				var atom1 = atom.args[0], list = atom.args[1];
				if( pl.type.is_variable( atom1 ) && pl.type.is_variable( list ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_variable( atom1 ) && !pl.type.is_atom( atom1 ) ) {
					thread.throw_error( pl.error.type( "atom", atom1, atom.indicator ) );
				} else {
					if( !pl.type.is_variable( atom1 ) ) {
						var list1 = new Term( "[]" );
						var unilen = stringLength(atom1.id);
						for( var i = unilen-1; i >= 0; i-- ) {
							list1 = new Term( ".", [new Num( codePointAt(atom1.id,i), false ), list1] );
						}
						thread.prepend( [new State( point.goal.replace( new Term( "=", [list, list1] ) ), point.substitution, point )] );
					} else {			
						var pointer = list;
						var v = pl.type.is_variable( atom1 );
						var str = "";
						while( pointer.indicator === "./2" ) {
							if( !pl.type.is_character_code( pointer.args[0] ) ) {
								if( pl.type.is_variable( pointer.args[0] ) && v ) {
									thread.throw_error( pl.error.instantiation( atom.indicator ) );
									return;
								} else if( !pl.type.is_variable( pointer.args[0] ) ) {
									thread.throw_error( pl.error.representation( "character_code", atom.indicator ) );
									return;
								}
							} else {
								str += fromCodePoint( pointer.args[0].value );
							}
							pointer = pointer.args[1];
						}
						if( pl.type.is_variable( pointer ) && v ) {
							thread.throw_error( pl.error.instantiation( atom.indicator ) );
						} else if( !pl.type.is_empty_list( pointer ) && !pl.type.is_variable( pointer ) ) {
							thread.throw_error( pl.error.type( "list", list, atom.indicator ) );
						} else {
							thread.prepend( [new State( point.goal.replace( new Term( "=", [new Term( str ), atom1] ) ), point.substitution, point )] );
						}
					}
				}
			},
			
			// char_code/2
			"char_code/2": function( thread, point, atom ) {
				var char = atom.args[0], code = atom.args[1];
				if( pl.type.is_variable( char ) && pl.type.is_variable( code ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_variable( char ) && !pl.type.is_character( char ) ) {
					thread.throw_error( pl.error.type( "character", char, atom.indicator ) );
				} else if( !pl.type.is_variable( code ) && !pl.type.is_integer( code ) ) {
					thread.throw_error( pl.error.type( "integer", code, atom.indicator ) );
				} else if( !pl.type.is_variable( code ) && !pl.type.is_character_code( code ) ) {
					thread.throw_error( pl.error.representation( "character_code", atom.indicator ) );
				} else {
					if( pl.type.is_variable( code ) ) {
						var code1 = new Num( codePointAt(char.id,0 ), false );
						thread.prepend( [new State( point.goal.replace( new Term( "=", [code1, code] ) ), point.substitution, point )] );
					} else {
						var char1 = new Term( fromCodePoint( code.value ) );
						thread.prepend( [new State( point.goal.replace( new Term( "=", [char1, char] ) ), point.substitution, point )] );
					}
				}
			},
			
			// number_chars/2
			"number_chars/2": function( thread, point, atom ) {
				var str, num = atom.args[0], list = atom.args[1];
				if( pl.type.is_variable( num ) && pl.type.is_variable( list ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_variable( num ) && !pl.type.is_number( num ) ) {
					thread.throw_error( pl.error.type( "number", num, atom.indicator ) );
				} else if( !pl.type.is_variable( list ) && !pl.type.is_list( list ) ) {
					thread.throw_error( pl.error.type( "list", list, atom.indicator ) );
				} else {
					var isvar = pl.type.is_variable( num );
					if( !pl.type.is_variable( list ) ) {	
						var pointer = list;
						var total = true;
						str = "";
						while( pointer.indicator === "./2" ) {
							if( !pl.type.is_character( pointer.args[0] ) ) {
								if( pl.type.is_variable( pointer.args[0] ) ) {
									total = false;
								} else if( !pl.type.is_variable( pointer.args[0] ) ) {
									thread.throw_error( pl.error.type( "character", pointer.args[0], atom.indicator ) );
									return;
								}
							} else {
								str += pointer.args[0].id;
							}
							pointer = pointer.args[1];
						}
						total = total && pl.type.is_empty_list( pointer );
						if( !pl.type.is_empty_list( pointer ) && !pl.type.is_variable( pointer ) ) {
							thread.throw_error( pl.error.type( "list", list, atom.indicator ) );
							return;
						}
						if( !total && isvar ) {
							thread.throw_error( pl.error.instantiation( atom.indicator ) );
							return;
						} else if( total ) {
							if( pl.type.is_variable( pointer ) && isvar ) {
								thread.throw_error( pl.error.instantiation( atom.indicator ) );
								return;
							} else {
								var expr = thread.parse( str );
								var num2 = expr.value;
								if( !pl.type.is_number( num2 ) || expr.tokens[expr.tokens.length-1].space ) {
									thread.throw_error( pl.error.syntax_by_predicate( "parseable_number", atom.indicator ) );
								} else {
									thread.prepend( [new State( point.goal.replace( new Term( "=", [num, num2] ) ), point.substitution, point )] );
								}
								return;
							}
						}
					}
					if( !isvar ) {
						str = num.toString();
						var list2 = new Term( "[]" );
						for( var i = str.length - 1; i >= 0; i-- ) {
							list2 = new Term( ".", [ new Term( str.charAt( i ) ), list2 ] );
						}
						thread.prepend( [new State( point.goal.replace( new Term( "=", [list, list2] ) ), point.substitution, point )] );
					}
				}
			},
			
			// number_codes/2
			"number_codes/2": function( thread, point, atom ) {
				var str, num = atom.args[0], list = atom.args[1];
				if( pl.type.is_variable( num ) && pl.type.is_variable( list ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_variable( num ) && !pl.type.is_number( num ) ) {
					thread.throw_error( pl.error.type( "number", num, atom.indicator ) );
				} else if( !pl.type.is_variable( list ) && !pl.type.is_list( list ) ) {
					thread.throw_error( pl.error.type( "list", list, atom.indicator ) );
				} else {
					var isvar = pl.type.is_variable( num );
					if( !pl.type.is_variable( list ) ) {	
						var pointer = list;
						var total = true;
						str = "";
						while( pointer.indicator === "./2" ) {
							if( !pl.type.is_character_code( pointer.args[0] ) ) {
								if( pl.type.is_variable( pointer.args[0] ) ) {
									total = false;
								} else if( !pl.type.is_variable( pointer.args[0] ) ) {
									thread.throw_error( pl.error.type( "character_code", pointer.args[0], atom.indicator ) );
									return;
								}
							} else {
								str += fromCodePoint( pointer.args[0].value );
							}
							pointer = pointer.args[1];
						}
						total = total && pl.type.is_empty_list( pointer );
						if( !pl.type.is_empty_list( pointer ) && !pl.type.is_variable( pointer ) ) {
							thread.throw_error( pl.error.type( "list", list, atom.indicator ) );
							return;
						}
						if( !total && isvar ) {
							thread.throw_error( pl.error.instantiation( atom.indicator ) );
							return;
						} else if( total ) {
							if( pl.type.is_variable( pointer ) && isvar ) {
								thread.throw_error( pl.error.instantiation( atom.indicator ) );
								return;
							} else {
								var expr = thread.parse( str );
								var num2 = expr.value;
								if( !pl.type.is_number( num2 ) || expr.tokens[expr.tokens.length-1].space ) {
									thread.throw_error( pl.error.syntax_by_predicate( "parseable_number", atom.indicator ) );
								} else {
									thread.prepend( [new State( point.goal.replace( new Term( "=", [num, num2] ) ), point.substitution, point )] );
								}
								return;
							}
						}
					}
					if( !isvar ) {
						str = num.toString();
						var list2 = new Term( "[]" );
						for( var i = str.length - 1; i >= 0; i-- ) {
							list2 = new Term( ".", [ new Num( codePointAt(str,i), false ), list2 ] );
						}
						thread.prepend( [new State( point.goal.replace( new Term( "=", [list, list2] ) ), point.substitution, point )] );
					}
				}
			},
			
			// upcase_atom/2
			"upcase_atom/2": function( thread, point, atom ) {
				var original = atom.args[0], upcase = atom.args[1];
				if( pl.type.is_variable( original ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_atom( original ) ) {
					thread.throw_error( pl.error.type( "atom", original, atom.indicator ) );
				} else if( !pl.type.is_variable( upcase ) && !pl.type.is_atom( upcase ) ) {
					thread.throw_error( pl.error.type( "atom", upcase, atom.indicator ) );
				} else {
					thread.prepend( [new State( point.goal.replace( new Term( "=", [upcase, new Term( original.id.toUpperCase(), [] )] ) ), point.substitution, point )] );
				}
			},
			
			// downcase_atom/2
			"downcase_atom/2": function( thread, point, atom ) {
				var original = atom.args[0], downcase = atom.args[1];
				if( pl.type.is_variable( original ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_atom( original ) ) {
					thread.throw_error( pl.error.type( "atom", original, atom.indicator ) );
				} else if( !pl.type.is_variable( downcase ) && !pl.type.is_atom( downcase ) ) {
					thread.throw_error( pl.error.type( "atom", downcase, atom.indicator ) );
				} else {
					thread.prepend( [new State( point.goal.replace( new Term( "=", [downcase, new Term( original.id.toLowerCase(), [] )] ) ), point.substitution, point )] );
				}
			},
			
			// atomic_list_concat/2
			"atomic_list_concat/2": function( thread, point, atom ) {
				var list = atom.args[0], concat = atom.args[1];
				thread.prepend( [new State( point.goal.replace( new Term( "atomic_list_concat", [list, new Term( "", [] ), concat] ) ), point.substitution, point )] );
			},
			
			// atomic_list_concat/3
			"atomic_list_concat/3": function( thread, point, atom ) {
				var list = atom.args[0], separator = atom.args[1], concat = atom.args[2];
				if( pl.type.is_variable( separator ) || pl.type.is_variable( list ) && pl.type.is_variable( concat ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_variable( list ) && !pl.type.is_list( list ) ) {
					thread.throw_error( pl.error.type( "list", list, atom.indicator ) );
				} else if( !pl.type.is_variable( concat ) && !pl.type.is_atom( concat ) ) {
					thread.throw_error( pl.error.type( "atom", concat, atom.indicator ) );
				} else {
					if( !pl.type.is_variable( concat ) ) {
						var atomic = arrayToList( map(
							concat.id.split( separator.id ),
							function( id ) {
								return new Term( id, [] );
							}
						) );
						thread.prepend( [new State( point.goal.replace( new Term( "=", [atomic, list] ) ), point.substitution, point )] );
					} else {
						var id = "";
						var pointer = list;
						while( pl.type.is_term( pointer ) && pointer.indicator === "./2" ) {
							if( !pl.type.is_atom( pointer.args[0] ) && !pl.type.is_number( pointer.args[0] ) ) {
								thread.throw_error( pl.error.type( "atomic", pointer.args[0], atom.indicator ) );
								return;
							}
							if( id !== "" )
								id += separator.id;
							if( pl.type.is_atom( pointer.args[0] ) )
								id += pointer.args[0].id;
							else
								id += "" + pointer.args[0].value;
							pointer = pointer.args[1];
						}
						id = new Term( id, [] );
						if( pl.type.is_variable( pointer ) ) {
							thread.throw_error( pl.error.instantiation( atom.indicator ) );
						} else if( !pl.type.is_term( pointer ) || pointer.indicator !== "[]/0" ) {
							thread.throw_error( pl.error.type( "list", list, atom.indicator ) );
						} else {
							thread.prepend( [new State( point.goal.replace( new Term( "=", [id, concat] ) ), point.substitution, point )] );
						}
					}
				}
			},
			
			// TERM COMPARISON
			
			"@=</2": function( thread, point, atom ) {
				if( pl.compare( atom.args[0], atom.args[1] ) <= 0 ) {
					thread.success( point );
				}
			},
			
			"==/2": function( thread, point, atom ) {
				if( pl.compare( atom.args[0], atom.args[1] ) === 0 ) {
					thread.success( point );
				}
			},
			
			"\\==/2": function( thread, point, atom ) {
				if( pl.compare( atom.args[0], atom.args[1] ) !== 0 ) {
					thread.success( point );
				}
			},
			
			"@</2": function( thread, point, atom ) {
				if( pl.compare( atom.args[0], atom.args[1] ) < 0 ) {
					thread.success( point );
				}
			},
			
			"@>/2": function( thread, point, atom ) {
				if( pl.compare( atom.args[0], atom.args[1] ) > 0 ) {
					thread.success( point );
				}
			},
			
			"@>=/2": function( thread, point, atom ) {
				if( pl.compare( atom.args[0], atom.args[1] ) >= 0 ) {
					thread.success( point );
				}
			},
			
			"compare/3": function( thread, point, atom ) {
				var order = atom.args[0], left = atom.args[1], right = atom.args[2];
				if( !pl.type.is_variable( order ) && !pl.type.is_atom( order ) ) {
					thread.throw_error( pl.error.type( "atom", order, atom.indicator ) );
				} else if( pl.type.is_atom( order ) && ["<", ">", "="].indexOf( order.id ) === -1 ) {
					thread.throw_error( pl.type.domain( "order", order, atom.indicator ) );
				} else {
					var compare = pl.compare( left, right );
					compare = compare === 0 ? "=" : (compare === -1 ? "<" : ">");
					thread.prepend( [new State( point.goal.replace( new Term( "=", [order, new Term( compare, [] )] ) ), point.substitution, point )] );
				}
			},
			
			// EVALUATION
			
			// is/2
			"is/2": function( thread, point, atom ) {
				var op = atom.args[1].interpret( thread );
				if( !pl.type.is_number( op ) ) {
					thread.throw_error( op );
				} else {
					thread.prepend( [new State( point.goal.replace( new Term( "=", [atom.args[0], op], thread.level ) ), point.substitution, point )] );
				}
			},
			
			// between/3
			"between/3": function( thread, point, atom ) {
				var lower = atom.args[0], upper = atom.args[1], bet = atom.args[2];
				if( pl.type.is_variable( lower ) || pl.type.is_variable( upper ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_integer( lower ) ) {
					thread.throw_error( pl.error.type( "integer", lower, atom.indicator ) );
				} else if( !pl.type.is_integer( upper ) ) {
					thread.throw_error( pl.error.type( "integer", upper, atom.indicator ) );
				} else if( !pl.type.is_variable( bet ) && !pl.type.is_integer( bet ) ) {
					thread.throw_error( pl.error.type( "integer", bet, atom.indicator ) );
				} else {
					if( pl.type.is_variable( bet ) ) {
						var states = [new State( point.goal.replace( new Term( "=", [bet, lower] ) ), point.substitution, point )];
						if( lower.value < upper.value )
							states.push( new State( point.goal.replace( new Term( "between", [new Num( lower.value+1, false ), upper, bet] ) ), point.substitution, point ) );
						thread.prepend( states );
					} else if( lower.value <= bet.value && upper.value >= bet.value ) {
						thread.success( point );
					}
				}
			},
			
			// succ/2
			"succ/2": function( thread, point, atom ) {
				var n = atom.args[0], m = atom.args[1];
				if( pl.type.is_variable( n ) && pl.type.is_variable( m ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_variable( n ) && !pl.type.is_integer( n ) ) {
					thread.throw_error( pl.error.type( "integer", n, atom.indicator ) );
				} else if( !pl.type.is_variable( m ) && !pl.type.is_integer( m ) ) {
					thread.throw_error( pl.error.type( "integer", m, atom.indicator ) );
				} else if( !pl.type.is_variable( n ) && n.value < 0 ) {
					thread.throw_error( pl.error.domain( "not_less_than_zero", n, atom.indicator ) );
				} else if( !pl.type.is_variable( m ) && m.value < 0 ) {
					thread.throw_error( pl.error.domain( "not_less_than_zero", m, atom.indicator ) );
				} else {
					if( pl.type.is_variable( m ) || m.value > 0 ) {
						if( pl.type.is_variable( n ) ) {
							thread.prepend( [new State( point.goal.replace( new Term( "=", [n, new Num( m.value-1, false )] ) ), point.substitution, point )] );
						} else {
							thread.prepend( [new State( point.goal.replace( new Term( "=", [m, new Num( n.value+1, false )] ) ), point.substitution, point )] );
						}
					}
				}
			},
			
			// =:=/2
			"=:=/2": function( thread, point, atom ) {
				var cmp = pl.arithmetic_compare( thread, atom.args[0], atom.args[1] );
				if( pl.type.is_term( cmp ) ) {
					thread.throw_error( cmp );
				} else if( cmp === 0 ) {
					thread.success( point );
				}
			},
			
			// =\=/2
			"=\\=/2": function( thread, point, atom ) {
				var cmp = pl.arithmetic_compare( thread, atom.args[0], atom.args[1] );
				if( pl.type.is_term( cmp ) ) {
					thread.throw_error( cmp );
				} else if( cmp !== 0 ) {
					thread.success( point );
				}
			},
			
			// </2
			"</2": function( thread, point, atom ) {
				var cmp = pl.arithmetic_compare( thread, atom.args[0], atom.args[1] );
				if( pl.type.is_term( cmp ) ) {
					thread.throw_error( cmp );
				} else if( cmp < 0 ) {
					thread.success( point );
				}
			},
			
			// =</2
			"=</2": function( thread, point, atom ) {
				var cmp = pl.arithmetic_compare( thread, atom.args[0], atom.args[1] );
				if( pl.type.is_term( cmp ) ) {
					thread.throw_error( cmp );
				} else if( cmp <= 0 ) {
					thread.success( point );
				}
			},
			
			// >/2
			">/2": function( thread, point, atom ) {
				var cmp = pl.arithmetic_compare( thread, atom.args[0], atom.args[1] );
				if( pl.type.is_term( cmp ) ) {
					thread.throw_error( cmp );
				} else if( cmp > 0 ) {
					thread.success( point );
				}
			},
			
			// >=/2
			">=/2": function( thread, point, atom ) {
				var cmp = pl.arithmetic_compare( thread, atom.args[0], atom.args[1] );
				if( pl.type.is_term( cmp ) ) {
					thread.throw_error( cmp );
				} else if( cmp >= 0 ) {
					thread.success( point );
				}
			},
			
			// TYPE TEST
			
			// var/1
			"var/1": function( thread, point, atom ) {
				if( pl.type.is_variable( atom.args[0] ) ) {
					thread.success( point );
				}
			},
			
			// atom/1
			"atom/1": function( thread, point, atom ) {
				if( pl.type.is_atom( atom.args[0] ) ) {
					thread.success( point );
				}
			},
			
			// atomic/1
			"atomic/1": function( thread, point, atom ) {
				if( pl.type.is_atomic( atom.args[0] ) ) {
					thread.success( point );
				}
			},
			
			// compound/1
			"compound/1": function( thread, point, atom ) {
				if( pl.type.is_compound( atom.args[0] ) ) {
					thread.success( point );
				}
			},
			
			// integer/1
			"integer/1": function( thread, point, atom ) {
				if( pl.type.is_integer( atom.args[0] ) ) {
					thread.success( point );
				}
			},
			
			// float/1
			"float/1": function( thread, point, atom ) {
				if( pl.type.is_float( atom.args[0] ) ) {
					thread.success( point );
				}
			},
			
			// number/1
			"number/1": function( thread, point, atom ) {
				if( pl.type.is_number( atom.args[0] ) ) {
					thread.success( point );
				}
			},
			
			// nonvar/1
			"nonvar/1": function( thread, point, atom ) {
				if( !pl.type.is_variable( atom.args[0] ) ) {
					thread.success( point );
				}
			},
			
			// ground/1
			"ground/1": function( thread, point, atom ) {
				if( atom.variables().length === 0 ) {
					thread.success( point );
				}
			},
			
			// acyclic_term/1
			"acyclic_term/1": function( thread, point, atom ) {
				var test = point.substitution.apply( point.substitution );
				var variables = atom.args[0].variables();
				for( var i = 0; i < variables.length; i++ )
					if( point.substitution.links[variables[i]] !== undefined && !point.substitution.links[variables[i]].equals( test.links[variables[i]] ) )
						return;
				thread.success( point );
			},
			
			// callable/1
			"callable/1": function( thread, point, atom ) {
				if( pl.type.is_callable( atom.args[0] ) ) {
					thread.success( point );
				}
			},

			// is_list/1
			"is_list/1": function( thread, point, atom ) {
				var list = atom.args[0];
				while( pl.type.is_term( list ) && list.indicator === "./2" )
					list = list.args[1];
				if( pl.type.is_term( list ) && list.indicator === "[]/0" )
					thread.success( point );
			},



			// STREAM SELECTION AND CONTROL

			// current_input/1
			"current_input/1": function( thread, point, atom ) {
				var stream = atom.args[0];
				if( !pl.type.is_variable( stream ) && !pl.type.is_stream( stream ) && !pl.type.is_atom( stream ) ) {
					thread.throw_error( pl.error.domain("stream", stream, atom.indicator) );
				} else {
					if( pl.type.is_atom( stream ) && thread.get_stream_by_alias( stream.id ) )
						stream = thread.get_stream_by_alias( stream.id );
					thread.prepend( [new State(
						point.goal.replace(new Term("=", [stream, thread.get_current_input()])),
						point.substitution,
						point)
					] );
				}
			},

			// current_output/1
			"current_output/1": function( thread, point, atom ) {
				var stream = atom.args[0];
				if( !pl.type.is_variable( stream ) && !pl.type.is_stream( stream ) && !pl.type.is_atom( stream ) ) {
					thread.throw_error( pl.error.domain("stream_or_alias", stream, atom.indicator) );
				} else {
					if( pl.type.is_atom( stream ) && thread.get_stream_by_alias( stream.id ) )
						stream = thread.get_stream_by_alias( stream.id );
					thread.prepend( [new State(
						point.goal.replace(new Term("=", [stream, thread.get_current_output()])),
						point.substitution,
						point)
					] );
				}
			},

			// set_input/1
			"set_input/1": function( thread, point, atom ) {
				var input = atom.args[0];
				var stream = pl.type.is_stream( input ) ? input : thread.get_stream_by_alias( input.id );
				if( pl.type.is_variable( input ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_variable( input ) && !pl.type.is_stream( input ) && !pl.type.is_atom( input ) ) {
					thread.throw_error( pl.error.domain( "stream_or_alias", input, atom.indicator ) );
				} else if( !pl.type.is_stream( stream ) ) {
					thread.throw_error( pl.error.existence( "stream", input, atom.indicator ) );
				} else if( stream.output === true ) {
					thread.throw_error( pl.error.permission( "input", "stream", input, atom.indicator ) );
				} else {
					thread.set_current_input( stream );
					thread.success( point );
				}
			},

			// set_output/1
			"set_output/1": function( thread, point, atom ) {
				var output = atom.args[0];
				var stream = pl.type.is_stream( output ) ? output : thread.get_stream_by_alias( output.id );
				if( pl.type.is_variable( output ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_variable( output ) && !pl.type.is_stream( output ) && !pl.type.is_atom( output ) ) {
					thread.throw_error( pl.error.domain( "stream_or_alias", output, atom.indicator ) );
				} else if( !pl.type.is_stream( stream ) ) {
					thread.throw_error( pl.error.existence( "stream", output, atom.indicator ) );
				} else if( stream.input === true ) {
					thread.throw_error( pl.error.permission( "output", "stream", output, atom.indicator ) );
				} else {
					thread.set_current_output( stream );
					thread.success( point );
				}
			},

			// open/3
			"open/3": function( thread, point, atom ) {
				var dest = atom.args[0], mode = atom.args[1], stream = atom.args[2];
				thread.prepend( [new State(
					point.goal.replace(new Term("open", [dest, mode, stream, new Term("[]", [])])),
					point.substitution,
					point
				)] );
			},

			// open/4
			"open/4": function( thread, point, atom ) {
				var dest = atom.args[0], mode = atom.args[1], stream = atom.args[2], options = atom.args[3];
				if( pl.type.is_variable( dest ) || pl.type.is_variable( mode ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_variable( mode ) && !pl.type.is_atom( mode ) ) {
					thread.throw_error( pl.error.type( "atom", mode, atom.indicator ) );
				} else if( !pl.type.is_list( options ) ) {
					thread.throw_error( pl.error.type( "list", options, atom.indicator ) );
				} else if( !pl.type.is_variable( stream ) ) {
					thread.throw_error( pl.error.type( "variable", stream, atom.indicator ) );
				} else if( !pl.type.is_atom( dest ) && !pl.type.is_streamable( dest ) ) {
					thread.throw_error( pl.error.domain( "source_sink", dest, atom.indicator ) );
				} else if( !pl.type.is_io_mode( mode ) ) {
					thread.throw_error( pl.error.domain( "io_mode", mode, atom.indicator ) );
				} else {
					var obj_options = {};
					var pointer = options;
					var property;
					while( pl.type.is_term(pointer) && pointer.indicator === "./2" ) {
						property = pointer.args[0];
						if( pl.type.is_variable( property ) ) {
							thread.throw_error( pl.error.instantiation( atom.indicator ) );
							return;
						} else if( !pl.type.is_stream_option( property ) ) {
							thread.throw_error( pl.error.domain( "stream_option", property, atom.indicator ) );
							return;
						}
						obj_options[property.id] = property.args[0].id;
						pointer = pointer.args[1];
					}
					if( pointer.indicator !== "[]/0" ) {
						if( pl.type.is_variable( pointer ) )
							thread.throw_error( pl.error.instantiation( atom.indicator ) );
						else
							thread.throw_error( pl.error.type( "list", options, atom.indicator ) );
						return;
					} else {
						var alias = obj_options["alias"];
						if( alias && thread.get_stream_by_alias(alias) ) {
							thread.throw_error( pl.error.permission( "open", "source_sink", new Term("alias", [new Term(alias, [])]), atom.indicator ) );
							return;
						}
						if( !obj_options["type"] )
							obj_options["type"] = "text";
						var file;
						if( pl.type.is_atom( dest ) )
							file = thread.file_system_open( dest.id, obj_options["type"], mode.id );
						else
							file = dest.stream( obj_options["type"], mode.id );
						if( file === false ) {
							thread.throw_error( pl.error.permission( "open", "source_sink", dest, atom.indicator ) );
							return;
						} else if( file === null ) {
							thread.throw_error( pl.error.existence( "source_sink", dest, atom.indicator ) );
							return;
						}
						var newstream = new Stream(
							file, mode.id,
							obj_options["alias"],
							obj_options["type"],
							obj_options["reposition"] === "true",
							obj_options["eof_action"] );
						if( alias )
							thread.session.streams[alias] = newstream;
						else
							thread.session.streams[newstream.id] = newstream;
						thread.prepend( [new State(
							point.goal.replace( new Term( "=", [stream, newstream] ) ),
							point.substitution,
							point
						)] );
					}
				}
			},

			// close/1
			"close/1": function( thread, point, atom ) {
				var stream = atom.args[0];
				thread.prepend( [new State(
					point.goal.replace(new Term("close", [stream, new Term("[]", [])])),
					point.substitution,
					point
				)] );
			},

			// close/2
			"close/2": function( thread, point, atom ) {
				var stream = atom.args[0], options = atom.args[1];
				var stream2 = pl.type.is_stream( stream ) ? stream : thread.get_stream_by_alias( stream.id );
				if( pl.type.is_variable( stream ) || pl.type.is_variable( options ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_list( options ) ) {
					thread.throw_error( pl.error.type( "list", options, atom.indicator ) );
				} else if( !pl.type.is_stream( stream ) && !pl.type.is_atom( stream ) ) {
					thread.throw_error( pl.error.domain( "stream_or_alias", stream, atom.indicator ) );
				} else if( !pl.type.is_stream( stream2 ) || stream2.stream === null ) {
					thread.throw_error( pl.error.existence( "stream", stream, atom.indicator ) );
				} else {
					// Get options
					var obj_options = {};
					var pointer = options;
					var property;
					while( pl.type.is_term(pointer) && pointer.indicator === "./2" ) {
						property = pointer.args[0];
						if( pl.type.is_variable( property ) ) {
							thread.throw_error( pl.error.instantiation( atom.indicator ) );
							return;
						} else if( !pl.type.is_close_option( property ) ) {
							thread.throw_error( pl.error.domain( "close_option", property, atom.indicator ) );
							return;
						}
						obj_options[property.id] = property.args[0].id === "true";
						pointer = pointer.args[1];
					}
					if( pointer.indicator !== "[]/0" ) {
						if( pl.type.is_variable( pointer ) )
							thread.throw_error( pl.error.instantiation( atom.indicator ) );
						else
							thread.throw_error( pl.error.type( "list", options, atom.indicator ) );
						return;
					} else {
						if( stream2 === thread.session.standard_input || stream2 === thread.session.standard_output ) {
							thread.success( point );
							return;
						} else if( stream2 === thread.session.current_input ) {
							thread.session.current_input = thread.session.standard_input;
						} else if( stream2 === thread.session.current_output ) {
							thread.session.current_output = thread.session.current_output;
						}
						if( stream2.alias !== null )
							delete thread.session.streams[stream2.alias];
						else
							delete thread.session.streams[stream2.id];
						if( stream2.output )
							stream2.stream.flush();
						var closed = stream2.stream.close();
						stream2.stream = null;
						if( obj_options.force === true || closed === true ) {
							thread.success( point );
						}
					}
				}
			},

			// flush_output/0
			"flush_output/0": function( thread, point, atom ) {
				thread.prepend( [new State( 
					point.goal.replace( new Term(",", [new Term("current_output", [new Var("S")]),new Term("flush_output", [new Var("S")])]) ),
					point.substitution,
					point
				)] );
			},

			// flush_output/1
			"flush_output/1": function( thread, point, atom ) {
				var stream = atom.args[0];
				var stream2 = pl.type.is_stream( stream ) ? stream : thread.get_stream_by_alias( stream.id );
				if( pl.type.is_variable( stream ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_stream( stream ) && !pl.type.is_atom( stream ) ) {
					thread.throw_error( pl.error.domain( "stream_or_alias", stream, atom.indicator ) );
				} else if( !pl.type.is_stream( stream2 ) || stream2.stream === null ) {
					thread.throw_error( pl.error.existence( "stream", stream, atom.indicator ) );
				} else if( stream.input === true ) {
					thread.throw_error( pl.error.permission( "output", "stream", output, atom.indicator ) );
				} else {
					stream2.stream.flush();
					thread.success( point );
				}
			},

			// stream_property/2
			"stream_property/2": function( thread, point, atom ) {
				var stream = atom.args[0], property = atom.args[1];
				var stream2 = pl.type.is_stream( stream ) ? stream : thread.get_stream_by_alias( stream.id );
				if( !pl.type.is_variable( stream ) && !pl.type.is_stream( stream ) && !pl.type.is_atom( stream ) ) {
					thread.throw_error( pl.error.domain( "stream_or_alias", stream, atom.indicator ) );
				} else if( !pl.type.is_variable( stream ) && (!pl.type.is_stream( stream2 ) || stream2.stream === null) ) {
					thread.throw_error( pl.error.existence( "stream", stream, atom.indicator ) );
				} else if( !pl.type.is_variable( property ) && !pl.type.is_stream_property( property ) ) {
					thread.throw_error( pl.error.domain( "stream_property", property, atom.indicator ) );
				} else {
					var streams = [];
					var states = [];
					if( !pl.type.is_variable( stream ) )
						streams.push( stream2 );
					else
						for( var key in thread.session.streams )
							streams.push( thread.session.streams[key] );
					for( var i = 0; i < streams.length; i++ ) {
						var properties = [];
						if( streams[i].filename )
							properties.push( new Term( "file_name", [new Term(streams[i].file_name, [])] ) );
						properties.push( new Term( "mode", [new Term(streams[i].mode, [])] ) );
						properties.push( new Term( streams[i].input ? "input" : "output", [] ) );
						if( streams[i].alias )
							properties.push( new Term( "alias", [new Term(streams[i].alias, [])] ) );
						properties.push( new Term( "position", [
							typeof streams[i].position === "number" ?
								new Num( streams[i].position, false ) :
								new Term( streams[i].position, [] )
						] ) );
						properties.push( new Term( "end_of_stream", [new Term(
							streams[i].position === "end_of_stream" ? "at" :
							streams[i].position === "past_end_of_stream" ? "past" :
							"not", [])] ) );
						properties.push( new Term( "eof_action", [new Term(streams[i].eof_action, [])] ) );
						properties.push( new Term( "reposition", [new Term(streams[i].reposition ? "true" : "false", [])] ) );
						properties.push( new Term( "type", [new Term(streams[i].type, [])] ) );
						for( var j = 0; j < properties.length; j++ ) {
							states.push( new State(
								point.goal.replace( new Term( ",", [
									new Term("=", [pl.type.is_variable( stream ) ? stream : stream2, streams[i]]),
									new Term("=", [property, properties[j]])]) ),
								point.substitution,
								point
							) );
						}
					}
					thread.prepend( states );
				}
			},

			// at_end_of_stream/0
			"at_end_of_stream/0": function( thread, point, atom ) {
				thread.prepend( [new State(
					point.goal.replace(
						new Term(",", [new Term("current_input", [new Var("S")]),new Term(",", [
							new Term("stream_property", [new Var("S"),new Term("end_of_stream", [new Var("E")])]),
							new Term(",", [new Term("!", []),new Term(";", [new Term("=", [new Var("E"),
							new Term("at", [])]),new Term("=", [new Var("E"),new Term("past", [])])])])])])
					),
					point.substitution,
					point
				)] );
			},

			// at_end_of_stream/1
			"at_end_of_stream/1": function( thread, point, atom ) {
				var stream = atom.args[0];
				thread.prepend( [new State(
					point.goal.replace(
						new Term(",", [new Term("stream_property", [stream,new Term("end_of_stream", [new Var("E")])]),
						new Term(",", [new Term("!", []),new Term(";", [new Term("=", [new Var("E"),new Term("at", [])]),
						new Term("=", [new Var("E"),new Term("past", [])])])])])
					),
					point.substitution,
					point
				)] );
			},

			// set_stream_position/2
			"set_stream_position/2": function( thread, point, atom ) {
				var stream = atom.args[0], position = atom.args[1];
				var stream2 = pl.type.is_stream( stream ) ? stream : thread.get_stream_by_alias( stream.id );
				if( pl.type.is_variable( stream ) || pl.type.is_variable( position ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_stream( stream ) && !pl.type.is_atom( stream ) ) {
					thread.throw_error( pl.error.domain( "stream_or_alias", stream, atom.indicator ) );
				} else if( !pl.type.is_stream( stream2 ) || stream2.stream === null ) {
					thread.throw_error( pl.error.existence( "stream", stream, atom.indicator ) );
				} else if( !pl.type.is_stream_position( position ) ) {
					thread.throw_error( pl.error.domain( "stream_position", position, atom.indicator ) );
				} else if( stream2.reposition === false ) {
					thread.throw_error( pl.error.permission( "reposition", "stream", stream, atom.indicator ) );
				} else {
					if( pl.type.is_integer( position ) )
						stream2.position = position.value;
					else
						stream2.position = position.id;
					thread.success( point );
				}
			},



			//  CHARACTER INPUT OUTPUT
			
			// get_char/1
			"get_char/1": function( thread, point, atom ) {
				var char = atom.args[0];
				thread.prepend( [new State( 
					point.goal.replace( new Term(",", [new Term("current_input", [new Var("S")]),new Term("get_char", [new Var("S"),char])]) ),
					point.substitution,
					point
				)] );
			},

			// get_char/2
			"get_char/2": function( thread, point, atom ) {
				var stream = atom.args[0], char = atom.args[1];
				var stream2 = pl.type.is_stream( stream ) ? stream : thread.get_stream_by_alias( stream.id );
				if( pl.type.is_variable( stream ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_variable( char ) && !pl.type.is_character( char ) ) {
					thread.throw_error( pl.error.type( "in_character", char, atom.indicator ) );
				} else if( !pl.type.is_stream( stream ) && !pl.type.is_atom( stream ) ) {
					thread.throw_error( pl.error.domain( "stream_or_alias", stream, atom.indicator ) );
				} else if( !pl.type.is_stream( stream2 ) || stream2.stream === null ) {
					thread.throw_error( pl.error.existence( "stream", stream, atom.indicator ) );
				} else if( stream2.output ) {
					thread.throw_error( pl.error.permission( "input", "stream", stream, atom.indicator ) );
				} else if( stream2.type === "binary" ) {
					thread.throw_error( pl.error.permission( "input", "binary_stream", stream, atom.indicator ) );
				} else if( stream2.position === "past_end_of_stream" && stream2.eof_action === "error" ) {
					thread.throw_error( pl.error.permission( "input", "past_end_of_stream", stream, atom.indicator ) );
				} else {
					var stream_char;
					if( stream2.position === "end_of_stream" ) {
						stream_char = "end_of_file";
						stream2.position = "past_end_of_stream";
					} else {
						stream_char = stream2.stream.get( 1, stream2.position );
						if( stream_char === null ) {
							thread.throw_error( pl.error.representation( "character", atom.indicator ) );
							return;
						}
						stream2.position++;
					}
					thread.prepend( [new State(
						point.goal.replace( new Term( "=", [new Term(stream_char,[]), char] ) ),
						point.substitution,
						point
					)] );
				}
			},

			// get_code/1
			"get_code/1": function( thread, point, atom ) {
				var code = atom.args[0];
				thread.prepend( [new State( 
					point.goal.replace( new Term(",", [new Term("current_input", [new Var("S")]),new Term("get_code", [new Var("S"),code])]) ),
					point.substitution,
					point
				)] );
			},

			// get_code/2
			"get_code/2": function( thread, point, atom ) {
				var stream = atom.args[0], code = atom.args[1];
				var stream2 = pl.type.is_stream( stream ) ? stream : thread.get_stream_by_alias( stream.id );
				if( pl.type.is_variable( stream ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_variable( code ) && !pl.type.is_integer( code ) ) {
					thread.throw_error( pl.error.type( "integer", char, atom.indicator ) );
				} else if( !pl.type.is_variable( stream ) && !pl.type.is_stream( stream ) && !pl.type.is_atom( stream ) ) {
					thread.throw_error( pl.error.domain( "stream_or_alias", stream, atom.indicator ) );
				} else if( !pl.type.is_stream( stream2 ) || stream2.stream === null ) {
					thread.throw_error( pl.error.existence( "stream", stream, atom.indicator ) );
				} else if( stream2.output ) {
					thread.throw_error( pl.error.permission( "input", "stream", stream, atom.indicator ) );
				} else if( stream2.type === "binary" ) {
					thread.throw_error( pl.error.permission( "input", "binary_stream", stream, atom.indicator ) );
				} else if( stream2.position === "past_end_of_stream" && stream2.eof_action === "error" ) {
					thread.throw_error( pl.error.permission( "input", "past_end_of_stream", stream, atom.indicator ) );
				} else {
					var stream_code;
					if( stream2.position === "end_of_stream" ) {
						stream_code = -1;
						stream2.position = "past_end_of_stream";
					} else {
						stream_code = stream2.stream.get( 1, stream2.position );
						if( stream_code === null ) {
							thread.throw_error( pl.error.representation( "character", atom.indicator ) );
							return;
						}
						stream_code = codePointAt( stream_code, 0 );
						stream2.position++;
					}
					thread.prepend( [new State(
						point.goal.replace( new Term( "=", [new Num(stream_code, false), code] ) ),
						point.substitution,
						point
					)] );
				}
			},

			// peek_char/1
			"peek_char/1": function( thread, point, atom ) {
				var char = atom.args[0];
				thread.prepend( [new State( 
					point.goal.replace( new Term(",", [new Term("current_input", [new Var("S")]),new Term("peek_char", [new Var("S"),char])]) ),
					point.substitution,
					point
				)] );
			},

			// peek_char/2
			"peek_char/2": function( thread, point, atom ) {
				var stream = atom.args[0], char = atom.args[1];
				var stream2 = pl.type.is_stream( stream ) ? stream : thread.get_stream_by_alias( stream.id );
				if( pl.type.is_variable( stream ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_variable( char ) && !pl.type.is_character( char ) ) {
					thread.throw_error( pl.error.type( "in_character", char, atom.indicator ) );
				} else if( !pl.type.is_stream( stream ) && !pl.type.is_atom( stream ) ) {
					thread.throw_error( pl.error.domain( "stream_or_alias", stream, atom.indicator ) );
				} else if( !pl.type.is_stream( stream2 ) || stream2.stream === null ) {
					thread.throw_error( pl.error.existence( "stream", stream, atom.indicator ) );
				} else if( stream2.output ) {
					thread.throw_error( pl.error.permission( "input", "stream", stream, atom.indicator ) );
				} else if( stream2.type === "binary" ) {
					thread.throw_error( pl.error.permission( "input", "binary_stream", stream, atom.indicator ) );
				} else if( stream2.position === "past_end_of_stream" && stream2.eof_action === "error" ) {
					thread.throw_error( pl.error.permission( "input", "past_end_of_stream", stream, atom.indicator ) );
				} else {
					var stream_char;
					if( stream2.position === "end_of_stream" ) {
						stream_char = "end_of_file";
						stream2.position = "past_end_of_stream";
					} else {
						stream_char = stream2.stream.get( 1, stream2.position );
						if( stream_char === null ) {
							thread.throw_error( pl.error.representation( "character", atom.indicator ) );
							return;
						}
					}
					thread.prepend( [new State(
						point.goal.replace( new Term( "=", [new Term(stream_char,[]), char] ) ),
						point.substitution,
						point
					)] );
				}
			},

			// peek_code/1
			"peek_code/1": function( thread, point, atom ) {
				var code = atom.args[0];
				thread.prepend( [new State( 
					point.goal.replace( new Term(",", [new Term("current_input", [new Var("S")]),new Term("peek_code", [new Var("S"),code])]) ),
					point.substitution,
					point
				)] );
			},

			// peek_code/2
			"peek_code/2": function( thread, point, atom ) {
				var stream = atom.args[0], code = atom.args[1];
				var stream2 = pl.type.is_stream( stream ) ? stream : thread.get_stream_by_alias( stream.id );
				if( pl.type.is_variable( stream ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_variable( code ) && !pl.type.is_integer( code ) ) {
					thread.throw_error( pl.error.type( "integer", char, atom.indicator ) );
				} else if( !pl.type.is_variable( stream ) && !pl.type.is_stream( stream ) && !pl.type.is_atom( stream ) ) {
					thread.throw_error( pl.error.domain( "stream_or_alias", stream, atom.indicator ) );
				} else if( !pl.type.is_stream( stream2 ) || stream2.stream === null ) {
					thread.throw_error( pl.error.existence( "stream", stream, atom.indicator ) );
				} else if( stream2.output ) {
					thread.throw_error( pl.error.permission( "input", "stream", stream, atom.indicator ) );
				} else if( stream2.type === "binary" ) {
					thread.throw_error( pl.error.permission( "input", "binary_stream", stream, atom.indicator ) );
				} else if( stream2.position === "past_end_of_stream" && stream2.eof_action === "error" ) {
					thread.throw_error( pl.error.permission( "input", "past_end_of_stream", stream, atom.indicator ) );
				} else {
					var stream_code;
					if( stream2.position === "end_of_stream" ) {
						stream_code = -1;
						stream2.position = "past_end_of_stream";
					} else {
						stream_code = stream2.stream.get( 1, stream2.position );
						if( stream_code === null ) {
							thread.throw_error( pl.error.representation( "character", atom.indicator ) );
							return;
						}
						stream_code = codePointAt( stream_code, 0 );
					}
					thread.prepend( [new State(
						point.goal.replace( new Term( "=", [new Num(stream_code, false), code] ) ),
						point.substitution,
						point
					)] );
				}
			},

			// put_char/1
			"put_char/1": function( thread, point, atom ) {
				var char = atom.args[0];
				thread.prepend( [new State( 
					point.goal.replace( new Term(",", [new Term("current_output", [new Var("S")]),new Term("put_char", [new Var("S"),char])]) ),
					point.substitution,
					point
				)] );
			},

			// put_char/2
			"put_char/2": function( thread, point, atom ) {
				var stream = atom.args[0], char = atom.args[1];
				var stream2 = pl.type.is_stream( stream ) ? stream : thread.get_stream_by_alias( stream.id );
				if( pl.type.is_variable( stream ) || pl.type.is_variable( char ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_character( char ) ) {
					thread.throw_error( pl.error.type( "character", char, atom.indicator ) );
				} else if( !pl.type.is_variable( stream ) && !pl.type.is_stream( stream ) && !pl.type.is_atom( stream ) ) {
					thread.throw_error( pl.error.domain( "stream_or_alias", stream, atom.indicator ) );
				} else if( !pl.type.is_stream( stream2 ) || stream2.stream === null ) {
					thread.throw_error( pl.error.existence( "stream", stream, atom.indicator ) );
				} else if( stream2.input ) {
					thread.throw_error( pl.error.permission( "output", "stream", stream, atom.indicator ) );
				} else if( stream2.type === "binary" ) {
					thread.throw_error( pl.error.permission( "output", "binary_stream", stream, atom.indicator ) );
				} else {
					if( stream2.stream.put( char.id, stream2.position ) ) {
						if(typeof stream2.position === "number")
							stream2.position++;
						thread.success( point );
					}
				}
			},

			// put_code/1
			"put_code/1": function( thread, point, atom ) {
				var code = atom.args[0];
				thread.prepend( [new State( 
					point.goal.replace( new Term(",", [new Term("current_output", [new Var("S")]),new Term("put_code", [new Var("S"),code])]) ),
					point.substitution,
					point
				)] );
			},

			// put_code/2
			"put_code/2": function( thread, point, atom ) {
				var stream = atom.args[0], code = atom.args[1];
				var stream2 = pl.type.is_stream( stream ) ? stream : thread.get_stream_by_alias( stream.id );
				if( pl.type.is_variable( stream ) || pl.type.is_variable( code ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_integer( code ) ) {
					thread.throw_error( pl.error.type( "integer", code, atom.indicator ) );
				} else if( !pl.type.is_character_code( code ) ) {
					thread.throw_error( pl.error.representation( "character_code", atom.indicator ) );
				} else if( !pl.type.is_variable( stream ) && !pl.type.is_stream( stream ) && !pl.type.is_atom( stream ) ) {
					thread.throw_error( pl.error.domain( "stream_or_alias", stream, atom.indicator ) );
				} else if( !pl.type.is_stream( stream2 ) || stream2.stream === null ) {
					thread.throw_error( pl.error.existence( "stream", stream, atom.indicator ) );
				} else if( stream2.input ) {
					thread.throw_error( pl.error.permission( "output", "stream", stream, atom.indicator ) );
				} else if( stream2.type === "binary" ) {
					thread.throw_error( pl.error.permission( "output", "binary_stream", stream, atom.indicator ) );
				} else {
					if( stream2.stream.put_char( fromCodePoint( code.value ), stream2.position ) ) {
						if(typeof stream2.position === "number")
							stream2.position++;
						thread.success( point );
					}
				}
			},

			// nl/0
			"nl/0": function( thread, point, atom ) {
				thread.prepend( [new State( 
					point.goal.replace( new Term(",", [new Term("current_output", [new Var("S")]),new Term("put_char", [new Var("S"), new Term("\n", [])])]) ),
					point.substitution,
					point
				)] );
			},

			// nl/1
			"nl/1": function( thread, point, atom ) {
				var stream = atom.args[0];
				thread.prepend( [new State( 
					point.goal.replace( new Term("put_char", [stream, new Term("\n", [])]) ),
					point.substitution,
					point
				)] );
			},



			// BYTE INPUT/OUTPUT

			// get_byte/1
			"get_byte/1": function( thread, point, atom ) {
				var byte = atom.args[0];
				thread.prepend( [new State( 
					point.goal.replace( new Term(",", [new Term("current_input", [new Var("S")]),new Term("get_byte", [new Var("S"),byte])]) ),
					point.substitution,
					point
				)] );
			},

			// get_byte/2
			"get_byte/2": function( thread, point, atom ) {
				var stream = atom.args[0], byte = atom.args[1];
				var stream2 = pl.type.is_stream( stream ) ? stream : thread.get_stream_by_alias( stream.id );
				if( pl.type.is_variable( stream ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_variable( byte ) && !pl.type.is_byte( byte ) ) {
					thread.throw_error( pl.error.type( "in_byte", char, atom.indicator ) );
				} else if( !pl.type.is_stream( stream ) && !pl.type.is_atom( stream ) ) {
					thread.throw_error( pl.error.domain( "stream_or_alias", stream, atom.indicator ) );
				} else if( !pl.type.is_stream( stream2 ) || stream2.stream === null ) {
					thread.throw_error( pl.error.existence( "stream", stream, atom.indicator ) );
				} else if( stream2.output ) {
					thread.throw_error( pl.error.permission( "input", "stream", stream, atom.indicator ) );
				} else if( stream2.type === "text" ) {
					thread.throw_error( pl.error.permission( "input", "text_stream", stream, atom.indicator ) );
				} else if( stream2.position === "past_end_of_stream" && stream2.eof_action === "error" ) {
					thread.throw_error( pl.error.permission( "input", "past_end_of_stream", stream, atom.indicator ) );
				} else {
					var stream_byte;
					if( stream2.position === "end_of_stream" ) {
						stream_byte = "end_of_file";
						stream2.position = "past_end_of_stream";
					} else {
						stream_byte = stream2.stream.get_byte( stream2.position );
						if( stream_byte === null ) {
							thread.throw_error( pl.error.representation( "byte", atom.indicator ) );
							return;
						}
						stream2.position++;
					}
					thread.prepend( [new State(
						point.goal.replace( new Term( "=", [new Num(stream_byte,false), byte] ) ),
						point.substitution,
						point
					)] );
				}
			},
			
			// peek_byte/1
			"peek_byte/1": function( thread, point, atom ) {
				var byte = atom.args[0];
				thread.prepend( [new State( 
					point.goal.replace( new Term(",", [new Term("current_input", [new Var("S")]),new Term("peek_byte", [new Var("S"),byte])]) ),
					point.substitution,
					point
				)] );
			},

			// peek_byte/2
			"peek_byte/2": function( thread, point, atom ) {
				var stream = atom.args[0], byte = atom.args[1];
				var stream2 = pl.type.is_stream( stream ) ? stream : thread.get_stream_by_alias( stream.id );
				if( pl.type.is_variable( stream ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_variable( byte ) && !pl.type.is_byte( byte ) ) {
					thread.throw_error( pl.error.type( "in_byte", char, atom.indicator ) );
				} else if( !pl.type.is_stream( stream ) && !pl.type.is_atom( stream ) ) {
					thread.throw_error( pl.error.domain( "stream_or_alias", stream, atom.indicator ) );
				} else if( !pl.type.is_stream( stream2 ) || stream2.stream === null ) {
					thread.throw_error( pl.error.existence( "stream", stream, atom.indicator ) );
				} else if( stream2.output ) {
					thread.throw_error( pl.error.permission( "input", "stream", stream, atom.indicator ) );
				} else if( stream2.type === "text" ) {
					thread.throw_error( pl.error.permission( "input", "text_stream", stream, atom.indicator ) );
				} else if( stream2.position === "past_end_of_stream" && stream2.eof_action === "error" ) {
					thread.throw_error( pl.error.permission( "input", "past_end_of_stream", stream, atom.indicator ) );
				} else {
					var stream_byte;
					if( stream2.position === "end_of_stream" ) {
						stream_byte = "end_of_file";
						stream2.position = "past_end_of_stream";
					} else {
						stream_byte = stream2.stream.get_byte( stream2.position );
						if( stream_byte === null ) {
							thread.throw_error( pl.error.representation( "byte", atom.indicator ) );
							return;
						}
					}
					thread.prepend( [new State(
						point.goal.replace( new Term( "=", [new Num(stream_byte,false), byte] ) ),
						point.substitution,
						point
					)] );
				}
			},

			// put_byte/1
			"put_byte/1": function( thread, point, atom ) {
				var byte = atom.args[0];
				thread.prepend( [new State( 
					point.goal.replace( new Term(",", [new Term("current_output", [new Var("S")]),new Term("put_byte", [new Var("S"),byte])]) ),
					point.substitution,
					point
				)] );
			},

			// put_byte/2
			"put_byte/2": function( thread, point, atom ) {
				var stream = atom.args[0], byte = atom.args[1];
				var stream2 = pl.type.is_stream( stream ) ? stream : thread.get_stream_by_alias( stream.id );
				if( pl.type.is_variable( stream ) || pl.type.is_variable( byte ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_byte( byte ) ) {
					thread.throw_error( pl.error.type( "byte", byte, atom.indicator ) );
				} else if( !pl.type.is_variable( stream ) && !pl.type.is_stream( stream ) && !pl.type.is_atom( stream ) ) {
					thread.throw_error( pl.error.domain( "stream_or_alias", stream, atom.indicator ) );
				} else if( !pl.type.is_stream( stream2 ) || stream2.stream === null ) {
					thread.throw_error( pl.error.existence( "stream", stream, atom.indicator ) );
				} else if( stream2.input ) {
					thread.throw_error( pl.error.permission( "output", "stream", stream, atom.indicator ) );
				} else if( stream2.type === "text" ) {
					thread.throw_error( pl.error.permission( "output", "text_stream", stream, atom.indicator ) );
				} else {
					if( stream2.stream.put_byte( byte.value, stream2.position ) ) {
						if(typeof stream2.position === "number")
							stream2.position++;
						thread.success( point );
					}
				}
			},



			// TERM INPUT/OUTPUT

			// read/1
			"read/1": function( thread, point, atom ) {
				var term = atom.args[0];
				thread.prepend( [new State( 
					point.goal.replace( new Term(",", [new Term("current_input", [new Var("S")]),new Term("read_term", [new Var("S"),term,new Term("[]",[])])]) ),
					point.substitution,
					point
				)] );
			},

			// read/2
			"read/2": function( thread, point, atom ) {
				var stream = atom.args[0], term = atom.args[1];
				thread.prepend( [new State( 
					point.goal.replace( new Term("read_term", [stream,term,new Term("[]",[])]) ),
					point.substitution,
					point
				)] );
			},

			// read_term/2
			"read_term/2": function( thread, point, atom ) {
				var term = atom.args[0], options = atom.args[1];
				thread.prepend( [new State( 
					point.goal.replace( new Term(",", [new Term("current_input", [new Var("S")]),new Term("read_term", [new Var("S"),term,options])]) ),
					point.substitution,
					point
				)] );
			},

			// read_term/3
			"read_term/3": function( thread, point, atom ) {
				var stream = atom.args[0], term = atom.args[1], options = atom.args[2];
				var stream2 = pl.type.is_stream( stream ) ? stream : thread.get_stream_by_alias( stream.id );
				if( pl.type.is_variable( stream ) || pl.type.is_variable( options ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_list( options ) ) {
					thread.throw_error( pl.error.type( "list", options, atom.indicator ) );
				} else if( !pl.type.is_stream( stream ) && !pl.type.is_atom( stream ) ) {
					thread.throw_error( pl.error.domain( "stream_or_alias", stream, atom.indicator ) );
				} else if( !pl.type.is_stream( stream2 ) || stream2.stream === null ) {
					thread.throw_error( pl.error.existence( "stream", stream, atom.indicator ) );
				} else if( stream2.output ) {
					thread.throw_error( pl.error.permission( "input", "stream", stream, atom.indicator ) );
				} else if( stream2.type === "binary" ) {
					thread.throw_error( pl.error.permission( "input", "binary_stream", stream, atom.indicator ) );
				} else if( stream2.position === "past_end_of_stream" && stream2.eof_action === "error" ) {
					thread.throw_error( pl.error.permission( "input", "past_end_of_stream", stream, atom.indicator ) );
				} else {
					// Get options
					var obj_options = {};
					var pointer = options;
					var property;
					while( pl.type.is_term(pointer) && pointer.indicator === "./2" ) {
						property = pointer.args[0];
						if( pl.type.is_variable( property ) ) {
							thread.throw_error( pl.error.instantiation( atom.indicator ) );
							return;
						} else if( !pl.type.is_read_option( property ) ) {
							thread.throw_error( pl.error.domain( "read_option", property, atom.indicator ) );
							return;
						}
						obj_options[property.id] = property.args[0];
						pointer = pointer.args[1];
					}
					if( pointer.indicator !== "[]/0" ) {
						if( pl.type.is_variable( pointer ) )
							thread.throw_error( pl.error.instantiation( atom.indicator ) );
						else
							thread.throw_error( pl.error.type( "list", options, atom.indicator ) );
						return;
					} else {
						var char, tokenizer, expr;
						var text = "";
						var tokens = [];
						var last_token = null;
						// Get term
						while( last_token === null || last_token.name !== "atom" || last_token.value !== "." ||
							(expr.type === ERROR && pl.flatten_error(new Term("throw",[expr.value])).found === "token_not_found") ) {
							char = stream2.stream.get( 1, stream2.position );
							if( char === null ) {
								thread.throw_error( pl.error.representation( "character", atom.indicator ) );
								return;
							}
							if( char === "end_of_file" || char === "past_end_of_file" ) {
								if( expr )
									thread.throw_error( pl.error.syntax( tokens[expr.len-1], ". or expression expected", false ) );
								else
									thread.throw_error( pl.error.syntax( null, "token not found", true ) );
								return;
							}
							stream2.position++;
							text += char;
							tokenizer = new Tokenizer( thread );
							tokenizer.new_text( text );
							tokens = tokenizer.get_tokens();
							last_token = tokens !== null && tokens.length > 0 ? tokens[tokens.length-1] : null;
							if( tokens === null )
								continue;
							expr = parseExpr(thread, tokens, 0, thread.__get_max_priority(), false);
						}
						// Succeed analyzing term
						if( expr.type === SUCCESS && expr.len === tokens.length-1 && last_token.value === "." ) {
							expr = expr.value.rename( thread );
							var eq = new Term( "=", [term, expr] );
							// Variables
							if( obj_options.variables ) {
								var vars = arrayToList( map( nub( expr.variables() ), function(v) { return new Var(v); } ) );
								eq = new Term( ",", [eq, new Term( "=", [obj_options.variables, vars] )] )
							}
							// Variable names
							if( obj_options.variable_names ) {
								var vars = arrayToList( map( nub( expr.variables() ), function(v) {
									var prop;
									for( prop in thread.session.renamed_variables ) {
										if( thread.session.renamed_variables.hasOwnProperty( prop ) ) {
											if( thread.session.renamed_variables[ prop ] === v )
												break;
										}
									}
									return new Term( "=", [new Term( prop, []), new Var(v)] );
								} ) );
								eq = new Term( ",", [eq, new Term( "=", [obj_options.variable_names, vars] )] )
							}
							// Singletons
							if( obj_options.singletons ) {
								var vars = arrayToList( map( new Rule( expr, null ).singleton_variables(), function(v) {
									var prop;
									for( prop in thread.session.renamed_variables ) {
										if( thread.session.renamed_variables.hasOwnProperty( prop ) ) {
											if( thread.session.renamed_variables[ prop ] === v )
												break;
										}
									}
									return new Term( "=", [new Term( prop, []), new Var(v)] );
								} ) );
								eq = new Term( ",", [eq, new Term( "=", [obj_options.singletons, vars] )] )
							}
							thread.prepend( [new State( point.goal.replace( eq ), point.substitution, point )] );
						// Failed analyzing term
						} else {
							if( expr.type === SUCCESS )
								thread.throw_error( pl.error.syntax( tokens[expr.len], "unexpected token", false ) );
							else
								thread.throw_error( expr.value );
						}
					}
				}
			},

			// write/1
			"write/1": function( thread, point, atom ) {
				var term = atom.args[0];
				thread.prepend( [new State( 
					point.goal.replace( new Term(",", [new Term("current_output", [new Var("S")]),new Term("write", [new Var("S"),term])]) ),
					point.substitution,
					point
				)] );
			},
			
			// write/2
			"write/2": function( thread, point, atom ) {
				var stream = atom.args[0], term = atom.args[1];
				thread.prepend( [new State( 
					point.goal.replace( new Term("write_term", [stream, term,
						new Term(".", [new Term("quoted", [new Term("false", [])]),
							new Term(".", [new Term("ignore_ops", [new Term("false")]),
								new Term(".", [new Term("numbervars", [new Term("true")]), new Term("[]",[])])])])]) ),
					point.substitution,
					point
				)] );
			},
			
			// writeq/1
			"writeq/1": function( thread, point, atom ) {
				var term = atom.args[0];
				thread.prepend( [new State( 
					point.goal.replace( new Term(",", [new Term("current_output", [new Var("S")]),new Term("writeq", [new Var("S"),term])]) ),
					point.substitution,
					point
				)] );
			},
			
			// writeq/2
			"writeq/2": function( thread, point, atom ) {
				var stream = atom.args[0], term = atom.args[1];
				thread.prepend( [new State( 
					point.goal.replace( new Term("write_term", [stream, term,
						new Term(".", [new Term("quoted", [new Term("true", [])]),
							new Term(".", [new Term("ignore_ops", [new Term("false")]),
								new Term(".", [new Term("numbervars", [new Term("true")]), new Term("[]",[])])])])]) ),
					point.substitution,
					point
				)] );
			},
			
			// write_canonical/1
			"write_canonical/1": function( thread, point, atom ) {
				var term = atom.args[0];
				thread.prepend( [new State( 
					point.goal.replace( new Term(",", [new Term("current_output", [new Var("S")]),new Term("write_canonical", [new Var("S"),term])]) ),
					point.substitution,
					point
				)] );
			},
			
			// write_canonical/2
			"write_canonical/2": function( thread, point, atom ) {
				var stream = atom.args[0], term = atom.args[1];
				thread.prepend( [new State( 
					point.goal.replace( new Term("write_term", [stream, term,
						new Term(".", [new Term("quoted", [new Term("true", [])]),
							new Term(".", [new Term("ignore_ops", [new Term("true")]),
								new Term(".", [new Term("numbervars", [new Term("false")]), new Term("[]",[])])])])]) ),
					point.substitution,
					point
				)] );
			},

			// write_term/2
			"write_term/2": function( thread, point, atom ) {
				var term = atom.args[0], options = atom.args[1];
				thread.prepend( [new State( 
					point.goal.replace( new Term(",", [new Term("current_output", [new Var("S")]),new Term("write_term", [new Var("S"),term,options])]) ),
					point.substitution,
					point
				)] );
			},
			
			// write_term/3
			"write_term/3": function( thread, point, atom ) {
				var stream = atom.args[0], term = atom.args[1], options = atom.args[2];
				var stream2 = pl.type.is_stream( stream ) ? stream : thread.get_stream_by_alias( stream.id );
				if( pl.type.is_variable( stream ) || pl.type.is_variable( options ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_list( options ) ) {
					thread.throw_error( pl.error.type( "list", options, atom.indicator ) );
				} else if( !pl.type.is_stream( stream ) && !pl.type.is_atom( stream ) ) {
					thread.throw_error( pl.error.domain( "stream_or_alias", stream, atom.indicator ) );
				} else if( !pl.type.is_stream( stream2 ) || stream2.stream === null ) {
					thread.throw_error( pl.error.existence( "stream", stream, atom.indicator ) );
				} else if( stream2.input ) {
					thread.throw_error( pl.error.permission( "output", "stream", stream, atom.indicator ) );
				} else if( stream2.type === "binary" ) {
					thread.throw_error( pl.error.permission( "output", "binary_stream", stream, atom.indicator ) );
				} else if( stream2.position === "past_end_of_stream" && stream2.eof_action === "error" ) {
					thread.throw_error( pl.error.permission( "output", "past_end_of_stream", stream, atom.indicator ) );
				} else {
					// Get options
					var obj_options = {};
					var pointer = options;
					var property;
					while( pl.type.is_term(pointer) && pointer.indicator === "./2" ) {
						property = pointer.args[0];
						if( pl.type.is_variable( property ) ) {
							thread.throw_error( pl.error.instantiation( atom.indicator ) );
							return;
						} else if( !pl.type.is_write_option( property ) ) {
							thread.throw_error( pl.error.domain( "write_option", property, atom.indicator ) );
							return;
						}
						obj_options[property.id] = property.args[0].id === "true";
						pointer = pointer.args[1];
					}
					if( pointer.indicator !== "[]/0" ) {
						if( pl.type.is_variable( pointer ) )
							thread.throw_error( pl.error.instantiation( atom.indicator ) );
						else
							thread.throw_error( pl.error.type( "list", options, atom.indicator ) );
						return;
					} else {
						obj_options.session = thread.session;
						var text = term.toString( obj_options );
						stream2.stream.put( text, stream2.position );
						if( typeof stream2.position === "number" )
							stream2.position += text.length;
						thread.success( point );
					}
				}
			},


			
			// IMPLEMENTATION DEFINED HOOKS
			
			// halt/0
			"halt/0": function( thread, point, _ ) {
				if( thread.get_flag("nodejs").indicator === "true/0" )
					process.exit();
				thread.points = [];
			},
			
			// halt/1
			"halt/1": function( thread, point, atom ) {
				var int = atom.args[0];
				if( pl.type.is_variable( int ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_integer( int ) ) {
					thread.throw_error( pl.error.type( "integer", int, atom.indicator ) );
				} else {
					if( thread.get_flag("nodejs").indicator === "true/0" )
						process.exit(int.value);
					thread.points = [];
				}
			},
			
			// current_prolog_flag/2
			"current_prolog_flag/2": function( thread, point, atom ) {
				var flag = atom.args[0], value = atom.args[1];
				if( !pl.type.is_variable( flag ) && !pl.type.is_atom( flag ) ) {
					thread.throw_error( pl.error.type( "atom", flag, atom.indicator ) );
				} else if( !pl.type.is_variable( flag ) && !pl.type.is_flag( flag ) ) {
					thread.throw_error( pl.error.domain( "prolog_flag", flag, atom.indicator ) );
				} else {
					var states = [];
					for( var name in pl.flag ) {
						if(!pl.flag.hasOwnProperty(name)) continue;
						var goal = new Term( ",", [new Term( "=", [new Term( name ), flag] ), new Term( "=", [thread.get_flag(name), value] )] );
						states.push( new State( point.goal.replace( goal ), point.substitution, point ) );
					}
					thread.prepend( states );
				}
			},
			
			// set_prolog_flag/2
			"set_prolog_flag/2": function( thread, point, atom ) {
				var flag = atom.args[0], value = atom.args[1];
				if( pl.type.is_variable( flag ) || pl.type.is_variable( value ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_atom( flag ) ) {
					thread.throw_error( pl.error.type( "atom", flag, atom.indicator ) );
				} else if( !pl.type.is_flag( flag ) ) {
					thread.throw_error( pl.error.domain( "prolog_flag", flag, atom.indicator ) );
				} else if( !pl.type.is_value_flag( flag, value ) ) {
					thread.throw_error( pl.error.domain( "flag_value", new Term( "+", [flag, value] ), atom.indicator ) );
				} else if( !pl.type.is_modifiable_flag( flag ) ) {
					thread.throw_error( pl.error.permission( "modify", "flag", flag ) );
				} else {
					thread.session.flag[flag.id] = value;
					thread.success( point );
				}
			},



			// LOAD PROLOG SOURCE FILES

			// consult/1
			"consult/1": function( thread, point, atom ) {
				var src = atom.args[0];
				if(pl.type.is_variable(src)) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if(!pl.type.is_atom(src)) {
					thread.throw_error( pl.error.type( "atom", src, atom.indicator ) );
				} else {
					if(thread.consult( src.id ))
						thread.success(point);
				}
			},



			// TIME AND DATES

			// get_time/1
			"get_time/1": function( thread, point, atom ) {
				var time = atom.args[0];
				if(!pl.type.is_variable(time) && !pl.type.is_number(time)) {
					thread.throw_error( pl.error.type( "number", time, atom.indicator ) );
				} else {
					var current = new Num(Date.now(), true);
					thread.prepend( [new State(
						point.goal.replace( new Term( "=", [time, current] ) ), 
						point.substitution,
						point
					)] );
				}
			},



			// GRAMMARS

			// phrase/3
			"phrase/3": function( thread, point, atom ) {
				var grbody = atom.args[0], s0 = atom.args[1], s = atom.args[2];
				if( pl.type.is_variable( grbody ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_callable( grbody ) ) {
					thread.throw_error( pl.error.type( "callable", grbody, atom.indicator ) );
				} else {
					var goal = body_to_dcg( grbody.clone(), s0, thread );
					if(goal !== null) {
						thread.prepend( [new State(
							point.goal.replace( new Term( ",", [goal.value, new Term("=", [goal.variable, s])] ) ), 
							point.substitution,
							point
						)] );
					}
				}
			},

			// phrase/2
			"phrase/2": function( thread, point, atom ) {
				var grbody = atom.args[0], s0 = atom.args[1];
				thread.prepend( [new State(
					point.goal.replace( new Term( "phrase", [grbody, s0, new Term("[]", [])] ) ), 
					point.substitution,
					point
				)] );
			},



			// TAU PROLOG INFORMATION

			// version/0
			"version/0": function( thread, point, atom ) {
				var msg = "Welcome to Tau Prolog version " + version.major + "." + version.minor + "." + version.patch + "\n";
				msg += "Tau Prolog comes with ABSOLUTELY NO WARRANTY. This is free software.\n";
				msg += "Please run ?- license. for legal details.\n";
				msg += "For online help and background, visit http:/tau-prolog.org";
				thread.prepend( [new State(
					point.goal.replace( new Term( "write", [new Term( msg, [] )] ) ), 
					point.substitution,
					point
				)] );
			},

			// license/0
			"license/0": function( thread, point, atom ) {
				var msg = "Tau Prolog. A Prolog interpreter in JavaScript.\n";
				msg += "Copyright (C) 2017 - 2020 José Antonio Riaza Valverde\n\n";
				msg += "Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:\n";
				msg += "1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.\n";
				msg += "2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.\n";
				msg += "3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.\n\n";
				msg += "THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n";
				msg += "You should have received a copy of the BSD 3-Clause License along with this program. If not, see https://opensource.org/licenses/BSD-3-Clause";
				thread.prepend( [new State(
					point.goal.replace( new Term( "write", [new Term( msg, [] )] ) ), 
					point.substitution,
					point
				)] );
			}

		},
		
		// Flags
		flag: {
			
			// Bounded numbers
			bounded: {
				allowed: [new Term( "true" ), new Term( "false" )],
				value: new Term( "true" ),
				changeable: false
			},
			
			// Maximum integer
			max_integer: {
				allowed: [new Num( Number.MAX_SAFE_INTEGER )],
				value: new Num( Number.MAX_SAFE_INTEGER ),
				changeable: false
			},
			
			// Minimum integer
			min_integer: {
				allowed: [new Num( Number.MIN_SAFE_INTEGER )],
				value: new Num( Number.MIN_SAFE_INTEGER ),
				changeable: false
			},
			
			// Rounding function
			integer_rounding_function: {
				allowed: [new Term( "down" ), new Term( "toward_zero" )],
				value: new Term( "toward_zero" ),
				changeable: false
			},
			
			// Character conversion
			char_conversion: {
				allowed: [new Term( "on" ), new Term( "off" )],
				value: new Term( "on" ),
				changeable: true
			},
			
			// Debugger
			debug: {
				allowed: [new Term( "on" ), new Term( "off" )],
				value: new Term( "off" ),
				changeable: true
			},
			
			// Maximum arity of predicates
			max_arity: {
				allowed: [new Term( "unbounded" )],
				value: new Term( "unbounded" ),
				changeable: false
			},
			
			// Unkwnow predicates behavior
			unknown: {
				allowed: [new Term( "error" ), new Term( "fail" ), new Term( "warning" )],
				value: new Term( "error" ),
				changeable: true
			},
			
			// Double quotes behavior
			double_quotes: {
				allowed: [new Term( "chars" ), new Term( "codes" ), new Term( "atom" )],
				value: new Term( "codes" ),
				changeable: true
			},
			
			// Occurs check behavior
			occurs_check: {
				allowed: [new Term( "false" ), new Term( "true" )],
				value: new Term( "false" ),
				changeable: true
			},
			
			// Dialect
			dialect: {
				allowed: [new Term( "tau" )],
				value: new Term( "tau" ),
				changeable: false
			},
			
			// Version
			version_data: {
				allowed: [new Term( "tau", [new Num(version.major,false), new Num(version.minor,false), new Num(version.patch,false), new Term(version.status)] )],
				value: new Term( "tau", [new Num(version.major,false), new Num(version.minor,false), new Num(version.patch,false), new Term(version.status)] ),
				changeable: false
			},
			
			// NodeJS
			nodejs: {
				allowed: [new Term( "true" ), new Term( "false" )],
				value: new Term( nodejs_flag ? "true" : "false" ),
				changeable: false
			},

			// Arguments
			argv: {
				allowed: [nodejs_arguments],
				value: nodejs_arguments,
				changeble: false
			}
			
		},
		
		// Unify
		unify: function( s, t, occurs_check ) {
			occurs_check = occurs_check === undefined ? false : occurs_check;
			var G = [{left: s, right: t}], links = {};
			while( G.length !== 0 ) {
				var eq = G.pop();
				s = eq.left;
				t = eq.right;
				if(s == t)
					continue;
				if( pl.type.is_term(s) && pl.type.is_term(t) ) {
					if( s.indicator !== t.indicator )
						return null;
					// list
					if(s.indicator === "./2") {
						var pointer_s = s, pointer_t = t;
						while(pointer_s.indicator === "./2" && pointer_t.indicator === "./2") {
							G.push( {left: pointer_s.args[0], right: pointer_t.args[0]} );
							pointer_s = pointer_s.args[1];
							pointer_t = pointer_t.args[1];
						}
						G.push( {left: pointer_s, right: pointer_t} );
					// compound term
					} else {
						for( var i = 0; i < s.args.length; i++ )
							G.push( {left: s.args[i], right: t.args[i]} );
					}
				} else if( pl.type.is_number(s) && pl.type.is_number(t) ) {
					if( s.value !== t.value || s.is_float !== t.is_float )
						return null;
				} else if( pl.type.is_variable(s) ) {
					// X = X
					if( pl.type.is_variable(t) && s.id === t.id )
						continue;
					// Occurs check
					if( occurs_check === true && indexOf( t.variables(), s.id ) !== -1 )
						return null;
					if( s.id !== "_" ) {
						var subs = new Substitution();
						subs.add( s.id, t );
						for( var i = 0; i < G.length; i++ ) {
							G[i].left = G[i].left.apply( subs );
							G[i].right = G[i].right.apply( subs );
						}
						for( var i in links )
							links[i] = links[i].apply( subs );
						links[s.id] = t;
					}
				} else if( pl.type.is_variable(t) ) {
					G.push( {left: t, right: s} );
				} else if( s.unify !== undefined ) {
					if( !s.unify(t) )
						return null;
				} else {
					return null;
				}
			}
			return new Substitution( links );
		},
		
		// Compare
		compare: function( obj1, obj2 ) {
			var type = pl.type.compare( obj1, obj2 );
			return type !== 0 ? type : obj1.compare( obj2 );
		},
		
		// Arithmetic comparison
		arithmetic_compare: function( thread, obj1, obj2 ) {
			var expr1 = obj1.interpret( thread );
			if( !pl.type.is_number( expr1 ) ) {
				return expr1;
			} else {
				var expr2 = obj2.interpret( thread );
				if( !pl.type.is_number( expr2 ) ) {
					return expr2;
				} else {
					return expr1.value < expr2.value ? -1 : (expr1.value > expr2.value ? 1 : 0);
				}
			}
		},
		
		// Operate
		operate: function( thread, obj ) {
			if( pl.type.is_operator( obj ) ) {
				var op = pl.type.is_operator( obj );
				var args = [], value;
				var type = false;
				for( var i = 0; i < obj.args.length; i++ ) {
					value = obj.args[i].interpret( thread );
					if( !pl.type.is_number( value ) ) {
						return value;
					} else if( op.type_args !== null && value.is_float !== op.type_args ) {
						return pl.error.type( op.type_args ? "float" : "integer", value, thread.__call_indicator );
					} else {
						args.push( value.value );
					}
					type = type || value.is_float;
				}
				args.push( thread );
				value = pl.arithmetic.evaluation[obj.indicator].fn.apply( this, args );
				type = op.type_result === null ? type : op.type_result;
				if( pl.type.is_term( value ) ) {
					return value;
				} else if( value === Number.POSITIVE_INFINITY || value === Number.NEGATIVE_INFINITY ) {
					return pl.error.evaluation( "overflow", thread.__call_indicator );
				} else if( type === false && thread.get_flag( "bounded" ).id === "true" && (value > thread.get_flag( "max_integer" ).value || value < thread.get_flag( "min_integer" ).value) ) {
					return pl.error.evaluation( "int_overflow", thread.__call_indicator );
				} else {
					return new Num( value, type );
				}
			} else {
				return pl.error.type( "evaluable", obj.indicator, thread.__call_indicator );
			}
		},
		
		// Errors
		error: {
			
			// Existence error
			existence: function( type, object, indicator ) {
				if( typeof object === "string" )
					object = str_indicator( object );
				return new Term( "error", [new Term( "existence_error", [new Term( type ), object] ), str_indicator( indicator )] );
			},
			
			// Type error
			type: function( expected, found, indicator ) {
				return new Term( "error", [new Term( "type_error", [new Term( expected ), found] ), str_indicator( indicator )] );
			},
			
			// Instantation error
			instantiation: function( indicator ) {
				return new Term( "error", [new Term( "instantiation_error" ), str_indicator( indicator )] );
			},
			
			// Domain error
			domain: function( expected, found, indicator ) {
				return new Term( "error", [new Term( "domain_error", [new Term( expected ), found]), str_indicator( indicator )] );
			},
			
			// Representation error
			representation: function( flag, indicator ) {
				return new Term( "error", [new Term( "representation_error", [new Term( flag )] ), str_indicator( indicator )] );
			},
			
			// Permission error
			permission: function( operation, type, found, indicator ) {
				return new Term( "error", [new Term( "permission_error", [new Term( operation ), new Term( type ), found] ), str_indicator( indicator )] );
			},
			
			// Evaluation error
			evaluation: function( error, indicator ) {
				return new Term( "error", [new Term( "evaluation_error", [new Term( error )] ), str_indicator( indicator )] );
			},
			
			// Syntax error
			syntax: function( token, expected, last ) {
				token = token || {value: "", line: 0, column: 0, matches: [""], start: 0};
				var position = last && token.matches.length > 0 ? token.start + token.matches[0].length : token.start;
				var found = last ? new Term("token_not_found") : new Term("found", [new Term(token.value.toString())]);
				var info = new Term( ".", [new Term( "line", [new Num(token.line+1)] ), new Term( ".", [new Term( "column", [new Num(position+1)] ), new Term( ".", [found, new Term( "[]", [] )] )] )] );
				return new Term( "error", [new Term( "syntax_error", [new Term( expected )] ), info] );
			},
			
			// Syntax error by predicate
			syntax_by_predicate: function( expected, indicator ) {
				return new Term( "error", [new Term( "syntax_error", [new Term( expected ) ] ), str_indicator( indicator )] );
			}
			
		},
		
		// Warnings
		warning: {
			
			// Singleton variables
			singleton: function( variables, rule, line ) {
				var list = new Term( "[]" );
				for( var i = variables.length-1; i >= 0; i-- )
					list = new Term( ".", [new Var(variables[i]), list] );
				return new Term( "warning", [new Term( "singleton_variables", [list, str_indicator(rule)]), new Term(".",[new Term( "line", [ new Num( line, false ) ]), new Term("[]")])] );
			},
			
			// Failed goal
			failed_goal: function( goal, line ) {
				return new Term( "warning", [new Term( "failed_goal", [goal]), new Term(".",[new Term( "line", [ new Num( line, false ) ]), new Term("[]")])] );
			}

		},
		
		// Format of renamed variables
		format_variable: function( variable ) {
			return "_" + variable;
		},
		
		// Format of computed answers
		format_answer: function( answer, thread, options ) {
			if( thread instanceof Session )
				thread = thread.thread;
			var options = options ? options : {};
			options.session = thread ? thread.session : undefined;
			if( pl.type.is_error( answer ) ) {
				return "uncaught exception: " + answer.args[0].toString();
			} else if( answer === false ) {
				return "false.";
			} else if( answer === null ) {
				return "limit exceeded ;";
			} else {
				var i = 0;
				var str = "";
				if( pl.type.is_substitution( answer ) ) {
					var dom = answer.domain( true );
					for( var link in answer.links ){
						if( !answer.links.hasOwnProperty(link) ) continue;
						if( pl.type.is_variable(answer.links[link]) ) {
							var links = {};
							links[answer.links[link].id] = new Var(link);
							answer = answer.apply( new Substitution(links) );
						}
					}
					answer = answer.filter( function( id, value ) {
						return !pl.type.is_variable( value ) ||
							pl.type.is_variable( value ) && answer.has_attributes( id ) ||
							indexOf( dom, value.id ) !== -1 && id !== value.id;
					} );
				}
				for( var link in answer.links ) {
					if(!answer.links.hasOwnProperty(link))
						continue;
					if( pl.type.is_variable( answer.links[link] ) && link === answer.links[link].id ) {
						var attrs = answer.attrs[link];
						for( var module in attrs ) {
							if(!attrs.hasOwnProperty(module))
								continue;
							i++;
							if( str !== "" )
								str += ", ";
							str += "put_attr(" + link + ", " + module + ", " + attrs[module].toString(options) + ")";
						}
					} else {
						i++;
						if( str !== "" )
							str += ", ";
						str += link.toString( options ) + " = " +
							answer.links[link].toString( options, {priority: "700", class: "xfx", indicator: "=/2"}, "right" );
					}
				}
				var delimiter = typeof thread === "undefined" || thread.points.length > 0 ? " ;" : "."; 
				if( i === 0 ) {
					return "true" + delimiter;
				} else {
					return str + delimiter;
				}
			}
		},
		
		// Flatten default errors
		flatten_error: function( error ) {
			if( !pl.type.is_error( error ) ) return null;
			error = error.args[0];
			var obj = {};
			obj.type = error.args[0].id;
			obj.thrown = obj.type === "syntax_error" ? null : error.args[1].id;
			obj.expected = null;
			obj.found = null;
			obj.representation = null;
			obj.existence = null;
			obj.existence_type = null;
			obj.line = null;
			obj.column = null;
			obj.permission_operation = null;
			obj.permission_type = null;
			obj.evaluation_type = null;
			if( obj.type === "type_error" || obj.type === "domain_error" ) {
				obj.expected = error.args[0].args[0].id;
				obj.found = error.args[0].args[1].toString();
			} else if( obj.type === "syntax_error" ) {
				if( error.args[1].indicator === "./2" ) {
					obj.expected = error.args[0].args[0].id;
					obj.found = error.args[1].args[1].args[1].args[0];
					obj.found = obj.found.id === "token_not_found" ? obj.found.id : obj.found.args[0].id;
					obj.line = error.args[1].args[0].args[0].value;
					obj.column = error.args[1].args[1].args[0].args[0].value;
				} else {
					obj.thrown = error.args[1].id;
				}
			} else if( obj.type === "permission_error" ) {
				obj.found = error.args[0].args[2].toString();
				obj.permission_operation = error.args[0].args[0].id;
				obj.permission_type = error.args[0].args[1].id;
			} else if( obj.type === "evaluation_error" ) {
				obj.evaluation_type = error.args[0].args[0].id;
			} else if( obj.type === "representation_error" ) {
				obj.representation = error.args[0].args[0].id;
			} else if( obj.type === "existence_error" ) {
				obj.existence = error.args[0].args[1].toString();
				obj.existence_type = error.args[0].args[0].id;
			}
			return obj;
		},
		
		// Create new session
		create: function( limit , output ) {
          return new pl.type.Session( limit, output );
		}
		
	};

	if( typeof module !== 'undefined' ) {
		prolog = pl;
	} else {
		window.prolog = pl;
	}
	
})();

(function( pl ) {

	var predicates = function() {
		
		return {
			
			// append/2
			"append/2": [
				new pl.type.Rule(new pl.type.Term("append", [new pl.type.Var("X"),new pl.type.Var("L")]), new pl.type.Term("foldl", [new pl.type.Term("append", []),new pl.type.Var("X"),new pl.type.Term("[]", []),new pl.type.Var("L")]))
			],

			// append/3
			"append/3": [
				new pl.type.Rule(new pl.type.Term("append", [new pl.type.Term("[]", []),new pl.type.Var("X"),new pl.type.Var("X")]), null),
				new pl.type.Rule(new pl.type.Term("append", [new pl.type.Term(".", [new pl.type.Var("H"),new pl.type.Var("T")]),new pl.type.Var("X"),new pl.type.Term(".", [new pl.type.Var("H"),new pl.type.Var("S")])]), new pl.type.Term("append", [new pl.type.Var("T"),new pl.type.Var("X"),new pl.type.Var("S")]))
			],
			
			// member/2
			"member/2": [
				new pl.type.Rule(new pl.type.Term("member", [new pl.type.Var("X"),new pl.type.Term(".", [new pl.type.Var("X"),new pl.type.Var("_")])]), null),
				new pl.type.Rule(new pl.type.Term("member", [new pl.type.Var("X"),new pl.type.Term(".", [new pl.type.Var("_"),new pl.type.Var("Xs")])]), new pl.type.Term("member", [new pl.type.Var("X"),new pl.type.Var("Xs")]))
			],
			
			// permutation/2
			"permutation/2": [
				new pl.type.Rule(new pl.type.Term("permutation", [new pl.type.Term("[]", []),new pl.type.Term("[]", [])]), null),
				new pl.type.Rule(new pl.type.Term("permutation", [new pl.type.Term(".", [new pl.type.Var("H"),new pl.type.Var("T")]),new pl.type.Var("S")]), new pl.type.Term(",", [new pl.type.Term("permutation", [new pl.type.Var("T"),new pl.type.Var("P")]),new pl.type.Term(",", [new pl.type.Term("append", [new pl.type.Var("X"),new pl.type.Var("Y"),new pl.type.Var("P")]),new pl.type.Term("append", [new pl.type.Var("X"),new pl.type.Term(".", [new pl.type.Var("H"),new pl.type.Var("Y")]),new pl.type.Var("S")])])]))
			],
			
			// maplist/2
			"maplist/2": [
				new pl.type.Rule(new pl.type.Term("maplist", [new pl.type.Var("_"),new pl.type.Term("[]", [])]), null),
				new pl.type.Rule(new pl.type.Term("maplist", [new pl.type.Var("P"),new pl.type.Term(".", [new pl.type.Var("X"),new pl.type.Var("Xs")])]), new pl.type.Term(",", [new pl.type.Term("call", [new pl.type.Var("P"),new pl.type.Var("X")]),new pl.type.Term("maplist", [new pl.type.Var("P"),new pl.type.Var("Xs")])]))
			],
			
			// maplist/3
			"maplist/3": [
				new pl.type.Rule(new pl.type.Term("maplist", [new pl.type.Var("_"),new pl.type.Term("[]", []),new pl.type.Term("[]", [])]), null),
				new pl.type.Rule(new pl.type.Term("maplist", [new pl.type.Var("P"),new pl.type.Term(".", [new pl.type.Var("A"),new pl.type.Var("As")]),new pl.type.Term(".", [new pl.type.Var("B"),new pl.type.Var("Bs")])]), new pl.type.Term(",", [new pl.type.Term("call", [new pl.type.Var("P"),new pl.type.Var("A"),new pl.type.Var("B")]),new pl.type.Term("maplist", [new pl.type.Var("P"),new pl.type.Var("As"),new pl.type.Var("Bs")])]))
			],
			
			// maplist/4
			"maplist/4": [
				new pl.type.Rule(new pl.type.Term("maplist", [new pl.type.Var("_"),new pl.type.Term("[]", []),new pl.type.Term("[]", []),new pl.type.Term("[]", [])]), null),
				new pl.type.Rule(new pl.type.Term("maplist", [new pl.type.Var("P"),new pl.type.Term(".", [new pl.type.Var("A"),new pl.type.Var("As")]),new pl.type.Term(".", [new pl.type.Var("B"),new pl.type.Var("Bs")]),new pl.type.Term(".", [new pl.type.Var("C"),new pl.type.Var("Cs")])]), new pl.type.Term(",", [new pl.type.Term("call", [new pl.type.Var("P"),new pl.type.Var("A"),new pl.type.Var("B"),new pl.type.Var("C")]),new pl.type.Term("maplist", [new pl.type.Var("P"),new pl.type.Var("As"),new pl.type.Var("Bs"),new pl.type.Var("Cs")])]))
			],
			
			// maplist/5
			"maplist/5": [
				new pl.type.Rule(new pl.type.Term("maplist", [new pl.type.Var("_"),new pl.type.Term("[]", []),new pl.type.Term("[]", []),new pl.type.Term("[]", []),new pl.type.Term("[]", [])]), null),
				new pl.type.Rule(new pl.type.Term("maplist", [new pl.type.Var("P"),new pl.type.Term(".", [new pl.type.Var("A"),new pl.type.Var("As")]),new pl.type.Term(".", [new pl.type.Var("B"),new pl.type.Var("Bs")]),new pl.type.Term(".", [new pl.type.Var("C"),new pl.type.Var("Cs")]),new pl.type.Term(".", [new pl.type.Var("D"),new pl.type.Var("Ds")])]), new pl.type.Term(",", [new pl.type.Term("call", [new pl.type.Var("P"),new pl.type.Var("A"),new pl.type.Var("B"),new pl.type.Var("C"),new pl.type.Var("D")]),new pl.type.Term("maplist", [new pl.type.Var("P"),new pl.type.Var("As"),new pl.type.Var("Bs"),new pl.type.Var("Cs"),new pl.type.Var("Ds")])]))
			],
			
			// maplist/6
			"maplist/6": [
				new pl.type.Rule(new pl.type.Term("maplist", [new pl.type.Var("_"),new pl.type.Term("[]", []),new pl.type.Term("[]", []),new pl.type.Term("[]", []),new pl.type.Term("[]", []),new pl.type.Term("[]", [])]), null),
				new pl.type.Rule(new pl.type.Term("maplist", [new pl.type.Var("P"),new pl.type.Term(".", [new pl.type.Var("A"),new pl.type.Var("As")]),new pl.type.Term(".", [new pl.type.Var("B"),new pl.type.Var("Bs")]),new pl.type.Term(".", [new pl.type.Var("C"),new pl.type.Var("Cs")]),new pl.type.Term(".", [new pl.type.Var("D"),new pl.type.Var("Ds")]),new pl.type.Term(".", [new pl.type.Var("E"),new pl.type.Var("Es")])]), new pl.type.Term(",", [new pl.type.Term("call", [new pl.type.Var("P"),new pl.type.Var("A"),new pl.type.Var("B"),new pl.type.Var("C"),new pl.type.Var("D"),new pl.type.Var("E")]),new pl.type.Term("maplist", [new pl.type.Var("P"),new pl.type.Var("As"),new pl.type.Var("Bs"),new pl.type.Var("Cs"),new pl.type.Var("Ds"),new pl.type.Var("Es")])]))
			],
			
			// maplist/7
			"maplist/7": [
				new pl.type.Rule(new pl.type.Term("maplist", [new pl.type.Var("_"),new pl.type.Term("[]", []),new pl.type.Term("[]", []),new pl.type.Term("[]", []),new pl.type.Term("[]", []),new pl.type.Term("[]", []),new pl.type.Term("[]", [])]), null),
				new pl.type.Rule(new pl.type.Term("maplist", [new pl.type.Var("P"),new pl.type.Term(".", [new pl.type.Var("A"),new pl.type.Var("As")]),new pl.type.Term(".", [new pl.type.Var("B"),new pl.type.Var("Bs")]),new pl.type.Term(".", [new pl.type.Var("C"),new pl.type.Var("Cs")]),new pl.type.Term(".", [new pl.type.Var("D"),new pl.type.Var("Ds")]),new pl.type.Term(".", [new pl.type.Var("E"),new pl.type.Var("Es")]),new pl.type.Term(".", [new pl.type.Var("F"),new pl.type.Var("Fs")])]), new pl.type.Term(",", [new pl.type.Term("call", [new pl.type.Var("P"),new pl.type.Var("A"),new pl.type.Var("B"),new pl.type.Var("C"),new pl.type.Var("D"),new pl.type.Var("E"),new pl.type.Var("F")]),new pl.type.Term("maplist", [new pl.type.Var("P"),new pl.type.Var("As"),new pl.type.Var("Bs"),new pl.type.Var("Cs"),new pl.type.Var("Ds"),new pl.type.Var("Es"),new pl.type.Var("Fs")])]))
			],
			
			// maplist/8
			"maplist/8": [
				new pl.type.Rule(new pl.type.Term("maplist", [new pl.type.Var("_"),new pl.type.Term("[]", []),new pl.type.Term("[]", []),new pl.type.Term("[]", []),new pl.type.Term("[]", []),new pl.type.Term("[]", []),new pl.type.Term("[]", []),new pl.type.Term("[]", [])]), null),
				new pl.type.Rule(new pl.type.Term("maplist", [new pl.type.Var("P"),new pl.type.Term(".", [new pl.type.Var("A"),new pl.type.Var("As")]),new pl.type.Term(".", [new pl.type.Var("B"),new pl.type.Var("Bs")]),new pl.type.Term(".", [new pl.type.Var("C"),new pl.type.Var("Cs")]),new pl.type.Term(".", [new pl.type.Var("D"),new pl.type.Var("Ds")]),new pl.type.Term(".", [new pl.type.Var("E"),new pl.type.Var("Es")]),new pl.type.Term(".", [new pl.type.Var("F"),new pl.type.Var("Fs")]),new pl.type.Term(".", [new pl.type.Var("G"),new pl.type.Var("Gs")])]), new pl.type.Term(",", [new pl.type.Term("call", [new pl.type.Var("P"),new pl.type.Var("A"),new pl.type.Var("B"),new pl.type.Var("C"),new pl.type.Var("D"),new pl.type.Var("E"),new pl.type.Var("F"),new pl.type.Var("G")]),new pl.type.Term("maplist", [new pl.type.Var("P"),new pl.type.Var("As"),new pl.type.Var("Bs"),new pl.type.Var("Cs"),new pl.type.Var("Ds"),new pl.type.Var("Es"),new pl.type.Var("Fs"),new pl.type.Var("Gs")])]))
			],
			
			// include/3
			"include/3": [
				new pl.type.Rule(new pl.type.Term("include", [new pl.type.Var("_"),new pl.type.Term("[]", []),new pl.type.Term("[]", [])]), null),
				new pl.type.Rule(new pl.type.Term("include", [new pl.type.Var("P"),new pl.type.Term(".", [new pl.type.Var("H"),new pl.type.Var("T")]),new pl.type.Var("L")]), new pl.type.Term(",", [new pl.type.Term("=..", [new pl.type.Var("P"),new pl.type.Var("A")]),new pl.type.Term(",", [new pl.type.Term("append", [new pl.type.Var("A"),new pl.type.Term(".", [new pl.type.Var("H"),new pl.type.Term("[]", [])]),new pl.type.Var("B")]),new pl.type.Term(",", [new pl.type.Term("=..", [new pl.type.Var("F"),new pl.type.Var("B")]),new pl.type.Term(",", [new pl.type.Term(";", [new pl.type.Term(",", [new pl.type.Term("call", [new pl.type.Var("F")]),new pl.type.Term(",", [new pl.type.Term("=", [new pl.type.Var("L"),new pl.type.Term(".", [new pl.type.Var("H"),new pl.type.Var("S")])]),new pl.type.Term("!", [])])]),new pl.type.Term("=", [new pl.type.Var("L"),new pl.type.Var("S")])]),new pl.type.Term("include", [new pl.type.Var("P"),new pl.type.Var("T"),new pl.type.Var("S")])])])])]))
			],
			
			// exclude/3
			"exclude/3": [
				new pl.type.Rule(new pl.type.Term("exclude", [new pl.type.Var("_"),new pl.type.Term("[]", []),new pl.type.Term("[]", [])]), null),
				new pl.type.Rule(new pl.type.Term("exclude", [new pl.type.Var("P"),new pl.type.Term(".", [new pl.type.Var("H"),new pl.type.Var("T")]),new pl.type.Var("S")]), new pl.type.Term(",", [new pl.type.Term("exclude", [new pl.type.Var("P"),new pl.type.Var("T"),new pl.type.Var("E")]),new pl.type.Term(",", [new pl.type.Term("=..", [new pl.type.Var("P"),new pl.type.Var("L")]),new pl.type.Term(",", [new pl.type.Term("append", [new pl.type.Var("L"),new pl.type.Term(".", [new pl.type.Var("H"),new pl.type.Term("[]", [])]),new pl.type.Var("Q")]),new pl.type.Term(",", [new pl.type.Term("=..", [new pl.type.Var("R"),new pl.type.Var("Q")]),new pl.type.Term(";", [new pl.type.Term(",", [new pl.type.Term("call", [new pl.type.Var("R")]),new pl.type.Term(",", [new pl.type.Term("!", []),new pl.type.Term("=", [new pl.type.Var("S"),new pl.type.Var("E")])])]),new pl.type.Term("=", [new pl.type.Var("S"),new pl.type.Term(".", [new pl.type.Var("H"),new pl.type.Var("E")])])])])])])]))
			],
			
			// foldl/4
			"foldl/4": [
				new pl.type.Rule(new pl.type.Term("foldl", [new pl.type.Var("_"),new pl.type.Term("[]", []),new pl.type.Var("I"),new pl.type.Var("I")]), null),
				new pl.type.Rule(new pl.type.Term("foldl", [new pl.type.Var("P"),new pl.type.Term(".", [new pl.type.Var("H"),new pl.type.Var("T")]),new pl.type.Var("I"),new pl.type.Var("R")]), new pl.type.Term(",", [new pl.type.Term("=..", [new pl.type.Var("P"),new pl.type.Var("L")]),new pl.type.Term(",", [new pl.type.Term("append", [new pl.type.Var("L"),new pl.type.Term(".", [new pl.type.Var("I"),new pl.type.Term(".", [new pl.type.Var("H"),new pl.type.Term(".", [new pl.type.Var("X"),new pl.type.Term("[]", [])])])]),new pl.type.Var("L2")]),new pl.type.Term(",", [new pl.type.Term("=..", [new pl.type.Var("P2"),new pl.type.Var("L2")]),new pl.type.Term(",", [new pl.type.Term("call", [new pl.type.Var("P2")]),new pl.type.Term("foldl", [new pl.type.Var("P"),new pl.type.Var("T"),new pl.type.Var("X"),new pl.type.Var("R")])])])])]))
			],
			
			// select/3
			"select/3": [
				new pl.type.Rule(new pl.type.Term("select", [new pl.type.Var("E"),new pl.type.Term(".", [new pl.type.Var("E"),new pl.type.Var("Xs")]),new pl.type.Var("Xs")]), null),
				new pl.type.Rule(new pl.type.Term("select", [new pl.type.Var("E"),new pl.type.Term(".", [new pl.type.Var("X"),new pl.type.Var("Xs")]),new pl.type.Term(".", [new pl.type.Var("X"),new pl.type.Var("Ys")])]), new pl.type.Term("select", [new pl.type.Var("E"),new pl.type.Var("Xs"),new pl.type.Var("Ys")]))
			],
			
			// sum_list/2
			"sum_list/2": [
				new pl.type.Rule(new pl.type.Term("sum_list", [new pl.type.Term("[]", []),new pl.type.Num(0, false)]), null),
				new pl.type.Rule(new pl.type.Term("sum_list", [new pl.type.Term(".", [new pl.type.Var("X"),new pl.type.Var("Xs")]),new pl.type.Var("S")]), new pl.type.Term(",", [new pl.type.Term("sum_list", [new pl.type.Var("Xs"),new pl.type.Var("Y")]),new pl.type.Term("is", [new pl.type.Var("S"),new pl.type.Term("+", [new pl.type.Var("X"),new pl.type.Var("Y")])])]))
			],
			
			// max_list/2
			"max_list/2": [
				new pl.type.Rule(new pl.type.Term("max_list", [new pl.type.Term(".", [new pl.type.Var("X"),new pl.type.Term("[]", [])]),new pl.type.Var("X")]), null),
				new pl.type.Rule(new pl.type.Term("max_list", [new pl.type.Term(".", [new pl.type.Var("X"),new pl.type.Var("Xs")]),new pl.type.Var("S")]), new pl.type.Term(",", [new pl.type.Term("max_list", [new pl.type.Var("Xs"),new pl.type.Var("Y")]),new pl.type.Term(";", [new pl.type.Term(",", [new pl.type.Term(">=", [new pl.type.Var("X"),new pl.type.Var("Y")]),new pl.type.Term(",", [new pl.type.Term("=", [new pl.type.Var("S"),new pl.type.Var("X")]),new pl.type.Term("!", [])])]),new pl.type.Term("=", [new pl.type.Var("S"),new pl.type.Var("Y")])])]))
			],
			
			// min_list/2
			"min_list/2": [
				new pl.type.Rule(new pl.type.Term("min_list", [new pl.type.Term(".", [new pl.type.Var("X"),new pl.type.Term("[]", [])]),new pl.type.Var("X")]), null),
				new pl.type.Rule(new pl.type.Term("min_list", [new pl.type.Term(".", [new pl.type.Var("X"),new pl.type.Var("Xs")]),new pl.type.Var("S")]), new pl.type.Term(",", [new pl.type.Term("min_list", [new pl.type.Var("Xs"),new pl.type.Var("Y")]),new pl.type.Term(";", [new pl.type.Term(",", [new pl.type.Term("=<", [new pl.type.Var("X"),new pl.type.Var("Y")]),new pl.type.Term(",", [new pl.type.Term("=", [new pl.type.Var("S"),new pl.type.Var("X")]),new pl.type.Term("!", [])])]),new pl.type.Term("=", [new pl.type.Var("S"),new pl.type.Var("Y")])])]))
			],
			
			// prod_list/2
			"prod_list/2": [
				new pl.type.Rule(new pl.type.Term("prod_list", [new pl.type.Term("[]", []),new pl.type.Num(1, false)]), null),
				new pl.type.Rule(new pl.type.Term("prod_list", [new pl.type.Term(".", [new pl.type.Var("X"),new pl.type.Var("Xs")]),new pl.type.Var("S")]), new pl.type.Term(",", [new pl.type.Term("prod_list", [new pl.type.Var("Xs"),new pl.type.Var("Y")]),new pl.type.Term("is", [new pl.type.Var("S"),new pl.type.Term("*", [new pl.type.Var("X"),new pl.type.Var("Y")])])]))
			],
			
			// last/2
			"last/2": [
				new pl.type.Rule(new pl.type.Term("last", [new pl.type.Term(".", [new pl.type.Var("X"),new pl.type.Term("[]", [])]),new pl.type.Var("X")]), null),
				new pl.type.Rule(new pl.type.Term("last", [new pl.type.Term(".", [new pl.type.Var("_"),new pl.type.Var("Xs")]),new pl.type.Var("X")]), new pl.type.Term("last", [new pl.type.Var("Xs"),new pl.type.Var("X")]))
			],
			
			// prefix/2
			"prefix/2": [
				new pl.type.Rule(new pl.type.Term("prefix", [new pl.type.Var("Part"),new pl.type.Var("Whole")]), new pl.type.Term("append", [new pl.type.Var("Part"),new pl.type.Var("_"),new pl.type.Var("Whole")]))
			],
			
			// nth0/3
			"nth0/3": [
				new pl.type.Rule(new pl.type.Term("nth0", [new pl.type.Var("X"),new pl.type.Var("Y"),new pl.type.Var("Z")]), new pl.type.Term(";", [new pl.type.Term("->", [new pl.type.Term("var", [new pl.type.Var("X")]),new pl.type.Term("nth", [new pl.type.Num(0, false),new pl.type.Var("X"),new pl.type.Var("Y"),new pl.type.Var("Z"),new pl.type.Var("_")])]),new pl.type.Term(",", [new pl.type.Term(">=", [new pl.type.Var("X"),new pl.type.Num(0, false)]),new pl.type.Term(",", [new pl.type.Term("nth", [new pl.type.Num(0, false),new pl.type.Var("X"),new pl.type.Var("Y"),new pl.type.Var("Z"),new pl.type.Var("_")]),new pl.type.Term("!", [])])])]))
			],
			
			// nth1/3
			"nth1/3": [
				new pl.type.Rule(new pl.type.Term("nth1", [new pl.type.Var("X"),new pl.type.Var("Y"),new pl.type.Var("Z")]), new pl.type.Term(";", [new pl.type.Term("->", [new pl.type.Term("var", [new pl.type.Var("X")]),new pl.type.Term("nth", [new pl.type.Num(1, false),new pl.type.Var("X"),new pl.type.Var("Y"),new pl.type.Var("Z"),new pl.type.Var("_")])]),new pl.type.Term(",", [new pl.type.Term(">", [new pl.type.Var("X"),new pl.type.Num(0, false)]),new pl.type.Term(",", [new pl.type.Term("nth", [new pl.type.Num(1, false),new pl.type.Var("X"),new pl.type.Var("Y"),new pl.type.Var("Z"),new pl.type.Var("_")]),new pl.type.Term("!", [])])])]))
			],
			
			// nth0/4
			"nth0/4": [
				new pl.type.Rule(new pl.type.Term("nth0", [new pl.type.Var("X"),new pl.type.Var("Y"),new pl.type.Var("Z"),new pl.type.Var("W")]), new pl.type.Term(";", [new pl.type.Term("->", [new pl.type.Term("var", [new pl.type.Var("X")]),new pl.type.Term("nth", [new pl.type.Num(0, false),new pl.type.Var("X"),new pl.type.Var("Y"),new pl.type.Var("Z"),new pl.type.Var("W")])]),new pl.type.Term(",", [new pl.type.Term(">=", [new pl.type.Var("X"),new pl.type.Num(0, false)]),new pl.type.Term(",", [new pl.type.Term("nth", [new pl.type.Num(0, false),new pl.type.Var("X"),new pl.type.Var("Y"),new pl.type.Var("Z"),new pl.type.Var("W")]),new pl.type.Term("!", [])])])]))
			],
			
			// nth1/4
			"nth1/4": [
				new pl.type.Rule(new pl.type.Term("nth1", [new pl.type.Var("X"),new pl.type.Var("Y"),new pl.type.Var("Z"),new pl.type.Var("W")]), new pl.type.Term(";", [new pl.type.Term("->", [new pl.type.Term("var", [new pl.type.Var("X")]),new pl.type.Term("nth", [new pl.type.Num(1, false),new pl.type.Var("X"),new pl.type.Var("Y"),new pl.type.Var("Z"),new pl.type.Var("W")])]),new pl.type.Term(",", [new pl.type.Term(">", [new pl.type.Var("X"),new pl.type.Num(0, false)]),new pl.type.Term(",", [new pl.type.Term("nth", [new pl.type.Num(1, false),new pl.type.Var("X"),new pl.type.Var("Y"),new pl.type.Var("Z"),new pl.type.Var("W")]),new pl.type.Term("!", [])])])]))
			],
			
			// nth/5
			// DO NOT EXPORT
			"nth/5": [
				new pl.type.Rule(new pl.type.Term("nth", [new pl.type.Var("N"),new pl.type.Var("N"),new pl.type.Term(".", [new pl.type.Var("X"),new pl.type.Var("Xs")]),new pl.type.Var("X"),new pl.type.Var("Xs")]), null),
				new pl.type.Rule(new pl.type.Term("nth", [new pl.type.Var("N"),new pl.type.Var("O"),new pl.type.Term(".", [new pl.type.Var("X"),new pl.type.Var("Xs")]),new pl.type.Var("Y"),new pl.type.Term(".", [new pl.type.Var("X"),new pl.type.Var("Ys")])]), new pl.type.Term(",", [new pl.type.Term("is", [new pl.type.Var("M"),new pl.type.Term("+", [new pl.type.Var("N"),new pl.type.Num(1, false)])]),new pl.type.Term("nth", [new pl.type.Var("M"),new pl.type.Var("O"),new pl.type.Var("Xs"),new pl.type.Var("Y"),new pl.type.Var("Ys")])]))
			],
			
			// length/2
			"length/2": function( thread, point, atom ) {
				var list = atom.args[0], length = atom.args[1];
				if( !pl.type.is_variable( length ) && !pl.type.is_integer( length ) ) {
					thread.throw_error( pl.error.type( "integer", length, atom.indicator ) );
				} else if( pl.type.is_integer( length ) && length.value < 0 ) {
					thread.throw_error( pl.error.domain( "not_less_than_zero", length, atom.indicator ) );
				} else {
					var newgoal = new pl.type.Term("length", [list, new pl.type.Num(0, false), length]);
					if( pl.type.is_integer( length ) )
						newgoal = new pl.type.Term( ",", [newgoal, new pl.type.Term( "!", [] )] );
					thread.prepend( [new pl.type.State(point.goal.replace(newgoal), point.substitution, point)] );
				}
			},
			
			// length/3
			// DO NOT EXPORT
			"length/3": [
				new pl.type.Rule(new pl.type.Term("length", [new pl.type.Term("[]", []),new pl.type.Var("N"),new pl.type.Var("N")]), null),
				new pl.type.Rule(new pl.type.Term("length", [new pl.type.Term(".", [new pl.type.Var("_"),new pl.type.Var("X")]),new pl.type.Var("A"),new pl.type.Var("N")]), new pl.type.Term(",", [new pl.type.Term("succ", [new pl.type.Var("A"),new pl.type.Var("B")]),new pl.type.Term("length", [new pl.type.Var("X"),new pl.type.Var("B"),new pl.type.Var("N")])]))
			],
			
			// replicate/3
			"replicate/3": function( thread, point, atom ) {
				var elem = atom.args[0], times = atom.args[1], list = atom.args[2];
				if( pl.type.is_variable( times ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_integer( times ) ) {
					thread.throw_error( pl.error.type( "integer", times, atom.indicator ) );
				} else if( times.value < 0 ) {
					thread.throw_error( pl.error.domain( "not_less_than_zero", times, atom.indicator ) );
				} else if( !pl.type.is_variable( list ) && !pl.type.is_list( list ) ) {
					thread.throw_error( pl.error.type( "list", list, atom.indicator ) );
				} else {
					var replicate = new pl.type.Term( "[]" );
					for( var i = 0; i < times.value; i++ ) {
						replicate = new pl.type.Term( ".", [elem, replicate] );
					}
					thread.prepend( [new pl.type.State( point.goal.replace( new pl.type.Term( "=", [replicate, list] ) ), point.substitution, point )] );
				}
			},

			// sort/2
			"sort/2": function( thread, point, atom ) {
				var list = atom.args[0], expected = atom.args[1];
				if( pl.type.is_variable( list ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_variable( expected ) && !pl.type.is_fully_list( expected ) ) {
					thread.throw_error( pl.error.type( "list", expected, atom.indicator ) );
				} else {
					var arr = [];
					var pointer = list;
					while( pointer.indicator === "./2" ) {
						arr.push( pointer.args[0] );
						pointer = pointer.args[1];
					}
					if( pl.type.is_variable( pointer ) ) {
						thread.throw_error( pl.error.instantiation( atom.indicator ) );
					} else if( !pl.type.is_empty_list( pointer ) ) {
						thread.throw_error( pl.error.type( "list", list, atom.indicator ) );
					} else {
						var sorted_arr = arr.sort( pl.compare );
						for( var i = sorted_arr.length-1; i > 0; i-- ) {
							if( sorted_arr[i].equals(sorted_arr[i-1]) )
								sorted_arr.splice(i,1);
						}
						var sorted_list = new pl.type.Term( "[]" );
						for( var i = sorted_arr.length-1; i >= 0; i-- ) {
							sorted_list = new pl.type.Term( ".", [sorted_arr[i], sorted_list] );
						}
						thread.prepend( [new pl.type.State( point.goal.replace( new pl.type.Term( "=", [sorted_list, expected] ) ), point.substitution, point )] );
					}
				}
			},
			
			// msort/2
			"msort/2": function( thread, point, atom ) {
				var list = atom.args[0], expected = atom.args[1];
				if( pl.type.is_variable( list ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_variable( expected ) && !pl.type.is_fully_list( expected ) ) {
					thread.throw_error( pl.error.type( "list", expected, atom.indicator ) );
				} else {
					var arr = [];
					var pointer = list;
					while( pointer.indicator === "./2" ) {
						arr.push( pointer.args[0] );
						pointer = pointer.args[1];
					}
					if( pl.type.is_variable( pointer ) ) {
						thread.throw_error( pl.error.instantiation( atom.indicator ) );
					} else if( !pl.type.is_empty_list( pointer ) ) {
						thread.throw_error( pl.error.type( "list", list, atom.indicator ) );
					} else {
						var sorted_arr = arr.sort( pl.compare );
						var sorted_list = new pl.type.Term( "[]" );
						for( var i = sorted_arr.length - 1; i >= 0; i-- ) {
							sorted_list = new pl.type.Term( ".", [sorted_arr[i], sorted_list] );
						}
						thread.prepend( [new pl.type.State( point.goal.replace( new pl.type.Term( "=", [sorted_list, expected] ) ), point.substitution, point )] );
					}
				}
			},
			
			// keysort/2
			"keysort/2": function( thread, point, atom ) {
				var list = atom.args[0], expected = atom.args[1];
				if( pl.type.is_variable( list ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_variable( expected ) && !pl.type.is_fully_list( expected ) ) {
					thread.throw_error( pl.error.type( "list", expected, atom.indicator ) );
				} else {
					var arr = [];
					var elem;
					var pointer = list;
					while( pointer.indicator === "./2" ) {
						elem = pointer.args[0];
						if( pl.type.is_variable( elem ) ) {
							thread.throw_error( pl.error.instantiation( atom.indicator ) );
							return;
						} else if( !pl.type.is_term( elem ) || elem.indicator !== "-/2" ) {
							thread.throw_error( pl.error.type( "pair", elem, atom.indicator ) );
							return;
						}
						elem.args[0].pair = elem.args[1];
						arr.push( elem.args[0] );
						pointer = pointer.args[1];
					}
					if( pl.type.is_variable( pointer ) ) {
						thread.throw_error( pl.error.instantiation( atom.indicator ) );
					} else if( !pl.type.is_empty_list( pointer ) ) {
						thread.throw_error( pl.error.type( "list", list, atom.indicator ) );
					} else {
						var sorted_arr = arr.sort( pl.compare );
						var sorted_list = new pl.type.Term( "[]" );
						for( var i = sorted_arr.length - 1; i >= 0; i-- ) {
							sorted_list = new pl.type.Term( ".", [new pl.type.Term( "-", [sorted_arr[i], sorted_arr[i].pair] ), sorted_list] );
							delete sorted_arr[i].pair;
						}
						thread.prepend( [new pl.type.State( point.goal.replace( new pl.type.Term( "=", [sorted_list, expected] ) ), point.substitution, point )] );
					}
				}
			},
			
			// take/3
			"take/3": function( thread, point, atom ) {
				var number = atom.args[0], list = atom.args[1], take = atom.args[2];
				if( pl.type.is_variable( list ) || pl.type.is_variable( number ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_list( list ) ) {
					thread.throw_error( pl.error.type( "list", list, atom.indicator ) );
				} else if( !pl.type.is_integer( number ) ) {
					thread.throw_error( pl.error.type( "integer", number, atom.indicator ) );
				} else if( !pl.type.is_variable( take ) && !pl.type.is_list( take ) ) {
					thread.throw_error( pl.error.type( "list", take, atom.indicator ) );
				} else {
					var i = number.value;
					var arr = [];
					var pointer = list;
					while( i > 0 && pointer.indicator === "./2" ) {
						arr.push( pointer.args[0] );
						pointer = pointer.args[1];
						i--;
					}
					if( i === 0 ) {
						var new_list = new pl.type.Term( "[]" );
						for( var i = arr.length - 1; i >= 0; i-- ) {
							new_list = new pl.type.Term( ".", [arr[i], new_list] );
						}
						thread.prepend( [new pl.type.State( point.goal.replace( new pl.type.Term( "=", [new_list, take] ) ), point.substitution, point )] );
					}
				}
			},
			
			// drop/3
			"drop/3": function( thread, point, atom ) {
				var number = atom.args[0], list = atom.args[1], drop = atom.args[2];
				if( pl.type.is_variable( list ) || pl.type.is_variable( number ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_list( list ) ) {
					thread.throw_error( pl.error.type( "list", list, atom.indicator ) );
				} else if( !pl.type.is_integer( number ) ) {
					thread.throw_error( pl.error.type( "integer", number, atom.indicator ) );
				} else if( !pl.type.is_variable( drop ) && !pl.type.is_list( drop ) ) {
					thread.throw_error( pl.error.type( "list", drop, atom.indicator ) );
				} else {
					var i = number.value;
					var arr = [];
					var pointer = list;
					while( i > 0 && pointer.indicator === "./2" ) {
						arr.push( pointer.args[0] );
						pointer = pointer.args[1];
						i--;
					}
					if( i === 0 )
						thread.prepend( [new pl.type.State( point.goal.replace( new pl.type.Term( "=", [pointer, drop] ) ), point.substitution, point )] );
				}
			},
			
			// reverse/2
			"reverse/2": function( thread, point, atom ) {
				var list = atom.args[0], reversed = atom.args[1];
				var ins_list = pl.type.is_instantiated_list( list );
				var ins_reversed = pl.type.is_instantiated_list( reversed );
				if( pl.type.is_variable( list ) && pl.type.is_variable( reversed ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_variable( list ) && !pl.type.is_fully_list( list ) ) {
					thread.throw_error( pl.error.type( "list", list, atom.indicator ) );
				} else if( !pl.type.is_variable( reversed ) && !pl.type.is_fully_list( reversed ) ) {
					thread.throw_error( pl.error.type( "list", reversed, atom.indicator ) );
				} else if( !ins_list && !ins_reversed ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else {
					var pointer = ins_list ? list : reversed;
					var new_reversed = new pl.type.Term( "[]", [] );
					while( pointer.indicator === "./2" ) {
						new_reversed = new pl.type.Term( ".", [pointer.args[0], new_reversed] );
						pointer = pointer.args[1];
					}
					thread.prepend( [new pl.type.State( point.goal.replace( new pl.type.Term( "=", [new_reversed, ins_list ? reversed : list] ) ), point.substitution, point )] );
				}
			},
			
			// list_to_set/2
			"list_to_set/2": function( thread, point, atom ) {
				var list = atom.args[0], lset = atom.args[1];
				if( pl.type.is_variable( list ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else {
					var pointer = list;
					var elems = [];
					while( pointer.indicator === "./2" ) {
						elems.push( pointer.args[0] );
						pointer = pointer.args[1];
					}
					if( pl.type.is_variable( pointer ) ) {
						thread.throw_error( pl.error.instantiation( atom.indicator ) );
					} else if( !pl.type.is_term( pointer ) || pointer.indicator !== "[]/0" ) {
						thread.throw_error( pl.error.type( "list", list, atom.indicator ) );
					} else {
						var arr = [], nub = new pl.type.Term( "[]", [] );
						var match;
						for( var i = 0; i < elems.length; i++ ) {
							match = false
							for( var j = 0; j < arr.length && !match; j++ ) {
								match = pl.compare( elems[i], arr[j] ) === 0;
							}
							if( !match )
								arr.push( elems[i] );
						}
						for( i = arr.length - 1; i >= 0; i-- )
							nub = new pl.type.Term( ".", [arr[i],nub] );
						thread.prepend( [new pl.type.State( point.goal.replace( new pl.type.Term( "=", [lset,nub] ) ), point.substitution, point )] );
					}
				}
			}
			
		};
	};
	
	var exports = ["append/2", "append/3", "member/2", "permutation/2", "maplist/2", "maplist/3", "maplist/4", "maplist/5", "maplist/6", "maplist/7", "maplist/8", "include/3", "exclude/3", "foldl/4", "sum_list/2", "max_list/2", "min_list/2", "prod_list/2", "last/2", "prefix/2", "nth0/3", "nth1/3", "nth0/4", "nth1/4", "length/2", "replicate/3", "select/3", "sort/2", "msort/2", "keysort/2", "take/3", "drop/3", "reverse/2", "list_to_set/2"];


	new pl.type.Module( "lists", predicates(), exports );

})( prolog );

(function( pl ) {

	var predicates = function() {
		
		return {
			
			// maybe/0
			"maybe/0": function( thread, point, _ ) {
				if( Math.random() < 0.5 ) {
					thread.success( point );
				}
			},
			
			// maybe/1
			"maybe/1": function( thread, point, atom ) {
				var num = atom.args[0];
				if( Math.random() < num.value ) {
					thread.success( point );
				}
			},
			
			// random/1
			"random/1": function( thread, point, atom ) {
				var rand = atom.args[0];
				if( !pl.type.is_variable( rand ) && !pl.type.is_number( rand ) ) {
					thread.throw_error( pl.error.type( "number", rand, atom.indicator ) );
				} else {
					var gen = Math.random();
					thread.prepend( [new pl.type.State(
						point.goal.replace( new pl.type.Term( "=", [rand, new pl.type.Num( gen, true )] ) ),
						point.substitution, point 
					)] );
				}
			},
			
			// random/3
			"random/3": function( thread, point, atom ) {
				var lower = atom.args[0], upper = atom.args[1], rand = atom.args[2];
				if( pl.type.is_variable( lower ) || pl.type.is_variable( upper ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_number( lower ) ) {
					thread.throw_error( pl.error.type( "number", lower, atom.indicator ) );
				} else if( !pl.type.is_number( upper ) ) {
					thread.throw_error( pl.error.type( "number", upper, atom.indicator ) );
				} else if( !pl.type.is_variable( rand ) && !pl.type.is_number( rand ) ) {
					thread.throw_error( pl.error.type( "number", rand, atom.indicator ) );
				} else {
					if( lower.value < upper.value ) {
						var float = lower.is_float || upper.is_float;
						var gen = lower.value + Math.random() * (upper.value - lower.value);
						if( !float )
							gen = Math.floor( gen );
						thread.prepend( [new pl.type.State(
							point.goal.replace( new pl.type.Term( "=", [rand, new pl.type.Num( gen, float )] ) ),
							point.substitution, point 
						)] );
					}
				}
			},
			
			// random_between/3
			"random_between/3": function( thread, point, atom ) {
				var lower = atom.args[0], upper = atom.args[1], rand = atom.args[2];
				if( pl.type.is_variable( lower ) || pl.type.is_variable( upper ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_integer( lower ) ) {
					thread.throw_error( pl.error.type( "integer", lower, atom.indicator ) );
				} else if( !pl.type.is_integer( upper ) ) {
					thread.throw_error( pl.error.type( "integer", upper, atom.indicator ) );
				} else if( !pl.type.is_variable( rand ) && !pl.type.is_integer( rand ) ) {
					thread.throw_error( pl.error.type( "integer", rand, atom.indicator ) );
				} else {
					if( lower.value < upper.value ) {
						var gen = Math.floor(lower.value + Math.random() * (upper.value - lower.value + 1));
						thread.prepend( [new pl.type.State(
							point.goal.replace( new pl.type.Term( "=", [rand, new pl.type.Num( gen, false )] ) ),
							point.substitution, point 
						)] );
					}
				}
			},
			
			// random_member/2
			"random_member/2": function( thread, point, atom ) {
				var member = atom.args[0], list = atom.args[1];
				if( pl.type.is_variable( list ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else {
					var array = [];
					var pointer = list;
					while( pointer.indicator === "./2" ) {
						array.push(pointer.args[0]);
						pointer = pointer.args[1];
					}
					if( array.length > 0 ) {
						var gen = Math.floor(Math.random() * array.length);
						thread.prepend( [new pl.type.State(
							point.goal.replace( new pl.type.Term( "=", [member, array[gen]] ) ),
							point.substitution, point 
						)] );
					}
				}
			},
			
			// random_permutation/2
			"random_permutation/2": function( thread, point, atom ) {
				var i;
				var list = atom.args[0], permutation = atom.args[1];
				var ins_list = pl.type.is_instantiated_list( list );
				var ins_permutation = pl.type.is_instantiated_list( permutation );
				if( pl.type.is_variable( list ) && pl.type.is_variable( permutation ) || !ins_list && !ins_permutation ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_variable( list ) && !pl.type.is_fully_list( list ) ) {
					thread.throw_error( pl.error.type( "list", list, atom.indicator ) );
				} else if( !pl.type.is_variable( permutation ) && !pl.type.is_fully_list( permutation ) ) {
					thread.throw_error( pl.error.type( "list", permutation, atom.indicator ) );
				} else {
					var pointer = ins_list ? list : permutation;
					var array = [];
					while( pointer.indicator === "./2" ) {
						array.push( pointer.args[0] );
						pointer = pointer.args[1];
					}
					for( i = 0; i < array.length; i++ ) {
						var rand = Math.floor( Math.random() * array.length );
						var tmp = array[i];
						array[i] = array[rand];
						array[rand] = tmp;
					}
					var new_list = new pl.type.Term( "[]", [] );
					for( i = array.length-1; i >= 0; i-- )
						new_list = new pl.type.Term( ".", [array[i], new_list] );
					thread.prepend( [new pl.type.State(
						point.goal.replace( new pl.type.Term( "=", [new_list, ins_list ? permutation : list] ) ),
						point.substitution, point 
					)] );
				}
			}
		
		};
		
	};
	
	var exports = ["maybe/0", "maybe/1", "random/1", "random/3", "random_between/3", "random_member/2", "random_permutation/2"];


	new pl.type.Module( "random", predicates(), exports );

})( prolog );

(function( pl ) {

	var predicates = function() {
		
		return {
			
			// time/1
			"time/1": function( thread, point, atom ) {
				var goal = atom.args[0];
				if( pl.type.is_variable( goal ) ) {
					thread.throw_error( pl.error.instantiation( thread.level ) );
				} else if( !pl.type.is_callable( goal ) ) {
					thread.throw_error( pl.error.type( "callable", goal, thread.level ) );
				} else {
					var points = thread.points;
					thread.points = [new pl.type.State( goal, point.substitution, point )];
					var t0 = Date.now();
					var c0 = pl.statistics.getCountTerms();
					var i0 = thread.total_steps;
					var format_success = thread.session.format_success;
					var format_error = thread.session.format_error;
					thread.session.format_success = function(x) { return x.substitution; };
					thread.session.format_error = function(x) { return x.goal; };
					var callback = function( answer ) {
						var t1 = Date.now();
						var c1 = pl.statistics.getCountTerms();
						var i1 = thread.total_steps;
						var newpoints = thread.points;
						thread.points = points;
						thread.session.format_success = format_success;
						thread.session.format_error = format_error;
						if( pl.type.is_error( answer ) ) {
							thread.throw_error( answer.args[0] );
						} else if( answer === null ) {
							thread.points = points;
							thread.prepend( [point] );
							thread.__calls.shift()( null );
						} else {
							console.log( "% Tau Prolog: executed in " + (t1-t0) + " milliseconds, " + (c1-c0) + " atoms created, " + (i1-i0) + " resolution steps performed.");
							if( answer !== false ) {
								for( var i = 0; i < newpoints.length; i++ ) {
									if( newpoints[i].goal === null )
										newpoints[i].goal = new pl.type.Term( "true", [] );
									newpoints[i].goal = point.goal.replace( new pl.type.Term( "time", [newpoints[i].goal] ) );
								}
								thread.points = points;
								thread.prepend( newpoints );
								thread.prepend( [ new pl.type.State( point.goal.apply(answer).replace(null), answer, point ) ] );
							}
						}
					};
					thread.__calls.unshift( callback );
				}
			},
			
			// statistics/0
			"statistics/0": function( thread, point, atom ) {
				var stats = "% Tau Prolog statistics";
				for(var x in statistics)
					stats += "\n%%% " + x + ": " + statistics[x](thread).toString();
				thread.prepend([new pl.type.State(
					point.goal.replace(new pl.type.Term("write", [new pl.type.Term(stats)])),
					point.substitution,
					point
				)]);
			},
			
			// statistics/2
			"statistics/2": function( thread, point, atom ) {
				var key = atom.args[0], value = atom.args[1];
				if( !pl.type.is_variable( key ) && !pl.type.is_atom( key ) ) {
					thread.throw_error( pl.error.type( "atom", key, atom.indicator ) );
				} else if( !pl.type.is_variable( key ) && statistics[key.id] === undefined ) {
					thread.throw_error( pl.error.domain( "statistics_key", key, atom.indicator ) );
				} else {
					if( !pl.type.is_variable( key ) ) {
						var value_ = statistics[key.id]( thread );
						thread.prepend( [new pl.type.State( point.goal.replace( new pl.type.Term( "=", [value, value_] ) ), point.substitution, point )] );
					} else {
						var states = [];
						for( var x in statistics ) {
							var value_ = statistics[x]( thread );
							states.push( new pl.type.State( point.goal.replace(
								new pl.type.Term( ",", [
									new pl.type.Term( "=", [key, new pl.type.Term( x, [] )] ),
									new pl.type.Term( "=", [value, value_] )
								] )
							), point.substitution, point ) );
						}
						thread.prepend( states );
					}
				}
			}

		};
	};
	
	var exports = ["time/1", "statistics/0", "statistics/2"];
	
	var statistics = {
		
		// Total number of defined atoms
		atoms: function( thread ) {
			return new pl.type.Num( pl.statistics.getCountTerms(), false );
		},
		
		// Total number of clauses
		clauses: function( thread ) {
			var total = 0;
			for( var x in thread.session.rules )
				if( thread.session.rules[x] instanceof Array )
					total += thread.session.rules[x].length;
			for( var i = 0; i < thread.session.modules.length; i++ ) {
				var module = pl.module[thread.session.modules[i]];
				for( var j = 0; j < module.exports.length; j++ ) {
					var predicate = module.rules[module.exports[j]];
					if( predicate instanceof Array )
						total += predicate.length;
					}
			}
			return new pl.type.Num( total, false );
		},
		
		// Total cpu time
		cputime: function( thread ) {
			return new pl.type.Num( thread.cpu_time , false );
		},
		
		// Time stamp when thread was started
		epoch: function( thread ) {
			return new pl.type.Num( thread.epoch, false );
		},
		
		// Total number of resolution steps
		inferences: function( thread ) {
			return new pl.type.Num( thread.total_steps, false );
		},
		
		// Total number of defined modules
		modules:  function( thread ) {
			return new pl.type.Num( thread.session.modules.length, false );
		},
		
		// Total number of predicates
		predicates: function( thread ) {
			var total = Object.keys( thread.session.rules ).length;
			for( var i = 0; i < thread.session.modules.length; i++ ) {
				var module = pl.module[thread.session.modules[i]];
				total += module.exports.length;
			}
			return new pl.type.Num( total, false );
		},
		
		// [CPU time, CPU time since last]
		runtime: function( thread ) {
			return new pl.type.Term( ".", [new pl.type.Num( thread.cpu_time, false ), new pl.type.Term( ".", [new pl.type.Num( thread.cpu_time_last, false ), new pl.type.Term( "[]", [] )] )] );
		},
		
		// Total number of threads in current session
		threads: function( thread ) {
			return new pl.type.Num( thread.session.total_threads, false );
		}
		
	};

	new pl.type.Module( "statistics", predicates(), exports );

})( prolog );

(function( pl ) {

	var predicates = function() {
		
		return {
			
			// EVENTS
			
			// bind/4
			"bind/4": function( thread, point, atom ) {
				var elem = atom.args[0], type = atom.args[1], event = atom.args[2], goal = atom.args[3];
				if( pl.type.is_variable( elem ) || pl.type.is_variable( type ) && pl.type.is_variable( goal ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_dom_object( elem ) ) {
					thread.throw_error( pl.error.type( "HTMLObject", elem, atom.indicator ) );
				} else if( !pl.type.is_atom( type ) ) {
					thread.throw_error( pl.error.type( "atom", type, atom.indicator ) );
				} else if( !pl.type.is_variable( event ) && !pl.type.is_dom_event_object( event ) ) {
					thread.throw_error( pl.error.type( "DOMEventObject", type, atom.indicator ) );
				} else if( !pl.type.is_variable( goal ) ) {
					var thread_ = new pl.type.Thread( thread.session );
					var eventObject = new pl.type.DOMEvent( type.id );
					var links = {};
					if( pl.type.is_variable( event ) )
						links[event.id] = eventObject;
					var subs = new pl.type.Substitution( links );
					var handler = function( e ) {
						eventObject.event = e;
						thread_.add_goal( goal.apply( subs ) );
						thread_.answer( thread.__calls[0] );
					};
					events.add( elem.object, type.id, handler );
					elem.object.tau_events = elem.object.tau_events === undefined ? {} : elem.object.tau_events;
					if( elem.object.tau_events[type.id] === undefined )
						elem.object.tau_events[type.id] = [];
					elem.object.tau_events[type.id].push( {goal: goal, fn: handler} );
					thread.prepend( [new pl.type.State( point.goal.replace( new pl.type.Term( "=", [eventObject, event] ) ), point.substitution, point )] );
				} else {
					var event = elem.object.tau_events ? elem.object.tau_events[type.id] : undefined;
					if( event !== undefined ) {
						var states = [];
						for( var i = 0; i < event.length; i++ )
							states.push( new pl.type.State( point.goal.replace( new pl.type.Term( "=", [goal, event[i].goal.rename(thread)] ) ), point.substitution, point ) );
						thread.prepend( states );
					}
				}
			},
			
			// unbind/2
			"unbind/2": function( thread, point, atom ) {
				var elem = atom.args[0], type = atom.args[1];
				if( pl.type.is_variable( elem ) || pl.type.is_variable( type ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_dom_object( elem ) ) {
					thread.throw_error( pl.error.type( "HTMLObject", elem, atom.indicator ) );
				} else if( !pl.type.is_atom( type ) ) {
					thread.throw_error( pl.error.type( "atom", type, atom.indicator ) );
				} else if( !pl.type.is_variable( goal ) ) {
					if( elem.object.tau_events && elem.object.tau_events[type.id] ) {
						var event = elem.object.tau_events[type.id];
						for( var i = 0; i < event.length; i++ ) {
							events.remove( elem.object, type.id, event[i].fn );
						}
						delete elem.object.tau_events[type.id];
					}
					thread.success( point );
				}
			},
			
			// unbind/3
			"unbind/3": function( thread, point, atom ) {
				var elem = atom.args[0], type = atom.args[1], goal = atom.args[2];
				if( pl.type.is_variable( elem ) || pl.type.is_variable( type ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_dom_object( elem ) ) {
					thread.throw_error( pl.error.type( "HTMLObject", elem, atom.indicator ) );
				} else if( !pl.type.is_atom( type ) ) {
					thread.throw_error( pl.error.type( "atom", type, atom.indicator ) );
				} else if( !pl.type.is_variable( goal ) && !pl.type.is_term( goal ) ) {
					thread.throw_error( pl.error.type( "term", goal, atom.indicator ) );
				} else if( !pl.type.is_variable( goal ) ) {
					if( elem.object.tau_events && elem.object.tau_events[type.id] ) {
						var event = elem.object.tau_events[type.id];
						var newevents = [];
						for( var i = 0; i < event.length; i++ ) {
							if( pl.unify( event[i].goal, goal ) !== null ) {
								events.remove( elem.object, type.id, event[i].fn );
							} else {
								newevents.push( event[i] );
							}
						}
						elem.object.tau_events[type.id] = newevents;
					}
					thread.success( point );
				}
			},
			
			// event_property/3
			"event_property/3": function( thread, point, atom ) {
				var event = atom.args[0], prop = atom.args[1], val = atom.args[2]
				if( pl.type.is_variable( event ) || pl.type.is_variable( prop ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_dom_event_object( event ) ) {
					thread.throw_error( pl.error.type( "DOMEventObject", event, atom.indicator ) );
				} else if( !pl.type.is_atom( prop ) ) {
					thread.throw_error( pl.error.type( "atom", prop, atom.indicator ) );
				} else if( !pl.type.is_variable( val ) && !pl.type.is_atomic( val ) ) {
					thread.throw_error( pl.error.type( "atomic", val, atom.indicator ) );
				} else {
					if( event.event !== null && event.event[prop.id] ) {
						var value = event.event[prop.id];
						value = isNaN(value) ? new pl.type.Term( value, [] ) : new pl.type.Num( value );
						thread.prepend( [new pl.type.State( point.goal.replace( new pl.type.Term( "=", [value, val] ) ), point.substitution, point )] );
					}
				}
			},
			
			// prevent_default/1
			"prevent_default/1": function( thread, point, atom ) {
				var event = atom.args[0];
				if( pl.type.is_variable( event ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_dom_event_object( event ) ) {
					thread.throw_error( pl.error.type( "eventObject", event, atom.indicator ) );
				} else {
					if( event.event !== null ) {
						event.event.preventDefault();
						thread.success( point );
					}
				}
			},
			
			// EFFECTS
			
			// hide/1
			"hide/1": function( thread, point, atom ) {
				var element = atom.args[0];
				if( pl.type.is_variable( element ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_dom_object( element ) ) {
					thread.throw_error( pl.error.type( "HTMLObject", element, atom.indicator ) );
				} else {
					var state = document.defaultView.getComputedStyle( element.object, "" ).display;
					if( state !== undefined && state !== "none" )
						element.object.tau_display = state;
					element.object.style.display = "none";
					thread.success( point );
				}
			},
			
			// show/1
			"show/1": function( thread, point, atom ) {
				var element = atom.args[0];
				if( pl.type.is_variable( element ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_dom_object( element ) ) {
					thread.throw_error( pl.error.type( "HTMLObject", element, atom.indicator ) );
				} else {
					element.object.style.display = element.object.tau_display !== undefined ? element.object.tau_display : "block";
					thread.success( point );
				}
			},
			
			// toggle/1
			"toggle/1": [
				new pl.type.Rule(new pl.type.Term("toggle", [new pl.type.Var("X")]), new pl.type.Term(";", [new pl.type.Term("->", [new pl.type.Term(",", [new pl.type.Term("style", [new pl.type.Var("X"),new pl.type.Term("display", []),new pl.type.Var("Y")]),new pl.type.Term("=", [new pl.type.Var("Y"),new pl.type.Term("none", [])])]),new pl.type.Term("show", [new pl.type.Var("X")])]),new pl.type.Term("hide", [new pl.type.Var("X")])]))
			],
			
			// DOM MANIPULATION

			// document/1
			"document/1": function( session, point, atom) {
				var doc = atom.args[0];
				var newdoc = new pl.type.DOM( document );
				session.prepend( [new pl.type.State(
					point.goal.replace(new pl.type.Term("=", [doc, newdoc])),
					point.substitution,
					point
				)] );
			},

			// head/1
			"head/1": function( session, point, atom) {
				var head = atom.args[0];
				var newhead = new pl.type.DOM( document.head );
				session.prepend( [new pl.type.State(
					point.goal.replace(new pl.type.Term("=", [head, newhead])),
					point.substitution,
					point
				)] );
			},

			// body/1
			"body/1": function( session, point, atom) {
				var body = atom.args[0];
				var newbody = new pl.type.DOM( document.body );
				session.prepend( [new pl.type.State(
					point.goal.replace(new pl.type.Term("=", [body, newbody])),
					point.substitution,
					point
				)] );
			},
			
			// get_by_id/2
			"get_by_id/2": function( session, point, atom ) {
				var id = atom.args[0], object = atom.args[1];
				if( pl.type.is_variable( id ) ) {
					session.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_atom( id ) ) {
					session.throw_error( pl.error.type( "atom", id, atom.indicator ) );
				} else if( !pl.type.is_variable( object ) && !pl.type.is_dom_object( object ) ) {
					session.throw_error( pl.error.type( "HTMLObject", object, atom.indicator ) );
				} else {
					var element = document.getElementById( id.id );
					if( element ) {
						var html = new pl.type.DOM( element );
						session.prepend( [new pl.type.State( point.goal.replace( new pl.type.Term( "=", [html, object] ) ), point.substitution, point )] );
					}
				}
			},

			// get_by_class/2
			"get_by_class/2": [
				new pl.type.Rule(new pl.type.Term("get_by_class", [new pl.type.Var("Class"),new pl.type.Var("Html")]), new pl.type.Term(",", [new pl.type.Term("document", [new pl.type.Var("D")]),new pl.type.Term("get_by_class", [new pl.type.Var("D"),new pl.type.Var("Class"),new pl.type.Var("Html")])]))
			],
			
			// get_by_class/3
			"get_by_class/3": function( session, point, atom ) {
				var parent = atom.args[0], name = atom.args[1], object = atom.args[2];
				if( pl.type.is_variable( parent ) ) {
					session.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_dom_object( parent ) ) {
					session.throw_error( pl.error.type( "HTMLObject", parent, atom.indicator ) );
				} else if( pl.type.is_variable( name ) ) {
					session.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_atom( name ) ) {
					session.throw_error( pl.error.type( "atom", name, atom.indicator ) );
				} else if( !pl.type.is_variable( object ) && !pl.type.is_dom_object( object ) ) {
					session.throw_error( pl.error.type( "HTMLObject", object, atom.indicator ) );
				} else {
					var elements = parent.object.getElementsByClassName( name.id );
					if( elements ) {
						var states = [];
						for( var i = 0; i < elements.length; i++ ) {
							var html = new pl.type.DOM( elements[i] );
							states.push( new pl.type.State( point.goal.replace( new pl.type.Term( "=", [html, object] ) ), point.substitution, point ) );
						}
						session.prepend( states );
					}
				}
			},

			// get_by_tag/2
			"get_by_tag/2": [
				new pl.type.Rule(new pl.type.Term("get_by_tag", [new pl.type.Var("Tag"),new pl.type.Var("Html")]), new pl.type.Term(",", [new pl.type.Term("document", [new pl.type.Var("D")]),new pl.type.Term("get_by_tag", [new pl.type.Var("D"),new pl.type.Var("Tag"),new pl.type.Var("Html")])]))
			],
			
			// get_by_tag/3
			"get_by_tag/3": function( session, point, atom ) {
				var parent = atom.args[0], tag = atom.args[1], object = atom.args[2];
				if( pl.type.is_variable( parent ) ) {
					session.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_dom_object( parent ) ) {
					session.throw_error( pl.error.type( "HTMLObject", parent, atom.indicator ) );
				} else if( pl.type.is_variable( tag ) ) {
					session.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_atom( tag ) ) {
					session.throw_error( pl.error.type( "atom", tag, atom.indicator ) );
				} else if( !pl.type.is_variable( object ) && !pl.type.is_dom_object( object ) ) {
					session.throw_error( pl.error.type( "HTMLObject", object, atom.indicator ) );
				} else {
					var elements = parent.object.getElementsByTagName( tag.id );
					if( elements ) {
						var states = [];
						for( var i = 0; i < elements.length; i++ ) {
							var html = new pl.type.DOM( elements[i] );
							states.push( new pl.type.State( point.goal.replace( new pl.type.Term( "=", [html, object] ) ), point.substitution, point ) );
						}
						session.prepend( states );
					}
				}
			},

			// get_by_name/2
			"get_by_name/2": function( session, point, atom ) {
				var name = atom.args[0], object = atom.args[1];
				if( pl.type.is_variable( name ) ) {
					session.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_atom( name ) ) {
					session.throw_error( pl.error.type( "atom", name, atom.indicator ) );
				} else if( !pl.type.is_variable( object ) && !pl.type.is_dom_object( object ) ) {
					session.throw_error( pl.error.type( "HTMLObject", object, atom.indicator ) );
				} else {
					var elements = document.getElementsByName( name.id );
					if( elements ) {
						var states = [];
						for( var i = 0; i < elements.length; i++ ) {
							var html = new pl.type.DOM( elements[i] );
							states.push( new pl.type.State( point.goal.replace( new pl.type.Term( "=", [html, object] ) ), point.substitution, point ) );
						}
						session.prepend( states );
					}
				}
			},

			// get_style/3
			"get_style/3": function( session, point, atom ) {
				var html = atom.args[0], property = atom.args[1], value = atom.args[2];
				if( pl.type.is_variable( html ) || pl.type.is_variable( property ) ) {
					session.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_dom_object( html ) ) {
					session.throw_error( pl.error.type( "HTMLObject", selector, atom.indicator ) );
				} else if( !pl.type.is_atom( property ) ) {
					session.throw_error( pl.error.type( "atom", property, atom.indicator ) );
				} else {
					if( html.object === document ) return;
					var style = document.defaultView.getComputedStyle( html.object, "" )[property.id] || "";
					if( style === '' && html.object.style[property.id] )
						style = html.object.style[property.id];
					var html_value = styleToProlog( style );
					session.prepend( [new pl.type.State( point.goal.replace( new pl.type.Term( "=", [value, html_value] ) ), point.substitution, point )] );
				}
			},

			// set_style/3
			"set_style/3": function( session, point, atom ) {
				var html = atom.args[0], property = atom.args[1], value = atom.args[2];
				var styleValue = styleFromProlog( value );
				var ground = pl.type.is_ground( value );
				if( pl.type.is_variable( html ) || pl.type.is_variable( property ) || !ground ) {
					session.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_dom_object( html ) ) {
					session.throw_error( pl.error.type( "HTMLObject", selector, atom.indicator ) );
				} else if( !pl.type.is_atom( property ) ) {
					session.throw_error( pl.error.type( "atom", property, atom.indicator ) );
				} else if( styleValue === false ) {
					session.throw_error( pl.error.domain( "style_value", value, atom.indicator ) );
				} else {
					if( html.object === document ) return;
					html.object.style[property.id] = styleValue;
					session.success( point );
				}
			},
			
			// style/3
			"style/3": function( session, point, atom ) {
				var html = atom.args[0], property = atom.args[1], value = atom.args[2];
				var styleValue = styleFromProlog( value );
				var ground = pl.type.is_ground( value );
				if( pl.type.is_variable( html ) || pl.type.is_variable( property ) ) {
					session.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_dom_object( html ) ) {
					session.throw_error( pl.error.type( "HTMLObject", html, atom.indicator ) );
				} else if( !pl.type.is_atom( property ) ) {
					session.throw_error( pl.error.type( "atom", property, atom.indicator ) );
				} else if( !pl.type.is_variable( value ) && ground && styleValue === false ) {
					session.throw_error( pl.error.domain( "style_value", value, atom.indicator ) );
				} else {
					if( html.object === document ) return;
					if( !ground ) {
						var style = document.defaultView.getComputedStyle( html.object, "" )[property.id] || "";
						if( style === '' && html.object.style[property.id] )
							style = html.object.style[property.id];
						var html_value = styleToProlog( style );
						session.prepend( [new pl.type.State( point.goal.replace( new pl.type.Term( "=", [value, html_value] ) ), point.substitution, point )] );
					} else {
						html.object.style[property.id] = styleValue;
						session.success( point );
					}
				}
			},

			// get_attr/3
			"get_attr/3": function( session, point, atom ) {
				var html = atom.args[0], attr = atom.args[1], value = atom.args[2];
				if( pl.type.is_variable( html ) || pl.type.is_variable( attr ) ) {
					session.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_dom_object( html ) ) {
					session.throw_error( pl.error.type( "HTMLObject", selector, atom.indicator ) );
				} else if( !pl.type.is_atom( attr ) ) {
					session.throw_error( pl.error.type( "atom", attr, atom.indicator ) );
				} else {
					if( html.object === document ) return;
					var html_value = attr.id === "value" ? new pl.type.Term(html.object.value) : styleToProlog(html.object.getAttribute(attr.id));
					if( html_value !== null && html_value !== undefined )
						session.prepend( [new pl.type.State(
							point.goal.replace( new pl.type.Term( "=", [value, html_value] ) ),
							point.substitution, point
						)] );
				}
			},

			// set_attr/3
			"set_attr/3": function( session, point, atom ) {
				var html = atom.args[0], attr = atom.args[1], value = atom.args[2];
				var styleValue = styleFromProlog( value );
				var ground = pl.type.is_ground( value );
				if( pl.type.is_variable( html ) || pl.type.is_variable( attr ) || !ground ) {
					session.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_dom_object( html ) ) {
					session.throw_error( pl.error.type( "HTMLObject", selector, atom.indicator ) );
				} else if( !pl.type.is_atom( attr ) ) {
					session.throw_error( pl.error.type( "atom", attr, atom.indicator ) );
				} else if( styleValue === false ) {
					session.throw_error( pl.error.domain( "attribute_value", value, atom.indicator ) );
				} else {
					if( html.object === document ) return;
					if( attr.id === "value" ) {
						html.object.value = styleValue;
					} else {
						html.object.setAttribute( attr.id, styleValue );
					}
					session.success( point );
				}
			},
			
			// attr/3
			"attr/3": function( session, point, atom ) {
				var html = atom.args[0], attr = atom.args[1], value = atom.args[2];
				var styleValue = styleFromProlog( value );
				var ground = pl.type.is_ground( value );
				if( pl.type.is_variable( html ) || pl.type.is_variable( attr ) ) {
					session.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_dom_object( html ) ) {
					session.throw_error( pl.error.type( "HTMLObject", selector, atom.indicator ) );
				} else if( !pl.type.is_atom( attr ) ) {
					session.throw_error( pl.error.type( "atom", attr, atom.indicator ) );
				} else if( !pl.type.is_variable( value ) && ground && styleValue === false ) {
					session.throw_error( pl.error.domain( "attribute_value", value, atom.indicator ) );
				} else {
					if( html.object === document ) return;
					if( !ground ) {
						var html_value = attr.id === "value" ? new pl.type.Term(html.object.value) : styleToProlog(html.object.getAttribute(attr.id));
						if( html_value !== null && html_value !== undefined )
							session.prepend( [new pl.type.State(
								point.goal.replace( new pl.type.Term( "=", [value, html_value] ) ),
								point.substitution, point
							)] );
					} else {
						if( attr.id === "value" ) {
							html.object.value = styleValue;
						} else {
							html.object.setAttribute( attr.id, styleValue );
						}
						session.success( point );
					}
				}
			},

			// get_html/2
			"get_html/2": function( thread, point, atom ) {
				var html = atom.args[0], value = atom.args[1];
				if( pl.type.is_variable( html ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_dom_object( html ) ) {
					session.throw_error( pl.error.type( "HTMLObject", html, atom.indicator ) );
				} else {
					if( html.object === document ) return;
					var inner = new pl.type.Term( html.object.innerHTML );
					thread.prepend( [new pl.type.State( point.goal.replace( new pl.type.Term( "=", [inner, value] ) ), point.substitution, point )] );
				}
			},

			// set_html/2
			"set_html/2": function( thread, point, atom ) {
				var html = atom.args[0], value = atom.args[1];
				if( pl.type.is_variable( html ) || pl.type.is_variable( value ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_dom_object( html ) ) {
					session.throw_error( pl.error.type( "HTMLObject", html, atom.indicator ) );
				} else {
					if( html.object === document ) return;
					if( pl.type.is_atom( value ) )
						html.object.innerHTML = value.id;
					else
						html.object.innerHTML = value.toString();
					thread.success( point );
				}
			},
			
			// html/2
			"html/2": function( thread, point, atom ) {
				var html = atom.args[0], value = atom.args[1];
				if( pl.type.is_variable( html ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else {
					if( html.object === document ) return;
					if( pl.type.is_variable( value ) ) {
						var inner = new pl.type.Term( html.object.innerHTML );
						thread.prepend( [new pl.type.State( point.goal.replace( new pl.type.Term( "=", [inner, value] ) ), point.substitution, point )] );
					} else {
						if( pl.type.is_atom( value ) )
							html.object.innerHTML = value.id;
						else
							html.object.innerHTML = value.toString();
						thread.success( point );
					}
				}
			},
			
			// create/2
			"create/2": function( thread, point, atom ) {
				var tag = atom.args[0], element = atom.args[1];
				if( pl.type.is_variable( tag ) && pl.type.is_variable( element ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_variable( tag ) && !pl.type.is_atom( tag ) ) {
					thread.throw_error( pl.error.type( "atom", tag, atom.indicator ) );
				} else if( !pl.type.is_variable( element ) && !pl.type.is_dom_object( element ) ) {
					thread.throw_error( pl.error.type( "HTMLObject", element, atom.indicator ) );
				} else if( pl.type.is_variable( element ) ) {
					var node = new pl.type.DOM( document.createElement( tag.id ) );
					thread.prepend( [new pl.type.State( point.goal.replace( new pl.type.Term( "=", [element, node] ) ), point.substitution, point )] );
				} else if( pl.type.is_variable( element ) ) {
					var node = new pl.type.DOM( document.createElement( tag.id.toLowerCase() ) );
					thread.prepend( [new pl.type.State( point.goal.replace( new pl.type.Term( "=", [element, node] ) ), point.substitution, point )] );
				} else if( pl.type.is_variable( tag ) ) {
					var type = new pl.type.Term( element.object.nodeName.toLowerCase() );
					thread.prepend( [new pl.type.State( point.goal.replace( new pl.type.Term( "=", [type, tag] ) ), point.substitution, point )] );
				}
			},
			
			// parent_of/2
			"parent_of/2": function( thread, point, atom ) {
				var child = atom.args[0], parent = atom.args[1];
				if( pl.type.is_variable( parent ) && pl.type.is_variable( child ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_variable( parent ) && !pl.type.is_dom_object( parent ) ) {
					thread.throw_error( pl.error.type( "HTMLObject", parent, atom.indicator ) );
				} else if( !pl.type.is_variable( child ) && !pl.type.is_dom_object( child ) ) {
					thread.throw_error( pl.error.type( "HTMLObject", child, atom.indicator ) );
				} else if( pl.type.is_variable( child ) ) {
					var children = parent.object.children;
					var states = [];
					for( var i = 0; i < children.length; i++ ) {
						states.push( new pl.type.State( point.goal.replace( new pl.type.Term( "=", [child, new pl.type.DOM( children[i] )] ) ), point.substitution, point ) );
					}
					thread.prepend( states );
				} else {
					if( child.object.parentNode ) {
						thread.prepend( [new pl.type.State( point.goal.replace( new pl.type.Term( "=", [parent, new pl.type.DOM( child.object.parentNode )] ) ), point.substitution, point )] );
					}
				}
			},
			
			// sibling/2
			"sibling/2": function( thread, point, atom ) {
				var left = atom.args[0], right = atom.args[1];
				if( pl.type.is_variable( left ) && pl.type.is_variable( right ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_variable( left ) && !pl.type.is_dom_object( left ) ) {
					thread.throw_error( pl.error.type( "HTMLObject", left, atom.indicator ) );
				} else if( !pl.type.is_variable( right ) && !pl.type.is_dom_object( right ) ) {
					thread.throw_error( pl.error.type( "HTMLObject", right, atom.indicator ) );
				} else {
					if( pl.type.is_variable( left ) && right.object.previousElementSibling ) {
						var elem = new pl.type.DOM( right.object.previousElementSibling );
						thread.prepend( [new pl.type.State( point.goal.replace( new pl.type.Term( "=", [left, elem] ) ), point.substitution, point )] );
					} else if( !pl.type.is_variable( left ) &&  left.object.nextElementSibling ) {
						var elem = new pl.type.DOM( left.object.nextElementSibling );
						thread.prepend( [new pl.type.State( point.goal.replace( new pl.type.Term( "=", [right, elem] ) ), point.substitution, point )] );
					}
				}
			},
			
			// remove/1
			"remove/1": function( thread, point, atom ) {
				var element = atom.args[0];
				if( pl.type.is_variable( element ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_dom_object( element ) ) {
					thread.throw_error( pl.error.type( "HTMLObject", element, atom.indicator ) );
				} else {
					if( element.object.parentNode ) {
						element.object.parentNode.removeChild( element.object );
						thread.success( point );
					}
				}
			},
			
			// insert_after/2
			"insert_after/2": function( thread, point, atom ) {
				var element = atom.args[0], reference = atom.args[1];
				if( pl.type.is_variable( element ) || pl.type.is_variable( reference ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_dom_object( element ) ) {
					thread.throw_error( pl.error.type( "HTMLObject", element, atom.indicator ) );
				} else if( !pl.type.is_dom_object( reference ) ) {
					thread.throw_error( pl.error.type( "HTMLObject", reference, atom.indicator ) );
				} else {
					if( reference.object.parentNode ) {
						reference.object.parentNode.insertBefore(element.object, reference.object.nextSibling);
						thread.success( point );
					}
				}
			},
			
			// insert_before/2
			"insert_before/2": function( thread, point, atom ) {
				var element = atom.args[0], reference = atom.args[1];
				if( pl.type.is_variable( element ) || pl.type.is_variable( reference ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_dom_object( element ) ) {
					thread.throw_error( pl.error.type( "HTMLObject", element, atom.indicator ) );
				} else if( !pl.type.is_dom_object( reference ) ) {
					thread.throw_error( pl.error.type( "HTMLObject", reference, atom.indicator ) );
				} else {
					if( reference.object.parentNode ) {
						reference.object.parentNode.insertBefore(element.object, reference.object);
						thread.success( point );
					}
				}
			},
			
			// prepend_child/2
			"prepend_child/2": function( thread, point, atom ) {
				var parent = atom.args[0], child = atom.args[1];
				if( pl.type.is_variable( parent ) || pl.type.is_variable( child ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_dom_object( parent ) ) {
					thread.throw_error( pl.error.type( "HTMLObject", parent, atom.indicator ) );
				} else if( !pl.type.is_dom_object( child ) ) {
					thread.throw_error( pl.error.type( "HTMLObject", child, atom.indicator ) );
				} else {
					if( parent.object.firstChild )
						parent.object.insertBefore( child.object, parent.object.firstChild );
					else
						parent.object.appendChild( child.object );
					thread.success( point );
				}
			},
			
			// append_child/2
			"append_child/2": function( thread, point, atom ) {
				var parent = atom.args[0], child = atom.args[1];
				if( pl.type.is_variable( parent ) || pl.type.is_variable( child ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_dom_object( parent ) ) {
					thread.throw_error( pl.error.type( "HTMLObject", parent, atom.indicator ) );
				} else if( !pl.type.is_dom_object( child ) ) {
					thread.throw_error( pl.error.type( "HTMLObject", child, atom.indicator ) );
				} else {
					parent.object.appendChild( child.object );
					thread.success( point );
				}
			},
			
			// add_class/2
			"add_class/2": function( thread, point, atom ) {
				var element = atom.args[0], name = atom.args[1];
				if( pl.type.is_variable( element ) || pl.type.is_variable( name ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_dom_object( element ) ) {
					thread.throw_error( pl.error.type( "HTMLObject", element, atom.indicator ) );
				} else if( !pl.type.is_atom( name ) ) {
					thread.throw_error( pl.error.type( "atom", name, atom.indicator ) );
				} else {
					var arr = element.object.className.split(" ");
					if( arr.indexOf( name.id ) === -1 ) {
						element.object.className += " " + name.id;
					}
					thread.success( point );
				}
			},
			
			// remove_class/2
			"remove_class/2": function( thread, point, atom ) {
				var element = atom.args[0], name = atom.args[1];
				if( pl.type.is_variable( element ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_dom_object( element ) ) {
					thread.throw_error( pl.error.type( "HTMLObject", element, atom.indicator ) );
				} else if( !pl.type.is_atom( name ) && !pl.type.is_variable( name ) ) {
					thread.throw_error( pl.error.type( "atom", name, atom.indicator ) );
				} else {
					var arr = element.object.className.split(" ");
					if( pl.type.is_variable( name ) ) {
						var states = [];
						for( var i = 0; i < arr.length; i++ ) {
							states.push( new pl.type.State( point.goal.replace(
								new pl.type.Term( ",", [
									new pl.type.Term( "=", [name, new pl.type.Term( arr[i], [] )] ),
									new pl.type.Term( "remove_class", [element, name] )
								] )
							), point.substitution, point ) );
						}
						thread.prepend( states );
					} else {
						var newclasses = "";
						for( var i = 0; i < arr.length; i++ )
							if( arr[i] !== name.id )
								newclasses += arr[i] + " ";
						element.object.className = newclasses;
						thread.success( point );
					}
				}
			},
			
			// has_class/2
			"hasClass/2": function( thread, point, atom ) {
				var element = atom.args[0], name = atom.args[1];
				if( pl.type.is_variable( element ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_dom_object( element ) ) {
					thread.throw_error( pl.error.type( "HTMLObject", element, atom.indicator ) );
				} else if( !pl.type.is_atom( name ) && !pl.type.is_variable( name ) ) {
					thread.throw_error( pl.error.type( "atom", name, atom.indicator ) );
				} else {
					var arr = element.object.className.split(" ");
					if( pl.type.is_variable( name ) ) {
						var states = [];
						for( var i = 0; i < arr.length; i++ )
							states.push( new pl.type.State( point.goal.replace( new pl.type.Term( "=", [name, new pl.type.Term( arr[i], [] )] ) ), point.substitution, point ) );
						thread.prepend( states );
					} else {
						if( arr.indexOf( name.id ) !== -1 )
							thread.success( point );
					}
				}
			}
			
		};
	};
	
	var exports = ["document/1", "head/1", "body/1", "show/1", "hide/1", "toggle/1", "create/2", "get_by_id/2", "get_by_tag/2", "get_by_tag/3", "get_by_class/2", "get_by_class/3", "get_by_name/2", "attr/3", "set_attr/3", "get_attr/3", "style/3", "set_style/3", "get_style/3", "html/2", "set_html/2", "get_html/2", "parent_of/2", "insert_after/2", "insert_before/2", "append_child/2", "prepend_child/2", "sibling/2", "remove/1", "add_class/2", "remove_class/2", "has_class/2", "bind/4", "unbind/2", "unbind/3", "event_property/3", "prevent_default/1"];
	
	
	
	// DOM HTML OBJECTS
	
	// Get value of style from Prolog object
	function styleFromProlog( obj ) {
		if( obj === undefined || obj === null )
			return false;
		else if( pl.type.is_number( obj ) )
			return obj.value;
		else if( pl.type.is_term( obj ) && obj.args.length === 0 )
			return obj.id;
		else if( pl.type.is_term( obj ) && obj.indicator === "px/1" && pl.type.is_number( obj.args[0] ) )
			return obj.args[0].value.toString() + "px";
		else if( pl.type.is_term( obj ) && obj.indicator === "%/1" && pl.type.is_number( obj.args[0] ) )
			return obj.args[0].value.toString() + "%";
		else if( pl.type.is_term( obj ) && obj.indicator === "url/1" && pl.type.is_atom( obj.args[0] ) )
			return "url(\"" + obj.args[0].id + "\")";
		else if( pl.type.is_term( obj ) && obj.indicator === "rgb/3" && pl.type.is_integer( obj.args[0] ) && pl.type.is_integer( obj.args[1] ) && pl.type.is_integer( obj.args[2] ) )
			return obj.toString();
		return false;
	}
	
	// Get value of style to Prolog object
	function styleToProlog( str ) {
		if( str === undefined || str === null )
			return
		else if( /^-?[0-9]*\.?[0-9]*\s*px\s*$/.test( str ) )
			return new pl.type.Term( "px", [new pl.type.Num( parseInt( str ) )] );
		else if( /^-?[0-9]*\.?[0-9]*\s*\%\s*$/.test( str ) )
			return new pl.type.Term( "%", [new pl.type.Num( parseFloat( str ) )] );
		else if( /^url\(["'].*["']\)$/.test( str ) )
			return new pl.type.Term( "url", [new pl.type.Term( str.substring(5, str.length-2) )] );
		else if( /^rgb\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*\)\s*$/.test( str ) ) {
			var rgb = str.replace("rgb","").replace("(","").replace(")","").split(",");
			return new pl.type.Term( "rgb", [new pl.type.Num(parseInt(rgb[0]), false), new pl.type.Num(parseInt(rgb[1]), false), new pl.type.Num(parseInt(rgb[2]), false)] );
		}
		return new pl.type.Term( str.toString(), [] );
	}
	
	// Is a DOM object
	pl.type.is_dom_object = function( obj ) {
		return obj instanceof pl.type.DOM;
	};

	// Ordering relation
	pl.type.order.push( pl.type.DOM );

	// DOM Prolog object
	pl.type.DOM = function( object ) {
		this.object = object;
	}

	// toString
	pl.type.DOM.prototype.toString = function() {
		return "<html>(" + (this.object.id !== "" && this.object.id !== undefined ? "#" + this.object.id : this.object.nodeName.toLowerCase().replace("#", "")) + ")";
	};

	// clone
	pl.type.DOM.prototype.clone = function() {
		return new pl.type.DOM( this.object );
	};

	// equals
	pl.type.DOM.prototype.equals = function( obj ) {
		return pl.type.is_dom_object( obj ) && this.object === obj.object;
	};

	// rename
	pl.type.DOM.prototype.rename = function( _ ) {
		return this;
	};

	// get variables
	pl.type.DOM.prototype.variables = function() {
		return [];
	};

	// apply substitutions
	pl.type.DOM.prototype.apply = function( _ ) {
		return this;
	};

	// unify
	pl.type.DOM.prototype.unify = function( obj, _ ) {
		if( pl.type.is_dom_object( obj ) && this.object === obj.object ) {
			return new pl.type.State( obj, new pl.type.Substitution() );
		}
		return null;
	};

	// interpret
	pl.type.DOM.prototype.interpret = function( indicator ) {
		return pl.error.instantiation( indicator );
	};

	// compare
	pl.type.DOM.prototype.compare = function( obj ) {
		if( this.object === obj.object ) {
			return 0;
		} else if( this.object < obj.object ) {
			return -1;
		} else if( this.object > obj.object ) {
			return 1;
		}
	};
	
	// to javascript
	pl.type.DOM.prototype.toJavaScript = function() {
		return this.object;
	};
	
	// from javascript
	pl.fromJavaScript.test.dom = function( obj ) {
		return obj instanceof HTMLElement;
	};
	pl.fromJavaScript.conversion.dom = function( obj ) {
		return new pl.type.DOM( obj );
	};
	
	// from javascript (collection)
	pl.fromJavaScript.test.dom_collection = function( obj ) {
		return obj instanceof HTMLCollection;
	};
	pl.fromJavaScript.conversion.dom_collection = function( obj ) {
		var arr = Array.prototype.slice.call( obj, 0 );
		return pl.fromJavaScript.apply( arr );
	};

	// Streamable
	pl.type.DOM.prototype.stream = function( options, mode ) {
		if( mode === "write" )
			if( this.object instanceof HTMLInputElement )
				this.object.value = "";
			else
				this.object.innerHTML = "";
		return {
			object: this.object,
			get: function( length, position ) {
				var text;
				if( this.object instanceof HTMLInputElement )
					text = this.object.value.substring( position, position+length );
				else
					text = this.object.innerHTML;
				if( position >= text.length )
					return "end_of_html";
				return text.substring( position, position+length );
			},
			put: function( text, position ) {
				if( position === "end_of_file" ) {
					if( this.object instanceof HTMLInputElement )
						this.object.value += text;
					else
						this.object.innerHTML += text;
					return true;
				} else if( position === "past_end_of_file" ) {
					return null;
				} else {
					if( this.object instanceof HTMLInputElement )
						this.object.value = this.object.value.substring(0, position) + text + this.object.value.substring(position+text.length);
					else
						this.object.innerHTML = this.object.innerHTML.substring(0, position) + text + this.object.innerHTML.substring(position+text.length);
					return true;
				}
			},
			get_byte: function( position ) {
				if( position === "end_of_stream" )
					return -1;
				var index = Math.floor(position/2);
				var text;
				if( this.object instanceof HTMLInputElement )
					text = this.object.value.substring( position, position+length );
				else
					text = this.object.innerHTML;
				if( text.length <= index )
					return -1;
				var code = pl.utils.codePointAt( text[Math.floor(position/2)], 0 );
				if( position % 2 === 0 )
					return code & 0xff;
				else
					return code / 256 >>> 0;
			},
			put_byte: function( byte, position ) {
				var text;
				if( this.object instanceof HTMLInputElement )
					text = this.object.value;
				else
					text = this.object.innerHTML;
				var index = position === "end_of_stream" ? text.length : Math.floor(position/2);
				if( text.length < index )
					return null;
				var code = text.length === index ? -1 : pl.utils.codePointAt( text[Math.floor(position/2)], 0 );
				if( position % 2 === 0 ) {
					code = code / 256 >>> 0;
					code = ((code & 0xff) << 8) | (byte & 0xff);
				} else {
					code = code & 0xff;
					code = ((byte & 0xff) << 8) | (code & 0xff);
				}
				if( text.length === index )
					text += pl.utils.fromCodePoint( code );
				else 
					text = text.substring( 0, index ) + pl.utils.fromCodePoint( code ) + text.substring( index+1 );
				if( this.object instanceof HTMLInputElement )
					this.object.value = text;
				else
					this.object.innerHTML = text;
				return true;
			},
			flush: function() {
				return true;
			},
			close: function() {
				return true;
			}
		};
	};
	
	
	
	// DOM EVENT OBJECTS
	
	// Is a DOM Event object
	pl.type.is_dom_event_object = function( obj ) {
		return obj instanceof pl.type.DOMEvent;
	};

	// Ordering relation
	pl.type.order.push( pl.type.DOMEvent );

	// DOM Event Prolog object
	pl.type.DOMEvent = function( type, event, epoch ) {
		this.type = type;
		this.event = event || null;
		this.epoch = epoch || (new Date).getTime();
	}

	// toString
	pl.type.DOMEvent.prototype.toString = function() {
		return "<event>(" + this.type.toLowerCase() + ")";
	};

	// clone
	pl.type.DOMEvent.prototype.clone = function() {
		return new pl.type.DOMEvent( this.type, this.event, this.epoch );
	};

	// equals
	pl.type.DOMEvent.prototype.equals = function( obj ) {
		return pl.type.is_dom_event_object( obj ) && this.type === obj.type && this.epoch === obj.epoch;
	};

	// rename
	pl.type.DOMEvent.prototype.rename = function( _ ) {
		return this;
	};

	// get variables
	pl.type.DOMEvent.prototype.variables = function() {
		return [];
	};

	// apply substitutions
	pl.type.DOMEvent.prototype.apply = function( _ ) {
		return this;
	};

	// unify
	pl.type.DOMEvent.prototype.unify = function( obj, _ ) {
		if( pl.type.is_dom_event_object( obj ) && this.type === obj.type && this.epoch === obj.epoch ) {
			return new pl.type.State( obj, new pl.type.Substitution() );
		}
		return null;
	};

	// interpret
	pl.type.DOMEvent.prototype.interpret = function( indicator ) {
		return pl.error.instantiation( indicator );
	};

	// compare
	pl.type.DOMEvent.prototype.compare = function( obj ) {
		if( this.epoch === obj.epoch ) {
			return 0;
		} else if( this.epoch < obj.epoch ) {
			return -1;
		} else if( this.epoch > obj.epoch ) {
			return 1;
		}
	};
	
	// to javascript
	pl.type.DOMEvent.prototype.toJavaScript = function() {
		return this.event;
	};
	
	// from javascript
	pl.fromJavaScript.test.event = function( obj ) {
		return obj instanceof Event;
	};
	pl.fromJavaScript.conversion.event = function( obj ) {
		return new pl.type.DOMEvent( obj.type, obj );
	};
	
	
	// EVENT HANDLING
	var events = (function() {

		var tau_fn_event = {};


		var add = function(element, evt, fn) {

			if(element.addEventListener !== undefined) {
				element.addEventListener(evt, fn);
				return true;
			}

			else if(element.attachEvent !== undefined) {
				element.attachEvent("on" + evt, fn);
				return true;
			}

			var prop = element["on" + evt];
			var fns = [];
			if(prop) {
				if(prop.tau_fn_event === tau_fn_event) {
					if(prop.fns.indexOf(fn) === -1 )
						prop.fns.push(fn);
					return true;
				} else {
					fns.push(prop);
				}
			}

			fns.push(fn);
			element["on" + evt] = function(e) {
				for(var i = 0; i < fns.length; i++)
					fns[i].call(element, e, element);	
			};
			element["on" + evt].fns = fns;
			element["on" + evt].tau_fn_event = tau_fn_event;
			return true;
		};

		var remove = function(element, evt, fn) {

			if(element.removeEventListener) {
				element.removeEventListener(evt, fn);
				return true;
			}

			else if(element.detachEvent) {
				element.detachEvent("on" + evt, fn);
				return true;
			}

			else if(element["on" + evt]) {
				var f = element["on" + evt];
				if(f === fn)
					element["on" + evt] = undefined;
				else if(f.tau_fn_event === tau_fn_event) {
					for(var i = 0; i < f.fns.length; i++) {
						if(f.fns[i] === fn) {
							f.fns.splice(i, 1);
							break;
						}
					}
					return true;
				}
				else
					return false;
			}

			return true;
		};

		return {
			add: add,
			remove: remove
		};
	})();

	

	new pl.type.Module( "dom", predicates(), exports );

})( prolog );

(function( pl ) {

	var predicates = function() {
		
		return {
			
			// global/1
			"global/1": function( thread, point, atom ) {
				thread.prepend( [new pl.type.State( point.goal.replace( new pl.type.Term( "=", [atom.args[0], pl.fromJavaScript.apply(pl.__env)] ) ), point.substitution, point )] );
			},
			
			// apply/3:
			"apply/3": [
				new pl.type.Rule(new pl.type.Term("apply", [new pl.type.Var("X"),new pl.type.Var("Y"),new pl.type.Var("Z")]), new pl.type.Term(",", [new pl.type.Term("global", [new pl.type.Var("G")]),new pl.type.Term("apply", [new pl.type.Var("G"),new pl.type.Var("X"),new pl.type.Var("Y"),new pl.type.Var("Z")])]))
			],
			
			// apply/4
			"apply/4": function( thread, point, atom ) {
				var context = atom.args[0], name = atom.args[1], args = atom.args[2], result = atom.args[3];
				if( pl.type.is_variable( context ) || pl.type.is_variable( name ) || pl.type.is_variable( args ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_atom( name ) && (!pl.type.is_js_object( name ) || typeof name.value !== "function") ) {
					thread.throw_error( pl.error.type( "atom_or_JSValueFUNCTION", name, atom.indicator ) );
				} else if( !pl.type.is_list( args ) ) {
					thread.throw_error( pl.error.type( "list", args, atom.indicator ) );
				}
				var ctx = context.toJavaScript();
				var fn = pl.type.is_atom( name ) ? ctx[name.id] : name.toJavaScript();
				if( typeof fn === "function" ) {
					var pointer = args;
					var pltojs;
					var arr = [];
					while( pointer.indicator === "./2" ) {
						pltojs = pointer.args[0].toJavaScript();
						if( pltojs === undefined ) {
							thread.throw_error( pl.error.domain( "javascript_object", pointer.args[0], atom.indicator ) );
							return undefined;
						}
						arr.push( pltojs );
						pointer = pointer.args[1];
					}
					if( pl.type.is_variable( pointer ) ) {
						thread.throw_error( pl.error.instantiation( atom.indicator ) );
						return;
					} else if( pointer.indicator !== "[]/0" ) {
						thread.throw_error( pl.error.type( "list", args, atom.indicator ) );
						return
					}
					var value;
					try {
						value = fn.apply( ctx, arr );
					} catch( e ) {
						thread.throw_error( pl.error.javascript( e.toString(), atom.indicator ) );
						return;
					}
					value = pl.fromJavaScript.apply( value );
					thread.prepend( [new pl.type.State( point.goal.replace( new pl.type.Term( "=", [value, result] ) ), point.substitution, point )] );
				}
			},
			
			// prop/2:
			"prop/2": [
				new pl.type.Rule(new pl.type.Term("prop", [new pl.type.Var("X"),new pl.type.Var("Y")]), new pl.type.Term(",", [new pl.type.Term("global", [new pl.type.Var("G")]),new pl.type.Term("prop", [new pl.type.Var("G"),new pl.type.Var("X"),new pl.type.Var("Y")])]))
			],
			
			// prop/3
			"prop/3": function( thread, point, atom ) {
				var context = atom.args[0], name = atom.args[1], result = atom.args[2];
				if( pl.type.is_variable( context ) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_variable( name ) && !pl.type.is_atom( name ) ) {
					thread.throw_error( pl.error.type( "atom", name, atom.indicator ) );
				} else {
					if( pl.type.is_atom( name ) ) {
						var fn = context.toJavaScript()[name.id];
						if( fn !== undefined ) {
							fn = pl.fromJavaScript.apply( fn );
							thread.prepend( [new pl.type.State( point.goal.replace( new pl.type.Term( "=", [fn, result] ) ), point.substitution, point )] );
						}
					} else {
						var fn = context.toJavaScript();
						var states = [];
						for( var x in fn ) {
							if( fn.hasOwnProperty( x ) ) {
								var fn_ = pl.fromJavaScript.apply( fn[x] );
								states.push( new pl.type.State( point.goal.replace( new pl.type.Term( ",", [
									new pl.type.Term( "=", [fn_, result] ),
									new pl.type.Term( "=", [new pl.type.Term(x, []), name] )
								]) ), point.substitution, point ) );
							}
						}
						thread.prepend( states );
					}
				}
			},

			// json_prolog/2
			"json_prolog/2": function( thread, point, atom ) {
				var json = atom.args[0], prolog = atom.args[1];
				if( pl.type.is_variable(json) && pl.type.is_variable(prolog) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_variable(json) && (!pl.type.is_js_object(json) || typeof(json.value) !== "object")) {
					thread.throw_error( pl.error.type( "JsValueOBJECT", json, atom.indicator ) );
				} else if( !pl.type.is_variable(prolog) && !pl.type.is_list(prolog) ) {
					thread.throw_error( pl.error.type( "list", prolog, atom.indicator ) );
				} else {
					if(pl.type.is_variable(prolog)) {
						var list = pl.fromJavaScript.apply(json.value, true);
						thread.prepend([new pl.type.State(
							point.goal.replace(new pl.type.Term("=", [prolog, list])),
							point.substitution,
							point
						)]);
					} else {
						var obj = new pl.type.JSValue(prolog.toJavaScript());
						thread.prepend([new pl.type.State(
							point.goal.replace(new pl.type.Term("=", [json, obj])),
							point.substitution,
							point
						)]);
					}
				}
			},

			// json_atom/2
			"json_atom/2": function( thread, point, atom ) {
				var json = atom.args[0], prolog = atom.args[1];
				if( pl.type.is_variable(json) && pl.type.is_variable(prolog) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_variable(json) && (!pl.type.is_js_object(json) || typeof(json.value) !== "object")) {
					thread.throw_error( pl.error.type( "JsValueOBJECT", json, atom.indicator ) );
				} else if( !pl.type.is_variable(prolog) && !pl.type.is_atom(prolog) ) {
					thread.throw_error( pl.error.type( "atom", prolog, atom.indicator ) );
				} else {
					if(pl.type.is_variable(prolog)) {
						try {
							var jatom = new pl.type.Term(JSON.stringify(json.value), []);
							thread.prepend([new pl.type.State(
								point.goal.replace(new pl.type.Term("=", [prolog, jatom])),
								point.substitution,
								point
							)]);
						} catch(ex) {}
					} else {
						try {
							console.log(JSON.parse(prolog.id));
							var obj = pl.fromJavaScript.apply(JSON.parse(prolog.id));
							thread.prepend([new pl.type.State(
								point.goal.replace(new pl.type.Term("=", [json, obj])),
								point.substitution,
								point
							)]);
						} catch(ex) {}
					}
				}
			},

			// ajax/3
			"ajax/3": [
				new pl.type.Rule(new pl.type.Term("ajax", [new pl.type.Var("Method"),new pl.type.Var("URL"),new pl.type.Var("Response")]), new pl.type.Term("ajax", [new pl.type.Var("Method"),new pl.type.Var("URL"),new pl.type.Var("Response"),new pl.type.Term("[]", [])]))
			],

			// ajax/4
			"ajax/4": function( thread, point, atom ) {
				var method = atom.args[0], url = atom.args[1], value = atom.args[2], options = atom.args[3];
				if(pl.type.is_variable(url) || pl.type.is_variable(method) || pl.type.is_variable(options)) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if(!pl.type.is_atom(url)) {
					thread.throw_error( pl.error.type( "atom", url, atom.indicator ) );
				} else if(!pl.type.is_atom(method)) {
					thread.throw_error( pl.error.type( "atom", method, atom.indicator ) );
				} else if(!pl.type.is_list(options)) {
					thread.throw_error( pl.error.type( "list", options, atom.indicator ) );
				} else if(["connect", "delete", "get", "head", "options", "patch", "post", "put", "trace"].indexOf(method.id) === -1) {
					thread.throw_error( pl.error.domain( "http_method", method, atom.indicator ) );
				} else {
					var pointer = options;
					var opt_type = null;
					var opt_timeout = 0;
					var opt_credentials = "false";
					var opt_async = "true";
					var opt_mime = null;
					var opt_headers = [];
					var opt_body = new FormData();
					var opt_user = null;
					var opt_password = null;
					// Options
					while(pl.type.is_term(pointer) && pointer.indicator === "./2") {
						var option = pointer.args[0];
						if(!pl.type.is_term(option) || option.args.length !== 1) {
							thread.throw_error( pl.error.domain( "ajax_option", option, atom.indicator ) );
							return;
						}
						var prop = option.args[0];
						// type/1
						if(option.indicator === "type/1") {
							if(!pl.type.is_atom(prop) || prop.id !== "text" && prop.id !== "json" && prop.id !== "document") {
								thread.throw_error( pl.error.domain( "ajax_option", option, atom.indicator ) );
								return;
							}
							opt_type = prop.id;
						// user/1
						} else if(option.indicator === "user/1") {
							if(!pl.type.is_atom(prop)) {
								thread.throw_error( pl.error.domain( "ajax_option", option, atom.indicator ) );
								return;
							}
							opt_user = prop.id;
						// password/1
						} else if(option.indicator === "password/1") {
							if(!pl.type.is_atom(prop)) {
								thread.throw_error( pl.error.domain( "ajax_option", option, atom.indicator ) );
								return;
							}
							opt_password = prop.id;
						// timeout/1
						} else if(option.indicator === "timeout/1") {
							if(!pl.type.is_integer(prop) || prop.value < 0) {
								thread.throw_error( pl.error.domain( "ajax_option", option, atom.indicator ) );
								return;
							}
							opt_timeout = prop.value;
						// async/1
						} else if(option.indicator === "async/1") {
							if(!pl.type.is_atom(prop) || prop.id !== "true" && prop.id !== "false") {
								thread.throw_error( pl.error.domain( "ajax_option", option, atom.indicator ) );
								return;
							}
							opt_async = prop.id;
						// credentials/1
						} else if(option.indicator === "credentials/1") {
							if(!pl.type.is_atom(prop) || prop.id !== "true" && prop.id !== "false") {
								thread.throw_error( pl.error.domain( "ajax_option", option, atom.indicator ) );
								return;
							}
							opt_credentials = prop.id;
						// mime/1
						} else if(option.indicator === "mime/1") {
							if(!pl.type.is_atom(prop)) {
								thread.throw_error( pl.error.domain( "ajax_option", option, atom.indicator ) );
								return;
							}
							opt_mime = prop.id;
						// headers/1
						} else if(option.indicator === "headers/1") {
							if(!pl.type.is_list(prop)) {
								thread.throw_error( pl.error.domain( "ajax_option", option, atom.indicator ) );
								return;
							}
							var hpointer = prop;
							while(pl.type.is_term(hpointer) && hpointer.indicator === "./2") {
								var header = hpointer.args[0];
								if(!pl.type.is_term(header) || header.indicator !== "-/2" || !pl.type.is_atom(header.args[0]) || !pl.type.is_atom(header.args[1])) {
									thread.throw_error( pl.error.domain( "ajax_option", option, atom.indicator ) );
									return;
								}
								opt_headers.push({header: header.args[0].id, value: header.args[1].id});
								hpointer = hpointer.args[1];
							}
							if(pl.type.is_variable(hpointer)) {
								thread.throw_error( pl.error.instantiation( atom.indicator ) );
								return;
							} else if(!pl.type.is_term(hpointer) || hpointer.indicator !== "[]/0") {
								thread.throw_error( pl.error.domain( "ajax_option", option, atom.indicator ) );
								return;
							}
						// body/1
						} else if(option.indicator === "body/1") {
							if(!pl.type.is_list(prop) && (pl.type.is_dom_object === undefined || !pl.type.is_dom_object(prop)) && !pl.type.is_atom(prop)) {
								thread.throw_error( pl.error.domain( "ajax_option", option, atom.indicator ) );
								return;
							}
							if(pl.type.is_list(prop)) {
								var hpointer = prop;
								while(pl.type.is_term(hpointer) && hpointer.indicator === "./2") {
									var body = hpointer.args[0];
									if(!pl.type.is_term(body) || body.indicator !== "-/2" || !pl.type.is_atom(body.args[0]) || !pl.type.is_atom(body.args[1])) {
										thread.throw_error( pl.error.domain( "ajax_option", option, atom.indicator ) );
										return;
									}
									opt_body.append(body.args[0].id, body.args[1].id);
									hpointer = hpointer.args[1];
								}
								if(pl.type.is_variable(hpointer)) {
									thread.throw_error( pl.error.instantiation( atom.indicator ) );
									return;
								} else if(!pl.type.is_term(hpointer) || hpointer.indicator !== "[]/0") {
									thread.throw_error( pl.error.domain( "ajax_option", option, atom.indicator ) );
									return;
								}
							} else if(pl.type.is_atom(prop)) {
								opt_body = prop.id;
							} else {
								opt_body = prop.value;
							}
						// otherwise
						} else {
							thread.throw_error( pl.error.domain( "ajax_option", option, atom.indicator ) );
							return;
						}
						pointer = pointer.args[1];
					}
					if(pl.type.is_variable(pointer)) {
						thread.throw_error( pl.error.instantiation( atom.indicator ) );
						return;
					} else if(!pl.type.is_term(pointer) || pointer.indicator !== "[]/0") {
						thread.throw_error( pl.error.type( "list", options, atom.indicator ) );
						return;
					}
					// Request
					var xhttp = new XMLHttpRequest();
					if(opt_mime !== null)
						xhttp.overrideMimeType(opt_mime);
					var fn = function() {
						if(this.readyState == 4) {
							if(this.status == 200) {
								// Get response
								var data = null;
								var content_type = this.getResponseHeader("Content-Type");
								if(this.responseType === "json" && this.response) {
									data = pl.fromJavaScript.apply(this.response);
								} else if(this.responseType === "" && content_type.indexOf("application/json") !== -1) {
									try {
										data = pl.fromJavaScript.apply(JSON.parse(this.responseText));
									} catch(e) {}
								}
								if(data === null) {
									if((this.responseType === "document" || this.responseType === "" && content_type.indexOf("text/html") !== -1 || this.responseType === "" && content_type.indexOf("application/xml") !== -1) && this.responseXML !== null && pl.type.DOM !== undefined) {
										data = new pl.type.DOM( this.responseXML );
									} else if(this.responseType === "" || this.responseType === "text") {
										data = new pl.type.Term(this.responseText, []);
									}
								}
								// Add answer
								if(data !== null)
									thread.prepend( [
										new pl.type.State(
											point.goal.replace(new pl.type.Term("=", [value, data])),
											point.substitution,
											point
										)
									] );
							}
							if(opt_async === "true")
								thread.again();
						}
					};
					xhttp.onreadystatechange = fn;
					xhttp.open(method.id.toUpperCase(), url.id, opt_async === "true", opt_user, opt_password);
					if(opt_type !== null && opt_async === "true")
						xhttp.responseType = opt_type;
					xhttp.withCredentials = opt_credentials === "true";
					if(opt_async === "true")
						xhttp.timeout = opt_timeout;
					for(var i = 0; i < opt_headers.length; i++)
						xhttp.setRequestHeader(opt_headers[i].header, opt_headers[i].value);
					xhttp.send(opt_body);
					if(opt_async === "true")
						return true;
					else
						fn.apply(xhttp);
				}
			}

		};
	};
	
	var exports = ["global/1", "apply/3", "apply/4", "prop/2", "prop/3", "json_prolog/2", "json_atom/2", "ajax/3", "ajax/4"];



	/*function prolog_to_json(prolog) {
		var pointer = prolog;
		var obj = {};
		while(pl.type.is_term(pointer) && pointer.indicator === "./2") {
			var pair = pointer.args[0];
			if(pl.type.is_variable(pair)) {
				return pl.error.instantiation( atom.indicator );
			} else if(!pl.type.is_term(pair) || pair.indicator !== "-/2" || !pl.type.is_atom(pair.args[0])) {
				return pl.error.domain( "pair", pair, atom.indicator );
			}
			if()
			obj[pair.args[0].id] = pair.args[1].toJavaScript();
			pointer = pointer.args[1];
		}
	}*/

	// JS OBJECTS
	function define_properties() {
		// Is a JS object
		pl.type.is_js_object = function( obj ) {
			return obj instanceof pl.type.JSValue;
		};

		// Ordering relation
		pl.type.order.push( pl.type.JSValue );

		// JSValue Prolog object
		pl.type.JSValue = function( value ) {
			this.value = value;
		}

		// toString
		pl.type.JSValue.prototype.toString = function() {
			return "<javascript>(" + (typeof this.value).toLowerCase() + ")";
		};

		// clone
		pl.type.JSValue.prototype.clone = function() {
			return new pl.type.JSValue( this.value );
		};

		// equals
		pl.type.JSValue.prototype.equals = function( obj ) {
			return pl.type.is_js_object( obj ) && this.value === obj.value;
		};

		// rename
		pl.type.JSValue.prototype.rename = function( _ ) {
			return this;
		};

		// get variables
		pl.type.JSValue.prototype.variables = function() {
			return [];
		};

		// apply substitutions
		pl.type.JSValue.prototype.apply = function( _ ) {
			return this;
		};

		// unify
		pl.type.JSValue.prototype.unify = function( obj, _ ) {
			if( pl.type.is_js_object( obj ) && this.value === obj.value ) {
				return new pl.type.State( obj, new pl.type.Substitution() );
			}
			return null;
		};

		// interpret
		pl.type.JSValue.prototype.interpret = function( indicator ) {
			return pl.error.instantiation( indicator );
		};

		// compare
		pl.type.JSValue.prototype.compare = function( obj ) {
			if( this.value === obj.value ) {
				return 0;
			} else if( this.value < obj.value ) {
				return -1;
			} else if( this.value > obj.value ) {
				return 1;
			}
		};

		// to javascript
		pl.type.JSValue.prototype.toJavaScript = function() {
			return this.value;
		};

		// from javascript
		pl.fromJavaScript.conversion.any = function( obj ) {
			return new pl.type.JSValue( obj );
		};



		// JavaScript error
		pl.error.javascript = function( error, indicator ) {
			return new pl.type.Term( "error", [new pl.type.Term( "javascript_error", [new pl.type.Term( error )] ), pl.utils.str_indicator( indicator )] );
		};
	}
	


	define_properties();
	new pl.type.Module( "js", predicates(), exports );

})( prolog );

(function( pl ) {

	var predicates = function() {
		
		return {
			
			// OPERATING SYSTEM INTERACTION

			// sleep/1
			"sleep/1": function( thread, point, atom ) {
				var time = atom.args[0];
				if( pl.type.is_variable( time ) ) {
					thread.throw_error( pl.error.instantiation( thread.level ) );
				} else if( !pl.type.is_integer( time ) ) {
					thread.throw_error( pl.error.type( "integer", time, thread.level ) );
				} else {
					setTimeout( function() {
						thread.success( point );
						thread.again();
					}, time.value );
					return true;
				}
			},

			// shell/1
			"shell/1": function( thread, point, atom ) {
				var command = atom.args[0];
				thread.prepend( [new pl.type.State(
					point.goal.replace( new pl.type.Term("shell", [command, new pl.type.Num(0, false)]) ),
					point.substitution,
					point
				)] );
			},

			// shell/2
			"shell/2": function( thread, point, atom ) {
				var command = atom.args[0], status = atom.args[1];
				if( pl.type.is_variable(command) ) {
					thread.throw_error( pl.error.instantiation( atom.indicator ) );
				} else if( !pl.type.is_atom(command) ) {
					thread.throw_error( pl.error.type( "atom", command, atom.indicator ) );
				} else if( !pl.type.is_variable(status) && !pl.type.is_integer(status) ) {
					thread.throw_error( pl.error.type( "integer", status, atom.indicator ) );
				} else {
					if(thread.get_flag("nodejs").indicator === "true/0") {
						const exec  = require('child_process').exec;
						exec( command.id, function() {} ).on( 'exit', function(code) {
							thread.prepend( [new pl.type.State(
								point.goal.replace( new pl.type.Term("=", [status, new pl.type.Num(code, false)]) ),
								point.substitution,
								point
							)] );
							thread.again();
						} );
						return true;
					} else {
						try {
							eval( command.id );
							thread.prepend( [new pl.type.State(
								point.goal.replace( new pl.type.Term("=", [status, new pl.type.Num(0, false)]) ),
								point.substitution,
								point
							)] );
						} catch( error ) {
							thread.prepend( [new pl.type.State(
								point.goal.replace( new pl.type.Term("=", [status, new pl.type.Num(1, false)]) ),
								point.substitution,
								point
							)] );
						}
					}
				}
			},

			// directory_files/2
			"directory_files/2": function(thread, point, atom) {
				var path = atom.args[0], entries = atom.args[1];
				if(pl.type.is_variable(path)) {
					thread.throw_error(pl.error.instantiation(atom.indicator));
				} else if(!pl.type.is_atom(path)) {
					thread.throw_error(pl.error.type("atom", path, atom.indicator));
				} else if(!pl.type.is_variable(entries) && !pl.type.is_list(entries)) {
					thread.throw_error(pl.error.type("list", entries, atom.indicator));
				} else {
					if(thread.get_flag("nodejs").indicator === "true/0") {
						var fs = require('fs');
						fs.readdir(path.id, function(error, items) {
							if(error) {
								thread.throw_error(pl.error.existence("directory", path, atom.indicator));
							} else {
								var listing = new pl.type.Term("[]", []);
								for(var i = items.length-1; i >= 0; i--)
									listing = new pl.type.Term(".", [new pl.type.Term(items[i], []), listing]);
								thread.prepend([new pl.type.State(
									point.goal.replace(new pl.type.Term("=", [entries, listing])),
									point.substitution,
									point
								)]);
							}
							thread.again();
						});
						return true;
					} else {
						var absolute = pl.utils.cd(thread.session.working_directory, path.id);
						var file = thread.session.file_system.get(absolute);
						if(pl.type.is_directory(file)) {
							var items = [];
							for(var prop in file.files)
								items.push(prop);
							var listing = new pl.type.Term("[]", []);
							for(var i = items.length-1; i >= 0; i--)
								listing = new pl.type.Term(".", [new pl.type.Term(items[i], []), listing]);
							thread.prepend([new pl.type.State(
								point.goal.replace(new pl.type.Term("=", [entries, listing])),
								point.substitution,
								point
							)]);
						} else {
							thread.throw_error(pl.error.existence("directory", path, atom.indicator));
						}
					}
				}
			},

			// working_directory/2
			"working_directory/2": function(thread, point, atom) {
				var oldcwd = atom.args[0], newcwd = atom.args[1];
				if(pl.type.is_variable(newcwd) && (!pl.type.is_variable(oldcwd) || oldcwd.id !== newcwd.id)) {
					thread.throw_error(pl.error.instantiation(atom.indicator));
				} else if(!pl.type.is_variable(oldcwd) && !pl.type.is_atom(oldcwd)) {
					thread.throw_error(pl.error.type("atom", oldcwd, atom.indicator));
				} else if(!pl.type.is_variable(newcwd) && !pl.type.is_atom(newcwd)) {
					thread.throw_error(pl.error.type("atom", newcwd, atom.indicator));
				} else {
					var wd;
					if(thread.get_flag("nodejs").indicator === "true/0") {
						wd = process.cwd();
						if(!pl.type.is_variable(newcwd))
							process.chdir(newcwd.id);
					} else {
						wd = thread.session.working_directory;
						if(!pl.type.is_variable(newcwd)) {
							thread.session.working_directory = pl.utils.cd(wd, newcwd.id);
						}
					}
					thread.prepend([new pl.type.State(
						point.goal.replace(new pl.type.Term("=", [oldcwd, new pl.type.Term(wd, [])])),
						point.substitution,
						point
					)]);
				}
			},

			// delete_file/1
			"delete_file/1": function(thread, point, atom) {
				var path = atom.args[0];
				if(pl.type.is_variable(path)) {
					thread.throw_error(pl.error.instantiation(atom.indicator));
				} else if(!pl.type.is_atom(path)) {
					thread.throw_error(pl.error.type("atom", path, atom.indicator));
				} else {
					if(thread.get_flag("nodejs").indicator === "true/0") {
						var fs = require('fs');
						fs.stat(path.id, function(error, stat) {
							if(!error && stat.isFile()) {
								fs.unlink(path.id, function(error) {
									if(error)
										thread.throw_error(pl.error.permission("delete", "source_sink", path, atom.indicator));
									else
										thread.success( point );
									thread.again();
								});
							} else {
								thread.throw_error(pl.error.existence("source_sink", path, atom.indicator));
								thread.again();
							}
						});
						return true;
					} else {
						var absolute = pl.utils.cd(thread.session.working_directory, path.id);
						var file = thread.session.file_system.get(absolute);
						if(pl.type.is_file(file)) {
							file.parent.remove(file.name);
							thread.success(point);
						} else {
							thread.throw_error(pl.error.existence("source_sink", path, atom.indicator));
						}
					}
				}
			},

			// delete_directory/1
			"delete_directory/1": function(thread, point, atom) {
				var path = atom.args[0];
				if(pl.type.is_variable(path)) {
					thread.throw_error(pl.error.instantiation(atom.indicator));
				} else if(!pl.type.is_atom(path)) {
					thread.throw_error(pl.error.type("atom", path, atom.indicator));
				} else {
					if(thread.get_flag("nodejs").indicator === "true/0") {
						var fs = require('fs');
						fs.stat(path.id, function(error, stat) {
							if(!error && stat.isDirectory()) {
								fs.rmdir(path.id, function(error) {
									if(error)
										thread.throw_error(pl.error.permission("delete", "directory", path, atom.indicator));
									else
										thread.success( point );
									thread.again();
								});
							} else {
								thread.throw_error(pl.error.existence("directory", path, atom.indicator));
								thread.again();
							}
						});
						return true;
					} else {
						var absolute = pl.utils.cd(thread.session.working_directory, path.id);
						var file = thread.session.file_system.get(absolute);
						if(pl.type.is_directory(file)) {
							if(file !== thread.session.file_system.files && file.empty()) {
								file.parent.remove(file.name);
								thread.success(point);
							} else {
								thread.throw_error(pl.error.permission("delete", "directory", path, atom.indicator));
							}
						} else {
							thread.throw_error(pl.error.existence("directory", path, atom.indicator));
						}
					}
				}
			},

			// make_directory/1
			"make_directory/1": function(thread, point, atom) {
				var path = atom.args[0];
				if(pl.type.is_variable(path)) {
					thread.throw_error(pl.error.instantiation(atom.indicator));
				} else if(!pl.type.is_atom(path)) {
					thread.throw_error(pl.error.type("atom", path, atom.indicator));
				} else {
					if(thread.get_flag("nodejs").indicator === "true/0") {
						var fs = require('fs');
						fs.stat(path.id, function(error, stat) {
							if(!error && (stat.isDirectory() || stat.isFile())) {
								thread.throw_error(pl.error.permission("create", "directory", path, atom.indicator));
								thread.again();
							} else {
								fs.mkdir(path.id, function(error) { 
									if(error)
										thread.throw_error(pl.error.existence("directory", path, atom.indicator));
									else
										thread.success(point);
									thread.again();
								});
							}
						});
						return true;
					} else {
						var absolute = pl.utils.cd(thread.session.working_directory, path.id);
						var dirs = absolute.replace(/\/$/, "").split("/");
						var dir = thread.session.file_system.files;
						var name = dirs[dirs.length-1];
						for(var i = 1; i < dirs.length-1; i++) {
							dir = dir.lookup(dirs[i]);
							if(!pl.type.is_directory(dir)) {
								thread.throw_error(pl.error.existence("directory", path, atom.indicator));
								return;
							}
						}
						if(dir.lookup(name)) {
							thread.throw_error(pl.error.permission("create", "directory", path, atom.indicator));
						} else {
							dir.push(name, new pl.type.Directory(name, dir));
							thread.success(point);
						}
					}
				}
			},

			// rename_file/2
			"rename_file/2": function(thread, point, atom) {
				var old_path = atom.args[0], new_path = atom.args[1];
				if(pl.type.is_variable(old_path) || pl.type.is_variable(new_path)) {
					thread.throw_error(pl.error.instantiation(atom.indicator));
				} else if(!pl.type.is_atom(old_path)) {
					thread.throw_error(pl.error.type("atom", old_path, atom.indicator));
				} else if(!pl.type.is_atom(new_path)) {
					thread.throw_error(pl.error.type("atom", new_path, atom.indicator));
				} else {
					if(thread.get_flag("nodejs").indicator === "true/0") {
						var fs = require('fs');
						fs.stat(old_path.id, function(error, stat) {
							if(error || !stat.isFile()) {
								thread.throw_error(pl.error.existence("source_sink", old_path, atom.indicator));
								thread.again();
							} else {
								fs.rename(old_path.id, new_path.id, function(error) { 
									if(error)
										thread.throw_error(pl.error.existence("source_sink", new_path, atom.indicator));
									else
										thread.success(point);
									thread.again();
								});
							}
						});
						return true;
					} else {
						var old_file = thread.file_system_open(old_path.id, "text", "read");
						if(old_file) {
							var new_file = thread.file_system_open(new_path.id, "text", "write");
							if(new_file) {
								new_file.text = old_file.text;
								var absolute = pl.utils.cd(thread.session.working_directory, old_path.id);
								var dirs = absolute.replace(/\/$/, "").split("/");
								var dir = thread.session.file_system.files;
								var name = dirs[dirs.length-1];
								for(var i = 1; i < dirs.length-1; i++)
									dir = dir.lookup(dirs[i]);
								dir.remove(name);
								thread.success(point);
							} else {
								thread.throw_error(pl.error.existence("source_sink", new_path, atom.indicator));
							}
						} else {
							thread.throw_error(pl.error.existence("source_sink", old_path, atom.indicator));
						}
					}
				}
			},
			
			// exists_file/1
			"exists_file/1": function(thread, point, atom) {
				var path = atom.args[0];
				if(pl.type.is_variable(path)) {
					thread.throw_error(pl.error.instantiation(atom.indicator));
				} else if(!pl.type.is_atom(path)) {
					thread.throw_error(pl.error.type("atom", path, atom.indicator));
				} else {
					if(thread.get_flag("nodejs").indicator === "true/0") {
						var fs = require('fs');
						fs.stat(path.id, function(error, stat) {
							if(!error && stat.isFile())
								thread.success(point);
							thread.again();
						});
						return true;
					} else {
						var absolute = pl.utils.cd(thread.session.working_directory, path.id);
						var file = thread.session.file_system.get(absolute);
						if(pl.type.is_file(file))
							thread.success(point);
					}
				}
			},

			// exists_directory/1
			"exists_directory/1": function(thread, point, atom) {
				var path = atom.args[0];
				if(pl.type.is_variable(path)) {
					thread.throw_error(pl.error.instantiation(atom.indicator));
				} else if(!pl.type.is_atom(path)) {
					thread.throw_error(pl.error.type("atom", path, atom.indicator));
				} else {
					if(thread.get_flag("nodejs").indicator === "true/0") {
						var fs = require('fs');
						fs.stat(path.id, function(error, stat) {
							if(!error && stat.isDirectory())
								thread.success(point);
							thread.again();
						});
						return true;
					} else {
						var absolute = pl.utils.cd(thread.session.working_directory, path.id);
						var file = thread.session.file_system.get(absolute);
						if(pl.type.is_directory(file))
							thread.success(point);
					}
				}
			},

			// same_file/2
			"same_file/2": function(thread, point, atom) {
				var fst_path = atom.args[0], snd_path = atom.args[1];
				if(pl.type.is_variable(fst_path) || pl.type.is_variable(snd_path)) {
					thread.throw_error(pl.error.instantiation(atom.indicator));
				} else if(!pl.type.is_atom(fst_path)) {
					thread.throw_error(pl.error.type("atom", fst_path, atom.indicator));
				} else if(!pl.type.is_atom(snd_path)) {
					thread.throw_error(pl.error.type("atom", snd_path, atom.indicator));
				} else {
					if(fst_path.id === snd_path.id) {
						thread.success(point);
					} else {
						if(thread.get_flag("nodejs").indicator === "true/0") {
							var fs = require('fs');
							fs.stat(fst_path.id, function(error, fst_stat) {
								if(!error)
									fs.stat(snd_path.id, function(error, snd_stat) {
										if(!error && fst_stat.dev === snd_stat.dev && fst_stat.ino === snd_stat.ino)
											thread.success(point);
										thread.again();
									});
								else
									thread.again();
							});
							return true;
						} else {
							var working_directory = thread.session.working_directory;
							var fst_file = thread.session.file_system.get(pl.utils.cd(working_directory, fst_path.id));
							var snd_file = thread.session.file_system.get(pl.utils.cd(working_directory, snd_path.id));
							if(fst_file && snd_file && fst_file === snd_file)
								thread.success(point);
						}
					}
				}
			},

			// absolute_file_name/2
			"absolute_file_name/2": function(thread, point, atom) {
				var filename = atom.args[0], absolute = atom.args[1];
				if(pl.type.is_variable(filename)) {
					thread.throw_error(pl.error.instantiation(atom.indicator));
				} else if(!pl.type.is_atom(filename)) {
					thread.throw_error(pl.error.type("atom", filename, atom.indicator));
				} else if(!pl.type.is_variable(absolute) && !pl.type.is_atom(absolute)) {
					thread.throw_error(pl.error.type("atom", absolute, atom.indicator));
				} else {
					var absolute_filename;
					if(thread.get_flag("nodejs").indicator === "true/0") {
						var path = require("path");
						absolute_filename = path.resolve(filename.id);
					} else {
						absolute_filename = pl.utils.cd(thread.session.working_directory, filename.id);
					}
					thread.prepend([new pl.type.State(
						point.goal.replace(new pl.type.Term("=", [
							absolute,
							new pl.type.Term(absolute_filename, [])])),
						point.substitution,
						point
					)]);
				}
			},

			// is_absolute_file_name/1
			"is_absolute_file_name/1": function(thread, point, atom) {
				var filename = atom.args[0];
				if(pl.type.is_variable(filename)) {
					thread.throw_error(pl.error.instantiation(atom.indicator));
				} else if(!pl.type.is_atom(filename)) {
					thread.throw_error(pl.error.type("atom", filename, atom.indicator));
				} else {
					if(thread.get_flag("nodejs").indicator === "true/0") {
						var path = require('path');
						if(path.isAbsolute(filename.id))
							thread.success(point);
					} else {
						if(filename.id.length > 0 && filename.id[0] === "/")
							thread.success(point);
					}
				}
			},

			// size_file/2
			"size_file/2": function(thread, point, atom) {
				var path = atom.args[0], size = atom.args[1];
				if(pl.type.is_variable(path)) {
					thread.throw_error(pl.error.instantiation(atom.indicator));
				} else if(!pl.type.is_atom(path)) {
					thread.throw_error(pl.error.type("atom", path, atom.indicator));
				} else if(!pl.type.is_variable(size) && !pl.type.is_integer(size)) {
					thread.throw_error(pl.error.type("integer", size, atom.indicator));
				} else {
					if(thread.get_flag("nodejs").indicator === "true/0") {
						var fs = require('fs');
						fs.stat(path.id, function(error, stat) {
							if(!error) {
								var filesize = stat.size;
								thread.prepend([new pl.type.State(
									point.goal.replace(new pl.type.Term("=", [size, new pl.type.Num(filesize, false)])),
									point.substitution,
									point
								)]);
							} else {
								thread.throw_error(pl.error.existence("source_sink", path, atom.indicator));
							}
							thread.again();
						});
						return true;
					} else {
						var absolute = pl.utils.cd(thread.session.working_directory, path.id);
						var file = thread.session.file_system.get(absolute);
						if(pl.type.is_file(file) || pl.type.is_directory(file)) {
							var filesize = file.size();
							thread.prepend([new pl.type.State(
								point.goal.replace(new pl.type.Term("=", [size, new pl.type.Num(filesize, false)])),
								point.substitution,
								point
							)]);
						} else {
							thread.throw_error(pl.error.existence("source_sink", path, atom.indicator));
						}
					}
				}
			},

			// time_file/2
			"time_file/2": function(thread, point, atom) {
				var path = atom.args[0], time = atom.args[1];
				if(pl.type.is_variable(path)) {
					thread.throw_error(pl.error.instantiation(atom.indicator));
				} else if(!pl.type.is_atom(path)) {
					thread.throw_error(pl.error.type("atom", path, atom.indicator));
				} else if(!pl.type.is_variable(time) && !pl.type.is_number(time)) {
					thread.throw_error(pl.error.type("number", time, atom.indicator));
				} else {
					if(thread.get_flag("nodejs").indicator === "true/0") {
						var fs = require('fs');
						fs.stat(path.id, function(error, stat) {
							if(!error) {
								var mtime = stat.mtime / 1000;
								thread.prepend([new pl.type.State(
									point.goal.replace(new pl.type.Term("=", [time, new pl.type.Num(mtime)])),
									point.substitution,
									point
								)]);
							} else {
								thread.throw_error(pl.error.existence("source_sink", path, atom.indicator));
							}
							thread.again();
						});
						return true;
					} else {
						var absolute = pl.utils.cd(thread.session.working_directory, path.id);
						var file = thread.session.file_system.get(absolute);
						if(pl.type.is_file(file) || pl.type.is_directory(file)) {
							var mtime = file.modified;
							thread.prepend([new pl.type.State(
								point.goal.replace(new pl.type.Term("=", [time, new pl.type.Num(mtime)])),
								point.substitution,
								point
							)]);
						} else {
							thread.throw_error(pl.error.existence("source_sink", path, atom.indicator));
						}
					}
				}
			}
		
		};
		
	};
	
	var exports = ["sleep/1", "shell/1", "shell/2", "directory_files/2", "working_directory/2", "delete_file/1", "delete_directory/1", "rename_file/2", "make_directory/1", "exists_file/1", "exists_directory/1", "same_file/2", "absolute_file_name/2", "is_absolute_file_name/1", "size_file/2", "time_file/2"];

	new pl.type.Module( "os", predicates(), exports );

})( prolog );

if (typeof module != 'undefined') module.exports=prolog;
};
BundleModuleCode['parser/papaparse']=function (module,exports){
/*!
	Papa Parse
	v4.1.2
	https://github.com/mholt/PapaParse
*/
(function(global)
{
	'use strict';

	var IS_WORKER = !global.document && !!global.postMessage,
		IS_PAPA_WORKER = IS_WORKER && /(\?|&)papaworker(=|&|$)/.test(global.location.search),
		LOADED_SYNC = false, AUTO_SCRIPT_PATH;
	var workers = {}, workerIdCounter = 0;

	var Papa = {};

	Papa.parse = CsvToJson;
	Papa.unparse = JsonToCsv;

	Papa.RECORD_SEP = String.fromCharCode(30);
	Papa.UNIT_SEP = String.fromCharCode(31);
	Papa.BYTE_ORDER_MARK = '\ufeff';
	Papa.BAD_DELIMITERS = ['\r', '\n', '"', Papa.BYTE_ORDER_MARK];
	Papa.WORKERS_SUPPORTED = !IS_WORKER && !!global.Worker;
	Papa.SCRIPT_PATH = null;	// Must be set by your code if you use workers and this lib is loaded asynchronously

	// Configurable chunk sizes for local and remote files, respectively
	Papa.LocalChunkSize = 1024 * 1024 * 10;	// 10 MB
	Papa.RemoteChunkSize = 1024 * 1024 * 5;	// 5 MB
	Papa.DefaultDelimiter = ',';			// Used if not specified and detection fails

	// Exposed for testing and development only
	Papa.Parser = Parser;
	Papa.ParserHandle = ParserHandle;
	Papa.NetworkStreamer = NetworkStreamer;
	Papa.FileStreamer = FileStreamer;
	Papa.StringStreamer = StringStreamer;

        function detectCSV (chunk, opts) {
          opts = opts || {}
          if (Buffer.isBuffer(chunk)) chunk = chunk + ''
          var delimiters = opts.delimiters || [',', ';', '\t', '|']
          var newlines = opts.newlines || ['\n', '\r']

          var lines = chunk.split(/[\n\r]+/g)

          var delimiter = determineMost(lines[0], delimiters)
          var newline = determineMost(chunk, newlines)

          if (!delimiter) {
            if (isQuoted(lines[0])) return { newline: newline }
            return null
          }

          return {
            delimiter: delimiter,
            newline: newline
          }
        }

        function determineMost (chunk, items) {
          var ignoreString = false
          var itemCount = {}
          var maxValue = 0
          var maxChar
          var currValue
          items.forEach(function (item) {
            itemCount[item] = 0
          })
          for (var i = 0; i < chunk.length; i++) {
            if (chunk[i] === '"') ignoreString = !ignoreString
            else if (!ignoreString && chunk[i] in itemCount) {
              currValue = ++itemCount[chunk[i]]
              if (currValue > maxValue) {
                maxValue = currValue
                maxChar = chunk[i]
              }
            }
          }
          return maxChar
        }

        function isQuoted (chunk) {
          // is correctly quoted
          var nextQuote = false
          if (chunk[0] !== '"') return false
          if (chunk[chunk.length - 1] !== '"') return false
          for (var i = 1; i < chunk.length - 1; i++) {
            if (chunk[i] === '"') nextQuote = !nextQuote
            else if (nextQuote) return false
          }
          return !nextQuote
        }

        Papa.detect = detectCSV;
        
	if (typeof module !== 'undefined' && module.exports)
	{
		// Export to Node...
		module.exports = Papa;
	}
	else if (isFunction(global.define) && global.define.amd)
	{
		// Wireup with RequireJS
		define(function() { return Papa; });
	}
	else
	{
		// ...or as browser global
		global.Papa = Papa;
	}

	if (global.jQuery)
	{
		var $ = global.jQuery;
		$.fn.parse = function(options)
		{
			var config = options.config || {};
			var queue = [];

			this.each(function(idx)
			{
				var supported = $(this).prop('tagName').toUpperCase() === 'INPUT'
								&& $(this).attr('type').toLowerCase() === 'file'
								&& global.FileReader;

				if (!supported || !this.files || this.files.length === 0)
					return true;	// continue to next input element

				for (var i = 0; i < this.files.length; i++)
				{
					queue.push({
						file: this.files[i],
						inputElem: this,
						instanceConfig: $.extend({}, config)
					});
				}
			});

			parseNextFile();	// begin parsing
			return this;		// maintains chainability


			function parseNextFile()
			{
				if (queue.length === 0)
				{
					if (isFunction(options.complete))
						options.complete();
					return;
				}

				var f = queue[0];

				if (isFunction(options.before))
				{
					var returned = options.before(f.file, f.inputElem);

					if (typeof returned === 'object')
					{
						if (returned.action === 'abort')
						{
							error('AbortError', f.file, f.inputElem, returned.reason);
							return;	// Aborts all queued files immediately
						}
						else if (returned.action === 'skip')
						{
							fileComplete();	// parse the next file in the queue, if any
							return;
						}
						else if (typeof returned.config === 'object')
							f.instanceConfig = $.extend(f.instanceConfig, returned.config);
					}
					else if (returned === 'skip')
					{
						fileComplete();	// parse the next file in the queue, if any
						return;
					}
				}

				// Wrap up the user's complete callback, if any, so that ours also gets executed
				var userCompleteFunc = f.instanceConfig.complete;
				f.instanceConfig.complete = function(results)
				{
					if (isFunction(userCompleteFunc))
						userCompleteFunc(results, f.file, f.inputElem);
					fileComplete();
				};

				Papa.parse(f.file, f.instanceConfig);
			}

			function error(name, file, elem, reason)
			{
				if (isFunction(options.error))
					options.error({name: name}, file, elem, reason);
			}

			function fileComplete()
			{
				queue.splice(0, 1);
				parseNextFile();
			}
		}
	}


	if (IS_PAPA_WORKER)
	{
		global.onmessage = workerThreadReceivedMessage;
	}
	else if (Papa.WORKERS_SUPPORTED)
	{
		AUTO_SCRIPT_PATH = getScriptPath();

		// Check if the script was loaded synchronously
		if (!document.body)
		{
			// Body doesn't exist yet, must be synchronous
			LOADED_SYNC = true;
		}
		else
		{
			document.addEventListener('DOMContentLoaded', function () {
				LOADED_SYNC = true;
			}, true);
		}
	}




	function CsvToJson(_input, _config)
	{
		_config = _config || {};

		if (_config.worker && Papa.WORKERS_SUPPORTED)
		{
			var w = newWorker();

			w.userStep = _config.step;
			w.userChunk = _config.chunk;
			w.userComplete = _config.complete;
			w.userError = _config.error;

			_config.step = isFunction(_config.step);
			_config.chunk = isFunction(_config.chunk);
			_config.complete = isFunction(_config.complete);
			_config.error = isFunction(_config.error);
			delete _config.worker;	// prevent infinite loop

			w.postMessage({
				input: _input,
				config: _config,
				workerId: w.id
			});

			return;
		}

		var streamer = null;
		if (typeof _input === 'string')
		{
			if (_config.download)
				streamer = new NetworkStreamer(_config);
			else
				streamer = new StringStreamer(_config);
		}
		else if ((global.File && _input instanceof File) || _input instanceof Object)	// ...Safari. (see issue #106)
			streamer = new FileStreamer(_config);

		return streamer.stream(_input);
	}






	function JsonToCsv(_input, _config)
	{
		var _output = '';
		var _fields = [];

		// Default configuration

		/** whether to surround every datum with quotes */
		var _quotes = false;

		/** delimiting character */
		var _delimiter = ',';

		/** newline character(s) */
		var _newline = '\r\n';

		unpackConfig();

		if (typeof _input === 'string')
			_input = JSON.parse(_input);

		if (_input instanceof Array)
		{
			if (!_input.length || _input[0] instanceof Array)
				return serialize(null, _input);
			else if (typeof _input[0] === 'object')
				return serialize(objectKeys(_input[0]), _input);
		}
		else if (typeof _input === 'object')
		{
			if (typeof _input.data === 'string')
				_input.data = JSON.parse(_input.data);

			if (_input.data instanceof Array)
			{
				if (!_input.fields)
					_input.fields =  _input.meta && _input.meta.fields;

				if (!_input.fields)
					_input.fields =  _input.data[0] instanceof Array
									? _input.fields
									: objectKeys(_input.data[0]);

				if (!(_input.data[0] instanceof Array) && typeof _input.data[0] !== 'object')
					_input.data = [_input.data];	// handles input like [1,2,3] or ['asdf']
			}

			return serialize(_input.fields || [], _input.data || []);
		}

		// Default (any valid paths should return before this)
		throw 'exception: Unable to serialize unrecognized input';


		function unpackConfig()
		{
			if (typeof _config !== 'object')
				return;

			if (typeof _config.delimiter === 'string'
				&& _config.delimiter.length === 1
				&& Papa.BAD_DELIMITERS.indexOf(_config.delimiter) === -1)
			{
				_delimiter = _config.delimiter;
			}

			if (typeof _config.quotes === 'boolean'
				|| _config.quotes instanceof Array)
				_quotes = _config.quotes;

			if (typeof _config.newline === 'string')
				_newline = _config.newline;
		}


		/** Turns an object's keys into an array */
		function objectKeys(obj)
		{
			if (typeof obj !== 'object')
				return [];
			var keys = [];
			for (var key in obj)
				keys.push(key);
			return keys;
		}

		/** The double for loop that iterates the data and writes out a CSV string including header row */
		function serialize(fields, data)
		{
			var csv = '';

			if (typeof fields === 'string')
				fields = JSON.parse(fields);
			if (typeof data === 'string')
				data = JSON.parse(data);

			var hasHeader = fields instanceof Array && fields.length > 0;
			var dataKeyedByField = !(data[0] instanceof Array);

			// If there a header row, write it first
			if (hasHeader)
			{
				for (var i = 0; i < fields.length; i++)
				{
					if (i > 0)
						csv += _delimiter;
					csv += safe(fields[i], i);
				}
				if (data.length > 0)
					csv += _newline;
			}

			// Then write out the data
			for (var row = 0; row < data.length; row++)
			{
				var maxCol = hasHeader ? fields.length : data[row].length;

				for (var col = 0; col < maxCol; col++)
				{
					if (col > 0)
						csv += _delimiter;
					var colIdx = hasHeader && dataKeyedByField ? fields[col] : col;
					csv += safe(data[row][colIdx], col);
				}

				if (row < data.length - 1)
					csv += _newline;
			}

			return csv;
		}

		/** Encloses a value around quotes if needed (makes a value safe for CSV insertion) */
		function safe(str, col)
		{
			if (typeof str === 'undefined' || str === null)
				return '';

			str = str.toString().replace(/"/g, '""');

			var needsQuotes = (typeof _quotes === 'boolean' && _quotes)
							|| (_quotes instanceof Array && _quotes[col])
							|| hasAny(str, Papa.BAD_DELIMITERS)
							|| str.indexOf(_delimiter) > -1
							|| str.charAt(0) === ' '
							|| str.charAt(str.length - 1) === ' ';

			return needsQuotes ? '"' + str + '"' : str;
		}

		function hasAny(str, substrings)
		{
			for (var i = 0; i < substrings.length; i++)
				if (str.indexOf(substrings[i]) > -1)
					return true;
			return false;
		}
	}

	/** ChunkStreamer is the base prototype for various streamer implementations. */
	function ChunkStreamer(config)
	{
		this._handle = null;
		this._paused = false;
		this._finished = false;
		this._input = null;
		this._baseIndex = 0;
		this._partialLine = '';
		this._rowCount = 0;
		this._start = 0;
		this._nextChunk = null;
		this.isFirstChunk = true;
		this._completeResults = {
			data: [],
			errors: [],
			meta: {}
		};
		replaceConfig.call(this, config);

		this.parseChunk = function(chunk)
		{
			// First chunk pre-processing
			if (this.isFirstChunk && isFunction(this._config.beforeFirstChunk))
			{
				var modifiedChunk = this._config.beforeFirstChunk(chunk);
				if (modifiedChunk !== undefined)
					chunk = modifiedChunk;
			}
			this.isFirstChunk = false;

			// Rejoin the line we likely just split in two by chunking the file
			var aggregate = this._partialLine + chunk;
			this._partialLine = '';

			var results = this._handle.parse(aggregate, this._baseIndex, !this._finished);

			if (this._handle.paused() || this._handle.aborted())
				return;

			var lastIndex = results.meta.cursor;

			if (!this._finished)
			{
				this._partialLine = aggregate.substring(lastIndex - this._baseIndex);
				this._baseIndex = lastIndex;
			}

			if (results && results.data)
				this._rowCount += results.data.length;

			var finishedIncludingPreview = this._finished || (this._config.preview && this._rowCount >= this._config.preview);

			if (IS_PAPA_WORKER)
			{
				global.postMessage({
					results: results,
					workerId: Papa.WORKER_ID,
					finished: finishedIncludingPreview
				});
			}
			else if (isFunction(this._config.chunk))
			{
				this._config.chunk(results, this._handle);
				if (this._paused)
					return;
				results = undefined;
				this._completeResults = undefined;
			}

			if (!this._config.step && !this._config.chunk) {
				this._completeResults.data = this._completeResults.data.concat(results.data);
				this._completeResults.errors = this._completeResults.errors.concat(results.errors);
				this._completeResults.meta = results.meta;
			}

			if (finishedIncludingPreview && isFunction(this._config.complete) && (!results || !results.meta.aborted))
				this._config.complete(this._completeResults, this._input);

			if (!finishedIncludingPreview && (!results || !results.meta.paused))
				this._nextChunk();

			return results;
		};

		this._sendError = function(error)
		{
			if (isFunction(this._config.error))
				this._config.error(error);
			else if (IS_PAPA_WORKER && this._config.error)
			{
				global.postMessage({
					workerId: Papa.WORKER_ID,
					error: error,
					finished: false
				});
			}
		};

		function replaceConfig(config)
		{
			// Deep-copy the config so we can edit it
			var configCopy = copy(config);
			configCopy.chunkSize = parseInt(configCopy.chunkSize);	// parseInt VERY important so we don't concatenate strings!
			if (!config.step && !config.chunk)
				configCopy.chunkSize = null;  // disable Range header if not streaming; bad values break IIS - see issue #196
			this._handle = new ParserHandle(configCopy);
			this._handle.streamer = this;
			this._config = configCopy;	// persist the copy to the caller
		}
	}


	function NetworkStreamer(config)
	{
		config = config || {};
		if (!config.chunkSize)
			config.chunkSize = Papa.RemoteChunkSize;
		ChunkStreamer.call(this, config);

		var xhr;

		if (IS_WORKER)
		{
			this._nextChunk = function()
			{
				this._readChunk();
				this._chunkLoaded();
			};
		}
		else
		{
			this._nextChunk = function()
			{
				this._readChunk();
			};
		}

		this.stream = function(url)
		{
			this._input = url;
			this._nextChunk();	// Starts streaming
		};

		this._readChunk = function()
		{
			if (this._finished)
			{
				this._chunkLoaded();
				return;
			}

			xhr = new XMLHttpRequest();

			if (this._config.withCredentials)
			{
				xhr.withCredentials = this._config.withCredentials;
			}

			if (!IS_WORKER)
			{
				xhr.onload = bindFunction(this._chunkLoaded, this);
				xhr.onerror = bindFunction(this._chunkError, this);
			}

			xhr.open('GET', this._input, !IS_WORKER);

			if (this._config.chunkSize)
			{
				var end = this._start + this._config.chunkSize - 1;	// minus one because byte range is inclusive
				xhr.setRequestHeader('Range', 'bytes='+this._start+'-'+end);
				xhr.setRequestHeader('If-None-Match', 'webkit-no-cache'); // https://bugs.webkit.org/show_bug.cgi?id=82672
			}

			try {
				xhr.send();
			}
			catch (err) {
				this._chunkError(err.message);
			}

			if (IS_WORKER && xhr.status === 0)
				this._chunkError();
			else
				this._start += this._config.chunkSize;
		}

		this._chunkLoaded = function()
		{
			if (xhr.readyState != 4)
				return;

			if (xhr.status < 200 || xhr.status >= 400)
			{
				this._chunkError();
				return;
			}

			this._finished = !this._config.chunkSize || this._start > getFileSize(xhr);
			this.parseChunk(xhr.responseText);
		}

		this._chunkError = function(errorMessage)
		{
			var errorText = xhr.statusText || errorMessage;
			this._sendError(errorText);
		}

		function getFileSize(xhr)
		{
			var contentRange = xhr.getResponseHeader('Content-Range');
			return parseInt(contentRange.substr(contentRange.lastIndexOf('/') + 1));
		}
	}
	NetworkStreamer.prototype = Object.create(ChunkStreamer.prototype);
	NetworkStreamer.prototype.constructor = NetworkStreamer;


	function FileStreamer(config)
	{
		config = config || {};
		if (!config.chunkSize)
			config.chunkSize = Papa.LocalChunkSize;
		ChunkStreamer.call(this, config);

		var reader, slice;

		// FileReader is better than FileReaderSync (even in worker) - see http://stackoverflow.com/q/24708649/1048862
		// But Firefox is a pill, too - see issue #76: https://github.com/mholt/PapaParse/issues/76
		var usingAsyncReader = typeof FileReader !== 'undefined';	// Safari doesn't consider it a function - see issue #105

		this.stream = function(file)
		{
			this._input = file;
			slice = file.slice || file.webkitSlice || file.mozSlice;

			if (usingAsyncReader)
			{
				reader = new FileReader();		// Preferred method of reading files, even in workers
				reader.onload = bindFunction(this._chunkLoaded, this);
				reader.onerror = bindFunction(this._chunkError, this);
			}
			else
				reader = new FileReaderSync();	// Hack for running in a web worker in Firefox

			this._nextChunk();	// Starts streaming
		};

		this._nextChunk = function()
		{
			if (!this._finished && (!this._config.preview || this._rowCount < this._config.preview))
				this._readChunk();
		}

		this._readChunk = function()
		{
			var input = this._input;
			if (this._config.chunkSize)
			{
				var end = Math.min(this._start + this._config.chunkSize, this._input.size);
				input = slice.call(input, this._start, end);
			}
			var txt = reader.readAsText(input, this._config.encoding);
			if (!usingAsyncReader)
				this._chunkLoaded({ target: { result: txt } });	// mimic the async signature
		}

		this._chunkLoaded = function(event)
		{
			// Very important to increment start each time before handling results
			this._start += this._config.chunkSize;
			this._finished = !this._config.chunkSize || this._start >= this._input.size;
			this.parseChunk(event.target.result);
		}

		this._chunkError = function()
		{
			this._sendError(reader.error);
		}

	}
	FileStreamer.prototype = Object.create(ChunkStreamer.prototype);
	FileStreamer.prototype.constructor = FileStreamer;


	function StringStreamer(config)
	{
		config = config || {};
		ChunkStreamer.call(this, config);

		var string;
		var remaining;
		this.stream = function(s)
		{
			string = s;
			remaining = s;
			return this._nextChunk();
		}
		this._nextChunk = function()
		{
			if (this._finished) return;
			var size = this._config.chunkSize;
			var chunk = size ? remaining.substr(0, size) : remaining;
			remaining = size ? remaining.substr(size) : '';
			this._finished = !remaining;
			return this.parseChunk(chunk);
		}
	}
	StringStreamer.prototype = Object.create(StringStreamer.prototype);
	StringStreamer.prototype.constructor = StringStreamer;



	// Use one ParserHandle per entire CSV file or string
	function ParserHandle(_config)
	{
		// One goal is to minimize the use of regular expressions...
		var FLOAT = /^\s*-?(\d*\.?\d+|\d+\.?\d*)(e[-+]?\d+)?\s*$/i;

		var self = this;
		var _stepCounter = 0;	// Number of times step was called (number of rows parsed)
		var _input;				// The input being parsed
		var _parser;			// The core parser being used
		var _paused = false;	// Whether we are paused or not
		var _aborted = false;   // Whether the parser has aborted or not
		var _delimiterError;	// Temporary state between delimiter detection and processing results
		var _fields = [];		// Fields are from the header row of the input, if there is one
		var _results = {		// The last results returned from the parser
			data: [],
			errors: [],
			meta: {}
		};

		if (isFunction(_config.step))
		{
			var userStep = _config.step;
			_config.step = function(results)
			{
				_results = results;

				if (needsHeaderRow())
					processResults();
				else	// only call user's step function after header row
				{
					processResults();

					// It's possbile that this line was empty and there's no row here after all
					if (_results.data.length === 0)
						return;

					_stepCounter += results.data.length;
					if (_config.preview && _stepCounter > _config.preview)
						_parser.abort();
					else
						userStep(_results, self);
				}
			};
		}

		/**
		 * Parses input. Most users won't need, and shouldn't mess with, the baseIndex
		 * and ignoreLastRow parameters. They are used by streamers (wrapper functions)
		 * when an input comes in multiple chunks, like from a file.
		 */
		this.parse = function(input, baseIndex, ignoreLastRow)
		{
			if (!_config.newline)
				_config.newline = guessLineEndings(input);

			_delimiterError = false;
			if (!_config.delimiter)
			{
				var delimGuess = guessDelimiter(input, _config.newline);
				if (delimGuess.successful)
					_config.delimiter = delimGuess.bestDelimiter;
				else
				{
					_delimiterError = true;	// add error after parsing (otherwise it would be overwritten)
					_config.delimiter = Papa.DefaultDelimiter;
				}
				_results.meta.delimiter = _config.delimiter;
			}

			var parserConfig = copy(_config);
			if (_config.preview && _config.header)
				parserConfig.preview++;	// to compensate for header row

			_input = input;
			_parser = new Parser(parserConfig);
			_results = _parser.parse(_input, baseIndex, ignoreLastRow);
			processResults();
			return _paused ? { meta: { paused: true } } : (_results || { meta: { paused: false } });
		};

		this.paused = function()
		{
			return _paused;
		};

		this.pause = function()
		{
			_paused = true;
			_parser.abort();
			_input = _input.substr(_parser.getCharIndex());
		};

		this.resume = function()
		{
			_paused = false;
			self.streamer.parseChunk(_input);
		};

		this.aborted = function () 
		{
			return _aborted;
		};

		this.abort = function()
		{
			_aborted = true;
			_parser.abort();
			_results.meta.aborted = true;
			if (isFunction(_config.complete))
				_config.complete(_results);
			_input = '';
		};

		function processResults()
		{
			if (_results && _delimiterError)
			{
				addError('Delimiter', 'UndetectableDelimiter', 'Unable to auto-detect delimiting character; defaulted to \''+Papa.DefaultDelimiter+'\'');
				_delimiterError = false;
			}

			if (_config.skipEmptyLines)
			{
				for (var i = 0; i < _results.data.length; i++)
					if (_results.data[i].length === 1 && _results.data[i][0] === '')
						_results.data.splice(i--, 1);
			}

			if (needsHeaderRow())
				fillHeaderFields();

			return applyHeaderAndDynamicTyping();
		}

		function needsHeaderRow()
		{
			return _config.header && _fields.length === 0;
		}

		function fillHeaderFields()
		{
			if (!_results)
				return;
			for (var i = 0; needsHeaderRow() && i < _results.data.length; i++)
				for (var j = 0; j < _results.data[i].length; j++)
					_fields.push(_results.data[i][j]);
			_results.data.splice(0, 1);
		}

		function applyHeaderAndDynamicTyping()
		{
			if (!_results || (!_config.header && !_config.dynamicTyping))
				return _results;

			for (var i = 0; i < _results.data.length; i++)
			{
				var row = {};

				for (var j = 0; j < _results.data[i].length; j++)
				{
					if (_config.dynamicTyping)
					{
						var value = _results.data[i][j];
						if (value === 'true' || value === 'TRUE')
							_results.data[i][j] = true;
						else if (value === 'false' || value === 'FALSE')
							_results.data[i][j] = false;
						else
							_results.data[i][j] = tryParseFloat(value);
					}

					if (_config.header)
					{
						if (j >= _fields.length)
						{
							if (!row['__parsed_extra'])
								row['__parsed_extra'] = [];
							row['__parsed_extra'].push(_results.data[i][j]);
						}
						else
							row[_fields[j]] = _results.data[i][j];
					}
				}

				if (_config.header)
				{
					_results.data[i] = row;
					if (j > _fields.length)
						addError('FieldMismatch', 'TooManyFields', 'Too many fields: expected ' + _fields.length + ' fields but parsed ' + j, i);
					else if (j < _fields.length)
						addError('FieldMismatch', 'TooFewFields', 'Too few fields: expected ' + _fields.length + ' fields but parsed ' + j, i);
				}
			}

			if (_config.header && _results.meta)
				_results.meta.fields = _fields;
			return _results;
		}

		function guessDelimiter(input, newline)
		{
			var delimChoices = [',', '\t', '|', ';', Papa.RECORD_SEP, Papa.UNIT_SEP];
			var bestDelim, bestDelta, fieldCountPrevRow;

			for (var i = 0; i < delimChoices.length; i++)
			{
				var delim = delimChoices[i];
				var delta = 0, avgFieldCount = 0;
				fieldCountPrevRow = undefined;

				var preview = new Parser({
					delimiter: delim,
					newline: newline,
					preview: 10
				}).parse(input);

				for (var j = 0; j < preview.data.length; j++)
				{
					var fieldCount = preview.data[j].length;
					avgFieldCount += fieldCount;

					if (typeof fieldCountPrevRow === 'undefined')
					{
						fieldCountPrevRow = fieldCount;
						continue;
					}
					else if (fieldCount > 1)
					{
						delta += Math.abs(fieldCount - fieldCountPrevRow);
						fieldCountPrevRow = fieldCount;
					}
				}

				if (preview.data.length > 0)
					avgFieldCount /= preview.data.length;

				if ((typeof bestDelta === 'undefined' || delta < bestDelta)
					&& avgFieldCount > 1.99)
				{
					bestDelta = delta;
					bestDelim = delim;
				}
			}

			_config.delimiter = bestDelim;

			return {
				successful: !!bestDelim,
				bestDelimiter: bestDelim
			}
		}

		function guessLineEndings(input)
		{
			input = input.substr(0, 1024*1024);	// max length 1 MB

			var r = input.split('\r');

			var n = input.split('\n');

			var nAppearsFirst = (n.length > 1 && n[0].length < r[0].length);

			if (r.length === 1 || nAppearsFirst)
				return '\n';

			var numWithN = 0;
			for (var i = 0; i < r.length; i++)
			{
				if (r[i][0] === '\n')
					numWithN++;
			}

			return numWithN >= r.length / 2 ? '\r\n' : '\r';
		}

		function tryParseFloat(val)
		{
			var isNumber = FLOAT.test(val);
			return isNumber ? parseFloat(val) : val;
		}

		function addError(type, code, msg, row)
		{
			_results.errors.push({
				type: type,
				code: code,
				message: msg,
				row: row
			});
		}
	}





	/** The core parser implements speedy and correct CSV parsing */
	function Parser(config)
	{
		// Unpack the config object
		config = config || {};
		var delim = config.delimiter;
		var newline = config.newline;
		var comments = config.comments;
		var step = config.step;
		var preview = config.preview;
		var fastMode = config.fastMode;

		// Delimiter must be valid
		if (typeof delim !== 'string'
			|| Papa.BAD_DELIMITERS.indexOf(delim) > -1)
			delim = ',';

		// Comment character must be valid
		if (comments === delim)
			throw 'Comment character same as delimiter';
		else if (comments === true)
			comments = '#';
		else if (typeof comments !== 'string'
			|| Papa.BAD_DELIMITERS.indexOf(comments) > -1)
			comments = false;

		// Newline must be valid: \r, \n, or \r\n
		if (newline != '\n' && newline != '\r' && newline != '\r\n')
			newline = '\n';

		// We're gonna need these at the Parser scope
		var cursor = 0;
		var aborted = false;

		this.parse = function(input, baseIndex, ignoreLastRow)
		{
			// For some reason, in Chrome, this speeds things up (!?)
			if (typeof input !== 'string')
				throw 'Input must be a string';

			// We don't need to compute some of these every time parse() is called,
			// but having them in a more local scope seems to perform better
			var inputLen = input.length,
				delimLen = delim.length,
				newlineLen = newline.length,
				commentsLen = comments.length;
			var stepIsFunction = typeof step === 'function';

			// Establish starting state
			cursor = 0;
			var data = [], errors = [], row = [], lastCursor = 0;

			if (!input)
				return returnable();

			if (fastMode || (fastMode !== false && input.indexOf('"') === -1))
			{
				var rows = input.split(newline);
				for (var i = 0; i < rows.length; i++)
				{
					var row = rows[i];
					cursor += row.length;
					if (i !== rows.length - 1)
						cursor += newline.length;
					else if (ignoreLastRow)
						return returnable();
					if (comments && row.substr(0, commentsLen) === comments)
						continue;
					if (stepIsFunction)
					{
						data = [];
						pushRow(row.split(delim));
						doStep();
						if (aborted)
							return returnable();
					}
					else
						pushRow(row.split(delim));
					if (preview && i >= preview)
					{
						data = data.slice(0, preview);
						return returnable(true);
					}
				}
				return returnable();
			}

			var nextDelim = input.indexOf(delim, cursor);
			var nextNewline = input.indexOf(newline, cursor);

			// Parser loop
			for (;;)
			{
				// Field has opening quote
				if (input[cursor] === '"')
				{
					// Start our search for the closing quote where the cursor is
					var quoteSearch = cursor;

					// Skip the opening quote
					cursor++;

					for (;;)
					{
						// Find closing quote
						var quoteSearch = input.indexOf('"', quoteSearch+1);

						if (quoteSearch === -1)
						{
							if (!ignoreLastRow) {
								// No closing quote... what a pity
								errors.push({
									type: 'Quotes',
									code: 'MissingQuotes',
									message: 'Quoted field unterminated',
									row: data.length,	// row has yet to be inserted
									index: cursor
								});
							}
							return finish();
						}

						if (quoteSearch === inputLen-1)
						{
							// Closing quote at EOF
							var value = input.substring(cursor, quoteSearch).replace(/""/g, '"');
							return finish(value);
						}

						// If this quote is escaped, it's part of the data; skip it
						if (input[quoteSearch+1] === '"')
						{
							quoteSearch++;
							continue;
						}

						if (input[quoteSearch+1] === delim)
						{
							// Closing quote followed by delimiter
							row.push(input.substring(cursor, quoteSearch).replace(/""/g, '"'));
							cursor = quoteSearch + 1 + delimLen;
							nextDelim = input.indexOf(delim, cursor);
							nextNewline = input.indexOf(newline, cursor);
							break;
						}

						if (input.substr(quoteSearch+1, newlineLen) === newline)
						{
							// Closing quote followed by newline
							row.push(input.substring(cursor, quoteSearch).replace(/""/g, '"'));
							saveRow(quoteSearch + 1 + newlineLen);
							nextDelim = input.indexOf(delim, cursor);	// because we may have skipped the nextDelim in the quoted field

							if (stepIsFunction)
							{
								doStep();
								if (aborted)
									return returnable();
							}

							if (preview && data.length >= preview)
								return returnable(true);

							break;
						}
					}

					continue;
				}

				// Comment found at start of new line
				if (comments && row.length === 0 && input.substr(cursor, commentsLen) === comments)
				{
					if (nextNewline === -1)	// Comment ends at EOF
						return returnable();
					cursor = nextNewline + newlineLen;
					nextNewline = input.indexOf(newline, cursor);
					nextDelim = input.indexOf(delim, cursor);
					continue;
				}

				// Next delimiter comes before next newline, so we've reached end of field
				if (nextDelim !== -1 && (nextDelim < nextNewline || nextNewline === -1))
				{
					row.push(input.substring(cursor, nextDelim));
					cursor = nextDelim + delimLen;
					nextDelim = input.indexOf(delim, cursor);
					continue;
				}

				// End of row
				if (nextNewline !== -1)
				{
					row.push(input.substring(cursor, nextNewline));
					saveRow(nextNewline + newlineLen);

					if (stepIsFunction)
					{
						doStep();
						if (aborted)
							return returnable();
					}

					if (preview && data.length >= preview)
						return returnable(true);

					continue;
				}

				break;
			}


			return finish();


			function pushRow(row)
			{
				data.push(row);
				lastCursor = cursor;
			}

			/**
			 * Appends the remaining input from cursor to the end into
			 * row, saves the row, calls step, and returns the results.
			 */
			function finish(value)
			{
				if (ignoreLastRow)
					return returnable();
				if (typeof value === 'undefined')
					value = input.substr(cursor);
				row.push(value);
				cursor = inputLen;	// important in case parsing is paused
				pushRow(row);
				if (stepIsFunction)
					doStep();
				return returnable();
			}

			/**
			 * Appends the current row to the results. It sets the cursor
			 * to newCursor and finds the nextNewline. The caller should
			 * take care to execute user's step function and check for
			 * preview and end parsing if necessary.
			 */
			function saveRow(newCursor)
			{
				cursor = newCursor;
				pushRow(row);
				row = [];
				nextNewline = input.indexOf(newline, cursor);
			}

			/** Returns an object with the results, errors, and meta. */
			function returnable(stopped)
			{
				return {
					data: data,
					errors: errors,
					meta: {
						delimiter: delim,
						linebreak: newline,
						aborted: aborted,
						truncated: !!stopped,
						cursor: lastCursor + (baseIndex || 0)
					}
				};
			}

			/** Executes the user's step function and resets data & errors. */
			function doStep()
			{
				step(returnable());
				data = [], errors = [];
			}
		};

		/** Sets the abort flag */
		this.abort = function()
		{
			aborted = true;
		};

		/** Gets the cursor position */
		this.getCharIndex = function()
		{
			return cursor;
		};
	}


	// If you need to load Papa Parse asynchronously and you also need worker threads, hard-code
	// the script path here. See: https://github.com/mholt/PapaParse/issues/87#issuecomment-57885358
	function getScriptPath()
	{
		var scripts = document.getElementsByTagName('script');
		return scripts.length ? scripts[scripts.length - 1].src : '';
	}

	function newWorker()
	{
		if (!Papa.WORKERS_SUPPORTED)
			return false;
		if (!LOADED_SYNC && Papa.SCRIPT_PATH === null)
			throw new Error(
				'Script path cannot be determined automatically when Papa Parse is loaded asynchronously. ' +
				'You need to set Papa.SCRIPT_PATH manually.'
			);
		var workerUrl = Papa.SCRIPT_PATH || AUTO_SCRIPT_PATH;
		// Append 'papaworker' to the search string to tell papaparse that this is our worker.
		workerUrl += (workerUrl.indexOf('?') !== -1 ? '&' : '?') + 'papaworker';
		var w = new global.Worker(workerUrl);
		w.onmessage = mainThreadReceivedMessage;
		w.id = workerIdCounter++;
		workers[w.id] = w;
		return w;
	}

	/** Callback when main thread receives a message */
	function mainThreadReceivedMessage(e)
	{
		var msg = e.data;
		var worker = workers[msg.workerId];
		var aborted = false;

		if (msg.error)
			worker.userError(msg.error, msg.file);
		else if (msg.results && msg.results.data)
		{
			var abort = function() {
				aborted = true;
				completeWorker(msg.workerId, { data: [], errors: [], meta: { aborted: true } });
			};

			var handle = {
				abort: abort,
				pause: notImplemented,
				resume: notImplemented
			};

			if (isFunction(worker.userStep))
			{
				for (var i = 0; i < msg.results.data.length; i++)
				{
					worker.userStep({
						data: [msg.results.data[i]],
						errors: msg.results.errors,
						meta: msg.results.meta
					}, handle);
					if (aborted)
						break;
				}
				delete msg.results;	// free memory ASAP
			}
			else if (isFunction(worker.userChunk))
			{
				worker.userChunk(msg.results, handle, msg.file);
				delete msg.results;
			}
		}

		if (msg.finished && !aborted)
			completeWorker(msg.workerId, msg.results);
	}

	function completeWorker(workerId, results) {
		var worker = workers[workerId];
		if (isFunction(worker.userComplete))
			worker.userComplete(results);
		worker.terminate();
		delete workers[workerId];
	}

	function notImplemented() {
		throw 'Not implemented.';
	}

	/** Callback when worker thread receives a message */
	function workerThreadReceivedMessage(e)
	{
		var msg = e.data;

		if (typeof Papa.WORKER_ID === 'undefined' && msg)
			Papa.WORKER_ID = msg.workerId;

		if (typeof msg.input === 'string')
		{
			global.postMessage({
				workerId: Papa.WORKER_ID,
				results: Papa.parse(msg.input, msg.config),
				finished: true
			});
		}
		else if ((global.File && msg.input instanceof File) || msg.input instanceof Object)	// thank you, Safari (see issue #106)
		{
			var results = Papa.parse(msg.input, msg.config);
			if (results)
				global.postMessage({
					workerId: Papa.WORKER_ID,
					results: results,
					finished: true
				});
		}
	}

	/** Makes a deep copy of an array or object (mostly) */
	function copy(obj)
	{
		if (typeof obj !== 'object')
			return obj;
		var cpy = obj instanceof Array ? [] : {};
		for (var key in obj)
			cpy[key] = copy(obj[key]);
		return cpy;
	}

	function bindFunction(f, self)
	{
		return function() { f.apply(self, arguments); };
	}

	function isFunction(func)
	{
		return typeof func === 'function';
	}
})(typeof window !== 'undefined' ? window : this);
};
BundleModuleCode['numerics/numerics']=function (module,exports){
var current=none;
var Aios=none;

var options = {
  version: '1.1.1'
}

module.exports = {
  current:function (module) { current=module.current; Aios=module; },
  agent: {
    fft:Require('numerics/fft'),
    matrix:Require('numerics/matrix'),
    vector:Require('numerics/vector'),
  }
}
};
BundleModuleCode['numerics/fft']=function (module,exports){
/*===========================================================================*\
 * Fast Fourier Transform (Cooley-Tukey Method)
 *
 * (c) Vail Systems. Joshua Jung and Ben Bryan. 2015
 *
 * This code is not designed to be highly optimized but as an educational
 * tool to understand the Fast Fourier Transform.
 *
 * Can be used with Vectors (typed arrays), Arrays
 *
 *
\*===========================================================================*/
var Vector = Require('numerics/vector');
var Matrix = Require('numerics/matrix');

//------------------------------------------------
// Note: Some of this code is not optimized and is
// primarily designed as an educational and testing
// tool.
// To get high performace would require transforming
// the recursive calls into a loop and then loop
// unrolling. All of this is best accomplished
// in C or assembly.
//-------------------------------------------------

//-------------------------------------------------
// The following code assumes a complex number is
// an array: [real, imaginary]
//-------------------------------------------------
var complex = {
  //-------------------------------------------------
  // Add two complex numbers
  //-------------------------------------------------
  add : function (a, b)
  {
      return [a[0] + b[0], a[1] + b[1]];
  },

  //-------------------------------------------------
  // Subtract two complex numbers
  //-------------------------------------------------
  subtract : function (a, b)
  {
      return [a[0] - b[0], a[1] - b[1]];
  },

  //-------------------------------------------------
  // Multiply two complex numbers
  //
  // (a + bi) * (c + di) = (ac - bd) + (ad + bc)i
  //-------------------------------------------------
  multiply : function (a, b) 
  {
      return [(a[0] * b[0] - a[1] * b[1]), 
              (a[0] * b[1] + a[1] * b[0])];
  },

  //-------------------------------------------------
  // Calculate |a + bi|
  //
  // sqrt(a*a + b*b)
  //-------------------------------------------------
  magnitude : function (c) 
  {
      return Math.sqrt(c[0]*c[0] + c[1]*c[1]); 
  },
  
  phase : function (c) 
  {
      return c[0]!=0?Math.atan(c[1]/c[0])*180/Math.PI:(c[1]>0?90:-90); 
  }

}
var mapExponent = {};
var fftUtil = {
  //-------------------------------------------------
  // By Eulers Formula:
  //
  // e^(i*x) = cos(x) + i*sin(x)
  //
  // and in DFT:
  //
  // x = -2*PI*(k/N)
  //-------------------------------------------------
  exponent : function (k, N) {
        var x = -2 * Math.PI * (k / N);

        mapExponent[N] = mapExponent[N] || {};
        mapExponent[N][k] = mapExponent[N][k] || [Math.cos(x), Math.sin(x)];// [Real, Imaginary]

        return mapExponent[N][k];
  },

  //-------------------------------------------------
  // Calculate FFT Magnitude for complex numbers.
  //-------------------------------------------------
  fftMag : function (fftBins) {
    if (isArray(fftBins)) {
      var ret = fftBins.map(complex.magnitude);
      return ret.slice(0, ret.length / 2);
    } else if (isVector(fftBins) || isMatrix(fftBins)) {
      // complex matrix (2 columns)
      if (fftBins.columns != 2) throw "fft.fftMag: Complex matrix columns != 2";
      var ret = Vector(fftBins.rows,{dtn:fftBins.dtn})
      return ret.eval(function (v,i) { 
       return complex.magnitude([fftBins.get(i,0),fftBins.get(i,1)]) }); 
    }
  },
  fftPha : function (fftBins) {
    if (isArray(fftBins)) {
      var ret = fftBins.map(complex.phase);
      return ret.slice(0, ret.length / 2);
    } else if (isVector(fftBins) || isMatrix(fftBins)) {
      // complex matrix (2 columns)
      if (fftBins.columns != 2) throw "fft.fftMag: Complex matrix columns != 2";
      var ret = Vector(fftBins.rows,{dtn:fftBins.dtn})
      return ret.eval(function (v,i) { 
       return complex.phase([fftBins.get(i,0),fftBins.get(i,1)]) }); 
    }
  },

  //-------------------------------------------------
  // Calculate Frequency Bins
  // 
  // Returns an array of the frequencies (in hertz) of
  // each FFT bin provided, assuming the sampleRate is
  // samples taken per second.
  //-------------------------------------------------
  fftFreq : function (fftBins, sampleRate) {
      var stepFreq = sampleRate / (fftBins.length);
      var ret = fftBins.slice(0, fftBins.length / 2);

      return ret.map(function (__, ix) {
          return ix * stepFreq;
      });
  }
}
    
// Bit-twiddle
var REVERSE_TABLE = new Array(256);
var INT_BITS = 32;  //Number of bits in an integer

(function(tab) {
  for(var i=0; i<256; ++i) {
    var v = i, r = i, s = 7;
    for (v >>>= 1; v; v >>>= 1) {
      r <<= 1;
      r |= v & 1;
      --s;
    }
    tab[i] = (r << s) & 0xff;
  }
})(REVERSE_TABLE);

var twiddle = {

  //Constants
  INT_BITS  : INT_BITS,
  INT_MAX   :  0x7fffffff,
  INT_MIN   : -1<<(INT_BITS-1),

  //Returns -1, 0, +1 depending on sign of x
  sign : function(v) {
    return (v > 0) - (v < 0);
  },

  //Computes absolute value of integer
  abs : function(v) {
    var mask = v >> (INT_BITS-1);
    return (v ^ mask) - mask;
  },

  //Computes minimum of integers x and y
  min : function(x, y) {
    return y ^ ((x ^ y) & -(x < y));
  },

  //Computes maximum of integers x and y
  max : function(x, y) {
    return x ^ ((x ^ y) & -(x < y));
  },

  //Checks if a number is a power of two
  isPow2 : function(v) {
    return !(v & (v-1)) && (!!v);
  },

  //Computes log base 2 of v
  log2 : function(v) {
    var r, shift;
    r =     (v > 0xFFFF) << 4; v >>>= r;
    shift = (v > 0xFF  ) << 3; v >>>= shift; r |= shift;
    shift = (v > 0xF   ) << 2; v >>>= shift; r |= shift;
    shift = (v > 0x3   ) << 1; v >>>= shift; r |= shift;
    return r | (v >> 1);
  },

  //Computes log base 10 of v
  log10 : function(v) {
    return  (v >= 1000000000) ? 9 : (v >= 100000000) ? 8 : (v >= 10000000) ? 7 :
            (v >= 1000000) ? 6 : (v >= 100000) ? 5 : (v >= 10000) ? 4 :
            (v >= 1000) ? 3 : (v >= 100) ? 2 : (v >= 10) ? 1 : 0;
  },

  //Counts number of bits
  popCount : function(v) {
    v = v - ((v >>> 1) & 0x55555555);
    v = (v & 0x33333333) + ((v >>> 2) & 0x33333333);
    return ((v + (v >>> 4) & 0xF0F0F0F) * 0x1010101) >>> 24;
  },

  //Counts number of trailing zeros
  countTrailingZeros : function (v) {
    var c = 32;
    v &= -v;
    if (v) c--;
    if (v & 0x0000FFFF) c -= 16;
    if (v & 0x00FF00FF) c -= 8;
    if (v & 0x0F0F0F0F) c -= 4;
    if (v & 0x33333333) c -= 2;
    if (v & 0x55555555) c -= 1;
    return c;
  },

  //Rounds to next power of 2
  nextPow2 : function(v) {
    v += v === 0;
    --v;
    v |= v >>> 1;
    v |= v >>> 2;
    v |= v >>> 4;
    v |= v >>> 8;
    v |= v >>> 16;
    return v + 1;
  },

  //Rounds down to previous power of 2
  prevPow2 : function(v) {
    v |= v >>> 1;
    v |= v >>> 2;
    v |= v >>> 4;
    v |= v >>> 8;
    v |= v >>> 16;
    return v - (v>>>1);
  },

  //Computes parity of word
  parity : function(v) {
    v ^= v >>> 16;
    v ^= v >>> 8;
    v ^= v >>> 4;
    v &= 0xf;
    return (0x6996 >>> v) & 1;
  },


  //Reverse bits in a 32 bit word
  reverse : function(v) {
    return  (REVERSE_TABLE[ v         & 0xff] << 24) |
            (REVERSE_TABLE[(v >>> 8)  & 0xff] << 16) |
            (REVERSE_TABLE[(v >>> 16) & 0xff] << 8)  |
             REVERSE_TABLE[(v >>> 24) & 0xff];
  },

  //Interleave bits of 2 coordinates with 16 bits.  Useful for fast quadtree codes
  interleave2 : function(x, y) {
    x &= 0xFFFF;
    x = (x | (x << 8)) & 0x00FF00FF;
    x = (x | (x << 4)) & 0x0F0F0F0F;
    x = (x | (x << 2)) & 0x33333333;
    x = (x | (x << 1)) & 0x55555555;

    y &= 0xFFFF;
    y = (y | (y << 8)) & 0x00FF00FF;
    y = (y | (y << 4)) & 0x0F0F0F0F;
    y = (y | (y << 2)) & 0x33333333;
    y = (y | (y << 1)) & 0x55555555;

    return x | (y << 1);
  },

  //Extracts the nth interleaved component
  deinterleave2 : function(v, n) {
    v = (v >>> n) & 0x55555555;
    v = (v | (v >>> 1))  & 0x33333333;
    v = (v | (v >>> 2))  & 0x0F0F0F0F;
    v = (v | (v >>> 4))  & 0x00FF00FF;
    v = (v | (v >>> 16)) & 0x000FFFF;
    return (v << 16) >> 16;
  },


  //Interleave bits of 3 coordinates, each with 10 bits.  Useful for fast octree codes
  interleave3 : function(x, y, z) {
    x &= 0x3FF;
    x  = (x | (x<<16)) & 4278190335;
    x  = (x | (x<<8))  & 251719695;
    x  = (x | (x<<4))  & 3272356035;
    x  = (x | (x<<2))  & 1227133513;

    y &= 0x3FF;
    y  = (y | (y<<16)) & 4278190335;
    y  = (y | (y<<8))  & 251719695;
    y  = (y | (y<<4))  & 3272356035;
    y  = (y | (y<<2))  & 1227133513;
    x |= (y << 1);

    z &= 0x3FF;
    z  = (z | (z<<16)) & 4278190335;
    z  = (z | (z<<8))  & 251719695;
    z  = (z | (z<<4))  & 3272356035;
    z  = (z | (z<<2))  & 1227133513;

    return x | (z << 2);
  },

  //Extracts nth interleaved component of a 3-tuple
  deinterleave3 : function(v, n) {
    v = (v >>> n)       & 1227133513;
    v = (v | (v>>>2))   & 3272356035;
    v = (v | (v>>>4))   & 251719695;
    v = (v | (v>>>8))   & 4278190335;
    v = (v | (v>>>16))  & 0x3FF;
    return (v<<22)>>22;
  },

  //Computes next combination in colexicographic order (this is mistakenly called nextPermutation on the bit twiddling hacks page)
  nextCombination : function(v) {
    var t = v | (v - 1);
    return (t + 1) | (((~t & -~t) - 1) >>> (twiddle.countTrailingZeros(v) + 1));
  }

}

function checkpow2(info,vector) {
  if (Math.floor(Math.log2(vector.length)) != Math.log2(vector.length))
    throw ('fft.'+info+' error: vector length must be a power of 2')
}

module.exports = {
  //-------------------------------------------------
  // Calculate FFT for vector where vector.length
  // is assumed to be a power of 2.
  //-------------------------------------------------
  fft: function fft(vector) {
    if (vector.data) vector=vector.data; // Matrix|Vector
    
    checkpow2('fft',vector);
    
    var X = [],
        N = vector.length;

    // Base case is X = x + 0i since our input is assumed to be real only.
    if (N == 1) {
      if (Array.isArray(vector[0])) //If input vector contains complex numbers
        return [[vector[0][0], vector[0][1]]];      
      else
        return [[vector[0], 0]];
    }

    // Recurse: all even samples
    var X_evens = fft(vector.filter(even)),

        // Recurse: all odd samples
        X_odds  = fft(vector.filter(odd));

    // Now, perform N/2 operations!
    for (var k = 0; k < N / 2; k++) {
      // t is a complex number!
      var t = X_evens[k],
          e = complex.multiply(fftUtil.exponent(k, N), X_odds[k]);

      X[k] = complex.add(t, e);
      X[k + (N / 2)] = complex.subtract(t, e);
    }

    function even(__, ix) {
      return ix % 2 == 0;
    }

    function odd(__, ix) {
      return ix % 2 == 1;
    }

    return X;
  },
  //-------------------------------------------------
  // Calculate FFT for vector where vector.length
  // is assumed to be a power of 2.  This is the in-
  // place implementation, to avoid the memory
  // footprint used by recursion.
  //-------------------------------------------------
  fftInPlace: function(vector) {
    if (vector.data) vector=vector.data; // Matrix|Vector
    checkpow2('fftInPlace',vector);
    
    var N = vector.length;

    var trailingZeros = twiddle.countTrailingZeros(N); //Once reversed, this will be leading zeros

    // Reverse bits
    for (var k = 0; k < N; k++) {
      var p = twiddle.reverse(k) >>> (twiddle.INT_BITS - trailingZeros);
      if (p > k) {
        var complexTemp = [vector[k], 0];
        vector[k] = vector[p];
        vector[p] = complexTemp;
      } else {
        vector[p] = [vector[p], 0];
      }
    }

    //Do the DIT now in-place
    for (var len = 2; len <= N; len += len) {
      for (var i = 0; i < len / 2; i++) {
        var w = fftUtil.exponent(i, len);
        for (var j = 0; j < N / len; j++) {
          var t = complex.multiply(w, vector[j * len + i + len / 2]);
          vector[j * len + i + len / 2] = complex.subtract(vector[j * len + i], t);
          vector[j * len + i] = complex.add(vector[j * len + i], t);
        }
      }
    }
  },
  ifft: function ifft(signal){
    if (signal.data) signal=signal.data; // Matrix
    checkpow2('ifft',signal);
    //Interchange real and imaginary parts
    var csignal=[];
    for(var i=0; i<signal.length; i++){
        csignal[i]=[signal[i][1], signal[i][0]];
    }

    //Apply fft
    var ps=module.exports.fft(csignal);

    //Interchange real and imaginary parts and normalize
    var res=[];
    for(var j=0; j<ps.length; j++){
        res[j]=[ps[j][1]/ps.length, ps[j][0]/ps.length];
    }
    return res;
  },
  fftMag: fftUtil.fftMag,
  fftPha: fftUtil.fftPha,
  fftFreq: fftUtil.fftFreq,
};
};
BundleModuleCode['numerics/vector']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.sblab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2006-2019 bLAB
 **    $CREATED:     1-1-19 by sbosse.
 **    $VERSION:     1.4.4
 **
 **    $INFO:
 ** 
 **   Vector module supporting typed and generic arrays.
 **   
 ** 
 **    $ENDOFINFO
 */

Require('numerics/polyfill')
var sprintf = Require('com/sprintf');

/********** TYPEDARRY/ARRAY Extension for Matrix/Vector compatibility *************/

if (typeof Array.prototype.get == 'undefined') {
  Object.defineProperty(Array.prototype, 'get', {value:function (i) {
    return this[i];
  }, configurable: true})

  Object.defineProperty(Array.prototype, 'set', {value: function (a,b) {
    this[a]=b;
  }, configurable: true})
}

  
if (typeof Array.prototype.print == 'undefined') {
  Object.defineProperty(Array.prototype, 'print', {value: function (format) {
    var i,s='',sep='', columns=this.length,complex=isArray(this[0]);
    if (!format) format = '%4.2f';
    for(i=0;i<columns;i++) {
      if (i!=0) s = s + '\n';
      if (complex) 
        s = s + sprintf.sprintf(format,this[i][0]) + ',' +
                sprintf.sprintf(format,this[i][1]);
      else
        s = s + sprintf.sprintf(format,this[i]) ;
    }    
    return s;
  }, configurable: true})
}


if (typeof Array.prototype.info == 'undefined') {
  Object.defineProperty(Array.prototype, 'info', {value: function () {
    return {
      dtn:'Array',
      size:this.length,
      columns:this.length,
      offset:0,
    }
  }, configurable: true})
}

/********************* STRING Conversion ******************************/
function toUTF8Array(str) {
    var utf8 = [];
    for (var i=0; i < str.length; i++) {
        var charcode = str.charCodeAt(i);
        if (charcode < 0x80) utf8.push(charcode);
        else if (charcode < 0x800) {
            utf8.push(0xc0 | (charcode >> 6), 
                      0x80 | (charcode & 0x3f));
        }
        else if (charcode < 0xd800 || charcode >= 0xe000) {
            utf8.push(0xe0 | (charcode >> 12), 
                      0x80 | ((charcode>>6) & 0x3f), 
                      0x80 | (charcode & 0x3f));
        }
        // surrogate pair
        else {
            i++;
            charcode = ((charcode&0x3ff)<<10)|(str.charCodeAt(i)&0x3ff)
            utf8.push(0xf0 | (charcode >>18), 
                      0x80 | ((charcode>>12) & 0x3f), 
                      0x80 | ((charcode>>6) & 0x3f), 
                      0x80 | (charcode & 0x3f));
        }
    }
    return utf8;
}

function fromUTF8Array(data) { // array of bytes
    var str = '', i;
    for (i = 0; i < data.length; i++) {
        var value = data[i];
        if (value < 0x80) {
            str += String.fromCharCode(value);
        } else if (value > 0xBF && value < 0xE0) {
            str += String.fromCharCode((value & 0x1F) << 6 | data[i + 1] & 0x3F);
            i += 1;
        } else if (value > 0xDF && value < 0xF0) {
            str += String.fromCharCode((value & 0x0F) << 12 | (data[i + 1] & 0x3F) << 6 | data[i + 2] & 0x3F);
            i += 2;
        } else {
            // surrogate pair
            var charCode = ((value & 0x07) << 18 | (data[i + 1] & 0x3F) << 12 | (data[i + 2] & 0x3F) << 6 | data[i + 3] & 0x3F) - 0x010000;

            str += String.fromCharCode(charCode >> 10 | 0xD800, charCode & 0x03FF | 0xDC00); 
            i += 3;
        }
    }
    return str;
}

var complex = {
  //-------------------------------------------------
  // Add two complex numbers
  //-------------------------------------------------
  add : function (a, b)
  {
      return [a[0] + b[0], a[1] + b[1]];
  },

  //-------------------------------------------------
  // Subtract two complex numbers
  //-------------------------------------------------
  subtract : function (a, b)
  {
      return [a[0] - b[0], a[1] - b[1]];
  },

  //-------------------------------------------------
  // Multiply two complex numbers
  //
  // (a + bi) * (c + di) = (ac - bd) + (ad + bc)i
  //-------------------------------------------------
  multiply : function (a, b) 
  {
      return [(a[0] * b[0] - a[1] * b[1]), 
              (a[0] * b[1] + a[1] * b[0])];
  },

  //-------------------------------------------------
  // Calculate |a + bi|
  //
  // sqrt(a*a + b*b)
  //-------------------------------------------------
  magnitude : function (offset,c) 
  {
      return Math.sqrt(c[offset]*c[offset] + c[offset+1]*c[offset+1]); 
  },
  
  phase : function (offset,c) 
  {
      return c[offset]!=0?Math.atan(c[offset+1]/c[offset])*180/Math.PI:(c[offset+1]>0?90:-90); 
  }

}

/*********** VECTOR ************/
function Vector(a,b) {
  var self = this;
  var i,columns,size,offset=0,dim=1,dtn,dt=Vector.options.dt,data;
  
  if (!(this instanceof Vector)) return new Vector(a,b);
  var options=isObject(b)?b:{};
  
  if (isNumber(a)) {
    // Create a new empty vector (rows=1)
    columns=a;
    if (options.type) dt=options.type;
    if (options.dtn)  dt=options.dtn=='Array'?Array:TypedArrayOfName[options.dtn];
    size=columns;
    if (options.complex) size *=2;
    if (options.dtn && !dt) throw ("Vector: Unknown array type dtn="+options.dtn)
    data=new dt(size);
  } else if (isArray(a)) {
    size=columns=a.length;
    if (options.type) dt=options.type;
    if (options.dtn)  dt=options.dtn=='Array'?Array:TypedArrayOfName[options.dtn];
    if (options.dtn && !dt) throw ("Vector: Unknown array type dtn="+options.dtn)
    if (options.dtn && options.dtn != 'Array') {
      // Create typedarray from generic array
      data=new dt(a);
    } else {
      // Matrix wrapper for generic arrays and array arrays
      // modify .get .set .getRow  prototype ...
      // no _Matrix.call
      dt=Array;
      data=a;
    }
  } else if (isObject(a)) {
    // partial object
    columns=a.columns;
    size=a.size||columns;
    scale=options.scale;
    if (options.dtn)  dt=options.dtn=='Array'?Array:TypedArrayOfName[options.dtn];
    if (options.dtn && !dt) throw ("Vector: Unknown array type dtn="+options.dtn)
    if (options.dtn && a.dtn != options.dtn) {
      // convert dtn
      if (isArray(a.data) && !scale)
        data=new dt(a.data);
      else {
        data=new dt(size);
        if (scale)  for(i=0;i<size;i++) data[i]=a.data[i]/scale;
        else        for(i=0;i<size;i++) data[i]=a.data[i];
      }
      dtn=options.dtn;
    } else {
      dtn=a.dtn;
      data=a.data;
      offset=a.offset;
    }
    if (a.scale) this.scale=a.scale;
    if (a.complex) this.complex=a.complex;
  } else if (isString(a)) {
    columns=a.length;
    if (options.type) dt=options.type;
    if (options.dtn)  dt=options.dtn=='Array'?Array:TypedArrayOfName[options.dtn];
    if (options.dtn && !dt) throw ("Vector: Unknown array type dtn="+options.dtn)
    data=new dt(toUTF8Array(a));
  }
  
  this.columns=columns;
  this.size=this.length=size;
  this.layout=1;
  this.data=data;
  this.dims=dim;
  this.offset=offset;
  if (options.complex) this.complex=true;
  if (options.scale)   this.scaler=options.scale;
  
  this.dtn=dtn||dt.name;
  
  if (this.dtn=='Array') this._arrayFix();
}
Vector.options = {
  dt : Float32Array,
  dtn : 'Float32Array'
}

/********* STATIC MEMBERS *********/
Vector.abs = function (m) {
  return Vector.clone(m).abs();
}

Vector.add = function (m,v) {
  return Vector.clone(m).add(v);
}

Vector.clone = function (src) {
  return Vector(src);
}

Vector.checkVector = function (o) {
  if (o instanceof Vector) return o;
  else return Vector(o);
}

Vector.cos = function (m) {
  return Vector.clone(m).cos();
}

Vector.div = function (m,v) {
  return Vector.clone(m).div(v);
}

Vector.empty = function (columns) {
  return Vector(columns);
}

Vector.exp = function (m) {
  return Vector.clone(m).exp();
}

isVector = Vector.isVector = function (o) {
  return (o instanceof Vector)
}

Vector.max =  function(vector1, vector2) {
  vector1 = Vector.checkVector(vector1);
  vector2 = Vector.checkVector(vector2);
  var columns =vector1.columns;
  var result = Vector(columns,{dtn:vector1.dtn});
  for (var i = 0; i< columns; i++) {
    result.data[i]= Math.max(vector1.data[i], vector2.data[i]);
  }
  return result;
}

Vector.min =  function(vector1, vector2) {
  vector1 = Vector.checkVector(vector1);
  vector2 = Vector.checkVector(vector2);
  var columns =vector1.columns;
  var result = Vector(columns,{dtn:vector1.dtn});
  for (var i = 0; i< columns; i++) {
    result.data[i]=Math.min(vector1.data[i], vector2.data[i]);
  }
  return result;
}

Vector.mod = function (m,v) {
  return Vector.clone(m).mod(v);
}

Vector.mul = function (m,v) {
  return Vector.clone(m).mul(v);
}

Vector.neg = function (m) {
  return Vector.clone(m).neg();
}

Vector.ones = function (columns) {
  return Vector(columns).fill(1);
}

Vector.rand = function (columns, rng) {
  if (rng==undefined) rng=Math.random;
  return Vector(columns).fill(function () {
    return rng();
  });
}

Vector.randInt = function (columns, maxValue, rng) {
  if (rng==undefined) rng=Math.random;
  return Vector(columns).fill(function () {
    return (rng()*maxValue)|0;
  });
}

Vector.sin = function (m) {
  return Vector.clone(m).sin();
}

Vector.sub = function (m,v) {
  return Vector.clone(m).sub(v);
}

Vector.zero = function (columns) {
  return Vector(columns).fill(0);
}



/********* INSTANCE MEMBERS *********/
// Fix some prototype methods for generic array data content
Vector.prototype._arrayFix = function () {
  var self=this;
  this.get=function (column)   { return self.data[self.offset+column] };
  this.set=function (column,v) { self.data[self.offset+column]=v };
}

Vector.prototype.abs = function (v) {
  var i,j;
  for(i=0;i<this.size;i++) this.data[i]=Math.abs(this.data[i]);
  return this; 
}

Vector.prototype.add = function (v) {
  var i,j;
  for(i=0;i<this.size;i++) this.data[i]+=v;
  return this; 
}

Vector.prototype.apply = function (f) {
  for(var i=0; i < this.columns; i++) 
    f.call(this,i)
}

Vector.prototype.clone = function () {
  return Vector(this);
}

Vector.prototype.cos = function (v) {
  var i,j;
  for(i=0;i<this.size;i++) this.data[i]=Math.cos(this.data[i]);
  return this; 
}

Vector.prototype.div = function (v) {
  var i,j;
  for(i=0;i<this.size;i++) this.data[i]/=v;
  return this; 
}

Vector.prototype.divide = function (column,k) {
  return this.data[column] /= k;
}

// Evaluate all elements x of matrix by applying function f(x)
Vector.prototype.eval = function (f) {
  var i;
  switch (this.dtn) {
    case 'Array':
      for(i=0; i < this.columns; i++) 
        this.set(i,f(this.get(i)))
      break;
    default:
      for(i=0;i<this.size;i++) this.data[i]=f(this.data[i],i);
  }
  return this;
}

Vector.prototype.exp = function (v) {
  var i,j;
  for(i=0;i<this.size;i++) this.data[i]=Math.exp(this.data[i]);
  return this; 
}

Vector.prototype.fill = function (valueOrFunction) {
  if (typeof valueOrFunction == 'function') {
      for(var i=0;i<this.columns;i++) {
        this.data[i]=valueOrFunction(i);
      } 
  } else this.data.fill(valueOrFunction);
  return this;
}

Vector.prototype.filter = function (f,asArray) {
  var i,j=0,res = Vector(this.columns,{dtn:asArray?'Array':this.dtn});
  for(i=0;i<this.columns;i++) {
    v=f(this.data[i],i);
    if (v) res.data[j]=this.data[i],j++;
  }
  return j<this.columns?res.slice(j):res;
}

Vector.prototype.get = function (column) {
  return this.data[this.offset+column];
}

Vector.prototype.imag = function (i) {
  if (this.complex) return this.get(i*2+1);
}

Vector.prototype.incr = function (column,delta) {
  return this.data[column] += delta;
}

Vector.prototype.info = function () {
  var i = {
    dtn:this.dtn,
    size:this.size,
    columns:this.columns,
    offset:this.offset,
  }
  if (this.scaler) i.scaler=this.scaler;
  if (this.complex) i.complex=true;
  return i;
}

isVector = Vector.isVector = function (o) {
  return (o instanceof Vector)
}

Vector.prototype.magnitude = function () {
  var res;
  if (this.complex) {
    res=Vector(this.columns,{dtn:this.dtn});
    for(var i=0; i < res.columns; i++) 
      res.data[i]=complex.magnitude(this.offset+i*2,this.data);
  }
  return res;
}

Vector.prototype.map = function (f,asArray) {
  var res = Vector(this.columns,{dtn:asArray?'Array':this.dtn});
  for(var i=0;j<this.columns;i++)
    res.data[i]=f(this.data[i],i);
  return res;
}

Vector.prototype.multiply = function (column,k) {
  return this.data[column] *= k;
}

Vector.prototype.mean = function (v) {
  return this.sum()/this.size;
}

Vector.prototype.mod = function (v) {
  var i,j;
  for(i=0;i<this.size;i++) this.data[i]=this.data[i]%v;
  return this; 
}

Vector.prototype.mul = function (v) {
  var i,j;
  for(var i=0;i<this.size;i++) this.data[i]*=v;
  return this; 
}

Vector.prototype.neg = function (v) {
  var i,j;
  for(var i=0;i<this.size;i++) this.data[i]=-this.data[i];
  return this; 
}

Vector.prototype.phase = function () {
  var res;
  if (this.complex) {
    res=Vector(this.columns,{dtn:this.dtn});
    for(var i=0; i < res.columns; i++) 
      res.data[i]=complex.phase(this.offset+i*2,this.data);
  }
  return res;
}

Vector.prototype.prod = function (v) {
  var i,j,v = 1;
  for (i = 0; i < this.size; i++) v *= this.data[i];
  return v;
}

Vector.prototype.print = function (format,transpose) {
  var j, s='';
  if (!format) format = '%4.2f';
  if (!this.complex)
    for(j=0;j<this.columns;j++) {
      if (j!=0) s = s + (transpose?' ':'\n');
      s = s + sprintf.sprintf(format,this.data[j]) ;
    }
  else
    for(j=0;j<this.columns;j=j+2) {
      if (j!=0) s = s + (transpose?' ':'\n');
      s = s + '('+sprintf.sprintf(format,this.data[j])+','+sprintf.sprintf(format,this.data[j+1])+')' ;
    }
  
  return s;
}

Vector.prototype.reduce = function (f) {
  return this.data.reduce(f);
}

Vector.prototype.real = function (i) {
  if (this.complex) return this.get(i*2);
}

Vector.prototype.resize = function (options) {
  if ((options.offset && (options.columns+options.offset) > this.columns) ||
      !options.columns) throw new Error('Vecotr.resize: invalid argument(s)');
  this.columns=options.columns;
  if (options.offset) this.offset=options.offset;
  this.size=this.length=this.columns;
  if (options.slice) 
    this.data=options.offset?this.data.slice(options.offset,options.columns+offset):
                             this.data.slice(0,options.columns);
  return this;
}

Vector.prototype.set = function (column,val) {
  this.data[this.offset+column]=val;
  return this;
}

Vector.prototype.sin = function (v) {
  var i,j;
  for(i=0;i<this.size;i++) this.data[i]=Math.sin(this.data[i]);
}

/*
size
Properties
size (number) : The number of elements in the matrix.
*/
Vector.prototype.size = function () {
  return  this.size;
}

/** Return new vecotr with sliced data
 *
 */
Vector.prototype.slice = function (columns,offset) {
  return Vector(this,{columns:columns,offset:offset,slice:true});
}

Vector.prototype.sub = function (v) {
  var i,j;
  for(i=0;i<this.size;i++) this.data[i]-=v;
  return this; 
}

Vector.prototype.subRange = function (columns,offset) {
  offset=checkOption(offset,0);
  var res=Vector({columns:columns,data:this.data.slice(offset,columns+offset),dtn:this.dtn});
  return res;
}

Vector.prototype.sum = function () {
  var sum=0;
  for(var i=0;i<this.size;i++) sum += this.data[i];
  return sum
}
module.exports = Vector;
};
BundleModuleCode['numerics/polyfill']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.sblab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2006-2019 bLAB
 **    $CREATED:     1-1-19 by sbosse.
 **    $VERSION:     1.2.1
 **
 **    $INFO:
 **
 **   Various core poylfills and extensions 
 **
 **    $ENDOFINFO
 */
/* TYPED ARRAY */
var typed_arrays = [
    Int8Array,
    Uint8Array,
    Int16Array,
    Uint16Array,
    Int32Array,
    Uint32Array,
    Float32Array,
    Float64Array
];

TypedArrayOfName = {
    Int8Array:Int8Array,
    Uint8Array:Uint8Array,
    Int16Array:Int16Array,
    Uint16Array:Uint16Array,
    Int32Array:Int32Array,
    Uint32Array:Uint32Array,
    Float32Array:Float32Array,
    Float64Array:Float64Array  
}

typed_arrays.forEach(function (typed_array) {

    if (!typed_array.prototype.slice) typed_array.prototype.slice = function (begin, end) {
        var len = this.length;;
        var size;
        var start = begin || 0;

        start = (start >= 0) ? start : Math.max(0, len + start);
        end = end || len;

        var up_to = (typeof end == 'number') ? Math.min(end, len) : len;
        if (end < 0) up_to = len + end;

        // actual expected size of the slice
        size = up_to - start;

        // if size is negative it should return an empty array
        if (size <= 0) size = 0;

        var typed_array_constructor = this.constructor;
        var cloned = new typed_array_constructor(size);
        for (var i = 0; i < size; i++) {
            cloned[i] = this[start + i];
        }

        return cloned;
    };
    if (!typed_array.prototype.print)  typed_array.prototype.print = function () {
      var s = '[ ', sep='';
      
      for (var i=0;i<this.length;i++) {
        s = s + (i>0?' , ':'') + this[i].toString();
      }
      return s+' ]';
    };
    if (!typed_array.prototype.reduce)  typed_array.prototype.reduce = function (apply) {
      var res=this[0];
      
      for (var i=1;i<this.length;i++) {
        res=apply(res,this[i]);
      }
      return res;
    };
});

isTypedArray = function (o) { 
  for(var t in typed_arrays) if (o instanceof typed_arrays[t]) return true;
  return false;
}
isArray   = function (v)    { return v instanceof Array }
isNumber  = function (v)    { return typeof v == 'number' }
isObject  = function (v)    { return typeof v == 'object' }
isBuffer  = function (v)    { return v instanceof Buffer}
isFunction = function (v)   { return typeof v == 'function' }
isString  = function (v)    { return typeof v == 'string' }
isBoolean = function (v)    { return typeof v == 'boolean' }


// ARRAY polyfills
if (typeof Array.prototype.scale != 'function') 
 Object.defineProperty(Array.prototype, 'scale', {value: function (k,off,inplace) {
  var ar = this;
  if (isBoolean(off)) inplave=off,off=undefined;
  if (off!=undefined) {
    if (inplace) for(var i=0;i< ar.length; i++) ar[i]=(ar[i]-off)*k; 
    else ar=this.map(function (v) { return (v-off)*k });  
  } else {
    if (inplace) for(var i=0;i< ar.length; i++) ar[i]=ar[i]*k; 
    else ar=this.map(function (v) { return v*k });
  }
  return ar;
}, configurable: true})

if (typeof Array.prototype.get == 'undefined') {
  Object.defineProperty(Array.prototype, 'get', {value:function (i) {
    return this[i];
  }, configurable: true})
  Object.defineProperty(Array.prototype, 'set', {value: function (i,v) {
    this[i]=v;
  }, configurable: true})
}

};
BundleModuleCode['numerics/matrix']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.sblab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2006-2019 bLAB
 **    $CREATED:     1-1-19 by sbosse.
 **    $VERSION:     1.3.3
 **
 **    $INFO:
 **
 **  Numerical Matrix Module associated with typed arrays, but with genery array compatibility. 
 **  A matrix provides a wrapper and multi-dimensional array view for one-dimensional byte arrays (typed arrays using buffers).
 **
 **    $ENDOFINFO
 */
 
Require('numerics/polyfill')
var sprintf = Require('com/sprintf');
var Vector = Require('numerics/vector');

var ALL = [], 
    FORALL = '*',
    FOREACH = 'x';
    
isRange   = function (v)  { return isArray(v) && v.length==2 }
isAll     = function (v)  { return v=='*' || (isArray(v) && v.length==0) }
isForEach = function (v)  { return v == FOREACH }
isArrayArray = function (v) { return isArray(v) && isArray(v[0]) }
isArrayArrayArray = function (v) { return isArray(v) && isArray(v[0]) && isArray(v[0][0]) }

integer = function (v)  { return Math.floor(v) }
divide = function (a,b) { return Math.floor(a/b) }


function todo (what) { throw ("Not implemented: Matrix."+what) }
function checkNumber(name, value) {
  if (typeof value !== 'number') {
    throw new TypeError(name+' must be a number');
  }
}
function transpose (layout) {
  switch (layout) {
    case 12: return 21;
    case 21: return 12;
  }
}
/********** TYPEDARRY/ARRAY Extension for Matrix/Vector compatibility *************/

// Most generic versions - always overwrite (polyfill/vector definitions)
  Object.defineProperty(Array.prototype, 'get', {value: function (i,j,k) {
    if (k!=undefined)
     return this[i][j][k];
    else if (j!=undefined)
     return this[i][j];
    else
     return this[i];
  }, configurable: true})

  Object.defineProperty(Array.prototype, 'getRow', {value: function (i) {
   return this[i];
  }, configurable: true})

  Object.defineProperty(Array.prototype, 'mapRow', {value:  function (f) {
   return this[i].map(f);
  }, configurable: true})

  Object.defineProperty(Array.prototype, 'set', {value: function (a,b,c,d) {
    if (d!=undefined)
     return this[a][b][c]=d;
    else if (c!=undefined)
     return this[a][b]=c;
    else
     return this[a]=b;
  }, configurable: true})

  Object.defineProperty(Array.prototype, 'setRow', {value: function (i,row) {
   return this[i]=row;
  }, configurable: true})

  Object.defineProperty(Array.prototype, 'print', {value: function (format) {
    var i,j,k,s='',sep='', info=this.info();
    if (!format) format = '%4.2f';
    switch (info.dims) {
      case 1:
        for(j=0;j<info.columns;j++) {
          if (j!=0) s = s + '\n';
          s = s + sprintf.sprintf(format,this[j]) ;
        }
        break;
      case 2:
        for(j=0;j<info.rows;j++) {
          sep = '';
          if (j!=0) s = s + '\n';
          for (i=0;i<info.columns;i++) {
            s = s + sep + sprintf.sprintf(format,this[j][i]) ;
            sep = ' ';
          }
        }
        break;
      case 3:
        for(k=0;k<info.levels;k++) {
          if (k!=0) s = s + '\n\n';
          for(j=0;j<info.rows;j++) {
            sep = '';
            if (j!=0) s = s + '\n';
            for (i=0;i<info.columns;i++) {
              s = s + sep + sprintf.sprintf(format,this[k][j][i]) ;
              sep = ' ';
            }
          }
        }
    }  
    return s;
  }, configurable: true})
  
  Object.defineProperty(Array.prototype, 'info', {value: function () {
    var rows,columns,levels;
    if (isArrayArrayArray(this)) levels=this.length,rows=this[0].length,columns=this[0][0].length;
    else if (isArrayArray(this)) rows=this.length,columns=this[0].length;
    else columns=this.length;
    if (levels) return {
      dtn:'Array',
      size:levels*rows*columns,
      levels:levels,
      rows:rows,
      columns:columns,
      dims:3,
      offset:0,
    }; else if (rows) return {
      dtn:'Array',
      size:rows*columns,
      rows:rows,
      columns:columns,
      dims:2,
      offset:0,
    }; else return {
      dtn:'Array',
      size:columns,
      columns:columns,
      dims:1,
      offset:0,
    }
  }, configurable: true})



/****************** MATRIX ***************************/
// Matrix object based on typed arrays!
// Supports mixed mode typed arrays and generic arrays!
// {type:function,dtn:string} specifies data type
//
// Usage:
// Matrix(columns:number)
// Matrix(rows:number,columns:number)
// Matrix(levels:number,rows:number,columns:number)
// Matrix(rows:number,columns:number,options:{dtn:string})
// Matrix([])
// Matrix([][])
// Matrix([][][])
// Matrix({data:buffer|typedarray,rows:numner,columns:number,dtn:..})
//
// typeof return = Matrix

function Matrix (a,b,c,d) {
  var self = this;
  var rows,columns,levels,dims=2,dtn,dt=Matrix.options.dt,data,
      layout=12,size,transpose;
  var options = isObject(d)?d:(isObject(c)?c:(isObject(b)?b:{}));
  if (!(this instanceof Matrix)) return new Matrix(a,b,c,d);
  if (isNumber(a) && isNumber(b)) {
    // Create new empty matrix (2/3 dims)
    rows=a;
    columns=b;
    if (isNumber(c)) levels=c;
    dims=levels?3:2;
    if (options.type) dt=options.type;
    if (options.dtn)  dt=options.dtn=='Array'?Array:TypedArrayOfName[options.dtn];
    if (options.layout) layout=options.layout;
    else layout=dims==2?12:123;
    size=rows*columns;
    if (levels) size *= levels;
    if (options.dtn && !dt) throw ("Matrix: Unknown array type dtn="+options.dtn)
    if (dt.name=='Array')
      data=new Array(rows).fill(null).map(function (row) { return new Array(columns).fill(0) });
    else
      data=new dt(size);
  } 
  else if (isNumber(a)) {
    // Create a new empty matrix vector (rows=1)
    rows=1;
    columns=a;
    dims=2;
    if (options.type) dt=options.type;
    if (options.dtn)  dt=TypedArrayOfName[options.dtn];
    if (options.layout) layout=options.layout;
    else layout=12;
    if (options.dtn && !dt) throw ("Matrix: Unknown array type dtn="+options.dtn)
    size=columns;
    data=new dt(size);
  } 
  else if (isArrayArray(a)) {
    rows=a.length;
    columns=a[0].length;
    if (isArrayArrayArray(a)) levels=rows,rows=a[0].length,columns=a[0][0].length;
    size=rows*columns;
    if (levels) size *= levels;
    dims=levels?3:2;
    if (options.type) dt=options.type;
    if (options.dtn)  {
      dt=TypedArrayOfName[options.dtn];
    }
    if (options.layout) layout=options.layout;
    else layout=dims==2?12:123;
    if (options.dtn && !dt) throw ("Matrix: Unknown array type dtn="+options.dtn)
    if (options.dtn && options.dtn != 'Array') {
      // Create typedarray from generic array
      data=new dt(size);
      switch (layout) {
        case 12:
          a.forEach(function (row,rowi) {
            row.forEach(function (col,coli) {
              data[coli+rowi*columns]=col;
            })
          });
          break;
        case 21:
          a.forEach(function (row,rowi) {
            row.forEach(function (col,coli) {
              data[rowi+coli*rows]=col;   // TBCHECK!
            })
          });
          break;
      }
    } else {
      // Matrix wrapper for generic arrays and array arrays
      // modify .get .set .getRow  prototype ...
      // no _Matrix.call
      dt=Array;
      data=a;
    }
  } else if (isArray(a)) {
    // Vector 
    rows=1;
    columns=a.length;
    size=columns;
    dims=2;
    if (options.type) dt=options.type;
    if (options.dtn)  dt=TypedArrayOfName[options.dtn];
    if (options.layout) layout=options.layout;
    else layout=12;
    if (options.dtn && !dt) throw ("Matrix: Unknown array type dtn="+options.dtn)
    if (options.dtn && options.dtn != 'Array') {
      // Create typedarray from generic array
      data=new dt(a);
    } else {
      // Matrix wrapper for generic arrays and array arrays
      // modify .get .set .getRow  prototype ...
      // no _Matrix.call
      dt=Array;
      data=[a];
    }
  } else if (a instanceof Matrix) {
    if (options.transpose) {
      // transposeView !
      rows=a.columns;
      columns=a.rows;
      levels=a.levels;
      size=a.size;
      dims=a.dims;
      transpose=true;
      data=a.data;
      dtn=a.dtn;
      switch (a.layout) {
        case 12: layout=21; break;
        case 21: layout=12; break;
        case 123: layout=321; break;
        case 321: layout=123; break;
      }
    } else {
      // Copy
      rows=options.rows||a.rows;
      columns=options.columns||a.columns;
      levels=options.levels||a.levels;
      dims=a.dims;
      size=rows*columns;
      if(levels) size*=levels;
      transpose=false;
      scale=options.scale;
      if ((options.dtn && options.dtn != a.dtn) || size != a.size) {
        // convert or resize dtn
        dtn = options.dtn;
        data=new dt(size);
        if (scale)  for(i=0;i<size;i++) data[i]=a.data[i]/scale;
        else        for(i=0;i<size;i++) data[i]=a.data[i];
      } else {
        dtn=a.dtn;
        if (dtn != 'Array')
          data=a.data.slice();
        else {
          // TODO dims=3
          data=a.data.map(function (row) { return row.slice() });
        }
      }
      if (a.scale) this.scale=a.scale;
      if (a.complex) this.complex=a.complex;
      layout=a.layout;
    }
  } else if (isObject(a) && a.data) {
    // Partial matrix object
      rows=a.rows||(a.y && a.x);
      columns=a.columns||a.y||a.x;
      levels=a.levels||a.z;
      size=a.size||((rows?rows:1)*(columns?columns:1)*(levels?levels:1));
      dims=a.dims||(levels?3:(rows?2:1));
      layout=a.layout||(levels?123:(rows?12:1));
      dtn=a.dtn;
      data=a.data;
  }
  if (levels) this.levels=levels;   // z
  this.rows=rows;                   // x
  this.columns=columns;             // x/y
  this.size=size;
  this.layout=layout;
  this.data=data;
  this.dims=dims;
  this.length=levels?levels:(rows?rows:columns);
  
  this.dtn=dtn||dt.name;
  if (options.complex) this.complex=true;
  if (options.scale)   this.scaler=options.scale;
  
  // get/set index order: 
  // 1. column(x)
  // 2. row(x),column(y)
  // 3. row(x),column(y),level(z)
  
  if (this.dtn=='Array') {
    switch (this.layout) {
      case 12:
        this.get=function (row,column)   { return this.data[row][column] };
        this.set=function (row,column,v) { this.data[row][column]=v };
        break;
      case 21:
        // transposed view
        this.get=function (column,row)   { return this.data[row][column] };
        this.set=function (column,row,v) { this.data[row][column]=v };
        break;
      case 123:
        this.get=function (row,column,level)   { return this.data[row][column][level] };
        this.set=function (row,column,level,v) { this.data[row][column][level]=v };
        break;
     }
  } else switch (this.layout) {
    case 1:
      // x=column
      this.index = function (x)     { return x }
      this.get = function (x)       { return this.data[x] }
      this.set = function (x,v)     { return this.data[x]=v }
      break;
    case 12:
      // x=row,y=column
      this.index = function (x,y)   { return x*self.columns+y}
      this.get = function (x,y)     { return this.data[x*this.columns+y] }
      this.set = function (x,y,v)   { return this.data[x*this.columns+y]=v }
      break;
    case 21:
      // x=row,y=column      
      this.index = function (x,y)   { return y*this.rows+x }
      this.get = function (x,y)     { return this.data[y*this.rows+x] }
      this.set = function (x,y,v)   { return this.data[y*this.rows+x]=v }
      break;
    case 123:
      // x=row,y=column,z=level
      this.index = function (x,y,z) { return z+y*self.columns+x*this.columns*this.rows }
      this.get = function (x,y,z)   { return this.data[z+y*this.levels*this.rows+x*this.levels] }
      this.set = function (x,y,z,v) { return this.data[z+y*this.levels*this.rows+x*this.levels]=v }
      break;
    case 321:
      // x=row,y=column,z=level
      // TBC
      this.index = function (x,y,z) { return x+y*self.rows+z*this.columns*this.rows }
      this.get = function (x,y,z)   { return this.data[x+y*this.rows+z*this.columns*this.rows] }
      this.set = function (x,y,z,v) { return this.data[x+y*self.rows+z*this.columns*this.rows]=v }
      break;
  }
}



Matrix.options = {
  dt : Float32Array,
  dtn : 'Float32Array'
}


/******** STATIC MEMBERS ********/
Matrix.abs = function (m) {
  return Matrix.clone(m).abs();
}

Matrix.add = function (m,v) {
  return Matrix.clone(m).add(v);
}

Matrix.clone = function (src) {
  return Matrix(src);
}

Matrix.columnVector = function (array) {
  return Matrix(array)
}

// Return an (typed)array
Matrix.checkArray = function (arrayOrMatrix) {
  if (arrayOrMatrix instanceof _MatrixConstructor) return arrayOrMatrix.data;
  else return arrayOrMatrix;
}

// Return a Matrix
Matrix.checkMatrix = function (arrayOrMatrix) {
  if (arrayOrMatrix instanceof Matrix) return arrayOrMatrix;
  else return Matrix(arrayOrMatrix);
}

Matrix.checkMatrixSize = function (matrix1,matrix2) {
  if (matrix1.dims != matrix2.dims) return false;
  if (matrix1.rows != matrix2.rows ||
      matrix1.columns != matrix2.columns ||
      matrix1.levels != matrix2.levels ) return false;
}

Matrix.cos = function (m) {
  return Matrix.clone(m).cos();
}

Matrix.diag = function (array,rows,columns) {
  if (!rows) rows=array.length;
  if (!columns) columns=rows;
  if (rows!=columns) Matrix.error("Not a square matrix",'diag');
  return Matrix(rows,columns).fill(function (i,j) {
    return i==j?array[i]:0;
  })
}

Matrix.div = function (m,v) {
  return Matrix.clone(m).div(v);
}

Matrix.empty = function (rows,columns) {
  return Matrix(rows,columns);
}

Matrix.error = function (what,where,ref) {
  throw new Error((where?('Matrix.'+where+': '):'')+what);
}

Matrix.errorRange = function (what,where,ref) {
  throw new RangeError((where?('Matrix.'+where+': '):'')+what);
}

Matrix.eye = function (rows,columns,val,options) {
  if (!val) val=1;
  if (!columns) columns=rows;
  return Matrix(rows,columns,options).fill(function (i,j) {
    return i==j?val:0;
  });
}

Matrix.exp = function (m) {
  return Matrix.clone(m).exp();
}

isMatrix = Matrix.isMatrix = function (o) {
  return (o instanceof Matrix)
}

Matrix.max =  function(matrix1, matrix2) {
  var result;
  matrix1 = this.checkMatrix(matrix1);
  matrix2 = this.checkMatrix(matrix2);
  if (!this.checkMatrixSize(matrix1,matrix2)) Matrix.error('matrix1 not compatble with matrix2','max');
  var rows = matrix1.rows;
  var columns = matrix1.columns;
  var levels = matrix1.levels;
  switch (matrix1.dims) {
    case 1:
      break;
    case 2:
      result = Matrix(rows, columns, {dtn:matrix1.dtn});
      for (var i = 0; i < rows; i++) {
        for (var j = 0; j < columns; j++) {
          result.set(i, j, Math.max(matrix1.get(i, j), matrix2.get(i, j)));
        }
      }
      break;
    case 3:
      break;
  }
  return result;
}

Matrix.min =  function(matrix1, matrix2) {
  var result;
  matrix1 = this.checkMatrix(matrix1);
  matrix2 = this.checkMatrix(matrix2);
  if (!this.checkMatrixSize(matrix1,matrix2)) Matrix.error('matrix1 not compatble with matrix2','min');
  var rows = matrix1.rows;
  var columns = matrix1.columns;
  var levels = matrix1.levels;
  switch (matrix1.dims) {
    case 1:
      break;
    case 2:
      result = Matrix(rows, columns, levels, {dtn:matrix1.dtn});
      for (var i = 0; i < rows; i++) {
        for (var j = 0; j < columns; j++) {
          result.set(i, j, Math.min(matrix1.get(i, j), matrix2.get(i, j)));
        }
      }
      break;
  }
  return result;
}


Matrix.mod = function (m,v) {
  return Matrix.clone(m).mod(v);
}

Matrix.mul = function (m,v) {
  return Matrix.clone(m).mul(v);
}

Matrix.neg = function (m) {
  return Matrix.clone(m).neg();
}

Matrix.ones = function (rows,columns) {
  return Matrix(rows,columns).fill(1);
}

Matrix.rand = function (rows, columns, rng) {
  if (rng==undefined) rng=Math.random;
  return Matrix(rows,columns).fill(function () {
    return rng();
  });
}

Matrix.randInt = function (rows, columns, maxValue, rng) {
  if (rng==undefined) rng=Math.random;
  return Matrix(rows,columns).fill(function () {
    return (rng()*maxValue)|0;
  });
}

Matrix.sin = function (m) {
  return Matrix.clone(m).sin();
}

Matrix.sub = function (m,v) {
  return Matrix.clone(m).sub(v);
}

Matrix.zero = function (rows,columns) {
  return Matrix(columns,rows).fill(0);
}


/********* INSTANCE MEMBERS *********/


Matrix.prototype.abs = function (v) {
  this.eval(Math.abs);
  return this; 
}

Matrix.prototype.add = function (v) {
  this.eval(function (x) {return x+v});
  return this; 
}

Matrix.prototype.apply = function (f) {
  var i,j,k;
  switch (this.dims) {
    case 1:
      for(j=0; j < this.columns; j++) 
        f.call(this,j)
      return this;
    case 2:
      for(i=0; i<this.rows;i++) 
        for(j=0; j < this.columns; j++) 
          f.call(this,i,j)
      return this;
    case 3:
      for(i=0; i<this.rows;i++) 
        for(j=0; j < this.columns; j++) 
          for(k=0; k<this.levels;k++) 
            f.call(this,i,j,k)
      return this;
  }
}

Matrix.prototype.checkMatrixDims = function(dims) {
  if (this.dims != dims) this.errorRange('Matrix has not expected dimension '+dims);
}

/**
 * @private
 * Check that a column index is not out of bounds
 * @param {Matrix} matrix
 * @param {number} index
 * @param {boolean} [outer]
 */
Matrix.prototype.checkColumnIndex = function(index, outer) {
  var max = outer ? this.columns : this.columns - 1;
  if (index < 0 || index > max) this.errorRange('Column index out of range');
}


/**
 * @private
 * Check that a row index is not out of bounds
 * @param {Matrix} matrix
 * @param {number} index
 * @param {boolean} [outer]
 */
Matrix.prototype.checkRowIndex = function(index, outer) {
  var max = outer ? this.rows : this.rows - 1;
  if (index < 0 || index > max)
    this.errorRange('Row index out of range');
}

/**
 * @private
 * Check that the provided vector is an array with the right length
 * @param {Matrix} matrix
 * @param {Array|Matrix} vector
 * @return {Array}
 * @throws {RangeError}
 */
Matrix.prototype.checkRowVector = function(vector) {
  if (vector.to1DArray) {
    vector = vector.to1DArray();
  }
  if (vector.length !== this.columns) 
    this.errorRange(
      'vector size must be the same as the number of columns'
    );
  
  return vector;
}

/**
 * @private
 * Check that the provided vector is an array with the right length
 * @param {Matrix} matrix
 * @param {Array|Matrix} vector
 * @return {Array}
 * @throws {RangeError}
 */
Matrix.prototype.checkColumnVector = function(vector) {
  if (vector.to1DArray) {
    vector = vector.to1DArray();
  }
  if (vector.length !== this.rows) 
    this.errorRange('vector size must be the same as the number of rows');
  
  return vector;
}

Matrix.prototype.checkIndices = function(rowIndices, columnIndices) {
  return {
    row: this.checkRowIndices(rowIndices),
    column: this.checkColumnIndices(columnIndices)
  };
}

Matrix.prototype.checkRowIndices = function(rowIndices) {
  var self=this;
  if (typeof rowIndices !== 'object') {
    throw new TypeError('unexpected type for row indices');
  }

  var rowOut = rowIndices.some(function (r) {
    return r < 0 || r >= self.rows;
  });

  if (rowOut) {
    throw new RangeError('row indices are out of range');
  }

  if (!Array.isArray(rowIndices)) rowIndices = Array.from(rowIndices);

  return rowIndices;
}

Matrix.prototype.checkColumnIndices = function(columnIndices) {
  var self=this;
  if (typeof columnIndices !== 'object') {
    throw new TypeError('unexpected type for column indices');
  }

  var columnOut = columnIndices.some(function (c) {
    return c < 0 || c >= self.columns;
  });

  if (columnOut) {
    throw new RangeError('column indices are out of range');
  }
  if (!Array.isArray(columnIndices)) columnIndices = Array.from(columnIndices);

  return columnIndices;
}

Matrix.prototype.checkRange = function(startRow, endRow, startColumn, endColumn) {
  if (arguments.length !== 4) {
    throw new RangeError('expected 4 arguments');
  }
  checkNumber('startRow', startRow);
  checkNumber('endRow', endRow);
  checkNumber('startColumn', startColumn);
  checkNumber('endColumn', endColumn);
  if (
    startRow > endRow ||
    startColumn > endColumn ||
    startRow < 0 ||
    startRow >= this.rows ||
    endRow < 0 ||
    endRow >= this.rows ||
    startColumn < 0 ||
    startColumn >= this.columns ||
    endColumn < 0 ||
    endColumn >= this.columns
  ) {
    throw new RangeError('Submatrix indices are out of range');
  }
}

Matrix.prototype.clone = function () {
  return Matrix(this);
}

/** Copy (1) a sorurce array (vector) into this matrix (row/column w or w/o subrange), or (2) create a copy of this matrix (empty argument list)
 *
 * copy()
 * copy([a,b]|[],[v1,v2,...])
 * copy(number,[a,b]|[],[v1,v2,...])
 * copy([a,b]|[],number,[v1,v2,...])
 * copy(number,number,[a,b]|[],[v1,v2,...])
 * ..
 */
 
Matrix.prototype.copy = function (a,b,c,d) {
  var x,y,z,rx,ry,rz,i,j,k,src;

  if (isNumber(a)) i=a;
  if (isNumber(b)) j=b;
  if (isNumber(c)) k=c;
  if (isArray(a)) rx=a;
  if (isArray(b)) ry=b;
  if (isArray(c)) rz=c;

  if (isArray(d)) src=d;
  if (isVector(d)) src=d;
  
  if (!src && !d && (isArray(c) || isVector(c))) src=c,rz=undefined;
  if (!src && !c && (isArray(b) || isVector(b))) src=b,ry=undefined;
  if (!src && !a && (isArray(a) || isVector(a))) src=a,rx=[0,this.columns-1];  // 1-dim only
    
  if (isVector(src)) src=src.data;
  if (!src) return Matrix({
    rows:this.rows,
    columns:this.columns,
    levels:this.levels,
    dtn:this.dtn,
    layout:this.layout,
    data:this.data.slice()
  })
  
  if (!src) throw "Matrix.copy: no source array provided";
  if (rx && rx.length==0) rx=[0,this.rows-1];
  if (ry && ry.length==0) ry=[0,this.columns-1];
  if (rz && rz.length==0) rz=[0,this.levels-1];
  if (rx && (rx[1]-rx[0]+1)!=src.length) throw "Matrix.copy: range mismatch (src)"
  if (ry && (ry[1]-ry[0]+1)!=src.length) throw "Matrix.copy: range mismatch (src)"
  if (rz && (rz[1]-rz[0]+1)!=src.length) throw "Matrix.copy: range mismatch (src)"
   
  switch (this.dims) {
    case 1:
      for(x=rx[0];x<rx[1];x++) this.data[x]=src[x-rx[0]];
      break;
    case 2:
      if (rx && j != undefined)
        for(x=rx[0];x<=rx[1];x++) 
          this.data[this.index(x,j)]=src[x-rx[0]];
      else if (i != undefined && ry)
        for(y=ry[0];y<=ry[1];y++) 
          this.data[this.index(i,y)]=src[y-ry[0]];
      else todo('copy 2'); 
      break;   
    case 3:
      if (rx && j != undefined && k != undefined)
        for(x=rx[0];x<=rx[1];x++) 
          this.data[this.index(x,j,k)]=src[x-rx[0]];
      else if (ry && i != undefined && k != undefined)
        for(y=ry[0];y<=ry[1];y++) 
          this.data[this.index(i,y,k)]=src[y-ry[0]];
      else if (rz && i != undefined && j != undefined)
        for(z=rz[0];z<=rz[1];z++) 
          this.data[this.index(i,j,z)]=src[z-rz[0]];
      else todo('copy 3');    
      break;
  }
  return this;
}

/** Convert size using a data filter.
 ** The target size must be provided.
 *  typeof @filter = 'mean' | 'peak' | 'min' | 'max' | 'win' | 'exp' | 'exp-peak' | function (a:number,b:number,i:number) -> number 
 */

Matrix.prototype.convert = function (a,b,c,d) {
  var i,j,k,d,v,m,ni,nj,nk,filter;
  
  if (isNumber(a)) i=a;
  if (isNumber(b)) j=b;
  if (isNumber(c)) k=c;
  if (isString(b)) filter=b;
  if (isString(c)) filter=c;
  if (isString(d)) filter=d;
  if (!filter) filter='mean';

  if (!i) throw "Matrix.convert: no target size (number)";
    
  m = Matrix(i,j,k);
      
  switch (filter) {
    case 'mean':      filter=function (a,b,i,n) { if (i==n-1) return (a+b)/n; else return a+b }; break;
    case 'exp':       filter=function (a,b,i,n) { return (a+b)/2 }; break;
    case 'exp-peak':  filter=function (a,b,i,n) { return (Math.abs(a)+Math.abs(b))/2 }; break;
    case 'peak':      filter=function (a,b,i,n) { if (Math.abs(a)>Math.abs(b)) return Math.abs(a); else return Math.abs(a); }; break;
    case 'min':       filter=function (a,b,i,n) { return a<b?a:b }; break;
    case 'max':       filter=function (a,b,i,n) { return a>b?a:b }; break;
    default:          filter = function () { return 0 }
  }
  switch (this.dims) {
    case 1:
      ni=Math.floor(this.i/m.i);
      for(i=0;i<m.i;i++) {
        v=this.data[i*ni]; 
        for(d=1;d<ni;d++) {
          v=filter(v,this.data[i*ni+d],d,ni);
        }
        m.data[i]=v;
      }
      break;
  }
  return m;
}


Matrix.prototype.cos = function (v) {
  this.eval(Math.cos);
  return this; 
}

Matrix.prototype.diag = function (v) {
  // TODO Vector
  var a = [];
  if (this.rows!=this.columns) return;
  for(var i=0;i<this.rows;i++) a.push(this.data[i+i*this.i]);
  return a; 
}

Matrix.prototype.dim = function () {
  switch (this.dims) {
    case 1: return [this.columns];
    case 2: return [this.rows,this.columns];
    case 3: return [this.rows,this.columns,this.levels];
  }
}

Matrix.prototype.div = function (v) {
  this.eval(function (x) {return x/v});
  return this; 
}

Matrix.prototype.divide = function (a,b,c,d) {
  switch (this.dims) {
    case 1: return this.set(a,this.get(a)/b);
    case 2: return this.set(a,b,this.get(a,b)/c);
    case 3: return this.set(a,b,c,this.get(a,b,c)/d);
  }
}

Matrix.prototype.error = function (what,where) {
  throw new Error((where?('Matrix.'+where+': '):'')+what);
}

Matrix.prototype.errorRange = function (what,where) {
  throw new RangeError((where?('Matrix.'+where+': '):'')+what);
}

// Evaluate all elements x of matrix by applying function f(x)
Matrix.prototype.eval = function (f) {
  var i,j,k;
  switch (this.dtn) {
    case 'Array':
      switch (this.dims) {
        case 1:
          for(i=0; i < this.columns; i++) 
            this.set(i,f(this.get(i)))
          break;
        case 2:
          for(i=0; i < this.rows;i++) 
            for(j=0; j < this.columns; j++) 
              this.set(i,j,f(this.get(i,j)))
          break
        case 3:
          for(i=0; i < this.rows;i++) 
            for(j=0; j < this.columns; j++) 
              for(k=0; k < this.levels; k++) 
                this.set(i,j,k,f(this.get(i,j,k)))
          break;
      }
      break;
    default:
      for(i=0;i<this.size;i++) this.data[i]=f(this.data[i]);
  }
  return this;
}

Matrix.prototype.exp = function (v) {
  this.eval(Math.exp);
  return this; 
}

Matrix.prototype.fill = function (valueOrFunction) {
  if (typeof valueOrFunction == 'function') {
    switch (this.dims) {
      case 1:
        for(i=0; i < this.columns; i++) 
          this.set(i,valueOrFunction(i.j))
        return this;
      case 2:
        for(i=0; i < this.rows;i++) 
          for(j=0; j < this.columns; j++) 
            this.set(i,j,valueOrFunction(i,j))
        return this;
      case 3:
        for(i=0; i < this.rows;i++) 
          for(j=0; j < this.columns; j++) 
            for(k=0; k < this.levels; k++) 
              this.set(i,j,k,valueOrFunction(i,j,k))
        return this;
    }
  } else this.data.fill(valueOrFunction);
  return this;
}


Matrix.prototype.getCol = function (index,asVector) {
  // TODO
}

// Return array or vector
Matrix.prototype.getRow = function (index,asVector) {
  this.checkMatrixDims(2);
  this.checkRowIndex(index);
  var row,data,i,j;
  switch (this.dtn) {
    case 'Array':
      if (this.layout==12) {
        if (!asVector)
          return this.data[index];
        else
          return Vector(this.data[index]);
      } else {
        // transposed view
        if (!asVector) {
          row = new Array(this.columns);
          if (this.rows==1) return this.data;
          for (i = 0; i < this.columns; i++) {
            row[i] = this.get(index, i);
          }
        } else {
          if (this.rows==1) return this.data;
          row= Vector(this.columns,{dtn:this.dtn});
          for (i = 0; i < this.columns; i++) {
            row.set(i, this.get(index, i));
          };
        }  
      }
      break;
    default:
      // With advanced slicing
      if (!asVector) {
        row = new Array(this.columns);
        if (this.rows==1) return this.data;
        for (i = 0; i < this.columns; i++) {
          row[i] = this.get(index, i);
        }
      } else if (this.layout == 12) {
        // data = this.data.slice(index*this.columns,(index+1)*this.columns);
        row= Vector({dtn:this.dtn,data:this.data,offset:index*this.columns,columns:this.columns});
      } else {
        if (this.rows==1) return this.data;
        row= Vector(this.columns,{dtn:this.dtn});
        for (i = 0; i < this.columns; i++) {
          row.set(i, this.get(index, i));
        };
      }   
  }
  
  return row;
}

// x += delta
Matrix.prototype.incr = function (a,b,c,d) {
  switch (this.dims) {
    case 1: return this.set(a,this.get(a)+b);
    case 2: return this.set(a,b,this.get(a,b)+c);
    case 3: return this.set(a,b,c,this.get(a,b,c)+d);
  }
}

Matrix.prototype.info = function () {
  var o = {
    dtn:this.dtn,
    size:this.size,
    columns:this.columns,
    layout:this.layout,
    dims:this.dims
  }
  if (this.rows) o.rows=this.rows;
  if (this.levels) o.levels=this.levels;
  if (this.scaler) o.scaler=this.scaler;
  if (this.complex) o.complex=true;
  return o;
}


Matrix.prototype.isColumnVector = function () {
   return this.columns === 1;
}

Matrix.prototype.isEchelonForm = function () {
  this.checkMatrixDims(2);
  var i = 0;
  var j = 0;
  var previousColumn = -1;
  var isEchelonForm = true;
  var checked = false;
  while ((i < this.rows) && (isEchelonForm)) {
    j = 0;
    checked = false;
    while ((j < this.columns) && (checked === false)) {
      if (this.get(i, j) === 0) {
        j++;
      } else if ((this.get(i, j) === 1) && (j > previousColumn)) {
        checked = true;
        previousColumn = j;
      } else {
        isEchelonForm = false;
        checked = true;
      }
    }
    i++;
  }
  return isEchelonForm;
}

Matrix.prototype.isReducedEchelonForm = function () {
  this.checkMatrixDims(2);
  var i = 0;
  var j = 0;
  var previousColumn = -1;
  var isReducedEchelonForm = true;
  var checked = false;
  while ((i < this.rows) && (isReducedEchelonForm)) {
    j = 0;
    checked = false;
    while ((j < this.columns) && (checked === false)) {
      if (this.get(i, j) === 0) {
        j++;
      } else if ((this.get(i, j) === 1) && (j > previousColumn)) {
        checked = true;
        previousColumn = j;
      } else {
        isReducedEchelonForm = false;
        checked = true;
      }
    }
    for (var k = j + 1; k < this.rows; k++) {
      if (this.get(i, k) !== 0) {
        isReducedEchelonForm = false;
      }
    }
    i++;
  }
  return isReducedEchelonForm;
}
Matrix.prototype.isRowVector = function () {
   return this.rows === 1;
}

Matrix.prototype.isSquare = function () {
  return this.rows==this.columns
}

Matrix.prototype.isSymmetric = function () {
  if (this.isSquare()) {
        for (var i = 0; i < this.rows; i++) {
          for (var j = 0; j <= i; j++) {
            if (this.get(i, j) !== this.get(j, i)) {
              return false;
            }
          }
        }
        return true;
      }
  return false;
}

/** Iterate over matrix elements
 * Parameter arrays specify iteration ranges, FORALL='*' specifies a target vector range
 * iter(function (@elem,@index,@array))
 * iter(number [],function)
 * iter(number [],number [],function)
 * iter(number [],number [],number [],function)
 * Examples: 
 *  m.iter(FORALL,[],[],f)   <=> for all x-vectors with y in [0,j-1], z in [0,k-1] do .. 
 *  m.iter([], FORALL,[],f)  <=> for all y-vectors with x in [0,j-1], z in [0,k-1] do .. 
 *  m.iter([],[],[],f)       <=> for all values with x in [0,i-1], y in [0,j-1], z in [0,k-1] do .. 
 *  m.iter(f)                <=> for all values with x in [0,i-1], y in [0,j-1], z in [0,k-1] do .. 
 *
 *
 */
  
Matrix.prototype.iter = function (a,b,c,d) {
  var func,rx,ry,rz,x,y,z,
      self=this;
  if (isFunction(a)) func=a;
  else if (isFunction(b)) func=b;
  else if (isFunction(c)) func=c;
  else if (isFunction(d)) func=d;
  if (isArray(a)) rx=a;
  if (isArray(b)) ry=b;
  if (isArray(c)) rz=c;
  if (isString(a)) rx=a;
  if (isString(b)) ry=b;
  if (isString(c)) rz=c;
  if (!func) throw "Matrx.iter: no function supplied";
  if (!rx && !ry && !rz) // linear iteration over all elements
    return this.data.forEach(func);
  switch (this.dims) {
    case 1: break;
  // TODO
      todo('iter 1')
    case 2: break;
  // TODO
      todo('iter 2')
    case 3:
      if (isArray(rx) && rx.length==0) rx=[0,this.rows];
      if (isArray(ry) && ry.length==0) ry=[0,this.columns];
      if (isArray(rz) && rz.length==0) rz=[0,this.levels];
      if (rz == FORALL) {
        for(x=rx[0];x<rx[1];x++) {
          for(y=ry[0];y<ry[1];y++) {
            func(x,y,this.subMatrixRange(x,y,ALL))
          }
        }
      } else if (rx==FORALL) {
  // TODO
        todo('iter 3.ryx=FORALL')
      
      } else if (ry==FORALL) {
  // TODO
        todo('iter 3.ry=FORALL')
      
      } else {
        // single data cell iteration
  // TODO
        todo('iter 3')
      }
  }
  // TODO
  return this;
}

Matrix.prototype.map = function (f,asArray) {
  var res,i,j,k;
  switch (this.dims) {
    case 1:
      res = Matrix(this.columns,{dtn:asArray?'Array':this.dtn});
      for(j=0;j<this.columns;j++)
        res.set(j,f(this.get(j),j));
      break;
    case 2:
      res = Matrix(this.rows,this.columns,{dtn:asArray?'Array':this.dtn});
      for(i=0;i<this.rows;i++)
        for(j=0;j<this.columns;j++)
          res.set(i,j,f(this.get(i,j),i,j));
      break;
    case 3:
      res = Matrix(this.rows,this.columns,this.levels,{dtn:asArray?'Array':this.dtn});
      for(i=0;i<this.rows;i++)
        for(j=0;j<this.columns;j++)
          for(k=0;k<this.levels;k++)
            res.set(i,j,k,f(this.get(i,j,k),i,j,k));
      break;
  }
  return res;
}


// Row mapping function
Matrix.prototype.mapRow = function (f) {
  var res=[];
  for(var row=0;row<this.rows;row++) {
    res.push(f(this.getRow(row)));
  }
  return res;
}

/** Return minimum and maximum value of the matrix
 *
 */
Matrix.prototype.minmax = function () {
  var d0=Number.MAX_VALUE,d1=Number.MIN_VALUE;
  for (i = 0;i < this.size; i++) {
    d0=Math.min(d0,this.data[i]);
    d1=Math.max(d1,this.data[i]);    
  }
  return { min:d0, max:d1 };
}

Matrix.prototype.mapToArray = function (f) {
  var res = new Array(this.size);
  for(var i=0;i<this.rows;i++)
    for(var j=0;j<this.columns;j++)
      res[i*this.columns+j]=f(this.get(i,j),i,j);
  return res;
}

// x *= k
Matrix.prototype.multiply = function (a,b,c,d) {
  switch (this.dims) {
    case 1: return this.set(a,this.get(a)*b);
    case 2: return this.set(a,b,this.get(a,b)*c);
    case 3: return this.set(a,b,c,this.get(a,b,c)*d);
  }
}

Matrix.prototype.mean = function (v) {
  return this.sum()/this.size;
}

Matrix.prototype.mod = function (v) {
  this.eval(function (x) {return x%v});
  return this; 
}

/**
     * Returns the matrix product between this and other
     * @param {Matrix} other
     * @return {Matrix}
     */
Matrix.prototype.mmul = function (other) {
  this.checkMatrixDims(2);
  other = Matrix.checkMatrix(other);
  if (this.columns !== other.rows) {
    // eslint-disable-next-line no-console
    console.warn('Number of columns of left matrix are not equal to number of rows of right matrix.');
  }

  var m = this.rows;
  var n = this.columns;
  var p = other.columns;

  var result = Matrix(m, p, {dtn:this.dtn});

  var Bcolj = new Array(n);
  for (var j = 0; j < p; j++) {
    for (var k = 0; k < n; k++) {
      Bcolj[k] = other.get(k, j);
    }
    for (var i = 0; i < m; i++) {
      var s = 0;
      for (k = 0; k < n; k++) {
        s += this.get(i, k) * Bcolj[k];
      }
      result.set(i, j, s);
    }
  }
  return result;
}

Matrix.prototype.mul = function (v) {
  this.eval(function (x) {return x*v});
  return this; 
}

Matrix.prototype.neg = function (v) {
  this.eval(function (x) {return -x});
  return this; 
}

Matrix.prototype.prod = function (v) {
  var i,j,k,v = 1;
  // Comp. mode
  switch (this.dtn+this.dims) {
    case 'Array1':
      for (i = 0; i < this.columns; i++) {
          v *= this.data[i];
      }
      break;
    case 'Array2':
      for (i = 0; i < this.rows; i++) {
        for (j = 0; j < this.columns; j++) {
          v *= this.data[i][j];
        }
      }
      break;
    case 'Array3':
      for (i = 0; i < this.rows; i++) {
        for (j = 0; j < this.columns; j++) {
          for (k = 0; k < this.levels; k++) {
            v *= this.data[i][j][k];
          }
        }
      }
      break;
    default:
      for (i = 0; i < this.size; i++) v *= this.data[i];
  }
  return v;
}

Matrix.prototype.print = function (format) {
  var i,j,k,s='',sep='';
  if (!format) format = '%4.2f';
  switch (this.dims) {
    case 1:
      for(i=0;i<this.columns;i++) {
        if (i!=0) s = s + '\n';
        s = s + sprintf.sprintf(format,this.get(i)) ;
      }
      break;
    case 2:
      for(i=0;i<this.rows;i++) {
        sep = '';
        if (i!=0) s = s + '\n';
        for (j=0;j<this.columns;j++) {
          s = s + sep + sprintf.sprintf(format,this.get(i,j)) ;
          sep = ' ';
        }
      }
      break;
    case 3:
      for(k=0;k<this.levels;k++) {
        if (k!=0) s = s + '\n\n';
        for(i=0;i<this.rows;i++) {
          sep = '';
          if (i!=0) s = s + '\n';
          for (j=0;j<this.columns;j++) {
            s = s + sep + sprintf.sprintf(format,this.get(i,j,k)) ;
            sep = ' ';
          }
        }
      }
  }  
  return s;
}

/** Reduce dimension: Linear matrix data reduction applying a function (a,b) -> c to all elements.
 *  Returns a scalar value or any other object accumulated by the supplied function
 */
Matrix.prototype.reduce = function (f) {
  return this.data.reduce(f);
}

/** resize matrix (only modifying meta data - not buffer data)
 *
 */
Matrix.prototype.resize = function (options) {
  for(var p in options) {
    switch (p) {
      case 'rows':
      case 'columns':
      case 'levels':
        this[p]=options[p];
        break;
    }
  }
  this.size=this.columns*(this.rows?this.rows:1)*(this.levels?this.levels:1);
  this.length=this.rows?this.rows:this.columns;
  return this
}


Matrix.prototype.reverseRow = function (row) {
  var t,len=this.columns;
  for(var i=0;i<(len/2)|0;i++) {
    t=this.get(row,i);
    this.set(row,i,this.get(row,len-i-1));
    this.set(row,len-i-1,t);
  }
  return this; 
}

/** Scale (and/or adjust offset optionally of) all matrix elements -= offset *= k
 * scale(k)
 * scale(k,inplace:boolean)
 * scale(k,offset)
 * scale(k,offset,inplace:boolean)
 */
 
Matrix.prototype.scale = function (a,b,c) {
  var m,k=1,offset,inplace=false;
  if (isNumber(a)) k=a;
  if (isBoolean(b)) inplace=b;
  else if (isBoolean(c)) inplace=c;
  if (isNumber(b)) offset=b;
  else if (isNumber(c)) offset=c;
  
  m = inplace?this:this.copy();
  if (k!=1) {
    if (offset)
      for(var i=0;i<m.data.length;i++) m.data[i]=(m.data[i]-offset)*k;
    else
      for(var i=0;i<m.data.length;i++) m.data[i]=m.data[i]*k;
  } else if (offset) {
      for(var i=0;i<m.data.length;i++) m.data[i]=m.data[i]-offset;  
  }
  return m;
}

/*
Return a new matrix based on a selection of rows and columns
selection(rowIndices: Array<number>, columnIndices: Array<number>): Matrix
Parameters
rowIndices (Array<number>) The row indices to select. Order matters and an index can be more than once.
columnIndices (Array<number>) The column indices to select. Order matters and an index can be use more than once.
Returns 
Matrix: The new matrix 
*/
Matrix.prototype.selection = function (rowIndices,columnIndices) {
  this.checkMatrixDims(2);
  var newMatrix = Matrix(rowIndices.length,columnIndices.length,{dtn:this.dtn});
  for (var i = 0; i < rowIndices.length; i++) {
    var rowIndex = rowIndices[i];
    for (var j = 0; j < columnIndices.length; j++) {
      var columnIndex = columnIndices[j];
      newMatrix.set(i,j, this.get(rowIndex, columnIndex));
    }
  }
  return newMatrix;
}


// Set a row of the matrix
Matrix.prototype.setRow = function (row,data) {
  data=Matrix.checkArray(data);
  for(var i=0;i<this.columns;i++) {
     this.set(row,i,data[i]); 
  }
}

// Slice of data in major dimension
Matrix.prototype.slice = function (i,offset) {
  var rows,columns,levels;
  switch (this.dims) {
    case 1:
      return Matrix(this,{columns:i,offset:offset,slice:true});
      break;
    case 2:
    case 3:
      return Matrix(this,{rows:i,offset:offset,slice:true});
      break;
  }
}

Matrix.prototype.sin = function () {
  this.eval(Math.sin);
  return this;
}

/*
size
Properties
size (number) : The number of elements in the matrix.
*/
Matrix.prototype.size = function () {
  return  this.size;
}


Matrix.prototype.sub = function (v) {
  this.eval(function (x) {return x-v});
  return this; 
}


Matrix.prototype.subMatrix = function (startRow, endRow, startColumn, endColumn) {
  this.checkMatrixDims(2);
  this.checkRange(startRow, endRow, startColumn, endColumn);
  var newMatrix = Matrix(endRow - startRow + 1, endColumn - startColumn + 1, {dtn:this.dtn});
  for (var i = startRow; i <= endRow; i++) {
    for (var j = startColumn; j <= endColumn; j++) {
      newMatrix.set(i - startRow,j - startColumn, this.get(i, j));
    }
  }
  return newMatrix;
}

/** Return a sub-matrix (1-3 dims)
 *
 */
Matrix.prototype.subMatrixRange = function (rx,ry,rz) {
  var i,j,i0,i1,x0,x1,y0,y1,z0,z1,res;
  switch (this.dims) {
    case 1:
      // simple case, return sliced array
      x0=0,x1=this.i-1;
      if (isRange(rx)) x0=rx[0],x1=rx[1];
      else throw "Matrix.subMatrixRange: no range";
      var i0=x0,i1=i0+1+x1;
      return Vector({data:this.data.slice(i0,i1),columns:i1-i0,dtn:this.dtn});
    case 2:
      todo('subMatrixRange 2')
    case 3:
      if ((isAll(rz) || (isRange(rz)) && isNumber(rx) && isNumber(ry) && this.layout==123)) {
        // simple case, return sliced array (1-dim matrix)
        z0=0,z1=this.levels-1;
        if (isRange(rz)) z0=rz[0],z1=rz[1];
        var i0=this.index(rx,ry,z0),i1=i0+1+z1;
        return Vector({data:this.data.slice(i0,i1),columns:i1-i0,dtn:this.dtn});
      } if ((isAll(rx) || isRange(rx)) && (isAll(ry) || isRange(ry)) && isNumber(rz)) {
        res = Matrix(this.rows,this.columns,{dtn:this.dtn});
        x0=0,x1=this.rows-1;
        if (isRange(rx)) x0=rx[0],x1=rx[1];
        y0=0,y1=this.columns-1;
        if (isRange(ry)) y0=ry[0],y1=ry[1];
        z0=rz;
        for(i=x0;i<x1;i++)
          for(j=y0;j<y1;j++)
            res.set(i,j, this.get(i,j,z0));
        return res;
      } 
      else todo('subMatrixRange 3.rx/ry')
  }
}

Matrix.prototype.subMatrixRow = function (indices, startColumn, endColumn) {
  this.checkMatrixDims(2);
  if (startColumn === undefined) startColumn = 0;
  if (endColumn === undefined) endColumn = this.columns - 1;
  if ((startColumn > endColumn) || (startColumn < 0) || (startColumn >= this.columns) || (endColumn < 0) || (endColumn >= this.columns)) {
    throw new RangeError('Argument out of range');
  }

  var newMatrix = Matrix(indices.length, endColumn - startColumn + 1, {dtn:this.dtn});
  for (var i = 0; i < indices.length; i++) {
    for (var j = startColumn; j <= endColumn; j++) {
      if (indices[i] < 0 || indices[i] >= this.rows) {
        throw new RangeError('Row index out of range: '+indices[i]);
      }
      newMatrix.set(i, j - startColumn, this.get(indices[i], j));
    }
  }
  return newMatrix;
}

Matrix.prototype.subMatrixColumn = function (indices, startRow, endRow) {
  this.checkMatrixDims(2);
  if (startRow === undefined) startRow = 0;
  if (endRow === undefined) endRow = this.rows - 1;
  if ((startRow > endRow) || (startRow < 0) || (startRow >= this.rows) || (endRow < 0) || (endRow >= this.rows)) {
    throw new RangeError('Argument out of range');
  }

  var newMatrix = Matrix(endRow - startRow + 1, indices.length, {dtn:this.dtn});
  for (var i = 0; i < indices.length; i++) {
    for (var j = startRow; j <= endRow; j++) {
      if (indices[i] < 0 || indices[i] >= this.columns) {
        throw new RangeError('Column index out of range: '+indices[i]);
      }
      newMatrix.set(j - startRow, i, this.get(j, indices[i]));
    }
  }
  return newMatrix;
}


Matrix.prototype.subRowVector = function (vector) {
  this.checkMatrixDims(2);
  vector = this.checkRowVector(vector);
  for (var i = 0; i < this.rows; i++) {
    for (var j = 0; j < this.columns; j++) {
      this.set(i, j, this.get(i, j) - vector[j]);
    }
  }
  return this;
}

Matrix.prototype.setSubMatrix = function (matrix, startRow, startColumn) {
  matrix = this.checkMatrix(matrix);
  this.checkMatrixDims(2);
  matrix.checkMatrixDims(2);
  var endRow = startRow + matrix.rows - 1;
  var endColumn = startColumn + matrix.columns - 1;
  this.checkRange(startRow, endRow, startColumn, endColumn);
  for (var i = 0; i < matrix.rows; i++) {
    for (var j = 0; j < matrix.columns; j++) {
      this.set(startRow + i,startColumn + j) = matrix.get(i, j);
    }
  }
  return this;
}

Matrix.prototype.sum = function (by) {
  var i,j,k,v=0;
  switch (by) {
    case 'row':
      return this.sumByRow();
    case 'column':
      return this.sumByColumn();
    default:
      switch (this.dtn+this.dims) {
        case 'Array1':
          for (i = 0; i < this.columns; i++) {
              v += this.data[i];
          }
          break;
        case 'Array2':
          for (i = 0; i < this.rows; i++) {
            for (j = 0; j < this.columns; j++) {
              v += this.data[i][j];
            }
          }
          break;
        case 'Array3':
          for (i = 0; i < this.rows; i++) {
            for (j = 0; j < this.columns; j++) {
              for (k = 0; k < this.levels; k++) {
                v += this.data[i][j][k];
              }
            }
          }
          break;
        default:
          for (i = 0; i < this.size; i++) v += this.data[i];
      }
      return v;
  }
}

Matrix.prototype.sumByRow = function () {
  var sum = Matrix.zeros(this.rows, 1);
  for (var i = 0; i < this.rows; ++i) {
    for (var j = 0; j < this.columns; ++j) {
      sum.set(i, 0, sum.get(i, 0) + this.get(i, j));
    }
  }
  return sum;
}

Matrix.prototype.sumByColumn = function() {
  var sum = Matrix.zeros(1, this.columns);
  for (var i = 0; i < this.rows; ++i) {
    for (var j = 0; j < this.columns; ++j) {
      sum.set(0, j, sum.get(0, j) + this.get(i, j));
    }
  }
  return sum;
}

Matrix.prototype.toArray = function (rx,ry,rz) {
  switch (this.dims) {
    case 1: return Array.from(this.data);
    case 2: todo('toArray 2')
    case 3: todo('toArray 3')
  }
}


Matrix.ALL=ALL;
Matrix.FOREACH=FOREACH;
Matrix.FORALL=FORALL;

module.exports = Matrix 
};
BundleModuleCode['ui/app/app']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2006-2021 bLAB
 **    $CREATED:     14-08-17 by sbosse.
 **    $VERSION:     1.8.2
 **
 **    $INFO:
 **
 **     Termnial GUI toolkit supporting pages using blessed/curses library.
 **     UI layout is similar to mobile application programs.
 **     A set of styles can defined used as default styles for various elements.
 **
 **     type styles = {
 **       button?:{fg?,bg?,border?},
 **       checkbox?:{},
 **       dialog?:{fg?,bg?},
 **       filemanager?:{fg?,bg?,arrows?:{fg?,bg?},border?,box:{bg?,border?},
 **                     label?:{fg?,bg?},input?:{fg?,bg?}},
 **       input:{fg?,bg?,border?,label?},
 **       keyboard?:{bg?,border?,label?:{fg,bg}},
 **       label:{bold?,fg?,bg?},
 **       
 **   New: UI Builder from compact template
 **
 **  Template (builder) Type Interface
 **  =================================
 **
 **  (@: smybolic identifier, parameter name, or wildcard place holder, $: type macro, ?:optional, ..:more,
 **   st {a,b,..}: sub type of object type st)
 **  
 **   typeof @border = { type? : string = 'line'|'none'|.., fg:string is color, .. }
 **    
 **   typeof @styles = {
 **     button?: { fg?, bg?, border? },
 **     input?:  { border? },
 **     tree?:   { border? },
 **     ..,
 **     @customstyle : { .. },
 **     ..
 **   }
 **
 ** Widget event handler:
 **
 ** type handler = function (wname:string,wval:boolean|number|string)
 ** type handlerT = function (wname:string,label:string,path:string,data:{name:string,parent:node,..})
 ** type handlerL = function (wname:string,label:string,data:{content:string,..})
 ** type handler1 = function (wname:string) is check handler
 ** type handlerp = function (pname:string) is page handler
 ** type handlerd = function (wname:string,data:*)
 **
 ** Widget styles and attributes:
 **
 ** $pos = top:number | left:number | right:number | center:boolean
 ** $geom = width:number | hight:number
 ** $cstyle = fg:string | bg:string 
 **
 ** Widget descriptor types:
 **
 ** type button =     { type='button', $pos?, $geom?, content:string, $cstyle, .. , onclick?:handler }
 ** type label =      { type='label', $pos?, $geom?,content:string, .. }
 ** type info =       { type='info', $pos?, $geom?, label:string, value?:string, .. }
 ** type checkbox =   { type='checkbox', $pos?, $geom?, text:string, value:string, .. , onclick?:handler, on?: on {check,uncheck} }
 ** type input =      { type='input', $pos, $geom?, wrap?:boolean, label:string, value?:string, onchange?:handler, .. }
 ** type radiobutton = { type='radiobutton', $pos, $geom?, .. }
 ** type group =      { type='group', name?:string, @but1:radiobutton, @but2:radiobutton, .., onclick?:handler }
 ** type tree =       { type='tree', $pos, $geom, .. ,  onclick?:handlerT }
 ** type list =       { type='list', depth?:number, $pos, $geom, .. , onclick?:handlerL }
 **
 ** type widget = button | label | checkbox | input | group | tree | ..
 **
 ** The builder creates widget objects from descriptors that can be accessed by UI.pages.@name.
 **
 ** method (object of info).update (string) 
 ** method (object of tree).update ({}) 
 ** method (object of info).update (string) 
 ** method (object of info).setValue (string) 
 ** method (object of input).getValue () 
 ** method (object of widget).setStyle ({})
 **
 ** type on = { click?:handler|string, onclick?:handler|string, 
 **             check?:handler1, uncheck?:handler1, 
 **             keypress:{key: string | string [], handler: fucntion (@char,@key)] [],
 **             selected?:handlerd, change?:handler,
 **             show?:handlerp, hide?: handlerp }
 ** 
 ** $pageparam = next:string | prev:string | on: on {show,hide} | show:function | hide:function
 ** $widget = @name : widget
 **
 ** typeof content = {
 **   pages : { main: { $widget, $widget, .. , $pageparam, $pageparam, .. }, @p2: { @widget, .. }, .. }
 **   info?: {}
 ** }
 **
 ** UI creation (w/o builder, programmatically):
 ** ============================================
 **
 **   var ui = UI.UI({
 **     pages   : number,
 **     styles  : {},
 **     terminal: '',
 **     title   : string
 **   });
 **   ui.pages[index]={
 **     id$    : ui.<constructor>(options),
 **   }
 **   ui.pages[index]["$id"].on(event,handler)
 **
 **   ui.start(main?)
 **
 ** Supported Widget Constructors 
 ** =============================
 **
 **    Button:   button({left,right,top,width,height:number,content:string, action:function|navigation string, styles,....}) -> button
 **    Chat Dialog:  chat({left,right,top,width,height:number,prompt:string,label:string,bot:style,user:style, styles,.. }}
 **    Checkbox: checkbox(left,right,top,width,height:number,content:string, value:boolean, action:function, styles,....})
 **    Info:     info({left,right,top,bottom,width,height:number,multiline:boolean,wrap:boolean,label:string, styles,.. }}
 **    Input:    input({left,right,top,width,height:number,multiline:boolean,wrap:boolean,label:string, styles,..}}
 **    Label:    label({left,right,top,center:boolean, width,height:number,content:string, styles,..}}
 **    Radiobut: radiobutton(left,right,top,width,height:number,content:string, group:number|string, value:boolean, action:function, styles,....})
 **    Status:   status({left,right,top,bottom,width,height:number,multiline:boolean,wrap:boolean,label:string, styles,..}}
 **    Text:     text({left,right,top,width,height:number,multiline:boolean,wrap:boolean,scrollable:boolean,label:string, styles,..}}
 **    Terminal: terminal({left,right,top,width,height:number,prompt:string,label:string, styles,..}}
 ** 
 **    type styles = {fg:string,bg:string, bold:boolean, ..}
 **    type navigation string = 'prev'|'next'|pageid
 **
 **    type checkbox'event = select:boolean 
 **    type radiobutton'event = select:boolean , check:object 
 **    type chat'event = eval:string 
 **    
 **    methods chat = { message:function (string), ask:function(string,options,callback), }
 **    methods input = { setValue:function (string), getValue:function(), }
 **    
 **
 ** ---
 **
 **  Examples (with template builder, see also ui/app/demo2.js)
 **
 **  UIApp = Require('ui/app/app');
 **  UI = UIApp.UI({});
 **  UI.init();
 **  styles = {}
 **  content = { pages : {
 **    main: { 
 **     lab1: {type:'label',  center:true, top:1, content:'Menu'},
 **     but1: {type:'button', left:1,  content:'QUIT',  bg:'red', onclick: process.exit },
 **     but2: {type:'button', right:1, content:'SETUP', onclick:'setup' },
 **    },
 **    setup: {  
 **     lab1: {type:'label',  center:true, top:1, content:'Menu'},
 **     but1: {type:'button', left:1,  content:'<< MENU',  onclick:'main' },
 **    }
 **  }}
 **  UI.builder(styles,content)
 **  UI.start()
 **
 ** ----------------------
 **
 **  Examples (without builder, programmatically):
 **
 **   ui=UIApp.UI({
 **     pages:7,
 **     terminal:this.options.terminal||'xterm-color',
 **     title:'JAMAPP (C) Stefan Bosse'
 **   });
 **   ui.init(); 
 **   page=ui.pages[1];
 **   page.b1= ui.button({left:1,content:'QUIT'});
 **   page.b1.on('press', function(data) {
 **    return process.exit(0);  
 **   });   
 **   page.l1 = ui.label({center:true,top:1,content:'Menu'});
 **   page.b2 = ui.button({right:1,content:'SETUP'});
 **   page.b2.on('press', function(data) {
 **     ui.pages.hide(1);    
 **     ui.pages.show(2);
 **   });
 **   // or
 **   page.b2.on('press', function(data) {
 **     ui.pages.hide();  
 **     ui.pages.show('next');
 **   });
 **
 **   page.l2 = ui.label({center:true,top:1,content:'Setup'});
 **   page.i1 = ui.input({top:4,left:4,label:'Broker IP Address',value:'localhost'});
 **   page.i1.setValue('127.0.0.1');
 **   url=page.i1.getValue();
 **
 **   page.i4 = ui.info({top:16,left:4,width:ui.screen.width-8,label:'JAM Status'});
 **
 **   page.l2 = ui.label({left:4,top:16,content:'Protocol'});
 **   page.ch21 = ui.radiobutton({left:4,top:18,text:'HTTP',value:false,group:2});
 **   page.ch22 = ui.radiobutton({left:4,top:20,text:'TCPIP',value:true,group:2});
 **
 **   page.l3 = ui.label({left:4,top:22,content:'Messages'});
 **   page.ch31 = ui.checkbox({left:4,top:24,text:'Agent ID',value:false});
 **   page.ch32 = ui.checkbox({left:4,top:26,text:'Parent ID',value:false});
 **
 **    $ENDOFINFO
 */
var Io = Require('com/io');
var Comp = Require('com/compat');
var blessed = Require('term/blessed');

var options = {
  version: '1.8.2'
}

/** pre-defined layouts based on screen rows and columns
 *
 */
var LAYOUT = {
  SMALL:'small',
  NORMAL:'normal',
  LARGE:'large',
  XLARGE:'xlarge',
  PORTRAIT:'portrait',
  LANDSCAPE:'landscape',
  from: function (screen) {
    if (screen.width>screen.height) {
      if(screen.width<70) return {small:true,landscape:true};
      else return {normal:true,landscape:true};
    } else {
      if(screen.height<70) return {small:true,portrait:true};
      else return {normal:true,portrait:true};
    } 
  }
}

/** Main User Interface providing a multiple page view
 *  Events: pages -> 'load'
 *  typeof options = {pages,terminal,forceCursor,styles}
*/ 
function UI(options) {
  var self=this,i;
  if (!(this instanceof UI)) {
    return new UI(options);
  }
  this.options=options||{};
  if (!this.options.pages) this.options.pages=1;
  if (!this.options.terminal) {
    this.options.terminal=
      (process.platform === 'win32' ? 'windows-ansi' : 'xterm-color');
  }
  if (this.options.forceCursor==undefined) this.options.forceCursor=true;
  
  this.page = 1;
  this.pages = [];           // Pages
  this.static = {};          // Top-level widegts visible on all pages
  this.styles=options.styles||{};
  
  for(i=0;i<=this.options.pages;i++) {
    this.pages.push({});
  }
  this.pages.show = function (page) {
    var thepage,p,current=self.page;
    switch (page) {
      case 'prev': 
        if (self.pages[self.page] && self.pages[self.page].prev) page=self.pages[self.page].prev;
        else if (self.pages[self.page-1]) page=self.page-1;
        break;
      case 'next': 
        if (self.pages[self.page] && self.pages[self.page].next) page=self.pages[self.page].next; 
        else if (self.pages[self.page+1]) page=self.page+1;
        break;
      case 'this': 
      case undefined:
        page=self.page; break;
    }
    thepage=self.pages[page];
    if (self.events[page] && self.events[page]['load'])
      self.events[page]['load']();
    for (p in thepage) {
      if (thepage[p] && thepage[p].show && !thepage[p].noshow) thepage[p].show();
    }
    self.screen.render();
    self.page=page;
  };
  this.pages.hide = function (page) {
    var thepage,p;
    if (page=='this' || page==undefined) page=self.page;
    thepage=self.pages[page];
    for (p in thepage) 
      if (thepage[p] && thepage[p].hide) thepage[p].hide();
    // hide cursor
    if (self.options.terminal.indexOf('xterm') != -1 && self.options.forceCursor)
       self.screen.program.hideCursor(true);
  };
  this.events=[];
  this.pages.on = function(page,event,callback) {
    if (!self.events[page]) self.events[page]=[];
    self.events[page][event]=callback;
  }
}

/** Button widget
 *  Methods: on
 *  Events: 'press'
 *  typeof @options ={
 *    width,content,center,left,right,top,
 *    fg is textcolor,bg is button color,border,
 *    click:function, 
 *    action:function|string
 *   }
 */
UI.prototype.button = function(options) {
  var self=this,width=options.width;
  if (Comp.obj.isString(options.width)) {
    // relative value in %!
    width=Comp.pervasives.int_of_string(options.width);
    width=this.screen.width*width/100;
  }
  var obj = blessed.button({
    width: options.width||(options.content.length+4),
    left: (options.center?int(this.screen.width/2-width/2):options.left),
    right : options.right,
    top: options.top||0,
    height: 3,
    align: 'center',
    content: options.content||'?',
    mouse:true,
    focus:false,
    border: options.border||this.getStyle('button.border',{
      type: 'line'
    }),
    style: {
      fg: options.fg||this.getStyle('button.fg','white'),
      bg: options.bg||this.getStyle('button.bg','blue'),
      bold:true,
      border: {
        fg: this.getStyle('button.border.fg','black'),
        bg: this.getStyle('button.border.bg',undefined),
      },
      hover: {
        border: {
          fg: 'red'
        }
      }
    }  
  });
  obj.noshow=options.hidden;
  this.screen.append(obj);
  // generic handler
  if (options.click) obj.on('press',options.click);
  // dedicated action handlers with page navigation support
  if (options.action) {
    switch (typeof options.action) {
      case 'function': obj.on('press',options.action); break;
      case 'string': 
        if (options.action=='next' || options.action=='prev') {
          obj.on('press',function () {
            if (self.pages[self.page][options.action]) {
              self.pages.hide('this');
              self.pages.show(self.pages[self.page][options.action]);
            }
          });
        } else {
         obj.on('press',function () {
            if (self.pages[options.action]) {
              self.pages.hide('this');
              self.pages.show(options.action);
            }
          });
        
        }
        break;
    }
  }
  return obj;
}

/** Chat terminal shell widget
 * typeof options = {top,left,right,width,height,label:string,bot:style,user:style,fg:style,bg:style}
 */
UI.prototype.chat = function(options) {
  var self=this, width=options.width||(this.screen.width-(options.left*2||2));
  options.scrollable=true;
  options.scrollbar = {
        ch: ' ',
        track: {
          bg: 'yellow'
        },
        style: {
          fg: 'cyan',
          inverse: true
        }
      };
      options.mouse=true;
  var obj = blessed.chat({
    label: options.label||'My Text',
    value: options.value||'',
    //fg: 'blue',
    bg: 'default',
    barBg: 'default',
    barFg: 'blue',
    width: width,
    height: options.height||8,
    left: (options.center?int(this.screen.width/2-width/2):options.left),
    right : options.right,
    top: options.top||0,
    keys: true,
    vi: false,
    mouse: true,
    inputOnFocus: true,
    tags:true,
    focus:true,
    wrap        : options.wrap,
    multiline   : true,
    scrollbar   : options.scrollbar,
    scrollable  : options.scrollable,
    //draggable:true,
    prompt:options.prompt,
    border: this.getStyle('input.border',{
      type: 'line'
    }),
    style: {
      fg: options.fg||this.getStyle('input.fg','blue'),
      bg: options.bg||this.getStyle('input.bg',undefined),
      user : options.user,
      bot  : options.bot,
      border: {
        fg: this.getStyle('input.border.fg','black'),
        bg: this.getStyle('input.border.bg',undefined),
      },
      label:this.getStyle('input.label',undefined),
      focus : {
        border: {
          fg: 'red'
        }      
      }
    }
  });
  obj.noshow=options.hidden;
  this.screen.append(obj);
  obj.on('focus',function () {
    if (!self.options.keyboard) // show cursor
      return self.options.terminal.indexOf('xterm')!=-1 && self.options.forceCursor?
             self.screen.program._write('\x1b[?12;25h'):0; 
    if (!self._keyboard) 
      self._keyboard=self.keyboard({
        width:self.screen.width<60?'100%':'80%',
        height:self.layout.small?'100%':'90%',
        compact:self.layout.small
      });
    self._keyboard.setCallback(function (v) {if (v) obj.setValue(v),obj.update();});
    self._keyboard.setValue(obj.getValue());
    self._keyboard.setLabel(obj.getLabel());
    self._keyboard.show();
  });
  return obj;
}

/** Checkbox widget
 *  Methods: on
 *  Events: 'check','uncheck'
 *  typeof @options ={value?,left?,right?,top?,text,hidden?}
 */
UI.prototype.checkbox = function(options) {
  var obj = blessed.checkbox({
    checked: options.value||false,  
    left: options.left,
    right : options.right,
    top: options.top||0,
    mouse: true,
    inputOnFocus: true,
    height: 1,
    text:options.text||'empty'
  });
  obj.noshow=options.hidden;
  this.screen.append(obj);
  return obj;
}

/** Dialog pop-up window widget
 *
 *  typeof @options = {width,height,center?,left?,right?,top?,okButton?,cancelButton}
 */
UI.prototype.dialog = function(options) {
  var width=options.width,height=options.height;
  if (Comp.obj.isString(options.width)) {
    // relative value in %!
    width=Comp.pervasives.int_of_string(options.width);
    width=int(this.screen.width*width/100);
  }
  if (Comp.obj.isString(options.height)) {
    // relative value in %!
    height=Comp.pervasives.int_of_string(options.height);
    height=int(this.screen.height*height/100);
  }
  var obj = blessed.Question({
    width: width,
    left: (options.center?int(this.screen.width/2-width/2):options.left),
    right : options.right,
    top: options.top||(options.center?int(this.screen.height/2-height/2):0),
    height: height,
    noshow:true,
    okButton     : options.okButton||'Okay',
    cancelButton : options.cancelButton,
    style: {
      bg:this.getStyle('dialog.bg','red'),
      fg:this.getStyle('dialog.fg','white'),
      bold:true
    }  
  });
  this.screen.append(obj);
  return obj;
}

/** File manager widget with buttons
 *
 *  typeof @options={fg?,bg?,parent?,border?,label?,height?,width?,top?,left?,autohide?,
 *                   okayBotton?,cancelButton?,input?,box?,arrows?}
 */
UI.prototype.fileManager = function(options) {
  if (options.box) {
    options.box.border=this.getStyle('filemanager.box.border',options.box.border);
    options.box.bg=this.getStyle('filemanager.box.bg',options.box.bg);
    options.input=this.getStyle('filemanager.input',options.input);
  }
  if (options.arrows) {
    options.arrows.fg=this.getStyle('filemanager.arrows.fg',options.arrows.fg);
    options.arrows.bg=this.getStyle('filemanager.arrows.bg',options.arrows.bg);
  }
  var obj = blessed.FileManager({
    parent:options.parent,
    border:options.border||this.getStyle('filemanager.border',{}),
    style: {
      fg: options.fg||this.getStyle('filemanager.fg',undefined),
      bg: options.bg||this.getStyle('filemanager.bg',undefined),
      label:options.label||this.getStyle('filemanager.label',undefined),
      selected: {
        bg: 'blue',
        fg:'white'
      },
      focus: {
        border: {
          fg: 'red'
        }
      }
    },
    height: options.height||'half',
    width: options.width||'half',
    top: options.top||'center',
    left: options.left||'center',
    label: '%path',
    cwd: options.cwd||process.env.PWD||process.env.CWD||process.env.HOME,
    autohide:options.autohide,
    hidden:options.hidden,
    noshow:options.hidden, // no show on page load
    keys: true,
    vi: true,
    scrollbar: {
      bg: 'white',
      ch: ' '
    },
    okayButton:options.okayButton||'OK',
    cancelButton:options.cancelButton||'Cancel',
    input:options.input,
    arrows:options.arrows,
    box:options.box,
    border:this.getStyle('filemanager.border',undefined)
  });
  
  this.screen.append(obj);
  return obj;
}

/** Filter supported widget options, transform special options (click,..). Used by page builder.
 *
 */
UI.prototype.filterOptions = function(options,wname) {
  var self=this,attr,wopts = {};
  for (attr in options) {
    switch (attr) {
      case 'type':
      case 'index':
        break;
      case 'on':
        switch (options.type) {
          case 'button' :
            if (options.on.click && typeof options.on.click == 'function') 
              wopts.click=function () { options[attr](wname) };
            if (options.on.onclick && typeof options.on.onclick == 'function') 
              wopts.click=function () { options[attr](wname) };
            break;
        }
        break;
      case 'click':
      case 'onclick':
        switch (options.type) {
          case 'button' :
            if (typeof options[attr] == 'string') // Its a page destination; show new page
              wopts.click=function () {
                if (!self.pages[options[attr]]) return;
                self.pages.hide('this');    
                self.pages.show(options[attr]);
              };
            else
              wopts.click=options[attr];
            break;
        }
        break;
      default:
        wopts[attr]=options[attr];
    }
  }
  return wopts;
}

/** getStyle
 *
 */
UI.prototype.getStyle = function(attr,def) {
  var path=attr.split('.'),elem,style=this.styles;
  while(path.length && style) {
    elem=path.shift();
    style=style[elem];
  }
  return style!=undefined?style:def;
}

/** Information message widget
 * Methods: setValue
 * typeof options = {width,top,left,right, height,label,wrap,multiline,scrollable,color}
 */
UI.prototype.info = function(options) {
  var width=options.width;
  if (Comp.obj.isString(options.width)) {
    // relative value in %!
    width=Comp.pervasives.int_of_string(options.width);
    width=this.screen.width*width/100;
  }
  if (options.scrollable && !options.scrollbar) {
      options.scrollbar = {
        ch: ' ',
        track: {
          bg: 'yellow'
        },
        style: {
          fg: 'cyan',
          inverse: true
        }
      };
      options.mouse=true;
  }
  var obj = blessed.textbox({
    top   : options.top||1,
    left  : (options.center?int(this.screen.width/2-width/2):options.left||(options.right?undefined:1)),
    right : options.right,
    width : options.width||(this.screen.width-(options.left*2||2)),
    height: options.height||3,
    label : options.label,
    value : options.value||'',
    focus : true,
    wrap  : options.wrap,
    multiline   : options.multiline,
    scrollbar  : options.scrollbar,
    scrollable  : options.scrollable,
    mouse       : options.mouse,
    //draggable:true,
    border: this.getStyle('info.border',{
      type: 'line'
    }),
    style: {
      fg:options.fg||this.getStyle('info.fg','blue'),
      bg: options.bg||this.getStyle('info.bg',undefined),
      label:this.getStyle('info.label',undefined),
      border: {
        fg: this.getStyle('info.border.fg','black'),
        bg: this.getStyle('info.border.bg',undefined),
      },
    }
  });
  
  obj.noshow=options.hidden;
  this.screen.append(obj);
  return obj;
}

/** Initialite APP and create screen
 *
 */
UI.prototype.init = function () {
  var self=this;
  // Information bar visible on all pages
  this.screen = blessed.screen({
    smartCSR: false,
    terminal: this.options.terminal,
    forceCursor:this.options.forceCursor,
    });
  this.screen.title = this.options.title||'APP (C) Stefan Bosse';
  this.screen.cursor.color='red';  
  this.layout=LAYOUT.from(this.screen);
  // restore cursor
  if (this.options.terminal.indexOf('xterm') != -1 && this.options.forceCursor) 
    process.on('exit',function () {self.screen.program._write('\x1b[?12;25h')});
}

/** Input field widget
 * typeof options = {top,left,right,width,height,label,value}
 * method getValue, setValue
 * events: {'set content'}
 */
UI.prototype.input = function(options) {
  var self=this, width=options.width||(this.screen.width-(options.left*2||2));
  var obj = blessed.textbox({
    label: options.label||'My Input',
    value: options.value||'',
    //fg: 'blue',
    bg: 'default',
    barBg: 'default',
    barFg: 'blue',
    width: width,
    height: options.height||3,
    left: (options.center?int(this.screen.width/2-width/2):options.left),
    right : options.right,
    top: options.top,
    bottom: options.bottom,
    keys: true,
    mouse: true,
    inputOnFocus: true,
    focus:true,
    wrap:options.wrap,
    multiline:options.multiline,
    //draggable:true,
    border: this.getStyle('input.border',{
      type: 'line'
    }),
    style: {
      fg: options.fg||this.getStyle('input.fg','blue'),
      bg: options.bg||this.getStyle('input.bg',undefined),
      border: {
        fg: this.getStyle('input.border.fg','black'),
        bg: this.getStyle('input.border.bg',undefined),
      },
      label:this.getStyle('input.label',undefined),
      focus : {
        border: {
          fg: 'red'
        }      
      }
    }
  });
  obj.noshow=options.hidden;
  this.screen.append(obj);
  obj.on('focus',function () {
    if (!self.options.keyboard) // show cursor
      return self.options.terminal.indexOf('xterm')!=-1 && self.options.forceCursor?
             self.screen.program._write('\x1b[?12;25h'):0; 
    if (!self._keyboard) 
      self._keyboard=self.keyboard({
        width:self.screen.width<60?'100%':'80%',
        height:self.layout.small?'100%':'90%',
        compact:self.layout.small
      });
    self._keyboard.setCallback(function (v) {if (v) obj.setValue(v),obj.update();});
    self._keyboard.setValue(obj.getValue());
    self._keyboard.setLabel(obj.getLabel());
    self._keyboard.show();
  });
  return obj;
}

/** Software keyboard widget
 *
 *  typeof options = { left,top, width, height, compact, okayButton, cancelButton, border, }
 */
UI.prototype.keyboard = function(options) {
  var obj = blessed.keyboard({
    parent:options.parent||this.screen,
    border: 'line',
    height: options.height||'half',
    width: options.width||'half',
    top: options.top||'center',
    left: options.left||'center',
    label: 'Keyboard',
    hidden:options.hidden,
    compact:options.compact,
    okayButton:options.okayButton||'OK',
    cancelButton:options.cancelButton||(this.layout.small?'CAN':'Cancel'),
    delButton:'DEL',
    shiftButton:'>>',
    border:options.border||this.getStyle('keyboard.border',{}),
    style:{
      bg: options.bg||this.getStyle('keyboard.bg',undefined),
      label:options.label||this.getStyle('keyboard.label',undefined),
    }
  });
  this.screen.append(obj);
  return obj;  
}

/** Generic label widget
 * method setValue(string)|mutable=true
 * typeof options = {width?,left?,right?,top?,center?,mutable?,content}
 */
UI.prototype.label = function(options) {
  var obj = blessed.text({
    width: options.width||(options.content.length),
    left: (options.center?int(this.screen.width/2-options.content.length/2):options.left),
    right : options.right,
    top: options.top||0,
    height: options.height||1,
    focus:false,
    align: 'center',
    content: options.content||'?',
    style: {
      bg:options.style?options.style.bg:this.getStyle('label.bg',undefined),
      fg:options.style?options.style.fg:this.getStyle('label.fg',undefined),
      bold:this.getStyle('label.bold',false)
    }  
  });
  if (options.mutable) 
    obj.setValue = function (content) {
      obj.setContent('');
      obj.position.left=(options.center?int(this.screen.width/2-content.length/2):options.left);
      obj.setContent(content);
    };
    
  obj.noshow=options.hidden;
  this.screen.append(obj);
  return obj;
}

/** Generic list navigator widget with scrollbar
 * typeof options = {top,left,width,height,label}
 * method set(data:object|array)
 */
UI.prototype.list = function(options) {
  var obj =  blessed.list({
    top: options.top,
    left: options.left,
    width: options.width||(this.screen.width-options.left*2),
    height: options.height||(this.screen.height-options.top-4),
    label: options.label||'Log',
    focus:true,
    mouse:true,
    keys:true,
    arrows:options.arrows,
    border: this.getStyle('list.border',{
      type: 'line'
    }),
    style: {
      bg: options.bg||this.getStyle('list.bg',undefined),
      selected:options.selected||{fg:'white',bg:'red',bold:true},
      item:options.item||{bold:true},
      border: {
        fg: this.getStyle('list.border.fg','black')
      },
      label:this.getStyle('list.label',undefined),
      hover: {
        border: {
          fg: 'red'
        }
      },
      focus : {
        border: {
          fg: 'red'
        }      
      }
    }
  });
  obj.noshow=options.hidden;
  obj.set = obj.update = function (data) {
    var p,items=[];
    obj.clearItems();
    if (Comp.obj.isArray(data)) items=data;
    else for (p in data) {
      items.push(p);
    }
    obj.setItems(items);
    obj.screen.render();
  }
  this.screen.append(obj);
  return obj;
}

/** Log message widget with scrollbar
 * typeof options = {left,top,width,height,label,scrollback,..}
 */
UI.prototype.log = function(options) {
  if (options.top == undefined) options.top=2;
  if (options.left == undefined && options.right==undefined) options.left=1;
  var obj = blessed.log({
    top: options.top,
    left: options.left,
    right: options.right,
    width: options.width||(this.screen.width-options.left*2),
    height: options.height||(this.screen.height-options.top-4),
    label: options.label||'Log',
    mouse:true,
    keys:true,
    scrollback:options.scrollback||100,
    border: this.getStyle('log.border',{
      type: 'line'
    }),
    scrollbar: {
      ch: ' ',
      track: {
        bg: 'yellow'
      },
      style: {
        fg: 'cyan',
        inverse: true
      }
    },
    alwaysScroll:true,
    scrollOnInput:true,
    style: {
      fg: options.fg||this.getStyle('log.fg','white'),
      bg: options.bg||this.getStyle('log.bg','black'),
      label:this.getStyle('log.label',undefined),
      border: {
        fg: this.getStyle('log.border.fg','green'),
        bg: this.getStyle('log.border.bg',undefined),
      },
      focus: {
        border: {
          fg: 'red'
        }
      }
    },
    arrows:options.arrows,
  });
  obj.noshow=options.hidden;
  this.screen.append(obj);
  return obj;
}

/** Apply post option actions (event handling)
 *
 */
UI.prototype.postOptions = function(options,widget,wname) {
  var self=this,attr;
  for (attr in options) {
    switch (attr) {
      case 'on':
        switch (options.type) {
          case 'checkbox' :
            if (options.on.click && typeof options.on.click == 'function') 
              widget.on('check',function () { options.on.click(wname,true) }),
              widget.on('uncheck',function () { options.on.click(wname,false) });
            if (options.on.onclick && typeof options.on.onclick == 'function') 
              widget.on('check',function () {  options.on.onclick(wname,true) }),
              widget.on('uncheck',function () {  options.on.onclick(wname,false) });
            if (options.on.check && typeof options.on.check == 'function') 
              widget.on('check',function () { options.on.check(wname,true) });
            if (options.on.uncheck && typeof options.on.uncheck == 'function') 
              widget.on('uncheck',function () { options.on.uncheck(wname,false) });
            if (options.on.selected && typeof options.on.selected == 'function') 
              widget.on('selected',function (data) { options.on.uncheck(wname,data) });
            if (options.on.change && typeof options.on.change == 'function') 
              widget.on('change',function (data) { options.on.change(wname,data) });
            break;
        }
        break;
      case 'click':
      case 'onclick':
        switch (options.type) {
          case 'checkbox' :
            if (typeof options[attr] == 'function') 
              widget.on('check',function () { options[attr](wname,true) }),
              widget.on('uncheck',function () { options[attr](wname,false) })
            break;
          case 'list':
            if (typeof options[attr] == 'function') 
              widget.on('selected',function (data) { options[attr](wname,data.content,data) })
            break;       
          case 'tree':
            if (typeof options[attr] == 'function') 
              widget.on('selected',function (data) { 
                  var _data=data,path=data.name;
                  data=data.parent;
                  while(data) 
                    path=data.name+(data.name!='/'?'/':'')+path,
                    data=data.parent;                
                  options[attr](wname,_data.name,path,_data) 
              })
            break;       
        }
        break;
      case 'onchange':
        switch (options.type) {
          case 'input' :
            if (typeof options[attr] == 'function') 
              widget.on('change',function (data) {
                var content = widget.getContent();
                options[attr](wname,content) 
              })
            break;
        }      
        break;
    }
  }
}

/** Radio button widget; can be grouped
 *
 */
UI.prototype.radiobutton = function(options) {
  var obj = blessed.radiobutton({
    checked: options.value||false,  
    left: options.left,
    right : options.right,
    top: options.top||0,
    group:options.group,
    mouse: true,
    inputOnFocus: true,
    height: 1,
    text:options.text||'empty'
  });
  obj.noshow=options.hidden;
  this.screen.append(obj);
  return obj;
}


UI.prototype.start = function (main) {
  var self=this;
  if (main==undefined) main=this.pages.main?'main':null;
  Object.keys(this.pages).forEach(function (page) {
    if (typeof self.pages[page] != 'object') return;
    if (Object.keys(self.pages[page]).length==0) return;
    self.pages.hide(page);
    if (!main) main=page;
  });
  if (main) this.pages.show(main);
  this.screen.render();
  this.screen.program.hideCursor(this.options.terminal.indexOf('xterm') != -1 && this.options.forceCursor);
}

/** Status field widget
 * typeof options = {top,bottom,left,right,width,height,label,value}
 * method  getvalue, setValue
 * events: {'set content'}
 */
UI.prototype.status = function(options) {
  var self=this, width=options.width||(this.screen.width-(options.left*2||2));
  var obj = blessed.textbox({
    label: options.label||'My Input',
    value: options.value||'',
    //fg: 'blue',
    bg: 'default',
    barBg: 'default',
    barFg: 'blue',
    width: width,
    height: options.height||3,
    left: (options.center?int(this.screen.width/2-width/2):options.left),
    right : options.right,
    top: options.top,
    bottom: options.bottom,
    wrap:options.wrap,
    multiline:options.multiline,
    //draggable:true,
    border: this.getStyle('input.border',{
      type: 'line'
    }),
    style: {
      fg: options.fg||this.getStyle('input.fg','blue'),
      bg: options.bg||this.getStyle('input.bg',undefined),
      border: {
        fg: this.getStyle('input.border.fg','black'),
        bg: this.getStyle('input.border.bg',undefined),
      },
      label:this.getStyle('input.label',undefined),
      focus : {
        border: {
          fg: 'red'
        }      
      }
    }
  });
  obj.noshow=options.hidden;
  this.screen.append(obj);
  return obj;
}

/** Table widget
 * typeof options = {left,right,top,width,height,label,header,cell,data,..}
 */
UI.prototype.table = function(options) {
  if (options.top == undefined) options.top=2;
  if (options.left == undefined && options.right==undefined) options.left=1;
  var obj = blessed.table({
    top: options.top,
    left: options.left,
    right: options.right,
    width: options.width||(this.screen.width-options.left*2),
    height: options.height||(this.screen.height-options.top-4),
    label: options.label||'Table',
    data: options.data,
    border: this.getStyle('table.border',{
      type: 'line'
    }),
    style: {
      fg: options.fg||this.getStyle('log.fg','white'),
      bg: options.bg||this.getStyle('log.bg','black'),
      label:this.getStyle('log.label',undefined),
      border: {
        fg: this.getStyle('log.border.fg','green'),
        bg: this.getStyle('log.border.bg',undefined),
      },
      header : options.header,
      cell   : options.cell,
      focus: {
        border: {
          fg: 'red'
        }
      }
    },
  });
  obj.noshow=options.hidden;
  this.screen.append(obj);
  return obj;
}

/** Terminal shell widget
 * typeof options = {top,left,right,width,height,label,header:style,cell:style}
 */
UI.prototype.terminal = function(options) {
  var self=this, width=options.width||(this.screen.width-(options.left*2||2));
  options.scrollable=true;
  if (options.scrollable && !options.scrollbar) {
      options.scrollbar = {
        ch: ' ',
        track: {
          bg: 'yellow'
        },
        style: {
          fg: 'cyan',
          inverse: true
        }
      };
      options.mouse=true;
  }
  var obj = blessed.terminal({
    label: options.label||'My Text',
    value: options.value||'',
    //fg: 'blue',
    bg: 'default',
    barBg: 'default',
    barFg: 'blue',
    width: width,
    height: options.height||8,
    left: (options.center?int(this.screen.width/2-width/2):options.left),
    right : options.right,
    top: options.top||0,
    keys: true,
    vi: false,
    mouse: true,
    inputOnFocus: true,
    focus:true,
    wrap        : options.wrap,
    multiline   : true,
    scrollbar   : options.scrollbar,
    scrollable  : options.scrollable,
    //draggable:true,
    prompt:options.prompt,
    border: this.getStyle('input.border',{
      type: 'line'
    }),
    style: {
      fg: options.fg||this.getStyle('input.fg','blue'),
      bg: options.bg||this.getStyle('input.bg',undefined),
      border: {
        fg: this.getStyle('input.border.fg','black'),
        bg: this.getStyle('input.border.bg',undefined),
      },
      label:this.getStyle('input.label',undefined),
      focus : {
        border: {
          fg: 'red'
        }      
      }
    }
  });
  obj.noshow=options.hidden;
  this.screen.append(obj);
  obj.on('focus',function () {
    if (!self.options.keyboard) // show cursor
      return self.options.terminal.indexOf('xterm')!=-1 && self.options.forceCursor?
             self.screen.program._write('\x1b[?12;25h'):0; 
    if (!self._keyboard) 
      self._keyboard=self.keyboard({
        width:self.screen.width<60?'100%':'80%',
        height:self.layout.small?'100%':'90%',
        compact:self.layout.small
      });
    self._keyboard.setCallback(function (v) {if (v) obj.setValue(v),obj.update();});
    self._keyboard.setValue(obj.getValue());
    self._keyboard.setLabel(obj.getLabel());
    self._keyboard.show();
  });
  return obj;
}

/** Text field widget
 * typeof options = {top,left,right,width,height,label,value}
 * method getValue, setValue
 * events: {'set content'}
 */
UI.prototype.text = function(options) {
  var self=this, width=options.width||(this.screen.width-(options.left*2||2));
  if (options.scrollable && !options.scrollbar) {
      options.scrollbar = {
        ch: ' ',
        track: {
          bg: 'yellow'
        },
        style: {
          fg: 'cyan',
          inverse: true
        }
      };
      options.mouse=true;
  }
  var obj = blessed.textarea({
    label: options.label||'My Text',
    value: options.value||'',
    //fg: 'blue',
    bg: 'default',
    barBg: 'default',
    barFg: 'blue',
    width: width,
    height: options.height||8,
    left: (options.center?int(this.screen.width/2-width/2):options.left),
    right : options.right,
    top: options.top||0,
    keys: true,
    vi: false,
    mouse: true,
    inputOnFocus: true,
    focus:true,
    wrap        : options.wrap,
    multiline   : true,
    scrollbar   : options.scrollbar,
    scrollable  : options.scrollable,
    //draggable:true,
    border: this.getStyle('input.border',{
      type: 'line'
    }),
    style: {
      fg: options.fg||this.getStyle('input.fg','blue'),
      bg: options.bg||this.getStyle('input.bg',undefined),
      border: {
        fg: this.getStyle('input.border.fg','black'),
        bg: this.getStyle('input.border.bg',undefined),
      },
      label:this.getStyle('input.label',undefined),
      focus : {
        border: {
          fg: 'red'
        }      
      }
    }
  });
  obj.noshow=options.hidden;
  this.screen.append(obj);
  obj.on('focus',function () {
    if (!self.options.keyboard) // show cursor
      return self.options.terminal.indexOf('xterm')!=-1 && self.options.forceCursor?
             self.screen.program._write('\x1b[?12;25h'):0; 
    if (!self._keyboard) 
      self._keyboard=self.keyboard({
        width:self.screen.width<60?'100%':'80%',
        height:self.layout.small?'100%':'90%',
        compact:self.layout.small
      });
    self._keyboard.setCallback(function (v) {if (v) obj.setValue(v),obj.update();});
    self._keyboard.setValue(obj.getValue());
    self._keyboard.setLabel(obj.getLabel());
    self._keyboard.show();
  });
  return obj;
}

/** Generic data object tree navigator widget with scrollbar
  * typeof options = {top,left,width,height,label,depth}
  * method set(dats)/update(data)
  *
  * Data object can contain _update attributes (function) modifying the data content of elements
  * before opening a tree branch. Root data _update must call self.update(new data)!
  * Deeper _update functions have only to modify the object data passed as an argument.
  * Scalar tree leafes can be updated before opening branch by a virtual object:
  * {_virtual:string|number|boolean,_update:function (data) {data._value=<newval>}}
*/

UI.prototype.tree = function(options) {
  var obj =  blessed.tree({
    top: options.top,
    left: options.left,
    width: options.width||(this.screen.width-options.left*2),
    height: options.height||(this.screen.height-options.top-4),
    label: options.label||'Log',
    focus:true,
    arrows:options.arrows,
    border: this.getStyle('tree.border',{
      type: 'line'
    }),
    style: {
      bold:true,
      border: {
        fg: this.getStyle('tree.border.fg','black')
      },
      label:this.getStyle('tree.label',undefined),
      hover: {
        border: {
          fg: 'red'
        }
      },
      focus : {
        border: {
          fg: 'red'
        }      
      }
    }
  });
  function makeleaf (element,reference,data) {
    var content,children,name,funpat,isfun,p;
    children={};
    name = element.toString();
    funpat = /function[\s0-9a-zA-Z_$]*\(/i;
    isfun=Comp.obj.isFunction(element)||funpat.test(name);
    if (isfun) {
      element=Comp.string.sub(name,0,name.indexOf('{'));
    }
    if (!isfun || (isfun && options.showfun)) {
      children[element]={};
      content={children : children,reference:reference,data:data};
    }
    return content;
  }
  function maketree (element,reference) {
    var content,children,p;
    children={};
    if (element && (Comp.obj.isObject(element) || Comp.obj.isArray(element))) {
    // console.log(element)    
      if (element._update != undefined) element._update(element);
      if (element._value != undefined) return makeleaf(element._value,_,element);
      for (p in element) {
        if (p != '_update')
           children[p]={};
      }
      content={
         children : children,
         data : element
      }
    } else if (element != undefined) {
      return makeleaf(element,reference);
    } else {
      children[element]={};
      content={children : children};    
    }
    return content;
  };
  obj.noshow=options.hidden;
  // Create sub-trees
  obj.on('preselect',function(node){
    var content,children,element,data,name;  
    if (node.name != '/' && !node.extended)  {
      // Io.out(node.extended);
      data = node.data;
      if (data != none && (Comp.obj.isObject(data) || Comp.obj.isArray(data))) {
        node.children = {};
        if (Comp.obj.isArray(data) && Comp.array.empty(data) && Comp.hashtbl.empty(data)) {
          node.children={'[]' : {}};
        } else {
          if (data._update != undefined) data._update(data);
          if (data._value != undefined) return node.children=makeleaf(data._value,_,data).children;
          for (var p in data) {
            if (p != '_update') {
              element = data[p];
              content=maketree(element,data);
              if (content) node.children[p]=content;
            }
          }
        } 
      } else if (data == none && node.reference) {
          node.children = {};
          element=node.reference[node.name];
          name=element.toString();
          var funpat = /function[\s0-9a-zA-Z_$]*\(/i;
          var isfun=Comp.obj.isFunction(element)||funpat.test(name);
          if (isfun) {
            element=Comp.string.sub(name,0,name.indexOf('{'));
          }          
          node.children[element]={};
      } 
    } else if (node.name == '/' && node.extended) {
      if (node.data && node.data._update) {
        node.data._update()
      }
    }
  });
  obj.set = obj.update = function (data) {
    obj.DATA = {
      name:'/',
      extended:true,
      children: {},
      data:data,
    };
    for (var p in data) {
      var element=data[p];
      var content=options.depth && options.depth==1?{}:maketree(element,data);
      if (content) obj.DATA.children[p]=content;
    }
    obj.setData(obj.DATA);
  };
  obj.DATA = {
    name:'/',
    extended:true,
    children: {},
  };
  obj.setData(obj.DATA);
  this.screen.append(obj);
  return obj;
}

/** Build an application GUI from compact styles and pages template.
*
* @typeof styles = {
*  button?: { .. },
*  input?: {..},
*  tree?: { .. },
*  ..,
*  @customstyle : { .. },
*  ..
* }
* @typeof content = {
*   pages : { @p1: { .. }, @p2: { .. }, .. }
*   static?: { w1: { .. }, .. }
* }
*
*
*
*
*/

UI.prototype.builder = function(styles,content,options) {
  var self=this,pagename,page,pagedesc,entryname,entry,handler,o,childs,index;
  // Update styles
  this.styles=Comp.obj.inherit(this.styles,styles);

  if (!content.pages) throw Error("UI.builder: empty or invalid pages template (missing pages)");
  if (!content.pages.main) throw Error("UI.builder: no main page");
  function addhandler(obj,event,handler,arg1,arg2)  { obj.on(event,function () { handler(arg1,arg2) })}
  function addhandlerP(page,event,handler,arg1,arg2)  { self.pages.on(page,event,function () { handler(arg1,arg2) })}
  
  for (pagename in content.pages) {
    pagedesc=content.pages[pagename];
    this.pages[pagename]=page = {};
    for (entryname in pagedesc) {
      entry=pagedesc[entryname];
      // Information and control entries?
      switch (entryname) {
        case 'next':
          page.next=entry;
          continue;
          break;
        case 'prev':
          page.prev=entry;
          continue;
          break;
        case 'on':
          if (pagedesc.on.show) addhandlerP(pagename,'load',pagedesc.on.show,pagename);  
          if (pagedesc.on.load) addhandlerP(pagename,'load',pagedesc.on.load,pagename);  
          continue;
          break;
        case 'onclick':
          continue;
          break;
      }
      // Supported widget class?
      switch (entry.type) {
        case 'label':
          page[entryname]=this.label(this.filterOptions(entry,entryname));
          break;
        case 'button':
          page[entryname]=this.button(this.filterOptions(entry,entryname));     
          break;
        case 'input':
          page[entryname]=this.input(this.filterOptions(entry,entryname));       
          this.postOptions(entry,page[entryname],entryname);      
          break;
        case 'info':
          page[entryname]=this.info(this.filterOptions(entry,entryname));
          break;
        case 'tree':
          page[entryname]=this.tree(this.filterOptions(entry,entryname)); 
          this.postOptions(entry,page[entryname],entryname);      
          break;
        case 'list':
          page[entryname]=this.list(this.filterOptions(entry,entryname)); 
          this.postOptions(entry,page[entryname],entryname);      
          break;
        case 'checkbox':
          page[entryname]=this.checkbox(this.filterOptions(entry,entryname)); 
          this.postOptions(entry,page[entryname],entryname);      
          break;
        case 'radiobutton':
          break;
        case 'log':
          page[entryname]=this.log(this.filterOptions(entry,entryname));       
          this.postOptions(entry,page[entryname],entryname);      
          break;
        case 'group':
          // Group of radiobuttons
          childs={};
          handler=undefined;
          for (o in entry) {
            switch (o) {
              case 'type':
                break;
              case 'onclick':
                handler=entry[o];
                break;
              default:
                if (entry[o].type=='radiobutton') childs[o]=entry[o];
            }
          }
          for (o in childs) {
            options=this.filterOptions(childs[o],entryname);
            options.group=entryname;
            page[entryname+'.'+o]=this.radiobutton(options);
            
            if (handler) {
              if (childs[o].index != undefined) index=childs[o].index;
              else index=childs[o].text;
              addhandler(page[entryname+'.'+o],'check',handler,entryname,index);
            }
          }
          break;
        default: throw Error("UI.builder: invalid or unsupported widget type "+entry.type+" in "+entryname);
      } 
    }
  }
  if (content.static) {
    for(entryname in content.static) {
      entry=content.static[entryname];
      switch (entry.type) { 
        case 'info':
          this.static[entryname]=this.info(this.filterOptions(entry,entryname));
          break;
      }
    }
  }
  if (content.on && content.on.keypress && Comp.obj.isArray(content.on.keypress)) {
    content.on.keypress.forEach(function (entry) {
      if (typeof entry.key == 'string') 
        self.screen.key([entry.key], entry.handler);
      else if (entry.key && Comp.obj.isArray(entry.key))
        self.screen.key(entry.key, entry.handler);
    });
  }
  
  for (page in this.pages) this.pages.hide(page);
  this.pages.show('main');
}

module.exports = {
  options:options,
  LAYOUT:LAYOUT,
  UI:UI
}
};
BundleModuleCode['term/blessed']=function (module,exports){
/**
 * blessed - a high-level terminal interface library for node.js
 * Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License).
 * https://github.com/chjj/blessed
 */

/**
 * Blessed
 */

function blessed() {
  return blessed.program.apply(null, arguments);
}

blessed.program = blessed.Program = Require('term/program');
blessed.tput = blessed.Tput = Require('term/tput');
blessed.widget = Require('term/widget');
blessed.colors = Require('term/colors');
blessed.unicode = Require('term/unicode');
blessed.helpers = Require('term/helpers');

blessed.helpers.sprintf = blessed.tput.sprintf;
blessed.helpers.tryRead = blessed.tput.tryRead;
blessed.helpers.merge(blessed, blessed.helpers);

blessed.helpers.merge(blessed, blessed.widget);

/**
 * Expose
 */

module.exports = blessed;
};
BundleModuleCode['term/program']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Christopher Jeffrey and contributors, Stefan Bosse
 **    $INITIAL:     (C) 2013-2016, Christopher Jeffrey and contributors
 **    $CREATED:     sbosse on 28-3-15.
 **    $VERSION:     1.9.3
 **
 **    $INFO:
 *
 * program.js - basic curses-like functionality for blessed.
 *
 **    $ENDOFINFO
 */
var options = {
  version:'1.9.3'
}
/**
 * Modules
 */
var Comp = Require('com/compat');

var EventEmitter = Require('events').EventEmitter
  , StringDecoder = Require('string_decoder').StringDecoder
  , cp = Require('child_process')
  , util = Require('util')
  , fs = Require('fs');

var Tput = Require('term/tput')
  , colors = Require('term/colors')
  , slice = Array.prototype.slice;

var nextTick = global.setImmediate || process.nextTick.bind(process);

/**
 * Program
 */

function Program(options) {
  var self = this;

  if (!instanceOf(this,Program)) {
    return new Program(options);
  }

  Program.bind(this);

  EventEmitter.call(this);

  //if (!options || options.__proto__ !== Object.prototype) {
  if (!options) {
    options = {
      input: arguments[0],
      output: arguments[1]
    };
  }

  this.options = options;
  this.input = options.input || process.stdin;
  this.output = options.output || process.stdout;

  options.log = options.log || options.dump;
  if (options.log) {
    this._logger = fs.createWriteStream(options.log);
    if (options.dump) this.setupDump();
  }

  this.zero = options.zero !== false;
  this.useBuffer = options.buffer;

  this.x = 0;
  this.y = 0;
  this.savedX = 0;
  this.savedY = 0;

  this.cols = this.output.columns || 80;
  this.rows = this.output.rows || 20;

  console.log('Terminal: '+this.cols + ' x ' + this.rows);
  this.scrollTop = 0;
  this.scrollBottom = this.rows - 1;

  this._terminal = options.terminal
    || options.term
    || process.env.TERM
    || (process.platform === 'win32' ? 'windows-ansi' : 'xterm');

  this._terminal = this._terminal.toLowerCase();

  // OSX
  this.isOSXTerm = process.env.TERM_PROGRAM === 'Apple_Terminal';
  this.isiTerm2 = process.env.TERM_PROGRAM === 'iTerm.app'
    || !!process.env.ITERM_SESSION_ID;

  // VTE
  // NOTE: lxterminal does not provide an env variable to check for.
  // NOTE: gnome-terminal and sakura use a later version of VTE
  // which provides VTE_VERSION as well as supports SGR events.
  this.isXFCE = /xfce/i.test(process.env.COLORTERM);
  this.isTerminator = !!process.env.TERMINATOR_UUID;
  this.isLXDE = false;
  this.isVTE = !!process.env.VTE_VERSION
    || this.isXFCE
    || this.isTerminator
    || this.isLXDE;

  // xterm and rxvt - not accurate
  this.isRxvt = /rxvt/i.test(process.env.COLORTERM);
  this.isXterm = false;

  this.tmux = !!process.env.TMUX;
  this.tmuxVersion = (function() {
    if (!self.tmux) return 2;
    try {
      var version = cp.execFileSync('tmux', ['-V'], { encoding: 'utf8' });
      return +/^tmux ([\d.]+)/i.exec(version.trim().split('\n')[0])[1];
    } catch (e) {
      return 2;
    }
  })();

  this._buf = '';
  this._flush = this.flush.bind(this);

  if (options.tput !== false) {
    this.setupTput();
  }
  // console.log(Require('com/io').write_file('/tmp/LOG',Require('os/inspect')(this)));
  this.listen();
  
  if (process.platform == 'win32') {
    process.winmouse.init(function (x,y,button,action) {
      var key = {
        name: 'mouse',
        ctrl: false,
        meta: false,
        shift: false,
        action: action,
        x:x,
        y:y
      };
      self.emit('mouse',key);
    });
  }
}

Program.global = null;

Program.total = 0;

Program.instances = [];

Program.bind = function(program) {
  if (!Program.global) {
    Program.global = program;
  }

  if (!~Program.instances.indexOf(program)) {
    Program.instances.push(program);
    program.index = Program.total;
    Program.total++;
  }

  if (Program._bound) return;
  Program._bound = true;

  unshiftEvent(process, 'exit', Program._exitHandler = function() {
    Program.instances.forEach(function(program) {
      // Potentially reset window title on exit:
      if (program._originalTitle) {
         program.setTitle(program._originalTitle);
      }
      // Ensure the buffer is flushed (it should
      // always be at this point, but who knows).
      program.flush();
      // Ensure _exiting is set (could technically
      // use process._exiting).
      program._exiting = true;
    });
  });
};

//Program.prototype.__proto__ = EventEmitter.prototype;
inheritPrototype(Program,EventEmitter);

Program.prototype.type = 'program';

Program.prototype.log = function() {
  return this._log('LOG',  util.format.apply(util, arguments));
};

Program.prototype.debug = function() {
  if (!this.options.debug) return;
  return this._log('DEBUG',  util.format.apply(util, arguments));
};

Program.prototype._log = function(pre, msg) {
  if (!this._logger) return;
  return this._logger.write(pre + ': ' + msg + '\n-\n');
};

Program.prototype.setupDump = function() {
  var self = this
    , write = this.output.write
    , decoder = new StringDecoder('utf8');

  function stringify(data) {
    return caret(data
      .replace(/\r/g, '\\r')
      .replace(/\n/g, '\\n')
      .replace(/\t/g, '\\t'))
      .replace(/[^ -~]/g, function(ch) {
        if (ch.charCodeAt(0) > 0xff) return ch;
        ch = ch.charCodeAt(0).toString(16);
        if (ch.length > 2) {
          if (ch.length < 4) ch = '0' + ch;
          return '\\u' + ch;
        }
        if (ch.length < 2) ch = '0' + ch;
        return '\\x' + ch;
      });
  }

  function caret(data) {
    return data.replace(/[\0\x80\x1b-\x1f\x7f\x01-\x1a]/g, function(ch) {
      switch (ch) {
        case '\0':
        case '\200':
          ch = '@';
          break;
        case '\x1b':
          ch = '[';
          break;
        case '\x1c':
          ch = '\\';
          break;
        case '\x1d':
          ch = ']';
          break;
        case '\x1e':
          ch = '^';
          break;
        case '\x1f':
          ch = '_';
          break;
        case '\x7f':
          ch = '?';
          break;
        default:
          ch = ch.charCodeAt(0);
          // From ('A' - 64) to ('Z' - 64).
          if (ch >= 1 && ch <= 26) {
            ch = String.fromCharCode(ch + 64);
          } else {
            return String.fromCharCode(ch);
          }
          break;
      }
      return '^' + ch;
    });
  }

  this.input.on('data', function(data) {
    self._log('IN', stringify(decoder.write(data)));
  });

  this.output.write = function(data) {
    self._log('OUT', stringify(data));
    return write.apply(this, arguments);
  };
};

Program.prototype.setupTput = function() {
  if (this._tputSetup) return;
  this._tputSetup = true;

  var self = this
    , options = this.options
    , write = this._write.bind(this);

  var tput = this.tput = new Tput({
    terminal: this.terminal,
    padding: options.padding,
    extended: options.extended,
    printf: options.printf,
    termcap: options.termcap,
    forceUnicode: options.forceUnicode
  });

  if (tput.error) {
    nextTick(function() {
      self.emit('warning', tput.error.message);
    });
  }

  if (tput.padding) {
    nextTick(function() {
      self.emit('warning', 'Terminfo padding has been enabled.');
    });
  }

  this.put = function() {
    var args = slice.call(arguments)
      , cap = args.shift();

    if (tput[cap]) {
      return this._write(tput[cap].apply(tput, args));
    }
  };

  Object.keys(tput).forEach(function(key) {
    if (self[key] == null) {
      self[key] = tput[key];
    }

    if (typeof tput[key] !== 'function') {
      self.put[key] = tput[key];
      return;
    }

    if (tput.padding) {
      self.put[key] = function() {
        return tput._print(tput[key].apply(tput, arguments), write);
      };
    } else {
      self.put[key] = function() {
        return self._write(tput[key].apply(tput, arguments));
      };
    }
  });
};

/* Depricated: 
Program.prototype.__defineGetter__('terminal', function() {
  return this._terminal;
});

Program.prototype.__defineSetter__('terminal', function(terminal) {
  this.setTerminal(terminal);
  return this.terminal;
});
*/

Object.defineProperty(Program.prototype,'terminal', {
  get: function () {return this._terminal;},
  set: function (terminal) {
    this.setTerminal(terminal);
    return this.terminal;  
  }
});

Program.prototype.setTerminal = function(terminal) {
  this._terminal = terminal.toLowerCase();
  delete this._tputSetup;
  this.setupTput();
};

Program.prototype.has = function(name) {
  return this.tput
    ? this.tput.has(name)
    : false;
};

Program.prototype.term = function(is) {
  return this.terminal.indexOf(is) === 0;
};

Program.prototype.listen = function() {
  var self = this;

  // Potentially reset window title on exit:
  // if (!this.isRxvt) {
  //   if (!this.isVTE) this.setTitleModeFeature(3);
  //   this.manipulateWindow(21, function(err, data) {
  //     if (err) return;
  //     self._originalTitle = data.text;
  //   });
  // }

  // Listen for keys/mouse on input
  if (!this.input._blessedInput) {
    this.input._blessedInput = 1;
    this._listenInput();
  } else {
    this.input._blessedInput++;
  }

  this.on('newListener', this._newHandler = function fn(type) {
    if (type === 'keypress' || type === 'mouse') {
      self.removeListener('newListener', fn);
      if (self.input.setRawMode && !self.input.isRaw) {
        self.input.setRawMode(true);
        self.input.resume();
      }
    }
  });

  this.on('newListener', function fn(type) {
    if (type === 'mouse') {
      self.removeListener('newListener', fn);
      self.bindMouse();
    }
  });

  // Listen for resize on output
  if (!this.output._blessedOutput) {
    this.output._blessedOutput = 1;
    this._listenOutput();
  } else {
    this.output._blessedOutput++;
  }
};
var _keys=Require('term/keys');
Program.prototype._listenInput = function() {
  var keys = _keys,
      self = this;

  // Input
  this.input.on('keypress', this.input._keypressHandler = function(ch, key) {
    key = key || { ch: ch };

    if (key.name === 'undefined'
        && (key.code === '[M' || key.code === '[I' || key.code === '[O')) {
      // A mouse sequence. The `keys` module doesn't understand these.
      return;
    }

    if (key.name === 'undefined') {
      // Not sure what this is, but we should probably ignore it.
      return;
    }

    if (key.name === 'enter' && key.sequence === '\n') {
      key.name = 'linefeed';
    }

    if (key.name === 'return' && key.sequence === '\r') {
      self.input.emit('keypress', ch, merge({}, key, { name: 'enter' }));
    }

    var name = (key.ctrl ? 'C-' : '')
      + (key.meta ? 'M-' : '')
      + (key.shift && key.name ? 'S-' : '')
      + (key.name || ch);

    key.full = name;

    Program.instances.forEach(function(program) {
      if (program.input !== self.input) return;
      program.emit('keypress', ch, key);
      program.emit('key ' + name, ch, key);
    });
  });

  this.input.on('data', this.input._dataHandler = function(data) {
    Program.instances.forEach(function(program) {
      if (program.input !== self.input) return;
      program.emit('data', data);
    });
  });

  keys.emitKeypressEvents(this.input);
};

Program.prototype._listenOutput = function() {
  var self = this;

  if (!this.output.isTTY) {
    nextTick(function() {
      self.emit('warning', 'Output is not a TTY');
    });
  }

  // Output
  function resize() {
    Program.instances.forEach(function(program) {
      if (program.output !== self.output) return;
      program.cols = program.output.columns;
      program.rows = program.output.rows;
      program.emit('resize');
    });
  }

  this.output.on('resize', this.output._resizeHandler = function() {
    Program.instances.forEach(function(program) {
      if (program.output !== self.output) return;
      if (!program.options.resizeTimeout) {
        return resize();
      }
      if (program._resizeTimer) {
        clearTimeout(program._resizeTimer);
        delete program._resizeTimer;
      }
      var time = typeof program.options.resizeTimeout === 'number'
        ? program.options.resizeTimeout
        : 300;
      program._resizeTimer = setTimeout(resize, time);
    });
  });
};

Program.prototype.destroy = function() {
  var index = Program.instances.indexOf(this);

  if (~index) {
    Program.instances.splice(index, 1);
    Program.total--;

    this.flush();
    this._exiting = true;

    Program.global = Program.instances[0];

    if (Program.total === 0) {
      Program.global = null;

      process.removeListener('exit', Program._exitHandler);
      delete Program._exitHandler;

      delete Program._bound;
    }

    this.input._blessedInput--;
    this.output._blessedOutput--;

    if (this.input._blessedInput === 0) {
      this.input.removeListener('keypress', this.input._keypressHandler);
      this.input.removeListener('data', this.input._dataHandler);
      delete this.input._keypressHandler;
      delete this.input._dataHandler;

      if (this.input.setRawMode) {
        if (this.input.isRaw) {
          this.input.setRawMode(false);
        }
        if (!this.input.destroyed) {
          this.input.pause();
        }
      }
    }

    if (this.output._blessedOutput === 0) {
      this.output.removeListener('resize', this.output._resizeHandler);
      delete this.output._resizeHandler;
    }

    this.removeListener('newListener', this._newHandler);
    delete this._newHandler;

    this.destroyed = true;
    this.emit('destroy');
  }
};

Program.prototype.key = function(key, listener) {
  if (typeof key === 'string') key = key.split(/\s*,\s*/);
  key.forEach(function(key) {
    return this.on('key ' + key, listener);
  }, this);
};

Program.prototype.onceKey = function(key, listener) {
  if (typeof key === 'string') key = key.split(/\s*,\s*/);
  key.forEach(function(key) {
    return this.once('key ' + key, listener);
  }, this);
};

Program.prototype.unkey =
Program.prototype.removeKey = function(key, listener) {
  if (typeof key === 'string') key = key.split(/\s*,\s*/);
  key.forEach(function(key) {
    return this.removeListener('key ' + key, listener);
  }, this);
};

// XTerm mouse events
// http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking
// To better understand these
// the xterm code is very helpful:
// Relevant files:
//   button.c, charproc.c, misc.c
// Relevant functions in xterm/button.c:
//   BtnCode, EmitButtonCode, EditorButton, SendMousePosition
// send a mouse event:
// regular/utf8: ^[[M Cb Cx Cy
// urxvt: ^[[ Cb ; Cx ; Cy M
// sgr: ^[[ Cb ; Cx ; Cy M/m
// vt300: ^[[ 24(1/3/5)~ [ Cx , Cy ] \r
// locator: CSI P e ; P b ; P r ; P c ; P p & w
// motion example of a left click:
// ^[[M 3<^[[M@4<^[[M@5<^[[M@6<^[[M@7<^[[M#7<
// mouseup, mousedown, mousewheel
// left click: ^[[M 3<^[[M#3<
// mousewheel up: ^[[M`3>
Program.prototype.bindMouse = function() {
  if (this._boundMouse) return;
  this._boundMouse = true;

  var decoder = new StringDecoder('utf8')
    , self = this;

  this.on('data', function(data) {
    var text,seq=[data],_data,i,n=0,m=0;
    if (data.length && data.indexOf(0x1b,1) > 0) {
      // Split data
      seq=[];
      while(n<data.length)  {
        m=data.indexOf(0x1b,m+1);
        if (m==-1) m=data.length;
        // console.log(n,m);
        seq.push(data.slice(n,m));
        n=m;        
      }
    }
    for(i in seq) {
      _data=seq[i];
      text = decoder.write(_data);
      if (!text) continue;
      // console.log(_data); 
      self._bindMouse(text, _data);
    }
  });
};

Program.prototype._bindMouse = function(s, buf) {
  var self = this
    , key
    , parts
    , b
    , x
    , y
    , mod
    , params
    , down
    , page
    , button;

  key = {
    name: undefined,
    ctrl: false,
    meta: false,
    shift: false
  };

  if (Buffer.isBuffer(s)) {
    if (s[0] > 127 && s[1] === undefined) {
      s[0] -= 128;
      s = '\x1b' + s.toString('utf-8');
    } else {
      s = s.toString('utf-8');
    }
  }
  
  // if (this.8bit) {
  //   s = s.replace(/\233/g, '\x1b[');
  //   buf = new Buffer(s, 'utf8');
  // }

  // XTerm / X10 for buggy VTE
  // VTE can only send unsigned chars and no unicode for coords. This limits
  // them to 0xff. However, normally the x10 protocol does not allow a byte
  // under 0x20, but since VTE can have the bytes overflow, we can consider
  // bytes below 0x20 to be up to 0xff + 0x20. This gives a limit of 287. Since
  // characters ranging from 223 to 248 confuse javascript's utf parser, we
  // need to parse the raw binary. We can detect whether the terminal is using
  // a bugged VTE version by examining the coordinates and seeing whether they
  // are a value they would never otherwise be with a properly implemented x10
  // protocol. This method of detecting VTE is only 99% reliable because we
  // can't check if the coords are 0x00 (255) since that is a valid x10 coord
  // technically.
  var bx = s.charCodeAt(4);
  var by = s.charCodeAt(5);
  
  if (buf[0] === 0x1b && buf[1] === 0x5b && buf[2] === 0x4d
      && (this.isVTE
      || bx >= 65533 || by >= 65533
      || (bx > 0x00 && bx < 0x20)
      || (by > 0x00 && by < 0x20)
      || (buf[4] > 223 && buf[4] < 248 && buf.length === 6)
      || (buf[5] > 223 && buf[5] < 248 && buf.length === 6))) {
    b = buf[3];
    x = buf[4];
    y = buf[5];

    // unsigned char overflow.
    if (x < 0x20) x += 0xff;
    if (y < 0x20) y += 0xff;

    // Convert the coordinates into a
    // properly formatted x10 utf8 sequence.
    s = '\x1b[M'
      + String.fromCharCode(b)
      + String.fromCharCode(x)
      + String.fromCharCode(y);
  }
  // XTerm / X10
  if (parts = /^\x1b\[M([\x00\u0020-\ufffe]{3})/.exec(s)) {
    b = parts[1].charCodeAt(0);
    x = parts[1].charCodeAt(1);
    y = parts[1].charCodeAt(2);

    key.name = 'mouse';
    key.type = 'X10';

    key.raw = [b, x, y, parts[0]];
    key.buf = buf;
    key.x = x - 32;
    key.y = y - 32;

    if (this.zero) key.x--, key.y--;

    if (x === 0) key.x = 255;
    if (y === 0) key.y = 255;

    mod = b >> 2;
    key.shift = !!(mod & 1);
    key.meta = !!((mod >> 1) & 1);
    key.ctrl = !!((mod >> 2) & 1);

    b -= 32;

    if ((b >> 6) & 1) {
      key.action = b & 1 ? 'wheeldown' : 'wheelup';
      key.button = 'middle';
    } else if (b === 3 && this._lastButton) {
      // NOTE: x10 and urxvt have no way
      // of telling which button mouseup used.
      // Emit mouseup only if there was a mousedown
      key.action = 'mouseup';
      key.button = this._lastButton;
      delete this._lastButton;
    } else if ((b&3) < 3) {
      // Emit mousedown only with valid button
      key.action = 'mousedown';
      button = b & 3;
      key.button =
        button === 0 ? 'left'
        : button === 1 ? 'middle'
        : button === 2 ? 'right'
        : 'unknown';
      this._lastButton = key.button;
    }
//console.log(b,key)
    // Probably a movement.
    // The *newer* VTE gets mouse movements comepletely wrong.
    // This presents a problem: older versions of VTE that get it right might
    // be confused by the second conditional in the if statement.
    // NOTE: Possibly just switch back to the if statement below.
    // none, shift, ctrl, alt
    // gnome: 32, 36, 48, 40
    // xterm: 35, _, 51, _
    // urxvt: 35, _, _, _
    // if (key.action === 'mousedown' && key.button === 'unknown') {
    if (b === 35 || b === 39 || b === 51 || b === 43
        || (this.isVTE && (b === 32 || b === 36 || b === 48 || b === 40))) {
      delete key.button;
      key.action = 'mousemove';
    }
    self.emit('mouse', key);
    return;
  }

  // URxvt
  if (parts = /^\x1b\[(\d+;\d+;\d+)M/.exec(s)) {
    params = parts[1].split(';');
    b = +params[0];
    x = +params[1];
    y = +params[2];

    key.name = 'mouse';
    key.type = 'urxvt';

    key.raw = [b, x, y, parts[0]];
    key.buf = buf;
    key.x = x;
    key.y = y;

    if (this.zero) key.x--, key.y--;

    mod = b >> 2;
    key.shift = !!(mod & 1);
    key.meta = !!((mod >> 1) & 1);
    key.ctrl = !!((mod >> 2) & 1);

    // XXX Bug in urxvt after wheelup/down on mousemove
    // NOTE: This may be different than 128/129 depending
    // on mod keys.
    if (b === 128 || b === 129) {
      b = 67;
    }

    b -= 32;

    if ((b >> 6) & 1) {
      key.action = b & 1 ? 'wheeldown' : 'wheelup';
      key.button = 'middle';
    } else if (b === 3) {
      // NOTE: x10 and urxvt have no way
      // of telling which button mouseup used.
      key.action = 'mouseup';
      key.button = this._lastButton || 'unknown';
      delete this._lastButton;
    } else {
      key.action = 'mousedown';
      button = b & 3;
      key.button =
        button === 0 ? 'left'
        : button === 1 ? 'middle'
        : button === 2 ? 'right'
        : 'unknown';
      // NOTE: 0/32 = mousemove, 32/64 = mousemove with left down
      // if ((b >> 1) === 32)
      this._lastButton = key.button;
    }

    // Probably a movement.
    // The *newer* VTE gets mouse movements comepletely wrong.
    // This presents a problem: older versions of VTE that get it right might
    // be confused by the second conditional in the if statement.
    // NOTE: Possibly just switch back to the if statement below.
    // none, shift, ctrl, alt
    // urxvt: 35, _, _, _
    // gnome: 32, 36, 48, 40
    // if (key.action === 'mousedown' && key.button === 'unknown') {
    if (b === 35 || b === 39 || b === 51 || b === 43
        || (this.isVTE && (b === 32 || b === 36 || b === 48 || b === 40))) {
      delete key.button;
      key.action = 'mousemove';
    }

    self.emit('mouse', key);

    return;
  }

  // SGR
  if (parts = /^\x1b\[<(\d+;\d+;\d+)([mM])/.exec(s)) {
    down = parts[2] === 'M';
    params = parts[1].split(';');
    b = +params[0];
    x = +params[1];
    y = +params[2];

    key.name = 'mouse';
    key.type = 'sgr';

    key.raw = [b, x, y, parts[0]];
    key.buf = buf;
    key.x = x;
    key.y = y;

    if (this.zero) key.x--, key.y--;

    mod = b >> 2;
    key.shift = !!(mod & 1);
    key.meta = !!((mod >> 1) & 1);
    key.ctrl = !!((mod >> 2) & 1);

    if ((b >> 6) & 1) {
      key.action = b & 1 ? 'wheeldown' : 'wheelup';
      key.button = 'middle';
    } else {
      key.action = down
        ? 'mousedown'
        : 'mouseup';
      button = b & 3;
      key.button =
        button === 0 ? 'left'
        : button === 1 ? 'middle'
        : button === 2 ? 'right'
        : 'unknown';
    }

    // Probably a movement.
    // The *newer* VTE gets mouse movements comepletely wrong.
    // This presents a problem: older versions of VTE that get it right might
    // be confused by the second conditional in the if statement.
    // NOTE: Possibly just switch back to the if statement below.
    // none, shift, ctrl, alt
    // xterm: 35, _, 51, _
    // gnome: 32, 36, 48, 40
    // if (key.action === 'mousedown' && key.button === 'unknown') {
    if (b === 35 || b === 39 || b === 51 || b === 43
        || (this.isVTE && (b === 32 || b === 36 || b === 48 || b === 40))) {
      delete key.button;
      key.action = 'mousemove';
    }

    self.emit('mouse', key);

    return;
  }

  // DEC
  // The xterm mouse documentation says there is a
  // `<` prefix, the DECRQLP says there is no prefix.
  if (parts = /^\x1b\[<(\d+;\d+;\d+;\d+)&w/.exec(s)) {
    params = parts[1].split(';');
    b = +params[0];
    x = +params[1];
    y = +params[2];
    page = +params[3];

    key.name = 'mouse';
    key.type = 'dec';

    key.raw = [b, x, y, parts[0]];
    key.buf = buf;
    key.x = x;
    key.y = y;
    key.page = page;

    if (this.zero) key.x--, key.y--;

    key.action = b === 3
      ? 'mouseup'
      : 'mousedown';

    key.button =
      b === 2 ? 'left'
      : b === 4 ? 'middle'
      : b === 6 ? 'right'
      : 'unknown';

    self.emit('mouse', key);

    return;
  }

  // vt300
  if (parts = /^\x1b\[24([0135])~\[(\d+),(\d+)\]\r/.exec(s)) {
    b = +parts[1];
    x = +parts[2];
    y = +parts[3];

    key.name = 'mouse';
    key.type = 'vt300';

    key.raw = [b, x, y, parts[0]];
    key.buf = buf;
    key.x = x;
    key.y = y;

    if (this.zero) key.x--, key.y--;

    key.action = 'mousedown';
    key.button =
      b === 1 ? 'left'
      : b === 2 ? 'middle'
      : b === 5 ? 'right'
      : 'unknown';

    self.emit('mouse', key);

    return;
  }

  if (parts = /^\x1b\[(O|I)/.exec(s)) {
    key.action = parts[1] === 'I'
      ? 'focus'
      : 'blur';

    self.emit('mouse', key);
    self.emit(key.action);

    return;
  }
};

// gpm support for linux vc
Program.prototype.enableGpm = function() {
  var self = this;
  var gpmclient = Require('term/gpmclient');

  if (this.gpm) return;

  this.gpm = gpmclient();

  this.gpm.on('btndown', function(btn, modifier, x, y) {
    x--, y--;

    var key = {
      name: 'mouse',
      type: 'GPM',
      action: 'mousedown',
      button: self.gpm.ButtonName(btn),
      raw: [btn, modifier, x, y],
      x: x,
      y: y,
      shift: self.gpm.hasShiftKey(modifier),
      meta: self.gpm.hasMetaKey(modifier),
      ctrl: self.gpm.hasCtrlKey(modifier)
    };

    self.emit('mouse', key);
  });

  this.gpm.on('btnup', function(btn, modifier, x, y) {
    x--, y--;

    var key = {
      name: 'mouse',
      type: 'GPM',
      action: 'mouseup',
      button: self.gpm.ButtonName(btn),
      raw: [btn, modifier, x, y],
      x: x,
      y: y,
      shift: self.gpm.hasShiftKey(modifier),
      meta: self.gpm.hasMetaKey(modifier),
      ctrl: self.gpm.hasCtrlKey(modifier)
    };

    self.emit('mouse', key);
  });

  this.gpm.on('move', function(btn, modifier, x, y) {
    x--, y--;

    var key = {
      name: 'mouse',
      type: 'GPM',
      action: 'mousemove',
      button: self.gpm.ButtonName(btn),
      raw: [btn, modifier, x, y],
      x: x,
      y: y,
      shift: self.gpm.hasShiftKey(modifier),
      meta: self.gpm.hasMetaKey(modifier),
      ctrl: self.gpm.hasCtrlKey(modifier)
    };

    self.emit('mouse', key);
  });

  this.gpm.on('drag', function(btn, modifier, x, y) {
    x--, y--;

    var key = {
      name: 'mouse',
      type: 'GPM',
      action: 'mousemove',
      button: self.gpm.ButtonName(btn),
      raw: [btn, modifier, x, y],
      x: x,
      y: y,
      shift: self.gpm.hasShiftKey(modifier),
      meta: self.gpm.hasMetaKey(modifier),
      ctrl: self.gpm.hasCtrlKey(modifier)
    };

    self.emit('mouse', key);
  });

  this.gpm.on('mousewheel', function(btn, modifier, x, y, dx, dy) {
    var key = {
      name: 'mouse',
      type: 'GPM',
      action: dy > 0 ? 'wheelup' : 'wheeldown',
      button: self.gpm.ButtonName(btn),
      raw: [btn, modifier, x, y, dx, dy],
      x: x,
      y: y,
      shift: self.gpm.hasShiftKey(modifier),
      meta: self.gpm.hasMetaKey(modifier),
      ctrl: self.gpm.hasCtrlKey(modifier)
    };

    self.emit('mouse', key);
  });
};

Program.prototype.disableGpm = function() {
  if (this.gpm) {
    this.gpm.stop();
    delete this.gpm;
  }
};

// All possible responses from the terminal
Program.prototype.bindResponse = function() {
  if (this._boundResponse) return;
  this._boundResponse = true;

  var decoder = new StringDecoder('utf8')
    , self = this;

  this.on('data', function(data) {
    data = decoder.write(data);
    if (!data) return;
    self._bindResponse(data);
  });
};

Program.prototype._bindResponse = function(s) {
  var out = {}
    , parts;

  if (Buffer.isBuffer(s)) {
    if (s[0] > 127 && s[1] === undefined) {
      s[0] -= 128;
      s = '\x1b' + s.toString('utf-8');
    } else {
      s = s.toString('utf-8');
    }
  }

  // CSI P s c
  // Send Device Attributes (Primary DA).
  // CSI > P s c
  // Send Device Attributes (Secondary DA).
  if (parts = /^\x1b\[(\?|>)(\d*(?:;\d*)*)c/.exec(s)) {
    parts = parts[2].split(';').map(function(ch) {
      return +ch || 0;
    });

    out.event = 'device-attributes';
    out.code = 'DA';

    if (parts[1] === '?') {
      out.type = 'primary-attribute';
      // VT100-style params:
      if (parts[0] === 1 && parts[2] === 2) {
        out.term = 'vt100';
        out.advancedVideo = true;
      } else if (parts[0] === 1 && parts[2] === 0) {
        out.term = 'vt101';
      } else if (parts[0] === 6) {
        out.term = 'vt102';
      } else if (parts[0] === 60
        && parts[1] === 1 && parts[2] === 2
        && parts[3] === 6 && parts[4] === 8
        && parts[5] === 9 && parts[6] === 15) {
        out.term = 'vt220';
      } else {
        // VT200-style params:
        parts.forEach(function(attr) {
          switch (attr) {
            case 1:
              out.cols132 = true;
              break;
            case 2:
              out.printer = true;
              break;
            case 6:
              out.selectiveErase = true;
              break;
            case 8:
              out.userDefinedKeys = true;
              break;
            case 9:
              out.nationalReplacementCharsets = true;
              break;
            case 15:
              out.technicalCharacters = true;
              break;
            case 18:
              out.userWindows = true;
              break;
            case 21:
              out.horizontalScrolling = true;
              break;
            case 22:
              out.ansiColor = true;
              break;
            case 29:
              out.ansiTextLocator = true;
              break;
          }
        });
      }
    } else {
      out.type = 'secondary-attribute';
      switch (parts[0]) {
        case 0:
          out.term = 'vt100';
          break;
        case 1:
          out.term = 'vt220';
          break;
        case 2:
          out.term = 'vt240';
          break;
        case 18:
          out.term = 'vt330';
          break;
        case 19:
          out.term = 'vt340';
          break;
        case 24:
          out.term = 'vt320';
          break;
        case 41:
          out.term = 'vt420';
          break;
        case 61:
          out.term = 'vt510';
          break;
        case 64:
          out.term = 'vt520';
          break;
        case 65:
          out.term = 'vt525';
          break;
      }
      out.firmwareVersion = parts[1];
      out.romCartridgeRegistrationNumber = parts[2];
    }

    // LEGACY
    out.deviceAttributes = out;

    this.emit('response', out);
    this.emit('response ' + out.event, out);

    return;
  }

  // CSI Ps n  Device Status Report (DSR).
  //     Ps = 5  -> Status Report.  Result (``OK'') is
  //   CSI 0 n
  // CSI ? Ps n
  //   Device Status Report (DSR, DEC-specific).
  //     Ps = 1 5  -> Report Printer status as CSI ? 1 0  n  (ready).
  //     or CSI ? 1 1  n  (not ready).
  //     Ps = 2 5  -> Report UDK status as CSI ? 2 0  n  (unlocked)
  //     or CSI ? 2 1  n  (locked).
  //     Ps = 2 6  -> Report Keyboard status as
  //   CSI ? 2 7  ;  1  ;  0  ;  0  n  (North American).
  //   The last two parameters apply to VT400 & up, and denote key-
  //   board ready and LK01 respectively.
  //     Ps = 5 3  -> Report Locator status as
  //   CSI ? 5 3  n  Locator available, if compiled-in, or
  //   CSI ? 5 0  n  No Locator, if not.
  if (parts = /^\x1b\[(\?)?(\d+)(?:;(\d+);(\d+);(\d+))?n/.exec(s)) {
    out.event = 'device-status';
    out.code = 'DSR';

    if (!parts[1] && parts[2] === '0' && !parts[3]) {
      out.type = 'device-status';
      out.status = 'OK';

      // LEGACY
      out.deviceStatus = out.status;

      this.emit('response', out);
      this.emit('response ' + out.event, out);

      return;
    }

    if (parts[1] && (parts[2] === '10' || parts[2] === '11') && !parts[3]) {
      out.type = 'printer-status';
      out.status = parts[2] === '10'
        ? 'ready'
        : 'not ready';

      // LEGACY
      out.printerStatus = out.status;

      this.emit('response', out);
      this.emit('response ' + out.event, out);

      return;
    }

    if (parts[1] && (parts[2] === '20' || parts[2] === '21') && !parts[3]) {
      out.type = 'udk-status';
      out.status = parts[2] === '20'
        ? 'unlocked'
        : 'locked';

      // LEGACY
      out.UDKStatus = out.status;

      this.emit('response', out);
      this.emit('response ' + out.event, out);

      return;
    }

    if (parts[1]
        && parts[2] === '27'
        && parts[3] === '1'
        && parts[4] === '0'
        && parts[5] === '0') {
      out.type = 'keyboard-status';
      out.status = 'OK';

      // LEGACY
      out.keyboardStatus = out.status;

      this.emit('response', out);
      this.emit('response ' + out.event, out);

      return;
    }

    if (parts[1] && (parts[2] === '53' || parts[2] === '50') && !parts[3]) {
      out.type = 'locator-status';
      out.status = parts[2] === '53'
          ? 'available'
          : 'unavailable';

      // LEGACY
      out.locator = out.status;

      this.emit('response', out);
      this.emit('response ' + out.event, out);

      return;
    }

    out.type = 'error';
    out.text = 'Unhandled: ' + JSON.stringify(parts);

    // LEGACY
    out.error = out.text;

    this.emit('response', out);
    this.emit('response ' + out.event, out);

    return;
  }

  // CSI Ps n  Device Status Report (DSR).
  //     Ps = 6  -> Report Cursor Position (CPR) [row;column].
  //   Result is
  //   CSI r ; c R
  // CSI ? Ps n
  //   Device Status Report (DSR, DEC-specific).
  //     Ps = 6  -> Report Cursor Position (CPR) [row;column] as CSI
  //     ? r ; c R (assumes page is zero).
  if (parts = /^\x1b\[(\?)?(\d+);(\d+)R/.exec(s)) {
    out.event = 'device-status';
    out.code = 'DSR';
    out.type = 'cursor-status';

    out.status = {
      x: +parts[3],
      y: +parts[2],
      page: !parts[1] ? undefined : 0
    };

    out.x = out.status.x;
    out.y = out.status.y;
    out.page = out.status.page;

    // LEGACY
    out.cursor = out.status;

    this.emit('response', out);
    this.emit('response ' + out.event, out);

    return;
  }

  // CSI Ps ; Ps ; Ps t
  //   Window manipulation (from dtterm, as well as extensions).
  //   These controls may be disabled using the allowWindowOps
  //   resource.  Valid values for the first (and any additional
  //   parameters) are:
  //     Ps = 1 1  -> Report xterm window state.  If the xterm window
  //     is open (non-iconified), it returns CSI 1 t .  If the xterm
  //     window is iconified, it returns CSI 2 t .
  //     Ps = 1 3  -> Report xterm window position.  Result is CSI 3
  //     ; x ; y t
  //     Ps = 1 4  -> Report xterm window in pixels.  Result is CSI
  //     4  ;  height ;  width t
  //     Ps = 1 8  -> Report the size of the text area in characters.
  //     Result is CSI  8  ;  height ;  width t
  //     Ps = 1 9  -> Report the size of the screen in characters.
  //     Result is CSI  9  ;  height ;  width t
  if (parts = /^\x1b\[(\d+)(?:;(\d+);(\d+))?t/.exec(s)) {
    out.event = 'window-manipulation';
    out.code = '';

    if ((parts[1] === '1' || parts[1] === '2') && !parts[2]) {
      out.type = 'window-state';
      out.state = parts[1] === '1'
        ? 'non-iconified'
        : 'iconified';

      // LEGACY
      out.windowState = out.state;

      this.emit('response', out);
      this.emit('response ' + out.event, out);

      return;
    }

    if (parts[1] === '3' && parts[2]) {
      out.type = 'window-position';

      out.position = {
        x: +parts[2],
        y: +parts[3]
      };
      out.x = out.position.x;
      out.y = out.position.y;

      // LEGACY
      out.windowPosition = out.position;

      this.emit('response', out);
      this.emit('response ' + out.event, out);

      return;
    }

    if (parts[1] === '4' && parts[2]) {
      out.type = 'window-size-pixels';
      out.size = {
        height: +parts[2],
        width: +parts[3]
      };
      out.height = out.size.height;
      out.width = out.size.width;

      // LEGACY
      out.windowSizePixels = out.size;

      this.emit('response', out);
      this.emit('response ' + out.event, out);

      return;
    }

    if (parts[1] === '8' && parts[2]) {
      out.type = 'textarea-size';
      out.size = {
        height: +parts[2],
        width: +parts[3]
      };
      out.height = out.size.height;
      out.width = out.size.width;

      // LEGACY
      out.textAreaSizeCharacters = out.size;

      this.emit('response', out);
      this.emit('response ' + out.event, out);

      return;
    }

    if (parts[1] === '9' && parts[2]) {
      out.type = 'screen-size';
      out.size = {
        height: +parts[2],
        width: +parts[3]
      };
      out.height = out.size.height;
      out.width = out.size.width;

      // LEGACY
      out.screenSizeCharacters = out.size;

      this.emit('response', out);
      this.emit('response ' + out.event, out);

      return;
    }

    out.type = 'error';
    out.text = 'Unhandled: ' + JSON.stringify(parts);

    // LEGACY
    out.error = out.text;

    this.emit('response', out);
    this.emit('response ' + out.event, out);

    return;
  }

  // rxvt-unicode does not support window manipulation
  //   Result Normal: OSC l/L 0xEF 0xBF 0xBD
  //   Result ASCII: OSC l/L 0x1c (file separator)
  //   Result UTF8->ASCII: OSC l/L 0xFD
  // Test with:
  //   echo -ne '\ePtmux;\e\e[>3t\e\\'
  //   sleep 2 && echo -ne '\ePtmux;\e\e[21t\e\\' & cat -v
  //   -
  //   echo -ne '\e[>3t'
  //   sleep 2 && echo -ne '\e[21t' & cat -v
  if (parts = /^\x1b\](l|L)([^\x07\x1b]*)$/.exec(s)) {
    parts[2] = 'rxvt';
    s = '\x1b]' + parts[1] + parts[2] + '\x1b\\';
  }

  // CSI Ps ; Ps ; Ps t
  //   Window manipulation (from dtterm, as well as extensions).
  //   These controls may be disabled using the allowWindowOps
  //   resource.  Valid values for the first (and any additional
  //   parameters) are:
  //     Ps = 2 0  -> Report xterm window's icon label.  Result is
  //     OSC  L  label ST
  //     Ps = 2 1  -> Report xterm window's title.  Result is OSC  l
  //     label ST
  if (parts = /^\x1b\](l|L)([^\x07\x1b]*)(?:\x07|\x1b\\)/.exec(s)) {
    out.event = 'window-manipulation';
    out.code = '';

    if (parts[1] === 'L') {
      out.type = 'window-icon-label';
      out.text = parts[2];

      // LEGACY
      out.windowIconLabel = out.text;

      this.emit('response', out);
      this.emit('response ' + out.event, out);

      return;
    }

    if (parts[1] === 'l') {
      out.type = 'window-title';
      out.text = parts[2];

      // LEGACY
      out.windowTitle = out.text;

      this.emit('response', out);
      this.emit('response ' + out.event, out);

      return;
    }

    out.type = 'error';
    out.text = 'Unhandled: ' + JSON.stringify(parts);

    // LEGACY
    out.error = out.text;

    this.emit('response', out);
    this.emit('response ' + out.event, out);

    return;
  }

  // CSI Ps ' |
  //   Request Locator Position (DECRQLP).
  //     -> CSI Pe ; Pb ; Pr ; Pc ; Pp &  w
  //   Parameters are [event;button;row;column;page].
  //   Valid values for the event:
  //     Pe = 0  -> locator unavailable - no other parameters sent.
  //     Pe = 1  -> request - xterm received a DECRQLP.
  //     Pe = 2  -> left button down.
  //     Pe = 3  -> left button up.
  //     Pe = 4  -> middle button down.
  //     Pe = 5  -> middle button up.
  //     Pe = 6  -> right button down.
  //     Pe = 7  -> right button up.
  //     Pe = 8  -> M4 button down.
  //     Pe = 9  -> M4 button up.
  //     Pe = 1 0  -> locator outside filter rectangle.
  //   ``button'' parameter is a bitmask indicating which buttons are
  //     pressed:
  //     Pb = 0  <- no buttons down.
  //     Pb & 1  <- right button down.
  //     Pb & 2  <- middle button down.
  //     Pb & 4  <- left button down.
  //     Pb & 8  <- M4 button down.
  //   ``row'' and ``column'' parameters are the coordinates of the
  //     locator position in the xterm window, encoded as ASCII deci-
  //     mal.
  //   The ``page'' parameter is not used by xterm, and will be omit-
  //   ted.
  // NOTE:
  // This is already implemented in the _bindMouse
  // method, but it might make more sense here.
  // The xterm mouse documentation says there is a
  // `<` prefix, the DECRQLP says there is no prefix.
  if (parts = /^\x1b\[(\d+(?:;\d+){4})&w/.exec(s)) {
    parts = parts[1].split(';').map(function(ch) {
      return +ch;
    });

    out.event = 'locator-position';
    out.code = 'DECRQLP';

    switch (parts[0]) {
      case 0:
        out.status = 'locator-unavailable';
        break;
      case 1:
        out.status = 'request';
        break;
      case 2:
        out.status = 'left-button-down';
        break;
      case 3:
        out.status = 'left-button-up';
        break;
      case 4:
        out.status = 'middle-button-down';
        break;
      case 5:
        out.status = 'middle-button-up';
        break;
      case 6:
        out.status = 'right-button-down';
        break;
      case 7:
        out.status = 'right-button-up';
        break;
      case 8:
        out.status = 'm4-button-down';
        break;
      case 9:
        out.status = 'm4-button-up';
        break;
      case 10:
        out.status = 'locator-outside';
        break;
    }

    out.mask = parts[1];
    out.row = parts[2];
    out.col = parts[3];
    out.page = parts[4];

    // LEGACY
    out.locatorPosition = out;

    this.emit('response', out);
    this.emit('response ' + out.event, out);

    return;
  }

  // OSC Ps ; Pt BEL
  // OSC Ps ; Pt ST
  // Set Text Parameters
  if (parts = /^\x1b\](\d+);([^\x07\x1b]+)(?:\x07|\x1b\\)/.exec(s)) {
    out.event = 'text-params';
    out.code = 'Set Text Parameters';
    out.ps = +s[1];
    out.pt = s[2];
    this.emit('response', out);
    this.emit('response ' + out.event, out);
  }
};

Program.prototype.response = function(name, text, callback, noBypass) {
  var self = this;

  if (arguments.length === 2) {
    callback = text;
    text = name;
    name = null;
  }

  if (!callback) {
    callback = function() {};
  }

  this.bindResponse();

  name = name
    ? 'response ' + name
    : 'response';

  var onresponse;

  this.once(name, onresponse = function(event) {
    if (timeout) clearTimeout(timeout);
    if (event.type === 'error') {
      return callback(new Error(event.event + ': ' + event.text));
    }
    return callback(null, event);
  });

  var timeout = setTimeout(function() {
    self.removeListener(name, onresponse);
    return callback(new Error('Timeout.'));
  }, 2000);

  return noBypass
    ? this._write(text)
    : this._twrite(text);
};

Program.prototype._owrite =
Program.prototype.write = function(text) {
  if (!this.output.writable) return;
  return this.output.write(text);
};

Program.prototype._buffer = function(text) {
  if (this._exiting) {
    this.flush();
    this._owrite(text);
    return;
  }

  if (this._buf) {
    this._buf += text;
    return;
  }

  this._buf = text;

  nextTick(this._flush);

  return true;
};

Program.prototype.flush = function() {
  if (!this._buf) return;
  this._owrite(this._buf);
  this._buf = '';
};

Program.prototype._write = function(text) {
  if (this.ret) return text;
  if (this.useBuffer) {
    return this._buffer(text);
  }
  return this._owrite(text);
};

// Example: `DCS tmux; ESC Pt ST`
// Real: `DCS tmux; ESC Pt ESC \`
Program.prototype._twrite = function(data) {
  var self = this
    , iterations = 0
    , timer;

  if (this.tmux) {
    // Replace all STs with BELs so they can be nested within the DCS code.
    data = data.replace(/\x1b\\/g, '\x07');

    // Wrap in tmux forward DCS:
    data = '\x1bPtmux;\x1b' + data + '\x1b\\';

    // If we've never even flushed yet, it means we're still in
    // the normal buffer. Wait for alt screen buffer.
    if (this.output.bytesWritten === 0) {
      timer = setInterval(function() {
        if (self.output.bytesWritten > 0 || ++iterations === 50) {
          clearInterval(timer);
          self.flush();
          self._owrite(data);
        }
      }, 100);
      return true;
    }

    // NOTE: Flushing the buffer is required in some cases.
    // The DCS code must be at the start of the output.
    this.flush();

    // Write out raw now that the buffer is flushed.
    return this._owrite(data);
  }

  return this._write(data);
};

Program.prototype.echo =
Program.prototype.print = function(text, attr) {
  return attr
    ? this._write(this.text(text, attr))
    : this._write(text);
};

Program.prototype._ncoords = function() {
  if (this.x < 0) this.x = 0;
  else if (this.x >= this.cols) this.x = this.cols - 1;
  if (this.y < 0) this.y = 0;
  else if (this.y >= this.rows) this.y = this.rows - 1;
};

Program.prototype.setx = function(x) {
  return this.cursorCharAbsolute(x);
  // return this.charPosAbsolute(x);
};

Program.prototype.sety = function(y) {
  return this.linePosAbsolute(y);
};

Program.prototype.move = function(x, y) {
  return this.cursorPos(y, x);
};

// TODO: Fix cud and cuu calls.
Program.prototype.omove = function(x, y) {
  if (!this.zero) {
    x = (x || 1) - 1;
    y = (y || 1) - 1;
  } else {
    x = x || 0;
    y = y || 0;
  }
  if (y === this.y && x === this.x) {
    return;
  }
  if (y === this.y) {
    if (x > this.x) {
      this.cuf(x - this.x);
    } else if (x < this.x) {
      this.cub(this.x - x);
    }
  } else if (x === this.x) {
    if (y > this.y) {
      this.cud(y - this.y);
    } else if (y < this.y) {
      this.cuu(this.y - y);
    }
  } else {
    if (!this.zero) x++, y++;
    this.cup(y, x);
  }
};

Program.prototype.rsetx = function(x) {
  // return this.HPositionRelative(x);
  if (!x) return;
  return x > 0
    ? this.forward(x)
    : this.back(-x);
};

Program.prototype.rsety = function(y) {
  // return this.VPositionRelative(y);
  if (!y) return;
  return y > 0
    ? this.up(y)
    : this.down(-y);
};

Program.prototype.rmove = function(x, y) {
  this.rsetx(x);
  this.rsety(y);
};

Program.prototype.simpleInsert = function(ch, i, attr) {
  return this._write(this.repeat(ch, i), attr);
};

Program.prototype.repeat = function(ch, i) {
  if (!i || i < 0) i = 0;
  return Array(i + 1).join(ch);
};

/* Depricated:
Program.prototype.__defineGetter__('title', function() {
  return this._title;
});

Program.prototype.__defineSetter__('title', function(title) {
  this.setTitle(title);
  return this._title;
});
*/
Object.defineProperty(Program.prototype,'title',{
  get: function () {  return this._title;},
  set: function (title) {
    this.setTitle(title);
    return this._title;  
  }
});

// Specific to iTerm2, but I think it's really cool.
// Example:
//  if (!screen.copyToClipboard(text)) {
//    execClipboardProgram(text);
//  }
Program.prototype.copyToClipboard = function(text) {
  if (this.isiTerm2) {
    this._twrite('\x1b]50;CopyToCliboard=' + text + '\x07');
    return true;
  }
  return false;
};

// Only XTerm and iTerm2. If you know of any others, post them.
Program.prototype.cursorShape = function(shape, blink) {
  if (this.isiTerm2) {
    switch (shape) {
      case 'block':
        if (!blink) {
          this._twrite('\x1b]50;CursorShape=0;BlinkingCursorEnabled=0\x07');
        } else {
          this._twrite('\x1b]50;CursorShape=0;BlinkingCursorEnabled=1\x07');
        }
        break;
      case 'underline':
        if (!blink) {
          // this._twrite('\x1b]50;CursorShape=n;BlinkingCursorEnabled=0\x07');
        } else {
          // this._twrite('\x1b]50;CursorShape=n;BlinkingCursorEnabled=1\x07');
        }
        break;
      case 'line':
        if (!blink) {
          this._twrite('\x1b]50;CursorShape=1;BlinkingCursorEnabled=0\x07');
        } else {
          this._twrite('\x1b]50;CursorShape=1;BlinkingCursorEnabled=1\x07');
        }
        break;
    }
    return true;
  } else if (this.term('xterm') || this.term('xterm-color') || this.term('screen')) {
    switch (shape) {
      case 'block':
        if (!blink) {
          this._twrite('\x1b[0 q');
        } else {
          this._twrite('\x1b[1 q');
        }
        break;
      case 'underline':
        if (!blink) {
          this._twrite('\x1b[2 q');
        } else {
          this._twrite('\x1b[3 q');
        }
        break;
      case 'line':
        if (!blink) {
          this._twrite('\x1b[4 q');
        } else {
          this._twrite('\x1b[5 q');
        }
        break;
    }
    return true;
  }
  return false;
};

Program.prototype.cursorColor = function(color) {
  if (this.term('xterm') || this.term('rxvt') || this.term('screen')) {
    this._twrite('\x1b]12;' + color + '\x07');
    return true;
  }
  return false;
};

Program.prototype.cursorReset =
Program.prototype.resetCursor = function() {
  if (this.term('xterm') || this.term('xterm-color') || this.term('rxvt') || this.term('screen')) {
    // XXX
    // return this.resetColors();
    this._twrite('\x1b[0 q');
    this._twrite('\x1b]112\x07');
    // urxvt doesnt support OSC 112
    this._twrite('\x1b]12;white\x07');
    return true;
  }
  return false;
};

Program.prototype.getTextParams = function(param, callback) {
  return this.response('text-params', '\x1b]' + param + ';?\x07', function(err, data) {
    if (err) return callback(err);
    return callback(null, data.pt);
  });
};

Program.prototype.getCursorColor = function(callback) {
  return this.getTextParams(12, callback);
};

/**
 * Normal
 */

//Program.prototype.pad =
Program.prototype.nul = function() {
  //if (this.has('pad')) return this.put.pad();
  return this._write('\200');
};

Program.prototype.bel =
Program.prototype.bell = function() {
  if (this.has('bel')) return this.put.bel();
  return this._write('\x07');
};

Program.prototype.vtab = function() {
  this.y++;
  this._ncoords();
  return this._write('\x0b');
};

Program.prototype.ff =
Program.prototype.form = function() {
  if (this.has('ff')) return this.put.ff();
  return this._write('\x0c');
};

Program.prototype.kbs =
Program.prototype.backspace = function() {
  this.x--;
  this._ncoords();
  if (this.has('kbs')) return this.put.kbs();
  return this._write('\x08');
};

Program.prototype.ht =
Program.prototype.tab = function() {
  this.x += 8;
  this._ncoords();
  if (this.has('ht')) return this.put.ht();
  return this._write('\t');
};

Program.prototype.shiftOut = function() {
  // if (this.has('S2')) return this.put.S2();
  return this._write('\x0e');
};

Program.prototype.shiftIn = function() {
  // if (this.has('S3')) return this.put.S3();
  return this._write('\x0f');
};

Program.prototype.cr =
Program.prototype.return = function() {
  this.x = 0;
  if (this.has('cr')) return this.put.cr();
  return this._write('\r');
};

Program.prototype.nel =
Program.prototype.newline =
Program.prototype.feed = function() {
  if (this.tput && this.tput.bools.eat_newline_glitch && this.x >= this.cols) {
    return;
  }
  this.x = 0;
  this.y++;
  this._ncoords();
  if (this.has('nel')) return this.put.nel();
  return this._write('\n');
};

/**
 * Esc
 */

// ESC D Index (IND is 0x84).
Program.prototype.ind =
Program.prototype.index = function() {
  this.y++;
  this._ncoords();
  if (this.tput) return this.put.ind();
  return this._write('\x1bD');
};

// ESC M Reverse Index (RI is 0x8d).
Program.prototype.ri =
Program.prototype.reverse =
Program.prototype.reverseIndex = function() {
  this.y--;
  this._ncoords();
  if (this.tput) return this.put.ri();
  return this._write('\x1bM');
};

// ESC E Next Line (NEL is 0x85).
Program.prototype.nextLine = function() {
  this.y++;
  this.x = 0;
  this._ncoords();
  if (this.has('nel')) return this.put.nel();
  return this._write('\x1bE');
};

// ESC c Full Reset (RIS).
Program.prototype.reset = function() {
  this.x = this.y = 0;
  if (this.has('rs1') || this.has('ris')) {
    return this.has('rs1')
      ? this.put.rs1()
      : this.put.ris();
  }
  return this._write('\x1bc');
};

// ESC H Tab Set (HTS is 0x88).
Program.prototype.tabSet = function() {
  if (this.tput) return this.put.hts();
  return this._write('\x1bH');
};

// ESC 7 Save Cursor (DECSC).
Program.prototype.sc =
Program.prototype.saveCursor = function(key) {
  if (key) return this.lsaveCursor(key);
  this.savedX = this.x || 0;
  this.savedY = this.y || 0;
  if (this.tput) return this.put.sc();
  return this._write('\x1b7');
};

// ESC 8 Restore Cursor (DECRC).
Program.prototype.rc =
Program.prototype.restoreCursor = function(key, hide) {
  if (key) return this.lrestoreCursor(key, hide);
  this.x = this.savedX || 0;
  this.y = this.savedY || 0;
  if (this.tput) return this.put.rc();
  return this._write('\x1b8');
};

// Save Cursor Locally
Program.prototype.lsaveCursor = function(key) {
  key = key || 'local';
  this._saved = this._saved || {};
  this._saved[key] = this._saved[key] || {};
  this._saved[key].x = this.x;
  this._saved[key].y = this.y;
  this._saved[key].hidden = this.cursorHidden;
};

// Restore Cursor Locally
Program.prototype.lrestoreCursor = function(key, hide) {
  var pos;
  key = key || 'local';
  if (!this._saved || !this._saved[key]) return;
  pos = this._saved[key];
  //delete this._saved[key];
  this.cup(pos.y, pos.x);
  if (hide && pos.hidden !== this.cursorHidden) {
    if (pos.hidden) {
      this.hideCursor();
    } else {
      this.showCursor();
    }
  }
};

// ESC # 3 DEC line height/width
Program.prototype.lineHeight = function() {
  return this._write('\x1b#');
};

// ESC (,),*,+,-,. Designate G0-G2 Character Set.
Program.prototype.charset = function(val, level) {
  level = level || 0;

  // See also:
  // acs_chars / acsc / ac
  // enter_alt_charset_mode / smacs / as
  // exit_alt_charset_mode / rmacs / ae
  // enter_pc_charset_mode / smpch / S2
  // exit_pc_charset_mode / rmpch / S3

  switch (level) {
    case 0:
      level = '(';
      break;
    case 1:
      level = ')';
      break;
    case 2:
      level = '*';
      break;
    case 3:
      level = '+';
      break;
  }

  var name = typeof val === 'string'
    ? val.toLowerCase()
    : val;

  switch (name) {
    case 'acs':
    case 'scld': // DEC Special Character and Line Drawing Set.
      if (this.tput) return this.put.smacs();
      val = '0';
      break;
    case 'uk': // UK
      val = 'A';
      break;
    case 'us': // United States (USASCII).
    case 'usascii':
    case 'ascii':
      if (this.tput) return this.put.rmacs();
      val = 'B';
      break;
    case 'dutch': // Dutch
      val = '4';
      break;
    case 'finnish': // Finnish
      val = 'C';
      val = '5';
      break;
    case 'french': // French
      val = 'R';
      break;
    case 'frenchcanadian': // FrenchCanadian
      val = 'Q';
      break;
    case 'german':  // German
      val = 'K';
      break;
    case 'italian': // Italian
      val = 'Y';
      break;
    case 'norwegiandanish': // NorwegianDanish
      val = 'E';
      val = '6';
      break;
    case 'spanish': // Spanish
      val = 'Z';
      break;
    case 'swedish': // Swedish
      val = 'H';
      val = '7';
      break;
    case 'swiss': // Swiss
      val = '=';
      break;
    case 'isolatin': // ISOLatin (actually /A)
      val = '/A';
      break;
    default: // Default
      if (this.tput) return this.put.rmacs();
      val = 'B';
      break;
  }

  return this._write('\x1b(' + val);
};

Program.prototype.enter_alt_charset_mode =
Program.prototype.as =
Program.prototype.smacs = function() {
  return this.charset('acs');
};

Program.prototype.exit_alt_charset_mode =
Program.prototype.ae =
Program.prototype.rmacs = function() {
  return this.charset('ascii');
};

// ESC N
// Single Shift Select of G2 Character Set
// ( SS2 is 0x8e). This affects next character only.
// ESC O
// Single Shift Select of G3 Character Set
// ( SS3 is 0x8f). This affects next character only.
// ESC n
// Invoke the G2 Character Set as GL (LS2).
// ESC o
// Invoke the G3 Character Set as GL (LS3).
// ESC |
// Invoke the G3 Character Set as GR (LS3R).
// ESC }
// Invoke the G2 Character Set as GR (LS2R).
// ESC ~
// Invoke the G1 Character Set as GR (LS1R).
Program.prototype.setG = function(val) {
  // if (this.tput) return this.put.S2();
  // if (this.tput) return this.put.S3();
  switch (val) {
    case 1:
      val = '~'; // GR
      break;
    case 2:
      val = 'n'; // GL
      val = '}'; // GR
      val = 'N'; // Next Char Only
      break;
    case 3:
      val = 'o'; // GL
      val = '|'; // GR
      val = 'O'; // Next Char Only
      break;
  }
  return this._write('\x1b' + val);
};

/**
 * OSC
 */

// OSC Ps ; Pt ST
// OSC Ps ; Pt BEL
//   Set Text Parameters.
Program.prototype.setTitle = function(title) {
  this._title = title;

  // if (this.term('screen')) {
  //   // Tmux pane
  //   // if (this.tmux) {
  //   //   return this._write('\x1b]2;' + title + '\x1b\\');
  //   // }
  //   return this._write('\x1bk' + title + '\x1b\\');
  // }

  return this._twrite('\x1b]0;' + title + '\x07');
};

// OSC Ps ; Pt ST
// OSC Ps ; Pt BEL
//   Reset colors
Program.prototype.resetColors = function(param) {
  if (this.has('Cr')) {
    return this.put.Cr(param);
  }
  return this._twrite('\x1b]112\x07');
  //return this._twrite('\x1b]112;' + param + '\x07');
};

// OSC Ps ; Pt ST
// OSC Ps ; Pt BEL
//   Change dynamic colors
Program.prototype.dynamicColors = function(param) {
  if (this.has('Cs')) {
    return this.put.Cs(param);
  }
  return this._twrite('\x1b]12;' + param + '\x07');
};

// OSC Ps ; Pt ST
// OSC Ps ; Pt BEL
//   Sel data
Program.prototype.selData = function(a, b) {
  if (this.has('Ms')) {
    return this.put.Ms(a, b);
  }
  return this._twrite('\x1b]52;' + a + ';' + b + '\x07');
};

/**
 * CSI
 */

// CSI Ps A
// Cursor Up Ps Times (default = 1) (CUU).
Program.prototype.cuu =
Program.prototype.up =
Program.prototype.cursorUp = function(param) {
  this.y -= param || 1;
  this._ncoords();
  if (this.tput) {
    if (!this.tput.strings.parm_up_cursor) {
      return this._write(this.repeat(this.tput.cuu1(), param));
    }
    return this.put.cuu(param);
  }
  return this._write('\x1b[' + (param || '') + 'A');
};

// CSI Ps B
// Cursor Down Ps Times (default = 1) (CUD).
Program.prototype.cud =
Program.prototype.down =
Program.prototype.cursorDown = function(param) {
  this.y += param || 1;
  this._ncoords();
  if (this.tput) {
    if (!this.tput.strings.parm_down_cursor) {
      return this._write(this.repeat(this.tput.cud1(), param));
    }
    return this.put.cud(param);
  }
  return this._write('\x1b[' + (param || '') + 'B');
};

// CSI Ps C
// Cursor Forward Ps Times (default = 1) (CUF).
Program.prototype.cuf =
Program.prototype.right =
Program.prototype.forward =
Program.prototype.cursorForward = function(param) {
  this.x += param || 1;
  this._ncoords();
  if (this.tput) {
    if (!this.tput.strings.parm_right_cursor) {
      return this._write(this.repeat(this.tput.cuf1(), param));
    }
    return this.put.cuf(param);
  }
  return this._write('\x1b[' + (param || '') + 'C');
};

// CSI Ps D
// Cursor Backward Ps Times (default = 1) (CUB).
Program.prototype.cub =
Program.prototype.left =
Program.prototype.back =
Program.prototype.cursorBackward = function(param) {
  this.x -= param || 1;
  this._ncoords();
  if (this.tput) {
    if (!this.tput.strings.parm_left_cursor) {
      return this._write(this.repeat(this.tput.cub1(), param));
    }
    return this.put.cub(param);
  }
  return this._write('\x1b[' + (param || '') + 'D');
};

// CSI Ps ; Ps H
// Cursor Position [row;column] (default = [1,1]) (CUP).
Program.prototype.cup =
Program.prototype.pos =
Program.prototype.cursorPos = function(row, col) {
  if (!this.zero) {
    row = (row || 1) - 1;
    col = (col || 1) - 1;
  } else {
    row = row || 0;
    col = col || 0;
  }
  this.x = col;
  this.y = row;
  this._ncoords();
  if (this.tput) return this.put.cup(row, col);
  return this._write('\x1b[' + (row + 1) + ';' + (col + 1) + 'H');
};

// CSI Ps J  Erase in Display (ED).
//     Ps = 0  -> Erase Below (default).
//     Ps = 1  -> Erase Above.
//     Ps = 2  -> Erase All.
//     Ps = 3  -> Erase Saved Lines (xterm).
// CSI ? Ps J
//   Erase in Display (DECSED).
//     Ps = 0  -> Selective Erase Below (default).
//     Ps = 1  -> Selective Erase Above.
//     Ps = 2  -> Selective Erase All.
Program.prototype.ed =
Program.prototype.eraseInDisplay = function(param) {
  if (this.tput) {
    switch (param) {
      case 'above':
        param = 1;
        break;
      case 'all':
        param = 2;
        break;
      case 'saved':
        param = 3;
        break;
      case 'below':
      default:
        param = 0;
        break;
    }
    // extended tput.E3 = ^[[3;J
    return this.put.ed(param);
  }
  switch (param) {
    case 'above':
      return this._write('\X1b[1J');
    case 'all':
      return this._write('\x1b[2J');
    case 'saved':
      return this._write('\x1b[3J');
    case 'below':
    default:
      return this._write('\x1b[J');
  }
};

Program.prototype.clear = function() {
  this.x = 0;
  this.y = 0;
  if (this.tput) return this.put.clear();
  return this._write('\x1b[H\x1b[J');
};

// CSI Ps K  Erase in Line (EL).
//     Ps = 0  -> Erase to Right (default).
//     Ps = 1  -> Erase to Left.
//     Ps = 2  -> Erase All.
// CSI ? Ps K
//   Erase in Line (DECSEL).
//     Ps = 0  -> Selective Erase to Right (default).
//     Ps = 1  -> Selective Erase to Left.
//     Ps = 2  -> Selective Erase All.
Program.prototype.el =
Program.prototype.eraseInLine = function(param) {
  if (this.tput) {
    //if (this.tput.back_color_erase) ...
    switch (param) {
      case 'left':
        param = 1;
        break;
      case 'all':
        param = 2;
        break;
      case 'right':
      default:
        param = 0;
        break;
    }
    return this.put.el(param);
  }
  switch (param) {
    case 'left':
      return this._write('\x1b[1K');
    case 'all':
      return this._write('\x1b[2K');
    case 'right':
    default:
      return this._write('\x1b[K');
  }
};

// CSI Pm m  Character Attributes (SGR).
//     Ps = 0  -> Normal (default).
//     Ps = 1  -> Bold.
//     Ps = 4  -> Underlined.
//     Ps = 5  -> Blink (appears as Bold).
//     Ps = 7  -> Inverse.
//     Ps = 8  -> Invisible, i.e., hidden (VT300).
//     Ps = 2 2  -> Normal (neither bold nor faint).
//     Ps = 2 4  -> Not underlined.
//     Ps = 2 5  -> Steady (not blinking).
//     Ps = 2 7  -> Positive (not inverse).
//     Ps = 2 8  -> Visible, i.e., not hidden (VT300).
//     Ps = 3 0  -> Set foreground color to Black.
//     Ps = 3 1  -> Set foreground color to Red.
//     Ps = 3 2  -> Set foreground color to Green.
//     Ps = 3 3  -> Set foreground color to Yellow.
//     Ps = 3 4  -> Set foreground color to Blue.
//     Ps = 3 5  -> Set foreground color to Magenta.
//     Ps = 3 6  -> Set foreground color to Cyan.
//     Ps = 3 7  -> Set foreground color to White.
//     Ps = 3 9  -> Set foreground color to default (original).
//     Ps = 4 0  -> Set background color to Black.
//     Ps = 4 1  -> Set background color to Red.
//     Ps = 4 2  -> Set background color to Green.
//     Ps = 4 3  -> Set background color to Yellow.
//     Ps = 4 4  -> Set background color to Blue.
//     Ps = 4 5  -> Set background color to Magenta.
//     Ps = 4 6  -> Set background color to Cyan.
//     Ps = 4 7  -> Set background color to White.
//     Ps = 4 9  -> Set background color to default (original).

//   If 16-color support is compiled, the following apply.  Assume
//   that xterm's resources are set so that the ISO color codes are
//   the first 8 of a set of 16.  Then the aixterm colors are the
//   bright versions of the ISO colors:
//     Ps = 9 0  -> Set foreground color to Black.
//     Ps = 9 1  -> Set foreground color to Red.
//     Ps = 9 2  -> Set foreground color to Green.
//     Ps = 9 3  -> Set foreground color to Yellow.
//     Ps = 9 4  -> Set foreground color to Blue.
//     Ps = 9 5  -> Set foreground color to Magenta.
//     Ps = 9 6  -> Set foreground color to Cyan.
//     Ps = 9 7  -> Set foreground color to White.
//     Ps = 1 0 0  -> Set background color to Black.
//     Ps = 1 0 1  -> Set background color to Red.
//     Ps = 1 0 2  -> Set background color to Green.
//     Ps = 1 0 3  -> Set background color to Yellow.
//     Ps = 1 0 4  -> Set background color to Blue.
//     Ps = 1 0 5  -> Set background color to Magenta.
//     Ps = 1 0 6  -> Set background color to Cyan.
//     Ps = 1 0 7  -> Set background color to White.

//   If xterm is compiled with the 16-color support disabled, it
//   supports the following, from rxvt:
//     Ps = 1 0 0  -> Set foreground and background color to
//     default.

//   If 88- or 256-color support is compiled, the following apply.
//     Ps = 3 8  ; 5  ; Ps -> Set foreground color to the second
//     Ps.
//     Ps = 4 8  ; 5  ; Ps -> Set background color to the second
//     Ps.
Program.prototype.sgr =
Program.prototype.attr =
Program.prototype.charAttributes = function(param, val) {
  return this._write(this._attr(param, val));
};

Program.prototype.text = function(text, attr) {
  return this._attr(attr, true) + text + this._attr(attr, false);
};

// NOTE: sun-color may not allow multiple params for SGR.
Program.prototype._attr = function(param, val) {
  var self = this
    , parts
    , color
    , m;

  if (Array.isArray(param)) {
    parts = param;
    param = parts[0] || 'normal';
  } else {
    param = param || 'normal';
    parts = param.split(/\s*[,;]\s*/);
  }

  if (parts.length > 1) {
    var used = {}
      , out = [];

    parts.forEach(function(part) {
      part = self._attr(part, val).slice(2, -1);
      if (part === '') return;
      if (used[part]) return;
      used[part] = true;
      out.push(part);
    });

    return '\x1b[' + out.join(';') + 'm';
  }

  if (param.indexOf('no ') === 0) {
    param = param.substring(3);
    val = false;
  } else if (param.indexOf('!') === 0) {
    param = param.substring(1);
    val = false;
  }

  switch (param) {
    // attributes
    case 'normal':
    case 'default':
      if (val === false) return '';
      return '\x1b[m';
    case 'bold':
      return val === false
        ? '\x1b[22m'
        : '\x1b[1m';
    case 'ul':
    case 'underline':
    case 'underlined':
      return val === false
        ? '\x1b[24m'
        : '\x1b[4m';
    case 'blink':
      return val === false
        ? '\x1b[25m'
        : '\x1b[5m';
    case 'inverse':
      return val === false
        ? '\x1b[27m'
        : '\x1b[7m';
    case 'invisible':
      return val === false
        ? '\x1b[28m'
        : '\x1b[8m';

    // 8-color foreground
    case 'black fg':
      return val === false
        ? '\x1b[39m'
        : '\x1b[30m';
    case 'red fg':
      return val === false
        ? '\x1b[39m'
        : '\x1b[31m';
    case 'green fg':
      return val === false
        ? '\x1b[39m'
        : '\x1b[32m';
    case 'yellow fg':
      return val === false
        ? '\x1b[39m'
        : '\x1b[33m';
    case 'blue fg':
      return val === false
        ? '\x1b[39m'
        : '\x1b[34m';
    case 'magenta fg':
      return val === false
        ? '\x1b[39m'
        : '\x1b[35m';
    case 'cyan fg':
      return val === false
        ? '\x1b[39m'
        : '\x1b[36m';
    case 'white fg':
    case 'light grey fg':
    case 'light gray fg':
    case 'bright grey fg':
    case 'bright gray fg':
      return val === false
        ? '\x1b[39m'
        : '\x1b[37m';
    case 'default fg':
      if (val === false) return '';
      return '\x1b[39m';

    // 8-color background
    case 'black bg':
      return val === false
        ? '\x1b[49m'
        : '\x1b[40m';
    case 'red bg':
      return val === false
        ? '\x1b[49m'
        : '\x1b[41m';
    case 'green bg':
      return val === false
        ? '\x1b[49m'
        : '\x1b[42m';
    case 'yellow bg':
      return val === false
        ? '\x1b[49m'
        : '\x1b[43m';
    case 'blue bg':
      return val === false
        ? '\x1b[49m'
        : '\x1b[44m';
    case 'magenta bg':
      return val === false
        ? '\x1b[49m'
        : '\x1b[45m';
    case 'cyan bg':
      return val === false
        ? '\x1b[49m'
        : '\x1b[46m';
    case 'white bg':
    case 'light grey bg':
    case 'light gray bg':
    case 'bright grey bg':
    case 'bright gray bg':
      return val === false
        ? '\x1b[49m'
        : '\x1b[47m';
    case 'default bg':
      if (val === false) return '';
      return '\x1b[49m';

    // 16-color foreground
    case 'light black fg':
    case 'bright black fg':
    case 'grey fg':
    case 'gray fg':
      return val === false
        ? '\x1b[39m'
        : '\x1b[90m';
    case 'light red fg':
    case 'bright red fg':
      return val === false
        ? '\x1b[39m'
        : '\x1b[91m';
    case 'light green fg':
    case 'bright green fg':
      return val === false
        ? '\x1b[39m'
        : '\x1b[92m';
    case 'light yellow fg':
    case 'bright yellow fg':
      return val === false
        ? '\x1b[39m'
        : '\x1b[93m';
    case 'light blue fg':
    case 'bright blue fg':
      return val === false
        ? '\x1b[39m'
        : '\x1b[94m';
    case 'light magenta fg':
    case 'bright magenta fg':
      return val === false
        ? '\x1b[39m'
        : '\x1b[95m';
    case 'light cyan fg':
    case 'bright cyan fg':
      return val === false
        ? '\x1b[39m'
        : '\x1b[96m';
    case 'light white fg':
    case 'bright white fg':
      return val === false
        ? '\x1b[39m'
        : '\x1b[97m';

    // 16-color background
    case 'light black bg':
    case 'bright black bg':
    case 'grey bg':
    case 'gray bg':
      return val === false
        ? '\x1b[49m'
        : '\x1b[100m';
    case 'light red bg':
    case 'bright red bg':
      return val === false
        ? '\x1b[49m'
        : '\x1b[101m';
    case 'light green bg':
    case 'bright green bg':
      return val === false
        ? '\x1b[49m'
        : '\x1b[102m';
    case 'light yellow bg':
    case 'bright yellow bg':
      return val === false
        ? '\x1b[49m'
        : '\x1b[103m';
    case 'light blue bg':
    case 'bright blue bg':
      return val === false
        ? '\x1b[49m'
        : '\x1b[104m';
    case 'light magenta bg':
    case 'bright magenta bg':
      return val === false
        ? '\x1b[49m'
        : '\x1b[105m';
    case 'light cyan bg':
    case 'bright cyan bg':
      return val === false
        ? '\x1b[49m'
        : '\x1b[106m';
    case 'light white bg':
    case 'bright white bg':
      return val === false
        ? '\x1b[49m'
        : '\x1b[107m';

    // non-16-color rxvt default fg and bg
    case 'default fg bg':
      if (val === false) return '';
      return this.term('rxvt')
        ? '\x1b[100m'
        : '\x1b[39;49m';

    default:
      // 256-color fg and bg
      if (param[0] === '#') {
        param = param.replace(/#(?:[0-9a-f]{3}){1,2}/i, colors.match);
      }

      m = /^(-?\d+) (fg|bg)$/.exec(param);
      if (m) {
        color = +m[1];

        if (val === false || color === -1) {
          return this._attr('default ' + m[2]);
        }

        color = colors.reduce(color, this.tput.colors);

        if (color < 16 || (this.tput && this.tput.colors <= 16)) {
          if (m[2] === 'fg') {
            if (color < 8) {
              color += 30;
            } else if (color < 16) {
              color -= 8;
              color += 90;
            }
          } else if (m[2] === 'bg') {
            if (color < 8) {
              color += 40;
            } else if (color < 16) {
              color -= 8;
              color += 100;
            }
          }
          return '\x1b[' + color + 'm';
        }

        if (m[2] === 'fg') {
          return '\x1b[38;5;' + color + 'm';
        }

        if (m[2] === 'bg') {
          return '\x1b[48;5;' + color + 'm';
        }
      }

      if (/^[\d;]*$/.test(param)) {
        return '\x1b[' + param + 'm';
      }

      return null;
  }
};

Program.prototype.fg =
Program.prototype.setForeground = function(color, val) {
  color = color.split(/\s*[,;]\s*/).join(' fg, ') + ' fg';
  return this.attr(color, val);
};

Program.prototype.bg =
Program.prototype.setBackground = function(color, val) {
  color = color.split(/\s*[,;]\s*/).join(' bg, ') + ' bg';
  return this.attr(color, val);
};

// CSI Ps n  Device Status Report (DSR).
//     Ps = 5  -> Status Report.  Result (``OK'') is
//   CSI 0 n
//     Ps = 6  -> Report Cursor Position (CPR) [row;column].
//   Result is
//   CSI r ; c R
// CSI ? Ps n
//   Device Status Report (DSR, DEC-specific).
//     Ps = 6  -> Report Cursor Position (CPR) [row;column] as CSI
//     ? r ; c R (assumes page is zero).
//     Ps = 1 5  -> Report Printer status as CSI ? 1 0  n  (ready).
//     or CSI ? 1 1  n  (not ready).
//     Ps = 2 5  -> Report UDK status as CSI ? 2 0  n  (unlocked)
//     or CSI ? 2 1  n  (locked).
//     Ps = 2 6  -> Report Keyboard status as
//   CSI ? 2 7  ;  1  ;  0  ;  0  n  (North American).
//   The last two parameters apply to VT400 & up, and denote key-
//   board ready and LK01 respectively.
//     Ps = 5 3  -> Report Locator status as
//   CSI ? 5 3  n  Locator available, if compiled-in, or
//   CSI ? 5 0  n  No Locator, if not.
Program.prototype.dsr =
Program.prototype.deviceStatus = function(param, callback, dec, noBypass) {
  if (dec) {
    return this.response('device-status',
      '\x1b[?' + (param || '0') + 'n', callback, noBypass);
  }
  return this.response('device-status',
    '\x1b[' + (param || '0') + 'n', callback, noBypass);
};

Program.prototype.getCursor = function(callback) {
  return this.deviceStatus(6, callback, false, true);
};

Program.prototype.saveReportedCursor = function(callback) {
  var self = this;
  if (this.tput.strings.user7 === '\x1b[6n' || this.term('screen')) {
    return this.getCursor(function(err, data) {
      if (data) {
        self._rx = data.status.x;
        self._ry = data.status.y;
      }
      if (!callback) return;
      return callback(err);
    });
  }
  if (!callback) return;
  return callback();
};

Program.prototype.restoreReportedCursor = function() {
  if (this._rx == null) return;
  return this.cup(this._ry, this._rx);
  // return this.nel();
};

/**
 * Additions
 */

// CSI Ps @
// Insert Ps (Blank) Character(s) (default = 1) (ICH).
Program.prototype.ich =
Program.prototype.insertChars = function(param) {
  this.x += param || 1;
  this._ncoords();
  if (this.tput) return this.put.ich(param);
  return this._write('\x1b[' + (param || 1) + '@');
};

// CSI Ps E
// Cursor Next Line Ps Times (default = 1) (CNL).
// same as CSI Ps B ?
Program.prototype.cnl =
Program.prototype.cursorNextLine = function(param) {
  this.y += param || 1;
  this._ncoords();
  return this._write('\x1b[' + (param || '') + 'E');
};

// CSI Ps F
// Cursor Preceding Line Ps Times (default = 1) (CNL).
// reuse CSI Ps A ?
Program.prototype.cpl =
Program.prototype.cursorPrecedingLine = function(param) {
  this.y -= param || 1;
  this._ncoords();
  return this._write('\x1b[' + (param || '') + 'F');
};

// CSI Ps G
// Cursor Character Absolute  [column] (default = [row,1]) (CHA).
Program.prototype.cha =
Program.prototype.cursorCharAbsolute = function(param) {
  if (!this.zero) {
    param = (param || 1) - 1;
  } else {
    param = param || 0;
  }
  this.x = param;
  this.y = 0;
  this._ncoords();
  if (this.tput) return this.put.hpa(param);
  return this._write('\x1b[' + (param + 1) + 'G');
};

// CSI Ps L
// Insert Ps Line(s) (default = 1) (IL).
Program.prototype.il =
Program.prototype.insertLines = function(param) {
  if (this.tput) return this.put.il(param);
  return this._write('\x1b[' + (param || '') + 'L');
};

// CSI Ps M
// Delete Ps Line(s) (default = 1) (DL).
Program.prototype.dl =
Program.prototype.deleteLines = function(param) {
  if (this.tput) return this.put.dl(param);
  return this._write('\x1b[' + (param || '') + 'M');
};

// CSI Ps P
// Delete Ps Character(s) (default = 1) (DCH).
Program.prototype.dch =
Program.prototype.deleteChars = function(param) {
  if (this.tput) return this.put.dch(param);
  return this._write('\x1b[' + (param || '') + 'P');
};

// CSI Ps X
// Erase Ps Character(s) (default = 1) (ECH).
Program.prototype.ech =
Program.prototype.eraseChars = function(param) {
  if (this.tput) return this.put.ech(param);
  return this._write('\x1b[' + (param || '') + 'X');
};

// CSI Pm `  Character Position Absolute
//   [column] (default = [row,1]) (HPA).
Program.prototype.hpa =
Program.prototype.charPosAbsolute = function(param) {
  this.x = param || 0;
  this._ncoords();
  if (this.tput) {
    return this.put.hpa.apply(this.put, arguments);
  }
  param = slice.call(arguments).join(';');
  return this._write('\x1b[' + (param || '') + '`');
};

// 141 61 a * HPR -
// Horizontal Position Relative
// reuse CSI Ps C ?
Program.prototype.hpr =
Program.prototype.HPositionRelative = function(param) {
  if (this.tput) return this.cuf(param);
  this.x += param || 1;
  this._ncoords();
  // Does not exist:
  // if (this.tput) return this.put.hpr(param);
  return this._write('\x1b[' + (param || '') + 'a');
};

// CSI Ps c  Send Device Attributes (Primary DA).
//     Ps = 0  or omitted -> request attributes from terminal.  The
//     response depends on the decTerminalID resource setting.
//     -> CSI ? 1 ; 2 c  (``VT100 with Advanced Video Option'')
//     -> CSI ? 1 ; 0 c  (``VT101 with No Options'')
//     -> CSI ? 6 c  (``VT102'')
//     -> CSI ? 6 0 ; 1 ; 2 ; 6 ; 8 ; 9 ; 1 5 ; c  (``VT220'')
//   The VT100-style response parameters do not mean anything by
//   themselves.  VT220 parameters do, telling the host what fea-
//   tures the terminal supports:
//     Ps = 1  -> 132-columns.
//     Ps = 2  -> Printer.
//     Ps = 6  -> Selective erase.
//     Ps = 8  -> User-defined keys.
//     Ps = 9  -> National replacement character sets.
//     Ps = 1 5  -> Technical characters.
//     Ps = 2 2  -> ANSI color, e.g., VT525.
//     Ps = 2 9  -> ANSI text locator (i.e., DEC Locator mode).
// CSI > Ps c
//   Send Device Attributes (Secondary DA).
//     Ps = 0  or omitted -> request the terminal's identification
//     code.  The response depends on the decTerminalID resource set-
//     ting.  It should apply only to VT220 and up, but xterm extends
//     this to VT100.
//     -> CSI  > Pp ; Pv ; Pc c
//   where Pp denotes the terminal type
//     Pp = 0  -> ``VT100''.
//     Pp = 1  -> ``VT220''.
//   and Pv is the firmware version (for xterm, this was originally
//   the XFree86 patch number, starting with 95).  In a DEC termi-
//   nal, Pc indicates the ROM cartridge registration number and is
//   always zero.
// More information:
//   xterm/charproc.c - line 2012, for more information.
//   vim responds with ^[[?0c or ^[[?1c after the terminal's response (?)
Program.prototype.da =
Program.prototype.sendDeviceAttributes = function(param, callback) {
  return this.response('device-attributes',
    '\x1b[' + (param || '') + 'c', callback);
};

// CSI Pm d
// Line Position Absolute  [row] (default = [1,column]) (VPA).
// NOTE: Can't find in terminfo, no idea why it has multiple params.
Program.prototype.vpa =
Program.prototype.linePosAbsolute = function(param) {
  this.y = param || 1;
  this._ncoords();
  if (this.tput) {
    return this.put.vpa.apply(this.put, arguments);
  }
  param = slice.call(arguments).join(';');
  return this._write('\x1b[' + (param || '') + 'd');
};

// 145 65 e * VPR - Vertical Position Relative
// reuse CSI Ps B ?
Program.prototype.vpr =
Program.prototype.VPositionRelative = function(param) {
  if (this.tput) return this.cud(param);
  this.y += param || 1;
  this._ncoords();
  // Does not exist:
  // if (this.tput) return this.put.vpr(param);
  return this._write('\x1b[' + (param || '') + 'e');
};

// CSI Ps ; Ps f
//   Horizontal and Vertical Position [row;column] (default =
//   [1,1]) (HVP).
Program.prototype.hvp =
Program.prototype.HVPosition = function(row, col) {
  if (!this.zero) {
    row = (row || 1) - 1;
    col = (col || 1) - 1;
  } else {
    row = row || 0;
    col = col || 0;
  }
  this.y = row;
  this.x = col;
  this._ncoords();
  // Does not exist (?):
  // if (this.tput) return this.put.hvp(row, col);
  if (this.tput) return this.put.cup(row, col);
  return this._write('\x1b[' + (row + 1) + ';' + (col + 1) + 'f');
};

// CSI Pm h  Set Mode (SM).
//     Ps = 2  -> Keyboard Action Mode (AM).
//     Ps = 4  -> Insert Mode (IRM).
//     Ps = 1 2  -> Send/receive (SRM).
//     Ps = 2 0  -> Automatic Newline (LNM).
// CSI ? Pm h
//   DEC Private Mode Set (DECSET).
//     Ps = 1  -> Application Cursor Keys (DECCKM).
//     Ps = 2  -> Designate USASCII for character sets G0-G3
//     (DECANM), and set VT100 mode.
//     Ps = 3  -> 132 Column Mode (DECCOLM).
//     Ps = 4  -> Smooth (Slow) Scroll (DECSCLM).
//     Ps = 5  -> Reverse Video (DECSCNM).
//     Ps = 6  -> Origin Mode (DECOM).
//     Ps = 7  -> Wraparound Mode (DECAWM).
//     Ps = 8  -> Auto-repeat Keys (DECARM).
//     Ps = 9  -> Send Mouse X & Y on button press.  See the sec-
//     tion Mouse Tracking.
//     Ps = 1 0  -> Show toolbar (rxvt).
//     Ps = 1 2  -> Start Blinking Cursor (att610).
//     Ps = 1 8  -> Print form feed (DECPFF).
//     Ps = 1 9  -> Set print extent to full screen (DECPEX).
//     Ps = 2 5  -> Show Cursor (DECTCEM).
//     Ps = 3 0  -> Show scrollbar (rxvt).
//     Ps = 3 5  -> Enable font-shifting functions (rxvt).
//     Ps = 3 8  -> Enter Tektronix Mode (DECTEK).
//     Ps = 4 0  -> Allow 80 -> 132 Mode.
//     Ps = 4 1  -> more(1) fix (see curses resource).
//     Ps = 4 2  -> Enable Nation Replacement Character sets (DECN-
//     RCM).
//     Ps = 4 4  -> Turn On Margin Bell.
//     Ps = 4 5  -> Reverse-wraparound Mode.
//     Ps = 4 6  -> Start Logging.  This is normally disabled by a
//     compile-time option.
//     Ps = 4 7  -> Use Alternate Screen Buffer.  (This may be dis-
//     abled by the titeInhibit resource).
//     Ps = 6 6  -> Application keypad (DECNKM).
//     Ps = 6 7  -> Backarrow key sends backspace (DECBKM).
//     Ps = 1 0 0 0  -> Send Mouse X & Y on button press and
//     release.  See the section Mouse Tracking.
//     Ps = 1 0 0 1  -> Use Hilite Mouse Tracking.
//     Ps = 1 0 0 2  -> Use Cell Motion Mouse Tracking.
//     Ps = 1 0 0 3  -> Use All Motion Mouse Tracking.
//     Ps = 1 0 0 4  -> Send FocusIn/FocusOut events.
//     Ps = 1 0 0 5  -> Enable Extended Mouse Mode.
//     Ps = 1 0 1 0  -> Scroll to bottom on tty output (rxvt).
//     Ps = 1 0 1 1  -> Scroll to bottom on key press (rxvt).
//     Ps = 1 0 3 4  -> Interpret "meta" key, sets eighth bit.
//     (enables the eightBitInput resource).
//     Ps = 1 0 3 5  -> Enable special modifiers for Alt and Num-
//     Lock keys.  (This enables the numLock resource).
//     Ps = 1 0 3 6  -> Send ESC   when Meta modifies a key.  (This
//     enables the metaSendsEscape resource).
//     Ps = 1 0 3 7  -> Send DEL from the editing-keypad Delete
//     key.
//     Ps = 1 0 3 9  -> Send ESC  when Alt modifies a key.  (This
//     enables the altSendsEscape resource).
//     Ps = 1 0 4 0  -> Keep selection even if not highlighted.
//     (This enables the keepSelection resource).
//     Ps = 1 0 4 1  -> Use the CLIPBOARD selection.  (This enables
//     the selectToClipboard resource).
//     Ps = 1 0 4 2  -> Enable Urgency window manager hint when
//     Control-G is received.  (This enables the bellIsUrgent
//     resource).
//     Ps = 1 0 4 3  -> Enable raising of the window when Control-G
//     is received.  (enables the popOnBell resource).
//     Ps = 1 0 4 7  -> Use Alternate Screen Buffer.  (This may be
//     disabled by the titeInhibit resource).
//     Ps = 1 0 4 8  -> Save cursor as in DECSC.  (This may be dis-
//     abled by the titeInhibit resource).
//     Ps = 1 0 4 9  -> Save cursor as in DECSC and use Alternate
//     Screen Buffer, clearing it first.  (This may be disabled by
//     the titeInhibit resource).  This combines the effects of the 1
//     0 4 7  and 1 0 4 8  modes.  Use this with terminfo-based
//     applications rather than the 4 7  mode.
//     Ps = 1 0 5 0  -> Set terminfo/termcap function-key mode.
//     Ps = 1 0 5 1  -> Set Sun function-key mode.
//     Ps = 1 0 5 2  -> Set HP function-key mode.
//     Ps = 1 0 5 3  -> Set SCO function-key mode.
//     Ps = 1 0 6 0  -> Set legacy keyboard emulation (X11R6).
//     Ps = 1 0 6 1  -> Set VT220 keyboard emulation.
//     Ps = 2 0 0 4  -> Set bracketed paste mode.
// Modes:
//   http://vt100.net/docs/vt220-rm/chapter4.html
Program.prototype.sm =
Program.prototype.setMode = function() {
  var param = slice.call(arguments).join(';');
  return this._write('\x1b[' + (param || '') + 'h');
};

Program.prototype.decset = function() {
  var param = slice.call(arguments).join(';');
  return this.setMode('?' + param);
};

Program.prototype.dectcem =
Program.prototype.cnorm =
Program.prototype.cvvis =
Program.prototype.showCursor = function() {
  this.cursorHidden = false;
  // NOTE: In xterm terminfo:
  // cnorm stops blinking cursor
  // cvvis starts blinking cursor
  if (this.tput) return this.put.cnorm();
  //if (this.tput) return this.put.cvvis();
  // return this._write('\x1b[?12l\x1b[?25h'); // cursor_normal
  // return this._write('\x1b[?12;25h'); // cursor_visible
  return this.setMode('?25');
};

Program.prototype.alternate =
Program.prototype.smcup =
Program.prototype.alternateBuffer = function() {
  this.isAlt = true;
  if (this.tput) return this.put.smcup();
  if (this.term('vt') || this.term('linux')) return;
  this.setMode('?47');
  return this.setMode('?1049');
};

// CSI Pm l  Reset Mode (RM).
//     Ps = 2  -> Keyboard Action Mode (AM).
//     Ps = 4  -> Replace Mode (IRM).
//     Ps = 1 2  -> Send/receive (SRM).
//     Ps = 2 0  -> Normal Linefeed (LNM).
// CSI ? Pm l
//   DEC Private Mode Reset (DECRST).
//     Ps = 1  -> Normal Cursor Keys (DECCKM).
//     Ps = 2  -> Designate VT52 mode (DECANM).
//     Ps = 3  -> 80 Column Mode (DECCOLM).
//     Ps = 4  -> Jump (Fast) Scroll (DECSCLM).
//     Ps = 5  -> Normal Video (DECSCNM).
//     Ps = 6  -> Normal Cursor Mode (DECOM).
//     Ps = 7  -> No Wraparound Mode (DECAWM).
//     Ps = 8  -> No Auto-repeat Keys (DECARM).
//     Ps = 9  -> Don't send Mouse X & Y on button press.
//     Ps = 1 0  -> Hide toolbar (rxvt).
//     Ps = 1 2  -> Stop Blinking Cursor (att610).
//     Ps = 1 8  -> Don't print form feed (DECPFF).
//     Ps = 1 9  -> Limit print to scrolling region (DECPEX).
//     Ps = 2 5  -> Hide Cursor (DECTCEM).
//     Ps = 3 0  -> Don't show scrollbar (rxvt).
//     Ps = 3 5  -> Disable font-shifting functions (rxvt).
//     Ps = 4 0  -> Disallow 80 -> 132 Mode.
//     Ps = 4 1  -> No more(1) fix (see curses resource).
//     Ps = 4 2  -> Disable Nation Replacement Character sets (DEC-
//     NRCM).
//     Ps = 4 4  -> Turn Off Margin Bell.
//     Ps = 4 5  -> No Reverse-wraparound Mode.
//     Ps = 4 6  -> Stop Logging.  (This is normally disabled by a
//     compile-time option).
//     Ps = 4 7  -> Use Normal Screen Buffer.
//     Ps = 6 6  -> Numeric keypad (DECNKM).
//     Ps = 6 7  -> Backarrow key sends delete (DECBKM).
//     Ps = 1 0 0 0  -> Don't send Mouse X & Y on button press and
//     release.  See the section Mouse Tracking.
//     Ps = 1 0 0 1  -> Don't use Hilite Mouse Tracking.
//     Ps = 1 0 0 2  -> Don't use Cell Motion Mouse Tracking.
//     Ps = 1 0 0 3  -> Don't use All Motion Mouse Tracking.
//     Ps = 1 0 0 4  -> Don't send FocusIn/FocusOut events.
//     Ps = 1 0 0 5  -> Disable Extended Mouse Mode.
//     Ps = 1 0 1 0  -> Don't scroll to bottom on tty output
//     (rxvt).
//     Ps = 1 0 1 1  -> Don't scroll to bottom on key press (rxvt).
//     Ps = 1 0 3 4  -> Don't interpret "meta" key.  (This disables
//     the eightBitInput resource).
//     Ps = 1 0 3 5  -> Disable special modifiers for Alt and Num-
//     Lock keys.  (This disables the numLock resource).
//     Ps = 1 0 3 6  -> Don't send ESC  when Meta modifies a key.
//     (This disables the metaSendsEscape resource).
//     Ps = 1 0 3 7  -> Send VT220 Remove from the editing-keypad
//     Delete key.
//     Ps = 1 0 3 9  -> Don't send ESC  when Alt modifies a key.
//     (This disables the altSendsEscape resource).
//     Ps = 1 0 4 0  -> Do not keep selection when not highlighted.
//     (This disables the keepSelection resource).
//     Ps = 1 0 4 1  -> Use the PRIMARY selection.  (This disables
//     the selectToClipboard resource).
//     Ps = 1 0 4 2  -> Disable Urgency window manager hint when
//     Control-G is received.  (This disables the bellIsUrgent
//     resource).
//     Ps = 1 0 4 3  -> Disable raising of the window when Control-
//     G is received.  (This disables the popOnBell resource).
//     Ps = 1 0 4 7  -> Use Normal Screen Buffer, clearing screen
//     first if in the Alternate Screen.  (This may be disabled by
//     the titeInhibit resource).
//     Ps = 1 0 4 8  -> Restore cursor as in DECRC.  (This may be
//     disabled by the titeInhibit resource).
//     Ps = 1 0 4 9  -> Use Normal Screen Buffer and restore cursor
//     as in DECRC.  (This may be disabled by the titeInhibit
//     resource).  This combines the effects of the 1 0 4 7  and 1 0
//     4 8  modes.  Use this with terminfo-based applications rather
//     than the 4 7  mode.
//     Ps = 1 0 5 0  -> Reset terminfo/termcap function-key mode.
//     Ps = 1 0 5 1  -> Reset Sun function-key mode.
//     Ps = 1 0 5 2  -> Reset HP function-key mode.
//     Ps = 1 0 5 3  -> Reset SCO function-key mode.
//     Ps = 1 0 6 0  -> Reset legacy keyboard emulation (X11R6).
//     Ps = 1 0 6 1  -> Reset keyboard emulation to Sun/PC style.
//     Ps = 2 0 0 4  -> Reset bracketed paste mode.
Program.prototype.rm =
Program.prototype.resetMode = function() {
  var param = slice.call(arguments).join(';');
  return this._write('\x1b[' + (param || '') + 'l');
};

Program.prototype.decrst = function() {
  var param = slice.call(arguments).join(';');
  return this.resetMode('?' + param);
};

Program.prototype.dectcemh =
Program.prototype.cursor_invisible =
Program.prototype.vi =
Program.prototype.civis =
Program.prototype.hideCursor = function(force) {
  this.cursorHidden = true;
  if (!force && this.tput) return this.put.civis();
  return this.resetMode('?25');
};

Program.prototype.rmcup =
Program.prototype.normalBuffer = function() {
  this.isAlt = false;
  if (this.tput) return this.put.rmcup();
  this.resetMode('?47');
  return this.resetMode('?1049');
};

Program.prototype.enableMouse = function() {
  if (process.env.BLESSED_FORCE_MODES) {
    var modes = process.env.BLESSED_FORCE_MODES.split(',');
    var options = {};
    for (var n = 0; n < modes.length; ++n) {
      var pair = modes[n].split('=');
      var v = pair[1] !== '0';
      switch (pair[0].toUpperCase()) {
        case 'SGRMOUSE':
          options.sgrMouse = v;
          break;
        case 'UTFMOUSE':
          options.utfMouse = v;
          break;
        case 'VT200MOUSE':
          options.vt200Mouse = v;
          break;
        case 'URXVTMOUSE':
          options.urxvtMouse = v;
          break;
        case 'X10MOUSE':
          options.x10Mouse = v;
          break;
        case 'DECMOUSE':
          options.decMouse = v;
          break;
        case 'PTERMMOUSE':
          options.ptermMouse = v;
          break;
        case 'JSBTERMMOUSE':
          options.jsbtermMouse = v;
          break;
        case 'VT200HILITE':
          options.vt200Hilite = v;
          break;
        case 'GPMMOUSE':
          options.gpmMouse = v;
          break;
        case 'CELLMOTION':
          options.cellMotion = v;
          break;
        case 'ALLMOTION':
          options.allMotion = v;
          break;
        case 'SENDFOCUS':
          options.sendFocus = v;
          break;
      }
    }
    return this.setMouse(options, true);
  }

  // NOTE:
  // Cell Motion isn't normally need for anything below here, but we'll
  // activate it for tmux (whether using it or not) in case our all-motion
  // passthrough does not work. It can't hurt.

  if (this.term('rxvt-unicode')) {
    return this.setMouse({
      urxvtMouse: true,
      cellMotion: true,
      allMotion: true
    }, true);
  }

  // rxvt does not support the X10 UTF extensions
  if (this.term('rxvt')) {
    return this.setMouse({
      vt200Mouse: true,
      x10Mouse: true,
      cellMotion: true,
      allMotion: true
    }, true);
  }

  // libvte is broken. Older versions do not support the
  // X10 UTF extension. However, later versions do support
  // SGR/URXVT.
  if (this.isVTE) {
    return this.setMouse({
      // NOTE: Could also use urxvtMouse here.
      sgrMouse: true,
      cellMotion: true,
      allMotion: true
    }, true);
  }

  if (this.term('linux')) {
    return this.setMouse({
      vt200Mouse: true,
      gpmMouse: true
    }, true);
  }

  if (this.term('xterm')
      || this.term('screen')
      || (this.tput && this.tput.strings.key_mouse)) {
    return this.setMouse({
      vt200Mouse: true,
      utfMouse: true,
      cellMotion: true,
      allMotion: true
    }, true);
  }
};

Program.prototype.disableMouse = function() {
  if (!this._currentMouse) return;

  var obj = {};

  Object.keys(this._currentMouse).forEach(function(key) {
    obj[key] = false;
  });

  return this.setMouse(obj, false);
};

// Set Mouse
Program.prototype.setMouse = function(opt, enable) {
  if (opt.normalMouse != null) {
    opt.vt200Mouse = opt.normalMouse;
    opt.allMotion = opt.normalMouse;
  }

  if (opt.hiliteTracking != null) {
    opt.vt200Hilite = opt.hiliteTracking;
  }

  if (enable === true) {
    if (this._currentMouse) {
      this.setMouse(opt);
      Object.keys(opt).forEach(function(key) {
        this._currentMouse[key] = opt[key];
      }, this);
      return;
    }
    this._currentMouse = opt;
    this.mouseEnabled = true;
  } else if (enable === false) {
    delete this._currentMouse;
    this.mouseEnabled = false;
  }

  //     Ps = 9  -> Send Mouse X & Y on button press.  See the sec-
  //     tion Mouse Tracking.
  //     Ps = 9  -> Don't send Mouse X & Y on button press.
  // x10 mouse
  if (opt.x10Mouse != null) {
    if (opt.x10Mouse) this.setMode('?9');
    else this.resetMode('?9');
  }

  //     Ps = 1 0 0 0  -> Send Mouse X & Y on button press and
  //     release.  See the section Mouse Tracking.
  //     Ps = 1 0 0 0  -> Don't send Mouse X & Y on button press and
  //     release.  See the section Mouse Tracking.
  // vt200 mouse
  if (opt.vt200Mouse != null) {
    if (opt.vt200Mouse) this.setMode('?1000');
    else this.resetMode('?1000');
  }

  //     Ps = 1 0 0 1  -> Use Hilite Mouse Tracking.
  //     Ps = 1 0 0 1  -> Don't use Hilite Mouse Tracking.
  if (opt.vt200Hilite != null) {
    if (opt.vt200Hilite) this.setMode('?1001');
    else this.resetMode('?1001');
  }

  //     Ps = 1 0 0 2  -> Use Cell Motion Mouse Tracking.
  //     Ps = 1 0 0 2  -> Don't use Cell Motion Mouse Tracking.
  // button event mouse
  if (opt.cellMotion != null) {
    if (opt.cellMotion) this.setMode('?1002');
    else this.resetMode('?1002');
  }

  //     Ps = 1 0 0 3  -> Use All Motion Mouse Tracking.
  //     Ps = 1 0 0 3  -> Don't use All Motion Mouse Tracking.
  // any event mouse
  if (opt.allMotion != null) {
    // NOTE: Latest versions of tmux seem to only support cellMotion (not
    // allMotion). We pass all motion through to the terminal.
    if (this.tmux && this.tmuxVersion >= 2) {
      if (opt.allMotion) this._twrite('\x1b[?1003h');
      else this._twrite('\x1b[?1003l');
    } else {
      if (opt.allMotion) this.setMode('?1003');
      else this.resetMode('?1003');
    }
  }

  //     Ps = 1 0 0 4  -> Send FocusIn/FocusOut events.
  //     Ps = 1 0 0 4  -> Don't send FocusIn/FocusOut events.
  if (opt.sendFocus != null) {
    if (opt.sendFocus) this.setMode('?1004');
    else this.resetMode('?1004');
  }

  //     Ps = 1 0 0 5  -> Enable Extended Mouse Mode.
  //     Ps = 1 0 0 5  -> Disable Extended Mouse Mode.
  if (opt.utfMouse != null) {
    if (opt.utfMouse) this.setMode('?1005');
    else this.resetMode('?1005');
  }

  // sgr mouse
  if (opt.sgrMouse != null) {
    if (opt.sgrMouse) this.setMode('?1006');
    else this.resetMode('?1006');
  }

  // urxvt mouse
  if (opt.urxvtMouse != null) {
    if (opt.urxvtMouse) this.setMode('?1015');
    else this.resetMode('?1015');
  }

  // dec mouse
  if (opt.decMouse != null) {
    if (opt.decMouse) this._write('\x1b[1;2\'z\x1b[1;3\'{');
    else this._write('\x1b[\'z');
  }

  // pterm mouse
  if (opt.ptermMouse != null) {
    if (opt.ptermMouse) this._write('\x1b[>1h\x1b[>6h\x1b[>7h\x1b[>1h\x1b[>9l');
    else this._write('\x1b[>1l\x1b[>6l\x1b[>7l\x1b[>1l\x1b[>9h');
  }

  // jsbterm mouse
  if (opt.jsbtermMouse != null) {
    // + = advanced mode
    if (opt.jsbtermMouse) this._write('\x1b[0~ZwLMRK+1Q\x1b\\');
    else this._write('\x1b[0~ZwQ\x1b\\');
  }

  // gpm mouse
  if (opt.gpmMouse != null) {
    if (opt.gpmMouse) this.enableGpm();
    else this.disableGpm();
  }
};

// CSI Ps ; Ps r
//   Set Scrolling Region [top;bottom] (default = full size of win-
//   dow) (DECSTBM).
// CSI ? Pm r
Program.prototype.decstbm =
Program.prototype.csr =
Program.prototype.setScrollRegion = function(top, bottom) {
  if (!this.zero) {
    top = (top || 1) - 1;
    bottom = (bottom || this.rows) - 1;
  } else {
    top = top || 0;
    bottom = bottom || (this.rows - 1);
  }
  this.scrollTop = top;
  this.scrollBottom = bottom;
  this.x = 0;
  this.y = 0;
  this._ncoords();
  if (this.tput) return this.put.csr(top, bottom);
  return this._write('\x1b[' + (top + 1) + ';' + (bottom + 1) + 'r');
};

// CSI s
//   Save cursor (ANSI.SYS).
Program.prototype.scA =
Program.prototype.saveCursorA = function() {
  this.savedX = this.x;
  this.savedY = this.y;
  if (this.tput) return this.put.sc();
  return this._write('\x1b[s');
};

// CSI u
//   Restore cursor (ANSI.SYS).
Program.prototype.rcA =
Program.prototype.restoreCursorA = function() {
  this.x = this.savedX || 0;
  this.y = this.savedY || 0;
  if (this.tput) return this.put.rc();
  return this._write('\x1b[u');
};

/**
 * Lesser Used
 */

// CSI Ps I
//   Cursor Forward Tabulation Ps tab stops (default = 1) (CHT).
Program.prototype.cht =
Program.prototype.cursorForwardTab = function(param) {
  this.x += 8;
  this._ncoords();
  if (this.tput) return this.put.tab(param);
  return this._write('\x1b[' + (param || 1) + 'I');
};

// CSI Ps S  Scroll up Ps lines (default = 1) (SU).
Program.prototype.su =
Program.prototype.scrollUp = function(param) {
  this.y -= param || 1;
  this._ncoords();
  if (this.tput) return this.put.parm_index(param);
  return this._write('\x1b[' + (param || 1) + 'S');
};

// CSI Ps T  Scroll down Ps lines (default = 1) (SD).
Program.prototype.sd =
Program.prototype.scrollDown = function(param) {
  this.y += param || 1;
  this._ncoords();
  if (this.tput) return this.put.parm_rindex(param);
  return this._write('\x1b[' + (param || 1) + 'T');
};

// CSI Ps ; Ps ; Ps ; Ps ; Ps T
//   Initiate highlight mouse tracking.  Parameters are
//   [func;startx;starty;firstrow;lastrow].  See the section Mouse
//   Tracking.
Program.prototype.initMouseTracking = function() {
  return this._write('\x1b[' + slice.call(arguments).join(';') + 'T');
};

// CSI > Ps; Ps T
//   Reset one or more features of the title modes to the default
//   value.  Normally, "reset" disables the feature.  It is possi-
//   ble to disable the ability to reset features by compiling a
//   different default for the title modes into xterm.
//     Ps = 0  -> Do not set window/icon labels using hexadecimal.
//     Ps = 1  -> Do not query window/icon labels using hexadeci-
//     mal.
//     Ps = 2  -> Do not set window/icon labels using UTF-8.
//     Ps = 3  -> Do not query window/icon labels using UTF-8.
//   (See discussion of "Title Modes").
Program.prototype.resetTitleModes = function() {
  return this._write('\x1b[>' + slice.call(arguments).join(';') + 'T');
};

// CSI Ps Z  Cursor Backward Tabulation Ps tab stops (default = 1) (CBT).
Program.prototype.cbt =
Program.prototype.cursorBackwardTab = function(param) {
  this.x -= 8;
  this._ncoords();
  if (this.tput) return this.put.cbt(param);
  return this._write('\x1b[' + (param || 1) + 'Z');
};

// CSI Ps b  Repeat the preceding graphic character Ps times (REP).
Program.prototype.rep =
Program.prototype.repeatPrecedingCharacter = function(param) {
  this.x += param || 1;
  this._ncoords();
  if (this.tput) return this.put.rep(param);
  return this._write('\x1b[' + (param || 1) + 'b');
};

// CSI Ps g  Tab Clear (TBC).
//     Ps = 0  -> Clear Current Column (default).
//     Ps = 3  -> Clear All.
// Potentially:
//   Ps = 2  -> Clear Stops on Line.
//   http://vt100.net/annarbor/aaa-ug/section6.html
Program.prototype.tbc =
Program.prototype.tabClear = function(param) {
  if (this.tput) return this.put.tbc(param);
  return this._write('\x1b[' + (param || 0) + 'g');
};

// CSI Pm i  Media Copy (MC).
//     Ps = 0  -> Print screen (default).
//     Ps = 4  -> Turn off printer controller mode.
//     Ps = 5  -> Turn on printer controller mode.
// CSI ? Pm i
//   Media Copy (MC, DEC-specific).
//     Ps = 1  -> Print line containing cursor.
//     Ps = 4  -> Turn off autoprint mode.
//     Ps = 5  -> Turn on autoprint mode.
//     Ps = 1  0  -> Print composed display, ignores DECPEX.
//     Ps = 1  1  -> Print all pages.
Program.prototype.mc =
Program.prototype.mediaCopy = function() {
  return this._write('\x1b[' + slice.call(arguments).join(';') + 'i');
};

Program.prototype.print_screen =
Program.prototype.ps =
Program.prototype.mc0 = function() {
  if (this.tput) return this.put.mc0();
  return this.mc('0');
};

Program.prototype.prtr_on =
Program.prototype.po =
Program.prototype.mc5 = function() {
  if (this.tput) return this.put.mc5();
  return this.mc('5');
};

Program.prototype.prtr_off =
Program.prototype.pf =
Program.prototype.mc4 = function() {
  if (this.tput) return this.put.mc4();
  return this.mc('4');
};

Program.prototype.prtr_non =
Program.prototype.pO =
Program.prototype.mc5p = function() {
  if (this.tput) return this.put.mc5p();
  return this.mc('?5');
};

// CSI > Ps; Ps m
//   Set or reset resource-values used by xterm to decide whether
//   to construct escape sequences holding information about the
//   modifiers pressed with a given key.  The first parameter iden-
//   tifies the resource to set/reset.  The second parameter is the
//   value to assign to the resource.  If the second parameter is
//   omitted, the resource is reset to its initial value.
//     Ps = 1  -> modifyCursorKeys.
//     Ps = 2  -> modifyFunctionKeys.
//     Ps = 4  -> modifyOtherKeys.
//   If no parameters are given, all resources are reset to their
//   initial values.
Program.prototype.setResources = function() {
  return this._write('\x1b[>' + slice.call(arguments).join(';') + 'm');
};

// CSI > Ps n
//   Disable modifiers which may be enabled via the CSI > Ps; Ps m
//   sequence.  This corresponds to a resource value of "-1", which
//   cannot be set with the other sequence.  The parameter identi-
//   fies the resource to be disabled:
//     Ps = 1  -> modifyCursorKeys.
//     Ps = 2  -> modifyFunctionKeys.
//     Ps = 4  -> modifyOtherKeys.
//   If the parameter is omitted, modifyFunctionKeys is disabled.
//   When modifyFunctionKeys is disabled, xterm uses the modifier
//   keys to make an extended sequence of functions rather than
//   adding a parameter to each function key to denote the modi-
//   fiers.
Program.prototype.disableModifiers = function(param) {
  return this._write('\x1b[>' + (param || '') + 'n');
};

// CSI > Ps p
//   Set resource value pointerMode.  This is used by xterm to
//   decide whether to hide the pointer cursor as the user types.
//   Valid values for the parameter:
//     Ps = 0  -> never hide the pointer.
//     Ps = 1  -> hide if the mouse tracking mode is not enabled.
//     Ps = 2  -> always hide the pointer.  If no parameter is
//     given, xterm uses the default, which is 1 .
Program.prototype.setPointerMode = function(param) {
  return this._write('\x1b[>' + (param || '') + 'p');
};

// CSI ! p   Soft terminal reset (DECSTR).
// http://vt100.net/docs/vt220-rm/table4-10.html
Program.prototype.decstr =
Program.prototype.rs2 =
Program.prototype.softReset = function() {
  //if (this.tput) return this.put.init_2string();
  //if (this.tput) return this.put.reset_2string();
  if (this.tput) return this.put.rs2();
  //return this._write('\x1b[!p');
  //return this._write('\x1b[!p\x1b[?3;4l\x1b[4l\x1b>'); // init
  return this._write('\x1b[!p\x1b[?3;4l\x1b[4l\x1b>'); // reset
};

// CSI Ps$ p
//   Request ANSI mode (DECRQM).  For VT300 and up, reply is
//     CSI Ps; Pm$ y
//   where Ps is the mode number as in RM, and Pm is the mode
//   value:
//     0 - not recognized
//     1 - set
//     2 - reset
//     3 - permanently set
//     4 - permanently reset
Program.prototype.decrqm =
Program.prototype.requestAnsiMode = function(param) {
  return this._write('\x1b[' + (param || '') + '$p');
};

// CSI ? Ps$ p
//   Request DEC private mode (DECRQM).  For VT300 and up, reply is
//     CSI ? Ps; Pm$ p
//   where Ps is the mode number as in DECSET, Pm is the mode value
//   as in the ANSI DECRQM.
Program.prototype.decrqmp =
Program.prototype.requestPrivateMode = function(param) {
  return this._write('\x1b[?' + (param || '') + '$p');
};

// CSI Ps ; Ps " p
//   Set conformance level (DECSCL).  Valid values for the first
//   parameter:
//     Ps = 6 1  -> VT100.
//     Ps = 6 2  -> VT200.
//     Ps = 6 3  -> VT300.
//   Valid values for the second parameter:
//     Ps = 0  -> 8-bit controls.
//     Ps = 1  -> 7-bit controls (always set for VT100).
//     Ps = 2  -> 8-bit controls.
Program.prototype.decscl =
Program.prototype.setConformanceLevel = function() {
  return this._write('\x1b[' + slice.call(arguments).join(';') + '"p');
};

// CSI Ps q  Load LEDs (DECLL).
//     Ps = 0  -> Clear all LEDS (default).
//     Ps = 1  -> Light Num Lock.
//     Ps = 2  -> Light Caps Lock.
//     Ps = 3  -> Light Scroll Lock.
//     Ps = 2  1  -> Extinguish Num Lock.
//     Ps = 2  2  -> Extinguish Caps Lock.
//     Ps = 2  3  -> Extinguish Scroll Lock.
Program.prototype.decll =
Program.prototype.loadLEDs = function(param) {
  return this._write('\x1b[' + (param || '') + 'q');
};

// CSI Ps SP q
//   Set cursor style (DECSCUSR, VT520).
//     Ps = 0  -> blinking block.
//     Ps = 1  -> blinking block (default).
//     Ps = 2  -> steady block.
//     Ps = 3  -> blinking underline.
//     Ps = 4  -> steady underline.
Program.prototype.decscusr =
Program.prototype.setCursorStyle = function(param) {
  switch (param) {
    case 'blinking block':
      param = 1;
      break;
    case 'block':
    case 'steady block':
      param = 2;
      break;
    case 'blinking underline':
      param = 3;
      break;
    case 'underline':
    case 'steady underline':
      param = 4;
      break;
    case 'blinking bar':
      param = 5;
      break;
    case 'bar':
    case 'steady bar':
      param = 6;
      break;
  }
  if (param === 2 && this.has('Se')) {
    return this.put.Se();
  }
  if (this.has('Ss')) {
    return this.put.Ss(param);
  }
  return this._write('\x1b[' + (param || 1) + ' q');
};

// CSI Ps " q
//   Select character protection attribute (DECSCA).  Valid values
//   for the parameter:
//     Ps = 0  -> DECSED and DECSEL can erase (default).
//     Ps = 1  -> DECSED and DECSEL cannot erase.
//     Ps = 2  -> DECSED and DECSEL can erase.
Program.prototype.decsca =
Program.prototype.setCharProtectionAttr = function(param) {
  return this._write('\x1b[' + (param || 0) + '"q');
};

// CSI ? Pm r
//   Restore DEC Private Mode Values.  The value of Ps previously
//   saved is restored.  Ps values are the same as for DECSET.
Program.prototype.restorePrivateValues = function() {
  return this._write('\x1b[?' + slice.call(arguments).join(';') + 'r');
};

// CSI Pt; Pl; Pb; Pr; Ps$ r
//   Change Attributes in Rectangular Area (DECCARA), VT400 and up.
//     Pt; Pl; Pb; Pr denotes the rectangle.
//     Ps denotes the SGR attributes to change: 0, 1, 4, 5, 7.
// NOTE: xterm doesn't enable this code by default.
Program.prototype.deccara =
Program.prototype.setAttrInRectangle = function() {
  return this._write('\x1b[' + slice.call(arguments).join(';') + '$r');
};

// CSI ? Pm s
//   Save DEC Private Mode Values.  Ps values are the same as for
//   DECSET.
Program.prototype.savePrivateValues = function() {
  return this._write('\x1b[?' + slice.call(arguments).join(';') + 's');
};

// CSI Ps ; Ps ; Ps t
//   Window manipulation (from dtterm, as well as extensions).
//   These controls may be disabled using the allowWindowOps
//   resource.  Valid values for the first (and any additional
//   parameters) are:
//     Ps = 1  -> De-iconify window.
//     Ps = 2  -> Iconify window.
//     Ps = 3  ;  x ;  y -> Move window to [x, y].
//     Ps = 4  ;  height ;  width -> Resize the xterm window to
//     height and width in pixels.
//     Ps = 5  -> Raise the xterm window to the front of the stack-
//     ing order.
//     Ps = 6  -> Lower the xterm window to the bottom of the
//     stacking order.
//     Ps = 7  -> Refresh the xterm window.
//     Ps = 8  ;  height ;  width -> Resize the text area to
//     [height;width] in characters.
//     Ps = 9  ;  0  -> Restore maximized window.
//     Ps = 9  ;  1  -> Maximize window (i.e., resize to screen
//     size).
//     Ps = 1 0  ;  0  -> Undo full-screen mode.
//     Ps = 1 0  ;  1  -> Change to full-screen.
//     Ps = 1 1  -> Report xterm window state.  If the xterm window
//     is open (non-iconified), it returns CSI 1 t .  If the xterm
//     window is iconified, it returns CSI 2 t .
//     Ps = 1 3  -> Report xterm window position.  Result is CSI 3
//     ; x ; y t
//     Ps = 1 4  -> Report xterm window in pixels.  Result is CSI
//     4  ;  height ;  width t
//     Ps = 1 8  -> Report the size of the text area in characters.
//     Result is CSI  8  ;  height ;  width t
//     Ps = 1 9  -> Report the size of the screen in characters.
//     Result is CSI  9  ;  height ;  width t
//     Ps = 2 0  -> Report xterm window's icon label.  Result is
//     OSC  L  label ST
//     Ps = 2 1  -> Report xterm window's title.  Result is OSC  l
//     label ST
//     Ps = 2 2  ;  0  -> Save xterm icon and window title on
//     stack.
//     Ps = 2 2  ;  1  -> Save xterm icon title on stack.
//     Ps = 2 2  ;  2  -> Save xterm window title on stack.
//     Ps = 2 3  ;  0  -> Restore xterm icon and window title from
//     stack.
//     Ps = 2 3  ;  1  -> Restore xterm icon title from stack.
//     Ps = 2 3  ;  2  -> Restore xterm window title from stack.
//     Ps >= 2 4  -> Resize to Ps lines (DECSLPP).
Program.prototype.manipulateWindow = function() {
  var args = slice.call(arguments);

  var callback = typeof args[args.length - 1] === 'function'
    ? args.pop()
    : function() {};

  return this.response('window-manipulation',
    '\x1b[' + args.join(';') + 't', callback);
};

Program.prototype.getWindowSize = function(callback) {
  return this.manipulateWindow(18, callback);
};

// CSI Pt; Pl; Pb; Pr; Ps$ t
//   Reverse Attributes in Rectangular Area (DECRARA), VT400 and
//   up.
//     Pt; Pl; Pb; Pr denotes the rectangle.
//     Ps denotes the attributes to reverse, i.e.,  1, 4, 5, 7.
// NOTE: xterm doesn't enable this code by default.
Program.prototype.decrara =
Program.prototype.reverseAttrInRectangle = function() {
  return this._write('\x1b[' + slice.call(arguments).join(';') + '$t');
};

// CSI > Ps; Ps t
//   Set one or more features of the title modes.  Each parameter
//   enables a single feature.
//     Ps = 0  -> Set window/icon labels using hexadecimal.
//     Ps = 1  -> Query window/icon labels using hexadecimal.
//     Ps = 2  -> Set window/icon labels using UTF-8.
//     Ps = 3  -> Query window/icon labels using UTF-8.  (See dis-
//     cussion of "Title Modes")
// XXX VTE bizarelly echos this:
Program.prototype.setTitleModeFeature = function() {
  return this._twrite('\x1b[>' + slice.call(arguments).join(';') + 't');
};

// CSI Ps SP t
//   Set warning-bell volume (DECSWBV, VT520).
//     Ps = 0  or 1  -> off.
//     Ps = 2 , 3  or 4  -> low.
//     Ps = 5 , 6 , 7 , or 8  -> high.
Program.prototype.decswbv =
Program.prototype.setWarningBellVolume = function(param) {
  return this._write('\x1b[' + (param || '') + ' t');
};

// CSI Ps SP u
//   Set margin-bell volume (DECSMBV, VT520).
//     Ps = 1  -> off.
//     Ps = 2 , 3  or 4  -> low.
//     Ps = 0 , 5 , 6 , 7 , or 8  -> high.
Program.prototype.decsmbv =
Program.prototype.setMarginBellVolume = function(param) {
  return this._write('\x1b[' + (param || '') + ' u');
};

// CSI Pt; Pl; Pb; Pr; Pp; Pt; Pl; Pp$ v
//   Copy Rectangular Area (DECCRA, VT400 and up).
//     Pt; Pl; Pb; Pr denotes the rectangle.
//     Pp denotes the source page.
//     Pt; Pl denotes the target location.
//     Pp denotes the target page.
// NOTE: xterm doesn't enable this code by default.
Program.prototype.deccra =
Program.prototype.copyRectangle = function() {
  return this._write('\x1b[' + slice.call(arguments).join(';') + '$v');
};

// CSI Pt ; Pl ; Pb ; Pr ' w
//   Enable Filter Rectangle (DECEFR), VT420 and up.
//   Parameters are [top;left;bottom;right].
//   Defines the coordinates of a filter rectangle and activates
//   it.  Anytime the locator is detected outside of the filter
//   rectangle, an outside rectangle event is generated and the
//   rectangle is disabled.  Filter rectangles are always treated
//   as "one-shot" events.  Any parameters that are omitted default
//   to the current locator position.  If all parameters are omit-
//   ted, any locator motion will be reported.  DECELR always can-
//   cels any prevous rectangle definition.
Program.prototype.decefr =
Program.prototype.enableFilterRectangle = function() {
  return this._write('\x1b[' + slice.call(arguments).join(';') + '\'w');
};

// CSI Ps x  Request Terminal Parameters (DECREQTPARM).
//   if Ps is a "0" (default) or "1", and xterm is emulating VT100,
//   the control sequence elicits a response of the same form whose
//   parameters describe the terminal:
//     Ps -> the given Ps incremented by 2.
//     Pn = 1  <- no parity.
//     Pn = 1  <- eight bits.
//     Pn = 1  <- 2  8  transmit 38.4k baud.
//     Pn = 1  <- 2  8  receive 38.4k baud.
//     Pn = 1  <- clock multiplier.
//     Pn = 0  <- STP flags.
Program.prototype.decreqtparm =
Program.prototype.requestParameters = function(param) {
  return this._write('\x1b[' + (param || 0) + 'x');
};

// CSI Ps x  Select Attribute Change Extent (DECSACE).
//     Ps = 0  -> from start to end position, wrapped.
//     Ps = 1  -> from start to end position, wrapped.
//     Ps = 2  -> rectangle (exact).
Program.prototype.decsace =
Program.prototype.selectChangeExtent = function(param) {
  return this._write('\x1b[' + (param || 0) + 'x');
};

// CSI Pc; Pt; Pl; Pb; Pr$ x
//   Fill Rectangular Area (DECFRA), VT420 and up.
//     Pc is the character to use.
//     Pt; Pl; Pb; Pr denotes the rectangle.
// NOTE: xterm doesn't enable this code by default.
Program.prototype.decfra =
Program.prototype.fillRectangle = function() {
  return this._write('\x1b[' + slice.call(arguments).join(';') + '$x');
};

// CSI Ps ; Pu ' z
//   Enable Locator Reporting (DECELR).
//   Valid values for the first parameter:
//     Ps = 0  -> Locator disabled (default).
//     Ps = 1  -> Locator enabled.
//     Ps = 2  -> Locator enabled for one report, then disabled.
//   The second parameter specifies the coordinate unit for locator
//   reports.
//   Valid values for the second parameter:
//     Pu = 0  <- or omitted -> default to character cells.
//     Pu = 1  <- device physical pixels.
//     Pu = 2  <- character cells.
Program.prototype.decelr =
Program.prototype.enableLocatorReporting = function() {
  return this._write('\x1b[' + slice.call(arguments).join(';') + '\'z');
};

// CSI Pt; Pl; Pb; Pr$ z
//   Erase Rectangular Area (DECERA), VT400 and up.
//     Pt; Pl; Pb; Pr denotes the rectangle.
// NOTE: xterm doesn't enable this code by default.
Program.prototype.decera =
Program.prototype.eraseRectangle = function() {
  return this._write('\x1b[' + slice.call(arguments).join(';') + '$z');
};

// CSI Pm ' {
//   Select Locator Events (DECSLE).
//   Valid values for the first (and any additional parameters)
//   are:
//     Ps = 0  -> only respond to explicit host requests (DECRQLP).
//                (This is default).  It also cancels any filter
//   rectangle.
//     Ps = 1  -> report button down transitions.
//     Ps = 2  -> do not report button down transitions.
//     Ps = 3  -> report button up transitions.
//     Ps = 4  -> do not report button up transitions.
Program.prototype.decsle =
Program.prototype.setLocatorEvents = function() {
  return this._write('\x1b[' + slice.call(arguments).join(';') + '\'{');
};

// CSI Pt; Pl; Pb; Pr$ {
//   Selective Erase Rectangular Area (DECSERA), VT400 and up.
//     Pt; Pl; Pb; Pr denotes the rectangle.
Program.prototype.decsera =
Program.prototype.selectiveEraseRectangle = function() {
  return this._write('\x1b[' + slice.call(arguments).join(';') + '${');
};

// CSI Ps ' |
//   Request Locator Position (DECRQLP).
//   Valid values for the parameter are:
//     Ps = 0 , 1 or omitted -> transmit a single DECLRP locator
//     report.

//   If Locator Reporting has been enabled by a DECELR, xterm will
//   respond with a DECLRP Locator Report.  This report is also
//   generated on button up and down events if they have been
//   enabled with a DECSLE, or when the locator is detected outside
//   of a filter rectangle, if filter rectangles have been enabled
//   with a DECEFR.

//     -> CSI Pe ; Pb ; Pr ; Pc ; Pp &  w

//   Parameters are [event;button;row;column;page].
//   Valid values for the event:
//     Pe = 0  -> locator unavailable - no other parameters sent.
//     Pe = 1  -> request - xterm received a DECRQLP.
//     Pe = 2  -> left button down.
//     Pe = 3  -> left button up.
//     Pe = 4  -> middle button down.
//     Pe = 5  -> middle button up.
//     Pe = 6  -> right button down.
//     Pe = 7  -> right button up.
//     Pe = 8  -> M4 button down.
//     Pe = 9  -> M4 button up.
//     Pe = 1 0  -> locator outside filter rectangle.
//   ``button'' parameter is a bitmask indicating which buttons are
//     pressed:
//     Pb = 0  <- no buttons down.
//     Pb & 1  <- right button down.
//     Pb & 2  <- middle button down.
//     Pb & 4  <- left button down.
//     Pb & 8  <- M4 button down.
//   ``row'' and ``column'' parameters are the coordinates of the
//     locator position in the xterm window, encoded as ASCII deci-
//     mal.
//   The ``page'' parameter is not used by xterm, and will be omit-
//   ted.
Program.prototype.decrqlp =
Program.prototype.req_mouse_pos =
Program.prototype.reqmp =
Program.prototype.requestLocatorPosition = function(param, callback) {
  // See also:
  // get_mouse / getm / Gm
  // mouse_info / minfo / Mi
  // Correct for tput?
  if (this.has('req_mouse_pos')) {
    var code = this.tput.req_mouse_pos(param);
    return this.response('locator-position', code, callback);
  }
  return this.response('locator-position',
    '\x1b[' + (param || '') + '\'|', callback);
};

// CSI P m SP }
// Insert P s Column(s) (default = 1) (DECIC), VT420 and up.
// NOTE: xterm doesn't enable this code by default.
Program.prototype.decic =
Program.prototype.insertColumns = function() {
  return this._write('\x1b[' + slice.call(arguments).join(';') + ' }');
};

// CSI P m SP ~
// Delete P s Column(s) (default = 1) (DECDC), VT420 and up
// NOTE: xterm doesn't enable this code by default.
Program.prototype.decdc =
Program.prototype.deleteColumns = function() {
  return this._write('\x1b[' + slice.call(arguments).join(';') + ' ~');
};

Program.prototype.out = function(name) {
  var args = Array.prototype.slice.call(arguments, 1);
  this.ret = true;
  var out = this[name].apply(this, args);
  this.ret = false;
  return out;
};

Program.prototype.sigtstp = function(callback) {
  var resume = this.pause();

  process.once('SIGCONT', function() {
    resume();
    if (callback) callback();
  });

  process.kill(process.pid, 'SIGTSTP');
};

Program.prototype.pause = function(callback) {
  var self = this
    , isAlt = this.isAlt
    , mouseEnabled = this.mouseEnabled;

  this.lsaveCursor('pause');
  //this.csr(0, screen.height - 1);
  if (isAlt) this.normalBuffer();
  this.showCursor();
  if (mouseEnabled) this.disableMouse();

  var write = this.output.write;
  this.output.write = function() {};
  if (this.input.setRawMode) {
    this.input.setRawMode(false);
  }
  this.input.pause();

  return this._resume = function() {
    delete self._resume;

    if (self.input.setRawMode) {
      self.input.setRawMode(true);
    }
    self.input.resume();
    self.output.write = write;

    if (isAlt) self.alternateBuffer();
    //self.csr(0, screen.height - 1);
    if (mouseEnabled) self.enableMouse();
    self.lrestoreCursor('pause', true);

    if (callback) callback();
  };
};

Program.prototype.resume = function() {
  if (this._resume) return this._resume();
};

/**
 * Helpers
 */

// We could do this easier by just manipulating the _events object, or for
// older versions of node, manipulating the array returned by listeners(), but
// neither of these methods are guaranteed to work in future versions of node.
function unshiftEvent(obj, event, listener) {
  var listeners;
  if (obj.listeners) {
    listeners = obj.listeners(event);
    obj.removeAllListeners(event);
    obj.on(event, listener);
    listeners.forEach(function(listener) {
      obj.on(event, listener);
    });
  } else {
    if (obj == process) process.on(event,listener);  
  }
}

function merge(out) {
  slice.call(arguments, 1).forEach(function(obj) {
    Object.keys(obj).forEach(function(key) {
      out[key] = obj[key];
    });
  });
  return out;
}

/**
 * Expose
 */

module.exports = Program;
};
BundleModuleCode['term/tput']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Christopher Jeffrey and contributors, Stefan Bosse 
 **    $INITIAL:     (C) 2013-2015, Christopher Jeffrey and contributors (MIT License)
 **    $REVESIO:     1.1.5
 **
 **    $INFO:
 *
 * tput.js - parse and compile terminfo caps to javascript.
 * Modification: Embedded file support.
 *
 *     $ENDINFO
 */

// Resources:
//   $ man term
//   $ man terminfo
//   http://invisible-island.net/ncurses/man/term.5.html
//   https://en.wikipedia.org/wiki/Terminfo

// Todo:
// - xterm's XT (set-title capability?) value should
//   be true (at least tmux thinks it should).
//   It's not parsed as true. Investigate.
// - Possibly switch to other method of finding the
//   extended data string table: i += h.symOffsetCount * 2;

/**
 * Modules
 */

var assert = Require('assert')
  , path = Require('path')
  , fs = Require('fs')
  , cp = Require('child_process');

/**
 * Tput
 */

FileEmbedd('term/def/xterm');
FileEmbedd('term/def/windows-ansi');

function Tput(options) {
  if (!(this instanceof Tput)) {
    return new Tput(options);
  }

  options = options || {};
  if (typeof options === 'string') {
    options = { terminal: options };
  }

  this.options = options;
  this.terminal = options.terminal
    || options.term
    || process.env.TERM
    || (process.platform === 'win32' ? 'windows-ansi' : 'xterm');

  this.terminal = this.terminal.toLowerCase();

  this.debug = options.debug;
  this.padding = options.padding;
  this.extended = options.extended;
  this.printf = options.printf;
  this.termcap = options.termcap;
  this.error = null;

  this.terminfoPrefix = options.terminfoPrefix;
  this.terminfoFile = options.terminfoFile;
  this.termcapFile = options.termcapFile;

  if (options.terminal || options.term) {
    this.setup();
  }
}

Tput.prototype.setup = function() {
  this.error = null;
  try {
    if (this.termcap) {
      try {
        this.injectTermcap();
      } catch (e) {
        if (this.debug) throw e;
        this.error = new Error('Termcap parse error.');
        this._useInternalCap(this.terminal);
      }
    } else {
      try {
        this.injectTerminfo();
      } catch (e) {
        if (this.debug) throw e;
        this.error = new Error('Terminfo parse error.');
        this._useInternalInfo(this.terminal);
      }
    }
  } catch (e) {
    // If there was an error, fallback
    // to an internally stored terminfo/cap.
    if (this.debug) throw e;
    this.error = new Error('Terminfo not found.');
    this._useXtermInfo();
  }
};

Tput.prototype.term = function(is) {
  return this.terminal.indexOf(is) === 0;
};

Tput.prototype._debug = function() {
  if (!this.debug) return;
  return console.log.apply(console, arguments);
};

/**
 * Fallback
 */

Tput.prototype._useVt102Cap = function() {
  return this.injectTermcap('vt102');
};

Tput.prototype._useXtermCap = function() {
  return this.injectTermcap('term/def/xterm.termcap');
};

Tput.prototype._useXtermInfo = function() {
  return this.injectTerminfo('term/def/xterm');
};

Tput.prototype._useInternalInfo = function(name) {
  name = path.basename(name);
  return this.injectTerminfo('term/def/' + name);
};

Tput.prototype._useInternalCap = function(name) {
  name = path.basename(name);
  return this.injectTermcap('term/def/' + name + '.termcap');
};

/**
 * Terminfo
 */

Tput.ipaths = [
  __dirname+'/..',
  process.env.TERMINFO || '',
  (process.env.TERMINFO_DIRS || '').split(':'),
  (process.env.HOME || '') + '/.terminfo',
  '/usr/share/terminfo',
  '/usr/share/lib/terminfo',
  '/usr/lib/terminfo',
  '/usr/local/share/terminfo',
  '/usr/local/share/lib/terminfo',
  '/usr/local/lib/terminfo',
  '/usr/local/ncurses/lib/terminfo',
  '/lib/terminfo'
];

Tput.prototype.readTerminfo = function(term) {
  var data
    , file
    , info;

  term = term || this.terminal;
  try {data = FileEmbedded(term);} catch (e) {};
  if (!data) {
    file = path.normalize(this._prefix(term));
    console.log('Reading '+file);
    data = fs.readFileSync(file);
  } else file=term;
  info = this.parseTerminfo(data, file);

  if (this.debug) {
    this._terminfo = info;
  }

  return info;
};

Tput._prefix =
Tput.prototype._prefix = function(term) {
  // If we have a terminfoFile, or our
  // term looks like a filename, use it.
  if (term) {
    if (term.indexOf(path.sep)==0) {
      return term;
    }
    if (this.terminfoFile) {
      return this.terminfoFile;
    }
  }
  var paths = Tput.ipaths.slice()
    , file;

  if (this.terminfoPrefix) {
    paths.unshift(this.terminfoPrefix);
  }


  // Try exact matches.
  file = this._tprefix(paths, term);
  if (file) return file;

  // Try similar matches.
  file = this._tprefix(paths, term, true);
  if (file) return file;

  // Not found.
  throw new Error('Terminfo directory not found.');
};

Tput._tprefix =
Tput.prototype._tprefix = function(prefix, term, soft) {
  if (!prefix) return;

  var file
    , dir
    , i
    , sdiff
    , sfile
    , list;


  try {
      file=prefix+'/'+term;
      fs.statSync(file);
      return file;
  } catch (e) {
      ;
  }

  if (Array.isArray(prefix)) {
    for (i = 0; i < prefix.length; i++) {
      file = this._tprefix(prefix[i], term, soft);
      if (file) return file;
    }
    return;
  }

  var find = function(word) {
    var file, ch;

    file = path.resolve(prefix, word[0]);
    
    try {
      fs.statSync(file);
      return file;
    } catch (e) {
      ;
    }

    ch = word[0].charCodeAt(0).toString(16);
    if (ch.length < 2) ch = '0' + ch;

    file = path.resolve(prefix, ch);

    try {
      fs.statSync(file);
      return file;
    } catch (e) {
      ;
    }
  };

  if (!term) {
    // Make sure the directory's sub-directories
    // are all one-letter, or hex digits.
    // return find('x') ? prefix : null;
    try {
      dir = fs.readdirSync(prefix).filter(function(file) {
        return file.length !== 1 && !/^[0-9a-fA-F]{2}$/.test(file);
      });
      if (!dir.length) {
        return prefix;
      }
    } catch (e) {
      ;
    }
    return;
  }

  term = path.basename(term);
  dir = find(term);

  if (!dir) return;

  if (soft) {
    try {
      list = fs.readdirSync(dir);
    } catch (e) {
      return;
    }

    list.forEach(function(file) {
      if (file.indexOf(term) === 0) {
        var diff = file.length - term.length;
        if (!sfile || diff < sdiff) {
          sdiff = diff;
          sfile = file;
        }
      }
    });

    return sfile && (soft || sdiff === 0)
      ? path.resolve(dir, sfile)
      : null;
  }

  file = path.resolve(dir, term);
  try {
    fs.statSync(file);
    return file;
  } catch (e) {
    ;
  }
};

/**
 * Terminfo Parser
 * All shorts are little-endian
 */

Tput.prototype.parseTerminfo = function(data, file) {
  var info = {}
    , extended
    , l = data.length
    , i = 0
    , v
    , o;

  var h = info.header = {
    dataSize: data.length,
    headerSize: 12,
    magicNumber: (data[1] << 8) | data[0],
    namesSize: (data[3] << 8) | data[2],
    boolCount: (data[5] << 8) | data[4],
    numCount: (data[7] << 8) | data[6],
    strCount: (data[9] << 8) | data[8],
    strTableSize: (data[11] << 8) | data[10]
  };
  console.log('parseTerminfo '+file+':'+data.length)

  h.total = h.headerSize
    + h.namesSize
    + h.boolCount
    + h.numCount * 2
    + h.strCount * 2
    + h.strTableSize;

  i += h.headerSize;

  // Names Section
  var names = data.toString('ascii', i, i + h.namesSize - 1)
    , parts = names.split('|')
    , name = parts[0]
    , desc = parts.pop();
  info.name = name;
  info.names = parts;
  info.desc = desc;
  
  info.dir = path.resolve(file, '..', '..');
  info.file = file;

  i += h.namesSize - 1;
  // Names is nul-terminated.
  assert.equal(data[i], 0);
  i++;

  // Booleans Section
  // One byte for each flag
  // Same order as <term.h>
  info.bools = {};
  l = i + h.boolCount;
  o = 0;
  for (; i < l; i++) {
    v = Tput.bools[o++];
    info.bools[v] = data[i] === 1;
  }

  // Null byte in between to make sure numbers begin on an even byte.
  if (i % 2) {
    assert.equal(data[i], 0);
    i++;
  }

  // Numbers Section
  info.numbers = {};
  l = i + h.numCount * 2;
  o = 0;
  for (; i < l; i += 2) {
    v = Tput.numbers[o++];
    if (data[i + 1] === 0377 && data[i] === 0377) {
      info.numbers[v] = -1;
    } else {
      info.numbers[v] = (data[i + 1] << 8) | data[i];
    }
  }

  // Strings Section
  info.strings = {};
  l = i + h.strCount * 2;
  o = 0;
  for (; i < l; i += 2) {
    v = Tput.strings[o++];
    if (data[i + 1] === 0377 && data[i] === 0377) {
      info.strings[v] = -1;
    } else {
      info.strings[v] = (data[i + 1] << 8) | data[i];
    }
  }

  // String Table
  Object.keys(info.strings).forEach(function(key) {
    if (info.strings[key] === -1) {
      delete info.strings[key];
      return;
    }

    // Workaround: fix an odd bug in the screen-256color terminfo where it tries
    // to set -1, but it appears to have {0xfe, 0xff} instead of {0xff, 0xff}.
    // TODO: Possibly handle errors gracefully below, as well as in the
    // extended info. Also possibly do: `if (info.strings[key] >= data.length)`.
    if (info.strings[key] === 65534) {
      delete info.strings[key];
      return;
    }

    var s = i + info.strings[key]
      , j = s;

    while (data[j]) j++;

    assert(j < data.length);

    info.strings[key] = data.toString('ascii', s, j);
  });

  // Extended Header
  if (this.extended !== false) {
    i--;
    i += h.strTableSize;
    if (i % 2) {
      assert.equal(data[i], 0);
      i++;
    }
    l = data.length;
    if (i < l - 1) {
      try {
        extended = this.parseExtended(data.slice(i));
      } catch (e) {
        if (this.debug) {
          throw e;
        }
        return info;
      }
      info.header.extended = extended.header;
      ['bools', 'numbers', 'strings'].forEach(function(key) {
        merge(info[key], extended[key]);
      });
    }
  }
  
  return info;
};

/**
 * Extended Parsing
 */

// Some data to help understand:

// For xterm, non-extended header:
// { dataSize: 3270,
//   headerSize: 12,
//   magicNumber: 282,
//   namesSize: 48,
//   boolCount: 38,
//   numCount: 15,
//   strCount: 413,
//   strTableSize: 1388,
//   total: 2342 }

// For xterm, header:
// Offset: 2342
// { header:
//    { dataSize: 928,
//      headerSize: 10,
//      boolCount: 2,
//      numCount: 1,
//      strCount: 57,
//      strTableSize: 117,
//      lastStrTableOffset: 680,
//      total: 245 },

// For xterm, layout:
// { header: '0 - 10', // length: 10
//   bools: '10 - 12', // length: 2
//   numbers: '12 - 14', // length: 2
//   strings: '14 - 128', // length: 114 (57 short)
//   symoffsets: '128 - 248', // length: 120 (60 short)
//   stringtable: '248 - 612', // length: 364
//   sym: '612 - 928' } // length: 316
//
// How lastStrTableOffset works:
//   data.length - h.lastStrTableOffset === 248
//     (sym-offset end, string-table start)
//   364 + 316 === 680 (lastStrTableOffset)
// How strTableSize works:
//   h.strCount + [symOffsetCount] === h.strTableSize
//   57 + 60 === 117 (strTableSize)
//   symOffsetCount doesn't actually exist in the header. it's just implied.
// Getting the number of sym offsets:
//   h.symOffsetCount = h.strTableSize - h.strCount;
//   h.symOffsetSize = (h.strTableSize - h.strCount) * 2;

Tput.prototype.parseExtended = function(data) {
  var info = {}
    , l = data.length
    , i = 0;

  var h = info.header = {
    dataSize: data.length,
    headerSize: 10,
    boolCount: (data[i + 1] << 8) | data[i + 0],
    numCount: (data[i + 3] << 8) | data[i + 2],
    strCount: (data[i + 5] << 8) | data[i + 4],
    strTableSize: (data[i + 7] << 8) | data[i + 6],
    lastStrTableOffset: (data[i + 9] << 8) | data[i + 8]
  };

  // h.symOffsetCount = h.strTableSize - h.strCount;

  h.total = h.headerSize
    + h.boolCount
    + h.numCount * 2
    + h.strCount * 2
    + h.strTableSize;

  i += h.headerSize;

  // Booleans Section
  // One byte for each flag
  var _bools = [];
  l = i + h.boolCount;
  for (; i < l; i++) {
    _bools.push(data[i] === 1);
  }

  // Null byte in between to make sure numbers begin on an even byte.
  if (i % 2) {
    assert.equal(data[i], 0);
    i++;
  }

  // Numbers Section
  var _numbers = [];
  l = i + h.numCount * 2;
  for (; i < l; i += 2) {
    if (data[i + 1] === 0377 && data[i] === 0377) {
      _numbers.push(-1);
    } else {
      _numbers.push((data[i + 1] << 8) | data[i]);
    }
  }

  // Strings Section
  var _strings = [];
  l = i + h.strCount * 2;
  for (; i < l; i += 2) {
    if (data[i + 1] === 0377 && data[i] === 0377) {
      _strings.push(-1);
    } else {
      _strings.push((data[i + 1] << 8) | data[i]);
    }
  }

  // Pass over the sym offsets and get to the string table.
  i = data.length - h.lastStrTableOffset;
  // Might be better to do this instead if the file has trailing bytes:
  // i += h.symOffsetCount * 2;

  // String Table
  var high = 0;
  _strings.forEach(function(offset, k) {
    if (offset === -1) {
      _strings[k] = '';
      return;
    }

    var s = i + offset
      , j = s;

    while (data[j]) j++;

    assert(j < data.length);

    // Find out where the string table ends by
    // getting the highest string length.
    if (high < j - i) {
      high = j - i;
    }

    _strings[k] = data.toString('ascii', s, j);
  });

  // Symbol Table
  // Add one to the highest string length because we didn't count \0.
  i += high + 1;
  l = data.length;

  var sym = []
    , j;

  for (; i < l; i++) {
    j = i;
    while (data[j]) j++;
    sym.push(data.toString('ascii', i, j));
    i = j;
  }

  // Identify by name
  j = 0;

  info.bools = {};
  _bools.forEach(function(bool) {
    info.bools[sym[j++]] = bool;
  });

  info.numbers = {};
  _numbers.forEach(function(number) {
    info.numbers[sym[j++]] = number;
  });

  info.strings = {};
  _strings.forEach(function(string) {
    info.strings[sym[j++]] = string;
  });

  // Should be the very last bit of data.
  assert.equal(i, data.length);

  return info;
};

Tput.prototype.compileTerminfo = function(term) {
  return this.compile(this.readTerminfo(term));
};

Tput.prototype.injectTerminfo = function(term) {
  return this.inject(this.compileTerminfo(term));
};

/**
 * Compiler - terminfo cap->javascript
 */

Tput.prototype.compile = function(info) {
  var self = this;

  if (!info) {
    throw new Error('Terminal not found.');
  }

  this.detectFeatures(info);

  this._debug(info);

  info.all = {};
  info.methods = {};

  ['bools', 'numbers', 'strings'].forEach(function(type) {
    Object.keys(info[type]).forEach(function(key) {
      info.all[key] = info[type][key];
      info.methods[key] = self._compile(info, key, info.all[key]);
    });
  });

  Tput.bools.forEach(function(key) {
    if (info.methods[key] == null) info.methods[key] = false;
  });

  Tput.numbers.forEach(function(key) {
    if (info.methods[key] == null) info.methods[key] = -1;
  });

  Tput.strings.forEach(function(key) {
    if (!info.methods[key]) info.methods[key] = noop;
  });

  Object.keys(info.methods).forEach(function(key) {
    if (!Tput.alias[key]) return;
    Tput.alias[key].forEach(function(alias) {
      info.methods[alias] = info.methods[key];
    });
    // Could just use:
    // Object.keys(Tput.aliasMap).forEach(function(key) {
    //   info.methods[key] = info.methods[Tput.aliasMap[key]];
    // });
  });

  return info;
};

Tput.prototype.inject = function(info) {
  var self = this
    , methods = info.methods || info;

  Object.keys(methods).forEach(function(key) {
    if (typeof methods[key] !== 'function') {
      self[key] = methods[key];
      return;
    }
    self[key] = function() {
      var args = Array.prototype.slice.call(arguments);
      return methods[key].call(self, args);
    };
  });

  this.info = info;
  this.all = info.all;
  this.methods = info.methods;
  this.bools = info.bools;
  this.numbers = info.numbers;
  this.strings = info.strings;

  if (!~info.names.indexOf(this.terminal)) {
    this.terminal = info.name;
  }

  this.features = info.features;
  Object.keys(info.features).forEach(function(key) {
    if (key === 'padding') {
      if (!info.features.padding && self.options.padding !== true) {
        self.padding = false;
      }
      return;
    }
    self[key] = info.features[key];
  });
};

// See:
// ~/ncurses/ncurses/tinfo/lib_tparm.c
// ~/ncurses/ncurses/tinfo/comp_scan.c
Tput.prototype._compile = function(info, key, str) {
  var v;

  this._debug('Compiling %s: %s', key, JSON.stringify(str));

  switch (typeof str) {
    case 'boolean':
      return str;
    case 'number':
      return str;
    case 'string':
      break;
    default:
      return noop;
  }

  if (!str) {
    return noop;
  }

  // See:
  // ~/ncurses/progs/tput.c - tput() - L149
  // ~/ncurses/progs/tset.c - set_init() - L992
  if (key === 'init_file' || key === 'reset_file') {
    try {
      str = fs.readFileSync(str, 'utf8');
      if (this.debug) {
        v = ('return ' + JSON.stringify(str) + ';')
          .replace(/\x1b/g, '\\x1b')
          .replace(/\r/g, '\\r')
          .replace(/\n/g, '\\n');
        process.stdout.write(v + '\n');
      }
      return function() { return str; };
    } catch (e) {
      return noop;
    }
  }

  var tkey = info.name + '.' + key
    , header = 'var v, dyn = {}, stat = {}, stack = [], out = [];'
    , footer = ';return out.join("");'
    , code = header
    , val = str
    , buff = ''
    , cap
    , ch
    , fi
    , then
    , els
    , end;

  function read(regex, no) {
    cap = regex.exec(val);
    if (!cap) return;
    val = val.substring(cap[0].length);
    ch = cap[1];
    if (!no) clear();
    return cap;
  }

  function stmt(c) {
    if (code[code.length - 1] === ',') {
      code = code.slice(0, -1);
    }
    code += c;
  }

  function expr(c) {
    code += c + ',';
  }

  function echo(c) {
    if (c === '""') return;
    expr('out.push(' + c + ')');
  }

  function print(c) {
    buff += c;
  }

  function clear() {
    if (buff) {
      echo(JSON.stringify(buff).replace(/\\u00([0-9a-fA-F]{2})/g, '\\x$1'));
      buff = '';
    }
  }

  while (val) {
    // Ignore newlines
    if (read(/^\n /, true)) {
      continue;
    }

    // '^A' -> ^A
    if (read(/^\^(.)/i, true)) {
      if (!(ch >= ' ' && ch <= '~')) {
        this._debug('%s: bad caret char.', tkey);
        // NOTE: ncurses appears to simply
        // continue in this situation, but
        // I could be wrong.
        print(cap[0]);
        continue;
      }
      if (ch === '?') {
        ch = '\x7f';
      } else {
        ch = ch.charCodeAt(0) & 31;
        if (ch === 0) ch = 128;
        ch = String.fromCharCode(ch);
      }
      print(ch);
      continue;
    }

    // 3 octal digits -> character
    if (read(/^\\([0-7]{3})/, true)) {
      print(String.fromCharCode(parseInt(ch, 8)));
      continue;
    }

    // '\e' -> ^[
    // '\n' -> \n
    // '\r' -> \r
    // '\0' -> \200 (special case)
    if (read(/^\\([eEnlrtbfs\^\\,:0]|.)/, true)) {
      switch (ch) {
        case 'e':
        case 'E':
          ch = '\x1b';
          break;
        case 'n':
          ch = '\n';
          break;
        case 'l':
          ch = '\x85';
          break;
        case 'r':
          ch = '\r';
          break;
        case 't':
          ch = '\t';
          break;
        case 'b':
          ch = '\x08';
          break;
        case 'f':
          ch = '\x0c';
          break;
        case 's':
          ch = ' ';
          break;
        case '^':
          ch = '^';
          break;
        case '\\':
          ch = '\\';
          break;
        case ',':
          ch = ',';
          break;
        case ':':
          ch = ':';
          break;
        case '0':
          ch = '\200';
          break;
        case 'a':
          ch = '\x07';
          break;
        default:
          this._debug('%s: bad backslash char.', tkey);
          ch = cap[0];
          break;
      }
      print(ch);
      continue;
    }

    // $<5> -> padding
    // e.g. flash_screen: '\u001b[?5h$<100/>\u001b[?5l',
    if (read(/^\$<(\d+)([*\/]{0,2})>/, true)) {
      if (this.padding) print(cap[0]);
      continue;
    }

    // %%   outputs `%'
    if (read(/^%%/, true)) {
      print('%');
      continue;
    }

    // %[[:]flags][width[.precision]][doxXs]
    //   as in printf, flags are [-+#] and space.  Use a `:' to allow the
    //   next character to be a `-' flag, avoiding interpreting "%-" as an
    //   operator.
    // %c   print pop() like %c in printf
    // Example from screen terminfo:
    //   S0: "\u001b(%p1%c"
    // %d   print pop()
    // "Print (e.g., "%d") is a special case."
    // %s   print pop() like %s in printf
    if (read(/^%((?::-|[+# ]){1,4})?(\d+(?:\.\d+)?)?([doxXsc])/)) {
      if (this.printf || cap[1] || cap[2] || ~'oxX'.indexOf(cap[3])) {
        echo('sprintf("'+ cap[0].replace(':-', '-') + '", stack.pop())');
      } else if (cap[3] === 'c') {
        echo('(v = stack.pop(), isFinite(v) '
          + '? String.fromCharCode(v || 0200) : "")');
      } else {
        echo('stack.pop()');
      }
      continue;
    }

    // %p[1-9]
    //   push i'th parameter
    if (read(/^%p([1-9])/)) {
      expr('(stack.push(v = params[' + (ch - 1) + ']), v)');
      continue;
    }

    // %P[a-z]
    //   set dynamic variable [a-z] to pop()
    if (read(/^%P([a-z])/)) {
      expr('dyn.' + ch + ' = stack.pop()');
      continue;
    }

    // %g[a-z]
    //   get dynamic variable [a-z] and push it
    if (read(/^%g([a-z])/)) {
      expr('(stack.push(dyn.' + ch + '), dyn.' + ch + ')');
      continue;
    }

    // %P[A-Z]
    //   set static variable [a-z] to pop()
    if (read(/^%P([A-Z])/)) {
      expr('stat.' + ch + ' = stack.pop()');
      continue;
    }

    // %g[A-Z]
    //   get static variable [a-z] and push it
    //   The  terms  "static"  and  "dynamic" are misleading.  Historically,
    //   these are simply two different sets of variables, whose values are
    //   not reset between calls to tparm.  However, that fact is not
    //   documented in other implementations.  Relying on it will adversely
    //   impact portability to other implementations.
    if (read(/^%g([A-Z])/)) {
      expr('(stack.push(v = stat.' + ch + '), v)');
      continue;
    }

    // %'c' char constant c
    // NOTE: These are stored as c chars, exemplified by:
    // cursor_address: "\u001b=%p1%' '%+%c%p2%' '%+%c"
    if (read(/^%'(.)'/)) {
      expr('(stack.push(v = ' + ch.charCodeAt(0) + '), v)');
      continue;
    }

    // %{nn}
    //   integer constant nn
    if (read(/^%\{(\d+)\}/)) {
      expr('(stack.push(v = ' + ch + '), v)');
      continue;
    }

    // %l   push strlen(pop)
    if (read(/^%l/)) {
      expr('(stack.push(v = (stack.pop() || "").length || 0), v)');
      continue;
    }

    // %+ %- %* %/ %m
    //   arithmetic (%m is mod): push(pop() op pop())
    // %& %| %^
    //   bit operations (AND, OR and exclusive-OR): push(pop() op pop())
    // %= %> %<
    //   logical operations: push(pop() op pop())
    if (read(/^%([+\-*\/m&|\^=><])/)) {
      if (ch === '=') ch = '===';
      else if (ch === 'm') ch = '%';
      expr('(v = stack.pop(),'
        + ' stack.push(v = (stack.pop() ' + ch + ' v) || 0),'
        + ' v)');
      continue;
    }

    // %A, %O
    //   logical AND and OR operations (for conditionals)
    if (read(/^%([AO])/)) {
      // Are we supposed to store the result on the stack?
      expr('(stack.push(v = (stack.pop() '
        + (ch === 'A' ? '&&' : '||')
        + ' stack.pop())), v)');
      continue;
    }

    // %! %~
    //   unary operations (logical and bit complement): push(op pop())
    if (read(/^%([!~])/)) {
      expr('(stack.push(v = ' + ch + 'stack.pop()), v)');
      continue;
    }

    // %i   add 1 to first two parameters (for ANSI terminals)
    if (read(/^%i/)) {
      // Are these supposed to go on the stack in certain situations?
      // ncurses doesn't seem to put them on the stack, but xterm.user6
      // seems to assume they're on the stack for some reason. Could
      // just be a bad terminfo string.
      // user6: "\u001b[%i%d;%dR" - possibly a termcap-style string.
      // expr('(params[0] |= 0, params[1] |= 0, params[0]++, params[1]++)');
      expr('(params[0]++, params[1]++)');
      continue;
    }

    // %? expr %t thenpart %e elsepart %;
    //   This forms an if-then-else.  The %e elsepart is optional.  Usually
    //   the %? expr part pushes a value onto the stack, and %t pops it from
    //   the stack, testing if it is nonzero (true).  If it is zero (false),
    //   control passes to the %e (else) part.
    //   It is possible to form else-if's a la Algol 68:
    //     %? c1 %t b1 %e c2 %t b2 %e c3 %t b3 %e c4 %t b4 %e %;
    //   where ci are conditions, bi are bodies.
    if (read(/^%\?/)) {
      end = -1;
      stmt(';if (');
      continue;
    }

    if (read(/^%t/)) {
      end = -1;
      // Technically this is supposed to pop everything off the stack that was
      // pushed onto the stack after the if statement, see man terminfo.
      // Right now, we don't pop anything off. This could cause compat issues.
      // Perhaps implement a "pushed" counter from the time the if statement
      // is added, to the time the then statement is added, and pop off
      // the appropriate number of elements.
      // while (pushed--) expr('stack.pop()');
      stmt(') {');
      continue;
    }

    // Terminfo does elseif's like
    // this: %?[expr]%t...%e[expr]%t...%;
    if (read(/^%e/)) {
      fi = val.indexOf('%?');
      then = val.indexOf('%t');
      els = val.indexOf('%e');
      end = val.indexOf('%;');
      if (end === -1) end = Infinity;
      if (then !== -1 && then < end
          && (fi === -1 || then < fi)
          && (els === -1 || then < els)) {
        stmt('} else if (');
      } else {
        stmt('} else {');
      }
      continue;
    }

    if (read(/^%;/)) {
      end = null;
      stmt('}');
      continue;
    }

    buff += val[0];
    val = val.substring(1);
  }

  // Clear the buffer of any remaining text.
  clear();

  // Some terminfos (I'm looking at you, atari-color), don't end an if
  // statement. It's assumed terminfo will automatically end it for
  // them, because they are a bunch of lazy bastards.
  if (end != null) {
    stmt('}');
  }

  // Add the footer.
  stmt(footer);

  // Optimize and cleanup generated code.
  v = code.slice(header.length, -footer.length);
  if (!v.length) {
    code = 'return "";';
  } else if (v = /^out\.push\(("(?:[^"]|\\")+")\)$/.exec(v)) {
    code = 'return ' + v[1] + ';';
  } else {
    // Turn `(stack.push(v = params[0]), v),out.push(stack.pop())`
    // into `out.push(params[0])`.
    code = code.replace(
      /\(stack\.push\(v = params\[(\d+)\]\), v\),out\.push\(stack\.pop\(\)\)/g,
      'out.push(params[$1])');

    // Remove unnecessary variable initializations.
    v = code.slice(header.length, -footer.length);
    if (!~v.indexOf('v = ')) code = code.replace('v, ', '');
    if (!~v.indexOf('dyn')) code = code.replace('dyn = {}, ', '');
    if (!~v.indexOf('stat')) code = code.replace('stat = {}, ', '');
    if (!~v.indexOf('stack')) code = code.replace('stack = [], ', '');

    // Turn `var out = [];out.push("foo"),` into `var out = ["foo"];`.
    code = code.replace(
      /out = \[\];out\.push\(("(?:[^"]|\\")+")\),/,
      'out = [$1];');
  }

  // Terminfos `wyse350-vb`, and `wy350-w`
  // seem to have a few broken strings.
  if (str === '\u001b%?') {
    code = 'return "\\x1b";';
  }

  if (this.debug) {
    v = code
      .replace(/\x1b/g, '\\x1b')
      .replace(/\r/g, '\\r')
      .replace(/\n/g, '\\n');
    process.stdout.write(v + '\n');
  }

  try {
    if (this.options.stringify && code.indexOf('return ') === 0) {
      return new Function('', code)();
    }
    return this.printf || ~code.indexOf('sprintf(')
      ? new Function('sprintf, params', code).bind(null, sprintf)
      : new Function('params', code);
  } catch (e) {
    console.error('');
    console.error('Error on %s:', tkey);
    console.error(JSON.stringify(str));
    console.error('');
    console.error(code.replace(/(,|;)/g, '$1\n'));
    e.stack = e.stack.replace(/\x1b/g, '\\x1b');
    throw e;
  }
};

// See: ~/ncurses/ncurses/tinfo/lib_tputs.c
Tput.prototype._print = function(code, print, done) {
  var xon = !this.bools.needs_xon_xoff || this.bools.xon_xoff;

  print = print || write;
  done = done || noop;

  if (!this.padding) {
    print(code);
    return done();
  }

  var parts = code.split(/(?=\$<[\d.]+[*\/]{0,2}>)/)
    , i = 0;

  (function next() {
    if (i === parts.length) {
      return done();
    }

    var part = parts[i++]
      , padding = /^\$<([\d.]+)([*\/]{0,2})>/.exec(part)
      , amount
      , suffix;
      // , affect;

    if (!padding) {
      print(part);
      return next();
    }

    part = part.substring(padding[0].length);
    amount = +padding[1];
    suffix = padding[2];

    // A `/'  suffix indicates  that  the  padding  is  mandatory and forces a
    // delay of the given number of milliseconds even on devices for which xon
    // is present to indicate flow control.
    if (xon && !~suffix.indexOf('/')) {
      print(part);
      return next();
    }

    // A `*' indicates that the padding required is proportional to the number
    // of lines affected by the operation, and  the amount  given  is the
    // per-affected-unit padding required.  (In the case of insert character,
    // the factor is still the number of lines affected.) Normally, padding is
    // advisory if the device has the xon capability; it is used for cost
    // computation but does not trigger delays.
    if (~suffix.indexOf('*')) {
      // XXX Disable this for now.
      amount = amount;
      // if (affect = /\x1b\[(\d+)[LM]/.exec(part)) {
      //   amount *= +affect[1];
      // }
      // The above is a huge workaround. In reality, we need to compile
      // `_print` into the string functions and check the cap name and
      // params.
      // if (cap === 'insert_line' || cap === 'delete_line') {
      //   amount *= params[0];
      // }
      // if (cap === 'clear_screen') {
      //   amount *= process.stdout.rows;
      // }
    }

    return setTimeout(function() {
      print(part);
      return next();
    }, amount);
  })();
};

// A small helper function if we want
// to easily output text with setTimeouts.
Tput.print = function() {
  var fake = {
    padding: true,
    bools: { needs_xon_xoff: true, xon_xoff: false }
  };
  return Tput.prototype._print.apply(fake, arguments);
};

/**
 * Termcap
 */

Tput.cpaths = [
  process.env.TERMCAP || '',
  (process.env.TERMPATH || '').split(/[: ]/),
  (process.env.HOME || '') + '/.termcap',
  '/usr/share/misc/termcap',
  '/etc/termcap'
];

Tput.prototype.readTermcap = function(term) {
  var self = this
    , terms
    , term_
    , root
    , paths;

  term = term || this.terminal;

  // Termcap has a bunch of terminals usually stored in one file/string,
  // so we need to find the one containing our desired terminal.
  if (~term.indexOf(path.sep) && (terms = this._tryCap(path.resolve(term)))) {
    term_ = path.basename(term).split('.')[0];
    if (terms[process.env.TERM]) {
      term = process.env.TERM;
    } else if (terms[term_]) {
      term = term_;
    } else {
      term = Object.keys(terms)[0];
    }
  } else {
    paths = Tput.cpaths.slice();

    if (this.termcapFile) {
      paths.unshift(this.termcapFile);
    }

    paths.push(Tput.termcap);

    terms = this._tryCap(paths, term);
  }

  if (!terms) {
    throw new Error('Cannot find termcap for: ' + term);
  }

  root = terms[term];

  if (this.debug) {
    this._termcap = terms;
  }

  (function tc(term) {
    if (term && term.strings.tc) {
      root.inherits = root.inherits || [];
      root.inherits.push(term.strings.tc);

      var names = terms[term.strings.tc]
        ? terms[term.strings.tc].names
        : [term.strings.tc];

      self._debug('%s inherits from %s.',
        term.names.join('/'), names.join('/'));

      var inherit = tc(terms[term.strings.tc]);
      if (inherit) {
        ['bools', 'numbers', 'strings'].forEach(function(type) {
          merge(term[type], inherit[type]);
        });
      }
    }
    return term;
  })(root);

  // Translate termcap names to terminfo-style names.
  root = this.translateTermcap(root);

  return root;
};

Tput.prototype._tryCap = function(file, term) {
  if (!file) return;

  var terms
    , data
    , i;

  if (Array.isArray(file)) {
    for (i = 0; i < file.length; i++) {
      data = this._tryCap(file[i], term);
      if (data) return data;
    }
    return;
  }

  // If the termcap string starts with `/`,
  // ncurses considers it a filename.
  data = file[0] === '/'
    ? tryRead(file)
    : file;

  if (!data) return;

  terms = this.parseTermcap(data, file);

  if (term && !terms[term]) {
    return;
  }

  return terms;
};

/**
 * Termcap Parser
 *  http://en.wikipedia.org/wiki/Termcap
 *  http://www.gnu.org/software
 *    /termutils/manual/termcap-1.3/html_mono/termcap.html
 *  http://www.gnu.org/software
 *    /termutils/manual/termcap-1.3/html_mono/termcap.html#SEC17
 *  http://tldp.org/HOWTO/Text-Terminal-HOWTO.html#toc16
 *  man termcap
 */

// Example:
// vt102|dec vt102:\
//  :do=^J:co#80:li#24:cl=50\E[;H\E[2J:\
//  :le=^H:bs:cm=5\E[%i%d;%dH:nd=2\E[C:up=2\E[A:\
//  :ce=3\E[K:cd=50\E[J:so=2\E[7m:se=2\E[m:us=2\E[4m:ue=2\E[m:\
//  :md=2\E[1m:mr=2\E[7m:mb=2\E[5m:me=2\E[m:is=\E[1;24r\E[24;1H:\
//  :rs=\E>\E[?3l\E[?4l\E[?5l\E[?7h\E[?8h:ks=\E[?1h\E=:ke=\E[?1l\E>:\
//  :ku=\EOA:kd=\EOB:kr=\EOC:kl=\EOD:kb=^H:\
//  :ho=\E[H:k1=\EOP:k2=\EOQ:k3=\EOR:k4=\EOS:pt:sr=5\EM:vt#3:\
//  :sc=\E7:rc=\E8:cs=\E[%i%d;%dr:vs=\E[?7l:ve=\E[?7h:\
//  :mi:al=\E[L:dc=\E[P:dl=\E[M:ei=\E[4l:im=\E[4h:

Tput.prototype.parseTermcap = function(data, file) {
  var terms = {}
    , parts
    , term
    , entries
    , fields
    , field
    , names
    , i
    , j
    , k;

  // remove escaped newlines
  data = data.replace(/\\\n[ \t]*/g, '');

  // remove comments
  data = data.replace(/^#[^\n]+/gm, '');

  // split entries
  entries = data.trim().split(/\n+/);

  for (i = 0; i < entries.length; i++) {
    fields = entries[i].split(/:+/);
    for (j = 0; j < fields.length; j++) {
      field = fields[j].trim();
      if (!field) continue;

      if (j === 0) {
        names = field.split('|');
        term = {
          name: names[0],
          names: names,
          desc: names.pop(),
          file: ~file.indexOf(path.sep)
            ? path.resolve(file)
            : file,
          termcap: true
        };

        for (k = 0; k < names.length; k++) {
          terms[names[k]] = term;
        }

        term.bools = {};
        term.numbers = {};
        term.strings = {};

        continue;
      }

      if (~field.indexOf('=')) {
        parts = field.split('=');
        term.strings[parts[0]] = parts.slice(1).join('=');
      } else if (~field.indexOf('#')) {
        parts = field.split('#');
        term.numbers[parts[0]] = +parts.slice(1).join('#');
      } else {
        term.bools[field] = true;
      }
    }
  }

  return terms;
};

/**
 * Termcap Compiler
 *  man termcap
 */

Tput.prototype.translateTermcap = function(info) {
  var self = this
    , out = {};

  if (!info) return;

  this._debug(info);

  ['name', 'names', 'desc', 'file', 'termcap'].forEach(function(key) {
    out[key] = info[key];
  });

  // Separate aliases for termcap
  var map = (function() {
    var out = {};

    Object.keys(Tput.alias).forEach(function(key) {
      var aliases = Tput.alias[key];
      out[aliases.termcap] = key;
    });

    return out;
  })();

  // Translate termcap cap names to terminfo cap names.
  // e.g. `up` -> `cursor_up`
  ['bools', 'numbers', 'strings'].forEach(function(key) {
    out[key] = {};
    Object.keys(info[key]).forEach(function(cap) {
      if (key === 'strings') {
        info.strings[cap] = self._captoinfo(cap, info.strings[cap], 1);
      }
      if (map[cap]) {
        out[key][map[cap]] = info[key][cap];
      } else {
        // NOTE: Possibly include all termcap names
        // in a separate alias.js file. Some are
        // missing from the terminfo alias.js file
        // which is why we have to do this:
        // See: $ man termcap
        out[key][cap] = info[key][cap];
      }
    });
  });

  return out;
};

Tput.prototype.compileTermcap = function(term) {
  return this.compile(this.readTermcap(term));
};

Tput.prototype.injectTermcap = function(term) {
  return this.inject(this.compileTermcap(term));
};

/**
 * _nc_captoinfo - ported to javascript directly from ncurses.
 * Copyright (c) 1998-2009,2010 Free Software Foundation, Inc.
 * See: ~/ncurses/ncurses/tinfo/captoinfo.c
 *
 * Convert a termcap string to terminfo format.
 * 'cap' is the relevant terminfo capability index.
 * 's' is the string value of the capability.
 * 'parameterized' tells what type of translations to do:
 *    % translations if 1
 *    pad translations if >=0
 */

Tput.prototype._captoinfo = function(cap, s, parameterized) {
  var self = this;

  var capstart;

  if (parameterized == null) {
    parameterized = 0;
  }

  var MAX_PUSHED = 16
    , stack = [];

  var stackptr = 0
    , onstack = 0
    , seenm = 0
    , seenn = 0
    , seenr = 0
    , param = 1
    , i = 0
    , out = '';

  function warn() {
    var args = Array.prototype.slice.call(arguments);
    args[0] = 'captoinfo: ' + (args[0] || '');
    return self._debug.apply(self, args);
  }

  function isdigit(ch) {
    return ch >= '0' && ch <= '9';
  }

  function isgraph(ch) {
    return ch > ' ' && ch <= '~';
  }

  // convert a character to a terminfo push
  function cvtchar(sp) {
    var c = '\0'
      , len;

    var j = i;

    switch (sp[j]) {
      case '\\':
        switch (sp[++j]) {
          case '\'':
          case '$':
          case '\\':
          case '%':
            c = sp[j];
            len = 2;
            break;
          case '\0':
            c = '\\';
            len = 1;
            break;
          case '0':
          case '1':
          case '2':
          case '3':
            len = 1;
            while (isdigit(sp[j])) {
              c = String.fromCharCode(8 * c.charCodeAt(0)
                + (sp[j++].charCodeAt(0) - '0'.charCodeAt(0)));
              len++;
            }
            break;
          default:
            c = sp[j];
            len = 2;
            break;
        }
        break;
      case '^':
        c = String.fromCharCode(sp[++j].charCodeAt(0) & 0x1f);
        len = 2;
        break;
      default:
        c = sp[j];
        len = 1;
    }
    if (isgraph(c) && c !== ',' && c !== '\'' && c !== '\\' && c !== ':') {
      out += '%\'';
      out += c;
      out += '\'';
    } else {
      out += '%{';
      if (c.charCodeAt(0) > 99) {
        out += String.fromCharCode(
          (c.charCodeAt(0) / 100 | 0) + '0'.charCodeAt(0));
      }
      if (c.charCodeAt(0) > 9) {
        out += String.fromCharCode(
          (c.charCodeAt(0) / 10 | 0) % 10 + '0'.charCodeAt(0));
      }
      out += String.fromCharCode(
        c.charCodeAt(0) % 10 + '0'.charCodeAt(0));
      out += '}';
    }

    return len;
  }

  // push n copies of param on the terminfo stack if not already there
  function getparm(parm, n) {
    if (seenr) {
      if (parm === 1) {
        parm = 2;
      } else if (parm === 2) {
        parm = 1;
      }
    }

    if (onstack === parm) {
      if (n > 1) {
        warn('string may not be optimal');
        out += '%Pa';
        while (n--) {
          out += '%ga';
        }
      }
      return;
    }

    if (onstack !== 0) {
      push();
    }

    onstack = parm;

    while (n--) {
      out += '%p';
      out += String.fromCharCode('0'.charCodeAt(0) + parm);
    }

    if (seenn && parm < 3) {
      out += '%{96}%^';
    }

    if (seenm && parm < 3) {
      out += '%{127}%^';
    }
  }

  // push onstack on to the stack
  function push() {
    if (stackptr >= MAX_PUSHED) {
      warn('string too complex to convert');
    } else {
      stack[stackptr++] = onstack;
    }
  }

  // pop the top of the stack into onstack
  function pop() {
    if (stackptr === 0) {
      if (onstack === 0) {
        warn('I\'m confused');
      } else {
        onstack = 0;
      }
    } else {
      onstack = stack[--stackptr];
    }
    param++;
  }

  function see03() {
    getparm(param, 1);
    out += '%3d';
    pop();
  }

  function invalid() {
    out += '%';
    i--;
    warn('unknown %% code %s (%#x) in %s',
      JSON.stringify(s[i]), s[i].charCodeAt(0), cap);
  }

  // skip the initial padding (if we haven't been told not to)
  capstart = null;
  if (s == null) s = '';

  if (parameterized >= 0 && isdigit(s[i])) {
    for (capstart = i;; i++) {
      if (!(isdigit(s[i]) || s[i] === '*' || s[i] === '.')) {
        break;
      }
    }
  }

  while (s[i]) {
    switch (s[i]) {
      case '%':
        i++;
        if (parameterized < 1) {
          out += '%';
          break;
        }
        switch (s[i++]) {
          case '%':
            out += '%';
            break;
          case 'r':
            if (seenr++ === 1) {
              warn('saw %%r twice in %s', cap);
            }
            break;
          case 'm':
            if (seenm++ === 1) {
              warn('saw %%m twice in %s', cap);
            }
            break;
          case 'n':
            if (seenn++ === 1) {
              warn('saw %%n twice in %s', cap);
            }
            break;
          case 'i':
            out += '%i';
            break;
          case '6':
          case 'B':
            getparm(param, 1);
            out += '%{10}%/%{16}%*';
            getparm(param, 1);
            out += '%{10}%m%+';
            break;
          case '8':
          case 'D':
            getparm(param, 2);
            out += '%{2}%*%-';
            break;
          case '>':
            getparm(param, 2);
            // %?%{x}%>%t%{y}%+%;
            out += '%?';
            i += cvtchar(s);
            out += '%>%t';
            i += cvtchar(s);
            out += '%+%;';
            break;
          case 'a':
            if ((s[i] === '=' || s[i] === '+' || s[i] === '-'
                || s[i] === '*' || s[i] === '/')
                && (s[i + 1] === 'p' || s[i + 1] === 'c')
                && s[i + 2] !== '\0' && s[i + 2]) {
              var l;
              l = 2;
              if (s[i] !== '=') {
                getparm(param, 1);
              }
              if (s[i + 1] === 'p') {
                getparm(param + s[i + 2].charCodeAt(0) - '@'.charCodeAt(0), 1);
                if (param !== onstack) {
                  pop();
                  param--;
                }
                l++;
              } else {
                i += 2, l += cvtchar(s), i -= 2;
              }
              switch (s[i]) {
                case '+':
                  out += '%+';
                  break;
                case '-':
                  out += '%-';
                  break;
                case '*':
                  out += '%*';
                  break;
                case '/':
                  out += '%/';
                  break;
                case '=':
                  if (seenr) {
                    if (param === 1) {
                      onstack = 2;
                    } else if (param === 2) {
                      onstack = 1;
                    } else {
                      onstack = param;
                    }
                  } else {
                    onstack = param;
                  }
                  break;
              }
              i += l;
              break;
            }
            getparm(param, 1);
            i += cvtchar(s);
            out += '%+';
            break;
          case '+':
            getparm(param, 1);
            i += cvtchar(s);
            out += '%+%c';
            pop();
            break;
          case 's':
// #ifdef WATERLOO
//          i += cvtchar(s);
//          getparm(param, 1);
//          out += '%-';
// #else
            getparm(param, 1);
            out += '%s';
            pop();
// #endif /* WATERLOO */
            break;
          case '-':
            i += cvtchar(s);
            getparm(param, 1);
            out += '%-%c';
            pop();
            break;
          case '.':
            getparm(param, 1);
            out += '%c';
            pop();
            break;
          case '0': // not clear any of the historical termcaps did this
            if (s[i] === '3') {
              see03(); // goto
              break;
            } else if (s[i] !== '2') {
              invalid(); // goto
              break;
            }
            // FALLTHRU
          case '2':
            getparm(param, 1);
            out += '%2d';
            pop();
            break;
          case '3':
            see03();
            break;
          case 'd':
            getparm(param, 1);
            out += '%d';
            pop();
            break;
          case 'f':
            param++;
            break;
          case 'b':
            param--;
            break;
          case '\\':
            out += '%\\';
            break;
          default:
            invalid();
            break;
        }
        break;
// #ifdef REVISIBILIZE
//    case '\\':
//      out += s[i++];
//      out += s[i++];
//      break;
//    case '\n':
//      out += '\\n';
//      i++;
//      break;
//    case '\t':
//      out += '\\t';
//      i++;
//      break;
//    case '\r':
//      out += '\\r';
//      i++;
//      break;
//    case '\200':
//      out += '\\0';
//      i++;
//      break;
//    case '\f':
//      out += '\\f';
//      i++;
//      break;
//    case '\b':
//      out += '\\b';
//      i++;
//      break;
//    case ' ':
//      out += '\\s';
//      i++;
//      break;
//    case '^':
//      out += '\\^';
//      i++;
//      break;
//    case ':':
//      out += '\\:';
//      i++;
//      break;
//    case ',':
//      out += '\\,';
//      i++;
//      break;
//    default:
//      if (s[i] === '\033') {
//        out += '\\E';
//        i++;
//      } else if (s[i].charCodeAt(0) > 0 && s[i].charCodeAt(0) < 32) {
//        out += '^';
//        out += String.fromCharCode(s[i].charCodeAt(0) + '@'.charCodeAt(0));
//        i++;
//      } else if (s[i].charCodeAt(0) <= 0 || s[i].charCodeAt(0) >= 127) {
//        out += '\\';
//        out += String.fromCharCode(
//          ((s[i].charCodeAt(0) & 0300) >> 6) + '0'.charCodeAt(0));
//        out += String.fromCharCode(
//          ((s[i].charCodeAt(0) & 0070) >> 3) + '0'.charCodeAt(0));
//        out += String.fromCharCode(
//          (s[i].charCodeAt(0) & 0007) + '0'.charCodeAt(0));
//        i++;
//      } else {
//        out += s[i++];
//      }
//      break;
// #else
      default:
        out += s[i++];
        break;
// #endif
    }
  }

  // Now, if we stripped off some leading padding, add it at the end
  // of the string as mandatory padding.
  if (capstart != null) {
    out += '$<';
    for (i = capstart;; i++) {
      if (isdigit(s[i]) || s[i] === '*' || s[i] === '.') {
        out += s[i];
      } else {
        break;
      }
    }
    out += '/>';
  }

  if (s !== out) {
    warn('Translating %s from %s to %s.',
      cap, JSON.stringify(s), JSON.stringify(out));
  }

  return out;
};

/**
 * Compile All Terminfo
 */

Tput.prototype.getAll = function() {
  var dir = this._prefix()
    , list = asort(fs.readdirSync(dir))
    , infos = [];

  list.forEach(function(letter) {
    var terms = asort(fs.readdirSync(path.resolve(dir, letter)));
    infos.push.apply(infos, terms);
  });

  function asort(obj) {
    return obj.sort(function(a, b) {
      a = a.toLowerCase().charCodeAt(0);
      b = b.toLowerCase().charCodeAt(0);
      return a - b;
    });
  }

  return infos;
};

Tput.prototype.compileAll = function(start) {
  var self = this
    , all = {};

  this.getAll().forEach(function(name) {
    if (start && name !== start) {
      return;
    } else {
      start = null;
    }
    all[name] = self.compileTerminfo(name);
  });

  return all;
};

/**
 * Detect Features / Quirks
 */

Tput.prototype.detectFeatures = function(info) {
  var data = this.parseACS(info);
  info.features = {
    unicode: this.detectUnicode(info),
    brokenACS: this.detectBrokenACS(info),
    PCRomSet: this.detectPCRomSet(info),
    magicCookie: this.detectMagicCookie(info),
    padding: this.detectPadding(info),
    setbuf: this.detectSetbuf(info),
    acsc: data.acsc,
    acscr: data.acscr
  };
  return info.features;
};

Tput.prototype.detectUnicode = function() {
  if (this.options.forceUnicode != null) {
    return this.options.forceUnicode;
  }

  var LANG = process.env.LANG
    + ':' + process.env.LANGUAGE
    + ':' + process.env.LC_ALL
    + ':' + process.env.LC_CTYPE;

  return /utf-?8/i.test(LANG) || (this.GetConsoleCP() === 65001);
};

// For some reason TERM=linux has smacs/rmacs, but it maps to `^[[11m`
// and it does not switch to the DEC SCLD character set. What the hell?
// xterm: \x1b(0, screen: \x0e, linux: \x1b[11m (doesn't work)
// `man console_codes` says:
// 11  select null mapping, set display control flag, reset tog‐
//     gle meta flag (ECMA-48 says "first alternate font").
// See ncurses:
// ~/ncurses/ncurses/base/lib_set_term.c
// ~/ncurses/ncurses/tinfo/lib_acs.c
// ~/ncurses/ncurses/tinfo/tinfo_driver.c
// ~/ncurses/ncurses/tinfo/lib_setup.c
Tput.prototype.detectBrokenACS = function(info) {
  // ncurses-compatible env variable.
  if (process.env.NCURSES_NO_UTF8_ACS != null) {
    return !!+process.env.NCURSES_NO_UTF8_ACS;
  }

  // If the terminal supports unicode, we don't need ACS.
  if (info.numbers.U8 >= 0) {
    return !!info.numbers.U8;
  }

  // The linux console is just broken for some reason.
  // Apparently the Linux console does not support ACS,
  // but it does support the PC ROM character set.
  if (info.name === 'linux') {
    return true;
  }

  // PC alternate charset
  // if (acsc.indexOf('+\x10,\x11-\x18.\x190') === 0) {
  if (this.detectPCRomSet(info)) {
    return true;
  }

  // screen termcap is bugged?
  if (this.termcap
      && info.name.indexOf('screen') === 0
      && process.env.TERMCAP
      && ~process.env.TERMCAP.indexOf('screen')
      && ~process.env.TERMCAP.indexOf('hhII00')) {
    if (~info.strings.enter_alt_charset_mode.indexOf('\016')
        || ~info.strings.enter_alt_charset_mode.indexOf('\017')
        || ~info.strings.set_attributes.indexOf('\016')
        || ~info.strings.set_attributes.indexOf('\017')) {
      return true;
    }
  }

  return false;
};

// If enter_pc_charset is the same as enter_alt_charset,
// the terminal does not support SCLD as ACS.
// See: ~/ncurses/ncurses/tinfo/lib_acs.c
Tput.prototype.detectPCRomSet = function(info) {
  var s = info.strings;
  if (s.enter_pc_charset_mode && s.enter_alt_charset_mode
      && s.enter_pc_charset_mode === s.enter_alt_charset_mode
      && s.exit_pc_charset_mode === s.exit_alt_charset_mode) {
    return true;
  }
  return false;
};

Tput.prototype.detectMagicCookie = function() {
  return process.env.NCURSES_NO_MAGIC_COOKIE == null;
};

Tput.prototype.detectPadding = function() {
  return process.env.NCURSES_NO_PADDING == null;
};

Tput.prototype.detectSetbuf = function() {
  return process.env.NCURSES_NO_SETBUF == null;
};

Tput.prototype.parseACS = function(info) {
  var data = {};

  data.acsc = {};
  data.acscr = {};

  // Possibly just return an empty object, as done here, instead of
  // specifically saying ACS is "broken" above. This would be more
  // accurate to ncurses logic. But it doesn't really matter.
  if (this.detectPCRomSet(info)) {
    return data;
  }

  // See: ~/ncurses/ncurses/tinfo/lib_acs.c: L208
  Object.keys(Tput.acsc).forEach(function(ch) {
    var acs_chars = info.strings.acs_chars || ''
      , i = acs_chars.indexOf(ch)
      , next = acs_chars[i + 1];

    if (!next || i === -1 || !Tput.acsc[next]) {
      return;
    }

    data.acsc[ch] = Tput.acsc[next];
    data.acscr[Tput.acsc[next]] = ch;
  });

  return data;
};

Tput.prototype.GetConsoleCP = function() {
  var ccp;

  if (process.platform !== 'win32') {
    return -1;
  }

  // Allow unicode on all windows consoles for now:
  if (+process.env.NCURSES_UNICODE !== 0) {
    return 65001;
  }

  // cp.execSync('chcp 65001', { stdio: 'ignore', timeout: 1500 });

  try {
    // Produces something like: 'Active code page: 437\n\n'
    ccp = cp.execFileSync(process.env.WINDIR + '\\system32\\chcp.com', [], {
      stdio: ['ignore', 'pipe', 'ignore'],
      encoding: 'ascii',
      timeout: 1500
    });
    // ccp = cp.execSync('chcp', {
    //   stdio: ['ignore', 'pipe', 'ignore'],
    //   encoding: 'ascii',
    //   timeout: 1500
    // });
  } catch (e) {
    ;
  }

  ccp = /\d+/.exec(ccp);

  if (!ccp) {
    return -1;
  }

  ccp = +ccp[0];

  return ccp;
};

/**
 * Helpers
 */

function noop() {
  return '';
}

noop.unsupported = true;

function merge(a, b) {
  Object.keys(b).forEach(function(key) {
    a[key] = b[key];
  });
  return a;
}

function write(data) {
  return process.stdout.write(data);
}

function tryRead(file) {
  if (Array.isArray(file)) {
    for (var i = 0; i < file.length; i++) {
      var data = tryRead(file[i]);
      if (data) return data;
    }
    return '';
  }
  if (!file) return '';
  file = path.resolve.apply(path, arguments);
  try {
    return fs.readFileSync(file, 'utf8');
  } catch (e) {
    return '';
  }
}

/**
 * sprintf
 *  http://www.cplusplus.com/reference/cstdio/printf/
 */

function sprintf(src) {
  var params = Array.prototype.slice.call(arguments, 1)
    , rule = /%([\-+# ]{1,4})?(\d+(?:\.\d+)?)?([doxXsc])/g
    , i = 0;

  return src.replace(rule, function(_, flag, width, type) {
    var flags = (flag || '').split('')
      , param = params[i] != null ? params[i] : ''
      , initial = param
      // , width = +width
      , opt = {}
      , pre = '';

    i++;

    switch (type) {
      case 'd': // signed int
        param = (+param).toString(10);
        break;
      case 'o': // unsigned octal
        param = (+param).toString(8);
        break;
      case 'x': // unsigned hex int
        param = (+param).toString(16);
        break;
      case 'X': // unsigned hex int uppercase
        param = (+param).toString(16).toUppercase();
        break;
      case 's': // string
        break;
      case 'c': // char
        param = isFinite(param)
          ? String.fromCharCode(param || 0200)
          : '';
        break;
    }

    flags.forEach(function(flag) {
      switch (flag) {
        // left-justify by width
        case '-':
          opt.left = true;
          break;
        // always precede numbers with their signs
        case '+':
          opt.signs = true;
          break;
        // used with o, x, X - value is preceded with 0, 0x, or 0X respectively.
        // used with a, A, e, E, f, F, g, G - forces written output to contain
        // a decimal point even if no more digits follow
        case '#':
          opt.hexpoint = true;
          break;
        // if no sign is going to be written, black space in front of the value
        case ' ':
          opt.space = true;
          break;
      }
    });

    width = +width.split('.')[0];

    // Should this be for opt.left too?
    // Example: %2.2X - turns 0 into 00
    if (width && !opt.left) {
      param = param + '';
      while (param.length < width) {
        param = '0' + param;
      }
    }

    if (opt.signs) {
      if (+initial >= 0) {
        pre += '+';
      }
    }

    if (opt.space) {
      if (!opt.signs && +initial >= 0) {
        pre += ' ';
      }
    }

    if (opt.hexpoint) {
      switch (type) {
        case 'o': // unsigned octal
          pre += '0';
          break;
        case 'x': // unsigned hex int
          pre += '0x';
          break;
        case 'X': // unsigned hex int uppercase
          pre += '0X';
          break;
      }
    }

    if (opt.left) {
      if (width > (pre.length + param.length)) {
        width -= pre.length + param.length;
        pre = Array(width + 1).join(' ') + pre;
      }
    }

    return pre + param;
  });
}

/**
 * Aliases
 */

Tput._alias = Require('term/alias');

Tput.alias = {};

['bools', 'numbers', 'strings'].forEach(function(type) {
  Object.keys(Tput._alias[type]).forEach(function(key) {
    var aliases = Tput._alias[type][key];
    Tput.alias[key] = [aliases[0]];
    Tput.alias[key].terminfo = aliases[0];
    Tput.alias[key].termcap = aliases[1];
  });
});

// Bools
Tput.alias.no_esc_ctlc.push('beehive_glitch');
Tput.alias.dest_tabs_magic_smso.push('teleray_glitch');

// Numbers
Tput.alias.micro_col_size.push('micro_char_size');

/**
 * Feature Checking
 */

Tput.aliasMap = {};

Object.keys(Tput.alias).forEach(function(key) {
  Tput.aliasMap[key] = key;
  Tput.alias[key].forEach(function(k) {
    Tput.aliasMap[k] = key;
  });
});

Tput.prototype.has = function(name) {
  name = Tput.aliasMap[name];

  var val = this.all[name];

  if (!name) return false;

  if (typeof val === 'number') {
    return val !== -1;
  }

  return !!val;
};

/**
 * Fallback Termcap Entry
 */

Tput.termcap = ''
  + 'vt102|dec vt102:'
  + ':do=^J:co#80:li#24:cl=50\\E[;H\\E[2J:'
  + ':le=^H:bs:cm=5\\E[%i%d;%dH:nd=2\\E[C:up=2\\E[A:'
  + ':ce=3\\E[K:cd=50\\E[J:so=2\\E[7m:se=2\\E[m:us=2\\E[4m:ue=2\\E[m:'
  + ':md=2\\E[1m:mr=2\\E[7m:mb=2\\E[5m:me=2\\E[m:is=\\E[1;24r\\E[24;1H:'
  + ':rs=\\E>\\E[?3l\\E[?4l\\E[?5l\\E[?7h\\E[?8h:ks=\\E[?1h\\E=:ke=\\E[?1l\\E>:'
  + ':ku=\\EOA:kd=\\EOB:kr=\\EOC:kl=\\EOD:kb=^H:\\\n'
  + ':ho=\\E[H:k1=\\EOP:k2=\\EOQ:k3=\\EOR:k4=\\EOS:pt:sr=5\\EM:vt#3:'
  + ':sc=\\E7:rc=\\E8:cs=\\E[%i%d;%dr:vs=\\E[?7l:ve=\\E[?7h:'
  + ':mi:al=\\E[L:dc=\\E[P:dl=\\E[M:ei=\\E[4l:im=\\E[4h:';

/**
 * Terminfo Data
 */

Tput.bools = [
  'auto_left_margin',
  'auto_right_margin',
  'no_esc_ctlc',
  'ceol_standout_glitch',
  'eat_newline_glitch',
  'erase_overstrike',
  'generic_type',
  'hard_copy',
  'has_meta_key',
  'has_status_line',
  'insert_null_glitch',
  'memory_above',
  'memory_below',
  'move_insert_mode',
  'move_standout_mode',
  'over_strike',
  'status_line_esc_ok',
  'dest_tabs_magic_smso',
  'tilde_glitch',
  'transparent_underline',
  'xon_xoff',
  'needs_xon_xoff',
  'prtr_silent',
  'hard_cursor',
  'non_rev_rmcup',
  'no_pad_char',
  'non_dest_scroll_region',
  'can_change',
  'back_color_erase',
  'hue_lightness_saturation',
  'col_addr_glitch',
  'cr_cancels_micro_mode',
  'has_print_wheel',
  'row_addr_glitch',
  'semi_auto_right_margin',
  'cpi_changes_res',
  'lpi_changes_res',

  // #ifdef __INTERNAL_CAPS_VISIBLE
  'backspaces_with_bs',
  'crt_no_scrolling',
  'no_correctly_working_cr',
  'gnu_has_meta_key',
  'linefeed_is_newline',
  'has_hardware_tabs',
  'return_does_clr_eol'
];

Tput.numbers = [
  'columns',
  'init_tabs',
  'lines',
  'lines_of_memory',
  'magic_cookie_glitch',
  'padding_baud_rate',
  'virtual_terminal',
  'width_status_line',
  'num_labels',
  'label_height',
  'label_width',
  'max_attributes',
  'maximum_windows',
  'max_colors',
  'max_pairs',
  'no_color_video',
  'buffer_capacity',
  'dot_vert_spacing',
  'dot_horz_spacing',
  'max_micro_address',
  'max_micro_jump',
  'micro_col_size',
  'micro_line_size',
  'number_of_pins',
  'output_res_char',
  'output_res_line',
  'output_res_horz_inch',
  'output_res_vert_inch',
  'print_rate',
  'wide_char_size',
  'buttons',
  'bit_image_entwining',
  'bit_image_type',

  // #ifdef __INTERNAL_CAPS_VISIBLE
  'magic_cookie_glitch_ul',
  'carriage_return_delay',
  'new_line_delay',
  'backspace_delay',
  'horizontal_tab_delay',
  'number_of_function_keys'
];

Tput.strings = [
  'back_tab',
  'bell',
  'carriage_return',
  'change_scroll_region',
  'clear_all_tabs',
  'clear_screen',
  'clr_eol',
  'clr_eos',
  'column_address',
  'command_character',
  'cursor_address',
  'cursor_down',
  'cursor_home',
  'cursor_invisible',
  'cursor_left',
  'cursor_mem_address',
  'cursor_normal',
  'cursor_right',
  'cursor_to_ll',
  'cursor_up',
  'cursor_visible',
  'delete_character',
  'delete_line',
  'dis_status_line',
  'down_half_line',
  'enter_alt_charset_mode',
  'enter_blink_mode',
  'enter_bold_mode',
  'enter_ca_mode',
  'enter_delete_mode',
  'enter_dim_mode',
  'enter_insert_mode',
  'enter_secure_mode',
  'enter_protected_mode',
  'enter_reverse_mode',
  'enter_standout_mode',
  'enter_underline_mode',
  'erase_chars',
  'exit_alt_charset_mode',
  'exit_attribute_mode',
  'exit_ca_mode',
  'exit_delete_mode',
  'exit_insert_mode',
  'exit_standout_mode',
  'exit_underline_mode',
  'flash_screen',
  'form_feed',
  'from_status_line',
  'init_1string',
  'init_2string',
  'init_3string',
  'init_file',
  'insert_character',
  'insert_line',
  'insert_padding',
  'key_backspace',
  'key_catab',
  'key_clear',
  'key_ctab',
  'key_dc',
  'key_dl',
  'key_down',
  'key_eic',
  'key_eol',
  'key_eos',
  'key_f0',
  'key_f1',
  'key_f10',
  'key_f2',
  'key_f3',
  'key_f4',
  'key_f5',
  'key_f6',
  'key_f7',
  'key_f8',
  'key_f9',
  'key_home',
  'key_ic',
  'key_il',
  'key_left',
  'key_ll',
  'key_npage',
  'key_ppage',
  'key_right',
  'key_sf',
  'key_sr',
  'key_stab',
  'key_up',
  'keypad_local',
  'keypad_xmit',
  'lab_f0',
  'lab_f1',
  'lab_f10',
  'lab_f2',
  'lab_f3',
  'lab_f4',
  'lab_f5',
  'lab_f6',
  'lab_f7',
  'lab_f8',
  'lab_f9',
  'meta_off',
  'meta_on',
  'newline',
  'pad_char',
  'parm_dch',
  'parm_delete_line',
  'parm_down_cursor',
  'parm_ich',
  'parm_index',
  'parm_insert_line',
  'parm_left_cursor',
  'parm_right_cursor',
  'parm_rindex',
  'parm_up_cursor',
  'pkey_key',
  'pkey_local',
  'pkey_xmit',
  'print_screen',
  'prtr_off',
  'prtr_on',
  'repeat_char',
  'reset_1string',
  'reset_2string',
  'reset_3string',
  'reset_file',
  'restore_cursor',
  'row_address',
  'save_cursor',
  'scroll_forward',
  'scroll_reverse',
  'set_attributes',
  'set_tab',
  'set_window',
  'tab',
  'to_status_line',
  'underline_char',
  'up_half_line',
  'init_prog',
  'key_a1',
  'key_a3',
  'key_b2',
  'key_c1',
  'key_c3',
  'prtr_non',
  'char_padding',
  'acs_chars',
  'plab_norm',
  'key_btab',
  'enter_xon_mode',
  'exit_xon_mode',
  'enter_am_mode',
  'exit_am_mode',
  'xon_character',
  'xoff_character',
  'ena_acs',
  'label_on',
  'label_off',
  'key_beg',
  'key_cancel',
  'key_close',
  'key_command',
  'key_copy',
  'key_create',
  'key_end',
  'key_enter',
  'key_exit',
  'key_find',
  'key_help',
  'key_mark',
  'key_message',
  'key_move',
  'key_next',
  'key_open',
  'key_options',
  'key_previous',
  'key_print',
  'key_redo',
  'key_reference',
  'key_refresh',
  'key_replace',
  'key_restart',
  'key_resume',
  'key_save',
  'key_suspend',
  'key_undo',
  'key_sbeg',
  'key_scancel',
  'key_scommand',
  'key_scopy',
  'key_screate',
  'key_sdc',
  'key_sdl',
  'key_select',
  'key_send',
  'key_seol',
  'key_sexit',
  'key_sfind',
  'key_shelp',
  'key_shome',
  'key_sic',
  'key_sleft',
  'key_smessage',
  'key_smove',
  'key_snext',
  'key_soptions',
  'key_sprevious',
  'key_sprint',
  'key_sredo',
  'key_sreplace',
  'key_sright',
  'key_srsume',
  'key_ssave',
  'key_ssuspend',
  'key_sundo',
  'req_for_input',
  'key_f11',
  'key_f12',
  'key_f13',
  'key_f14',
  'key_f15',
  'key_f16',
  'key_f17',
  'key_f18',
  'key_f19',
  'key_f20',
  'key_f21',
  'key_f22',
  'key_f23',
  'key_f24',
  'key_f25',
  'key_f26',
  'key_f27',
  'key_f28',
  'key_f29',
  'key_f30',
  'key_f31',
  'key_f32',
  'key_f33',
  'key_f34',
  'key_f35',
  'key_f36',
  'key_f37',
  'key_f38',
  'key_f39',
  'key_f40',
  'key_f41',
  'key_f42',
  'key_f43',
  'key_f44',
  'key_f45',
  'key_f46',
  'key_f47',
  'key_f48',
  'key_f49',
  'key_f50',
  'key_f51',
  'key_f52',
  'key_f53',
  'key_f54',
  'key_f55',
  'key_f56',
  'key_f57',
  'key_f58',
  'key_f59',
  'key_f60',
  'key_f61',
  'key_f62',
  'key_f63',
  'clr_bol',
  'clear_margins',
  'set_left_margin',
  'set_right_margin',
  'label_format',
  'set_clock',
  'display_clock',
  'remove_clock',
  'create_window',
  'goto_window',
  'hangup',
  'dial_phone',
  'quick_dial',
  'tone',
  'pulse',
  'flash_hook',
  'fixed_pause',
  'wait_tone',
  'user0',
  'user1',
  'user2',
  'user3',
  'user4',
  'user5',
  'user6',
  'user7',
  'user8',
  'user9',
  'orig_pair',
  'orig_colors',
  'initialize_color',
  'initialize_pair',
  'set_color_pair',
  'set_foreground',
  'set_background',
  'change_char_pitch',
  'change_line_pitch',
  'change_res_horz',
  'change_res_vert',
  'define_char',
  'enter_doublewide_mode',
  'enter_draft_quality',
  'enter_italics_mode',
  'enter_leftward_mode',
  'enter_micro_mode',
  'enter_near_letter_quality',
  'enter_normal_quality',
  'enter_shadow_mode',
  'enter_subscript_mode',
  'enter_superscript_mode',
  'enter_upward_mode',
  'exit_doublewide_mode',
  'exit_italics_mode',
  'exit_leftward_mode',
  'exit_micro_mode',
  'exit_shadow_mode',
  'exit_subscript_mode',
  'exit_superscript_mode',
  'exit_upward_mode',
  'micro_column_address',
  'micro_down',
  'micro_left',
  'micro_right',
  'micro_row_address',
  'micro_up',
  'order_of_pins',
  'parm_down_micro',
  'parm_left_micro',
  'parm_right_micro',
  'parm_up_micro',
  'select_char_set',
  'set_bottom_margin',
  'set_bottom_margin_parm',
  'set_left_margin_parm',
  'set_right_margin_parm',
  'set_top_margin',
  'set_top_margin_parm',
  'start_bit_image',
  'start_char_set_def',
  'stop_bit_image',
  'stop_char_set_def',
  'subscript_characters',
  'superscript_characters',
  'these_cause_cr',
  'zero_motion',
  'char_set_names',
  'key_mouse',
  'mouse_info',
  'req_mouse_pos',
  'get_mouse',
  'set_a_foreground',
  'set_a_background',
  'pkey_plab',
  'device_type',
  'code_set_init',
  'set0_des_seq',
  'set1_des_seq',
  'set2_des_seq',
  'set3_des_seq',
  'set_lr_margin',
  'set_tb_margin',
  'bit_image_repeat',
  'bit_image_newline',
  'bit_image_carriage_return',
  'color_names',
  'define_bit_image_region',
  'end_bit_image_region',
  'set_color_band',
  'set_page_length',
  'display_pc_char',
  'enter_pc_charset_mode',
  'exit_pc_charset_mode',
  'enter_scancode_mode',
  'exit_scancode_mode',
  'pc_term_options',
  'scancode_escape',
  'alt_scancode_esc',
  'enter_horizontal_hl_mode',
  'enter_left_hl_mode',
  'enter_low_hl_mode',
  'enter_right_hl_mode',
  'enter_top_hl_mode',
  'enter_vertical_hl_mode',
  'set_a_attributes',
  'set_pglen_inch',

  // #ifdef __INTERNAL_CAPS_VISIBLE
  'termcap_init2',
  'termcap_reset',
  'linefeed_if_not_lf',
  'backspace_if_not_bs',
  'other_non_function_keys',
  'arrow_key_map',
  'acs_ulcorner',
  'acs_llcorner',
  'acs_urcorner',
  'acs_lrcorner',
  'acs_ltee',
  'acs_rtee',
  'acs_btee',
  'acs_ttee',
  'acs_hline',
  'acs_vline',
  'acs_plus',
  'memory_lock',
  'memory_unlock',
  'box_chars_1'
];

// DEC Special Character and Line Drawing Set.
// Taken from tty.js.
Tput.acsc = {    // (0
  '`': '\u25c6', // '◆'
  'a': '\u2592', // '▒'
  'b': '\u0009', // '\t'
  'c': '\u000c', // '\f'
  'd': '\u000d', // '\r'
  'e': '\u000a', // '\n'
  'f': '\u00b0', // '°'
  'g': '\u00b1', // '±'
  'h': '\u2424', // '\u2424' (NL)
  'i': '\u000b', // '\v'
  'j': '\u2518', // '┘'
  'k': '\u2510', // '┐'
  'l': '\u250c', // '┌'
  'm': '\u2514', // '└'
  'n': '\u253c', // '┼'
  'o': '\u23ba', // '⎺'
  'p': '\u23bb', // '⎻'
  'q': '\u2500', // '─'
  'r': '\u23bc', // '⎼'
  's': '\u23bd', // '⎽'
  't': '\u251c', // '├'
  'u': '\u2524', // '┤'
  'v': '\u2534', // '┴'
  'w': '\u252c', // '┬'
  'x': '\u2502', // '│'
  'y': '\u2264', // '≤'
  'z': '\u2265', // '≥'
  '{': '\u03c0', // 'π'
  '|': '\u2260', // '≠'
  '}': '\u00a3', // '£'
  '~': '\u00b7'  // '·'
};

// Convert ACS unicode characters to the
// most similar-looking ascii characters.
Tput.utoa = Tput.prototype.utoa = {
  '\u25c6': '*', // '◆'
  '\u2592': ' ', // '▒'
  // '\u0009': '\t', // '\t'
  // '\u000c': '\f', // '\f'
  // '\u000d': '\r', // '\r'
  // '\u000a': '\n', // '\n'
  '\u00b0': '*', // '°'
  '\u00b1': '+', // '±'
  '\u2424': '\n', // '\u2424' (NL)
  // '\u000b': '\v', // '\v'
  '\u2518': '+', // '┘'
  '\u2510': '+', // '┐'
  '\u250c': '+', // '┌'
  '\u2514': '+', // '└'
  '\u253c': '+', // '┼'
  '\u23ba': '-', // '⎺'
  '\u23bb': '-', // '⎻'
  '\u2500': '-', // '─'
  '\u23bc': '-', // '⎼'
  '\u23bd': '_', // '⎽'
  '\u251c': '+', // '├'
  '\u2524': '+', // '┤'
  '\u2534': '+', // '┴'
  '\u252c': '+', // '┬'
  '\u2502': '|', // '│'
  '\u2264': '<', // '≤'
  '\u2265': '>', // '≥'
  '\u03c0': '?', // 'π'
  '\u2260': '=', // '≠'
  '\u00a3': '?', // '£'
  '\u00b7': '*'  // '·'
};

/**
 * Expose
 */

exports = Tput;
exports.sprintf = sprintf;
exports.tryRead = tryRead;

module.exports = exports;
};
BundleModuleCode['term/alias']=function (module,exports){
/**
 * alias.js - terminfo/cap aliases for blessed.
 * https://github.com/chjj/blessed
 * Taken from terminfo(5) man page.
 */

/* jshint maxlen: 300 */
// jscs:disable maximumLineLength
// jscs:disable

var alias = exports;

// These are the boolean capabilities:
alias.bools = {
  //         Variable                                      Cap-                               TCap                                  Description
  //         Booleans                                      name                               Code
  'auto_left_margin':                                      ['bw',                                 'bw'], //                                cub1 wraps from col‐ umn 0 to last column
  'auto_right_margin':                                     ['am',                                 'am'], //                                terminal has auto‐ matic margins
  'back_color_erase':                                      ['bce',                                'ut'], //                                screen erased with background color
  'can_change':                                            ['ccc',                                'cc'], //                                terminal can re- define existing col‐ ors
  'ceol_standout_glitch':                                  ['xhp',                                'xs'], //                                standout not erased by overwriting (hp)
  'col_addr_glitch':                                       ['xhpa',                               'YA'], //                                only positive motion for hpa/mhpa caps
  'cpi_changes_res':                                       ['cpix',                               'YF'], //                                changing character pitch changes reso‐ lution
  'cr_cancels_micro_mode':                                 ['crxm',                               'YB'], //                                using cr turns off micro mode
  'dest_tabs_magic_smso':                                  ['xt',                                 'xt'], //                                tabs destructive, magic so char (t1061)
  'eat_newline_glitch':                                    ['xenl',                               'xn'], //                                newline ignored after 80 cols (con‐ cept)
  'erase_overstrike':                                      ['eo',                                 'eo'], //                                can erase over‐ strikes with a blank
  'generic_type':                                          ['gn',                                 'gn'], //                                generic line type
  'hard_copy':                                             ['hc',                                 'hc'], //                                hardcopy terminal
  'hard_cursor':                                           ['chts',                               'HC'], //                                cursor is hard to see
  'has_meta_key':                                          ['km',                                 'km'], //                                Has a meta key (i.e., sets 8th-bit)
  'has_print_wheel':                                       ['daisy',                              'YC'], //                                printer needs opera‐ tor to change char‐ acter set
  'has_status_line':                                       ['hs',                                 'hs'], //                                has extra status line
  'hue_lightness_saturation':                              ['hls',                                'hl'], //                                terminal uses only HLS color notation (Tektronix)
  'insert_null_glitch':                                    ['in',                                 'in'], //                                insert mode distin‐ guishes nulls
  'lpi_changes_res':                                       ['lpix',                               'YG'], //                                changing line pitch changes resolution
  'memory_above':                                          ['da',                                 'da'], //                                display may be retained above the screen
  'memory_below':                                          ['db',                                 'db'], //                                display may be retained below the screen
  'move_insert_mode':                                      ['mir',                                'mi'], //                                safe to move while in insert mode
  'move_standout_mode':                                    ['msgr',                               'ms'], //                                safe to move while in standout mode
  'needs_xon_xoff':                                        ['nxon',                               'nx'], //                                padding will not work, xon/xoff required
  'no_esc_ctlc':                                           ['xsb',                                'xb'], //                                beehive (f1=escape, f2=ctrl C)
  'no_pad_char':                                           ['npc',                                'NP'], //                                pad character does not exist
  'non_dest_scroll_region':                                ['ndscr',                              'ND'], //                                scrolling region is non-destructive
  'non_rev_rmcup':                                         ['nrrmc',                              'NR'], //                                smcup does not reverse rmcup
  'over_strike':                                           ['os',                                 'os'], //                                terminal can over‐ strike
  'prtr_silent':                                           ['mc5i',                               '5i'], //                                printer will not echo on screen
  'row_addr_glitch':                                       ['xvpa',                               'YD'], //                                only positive motion for vpa/mvpa caps
  'semi_auto_right_margin':                                ['sam',                                'YE'], //                                printing in last column causes cr
  'status_line_esc_ok':                                    ['eslok',                              'es'], //                                escape can be used on the status line
  'tilde_glitch':                                          ['hz',                                 'hz'], //                                cannot print ~'s (hazeltine)
  'transparent_underline':                                 ['ul',                                 'ul'], //                                underline character overstrikes
  'xon_xoff':                                              ['xon',                                'xo']  //                                terminal uses xon/xoff handshaking
};

// These are the numeric capabilities:
alias.numbers = {
  //         Variable                                      Cap-                               TCap                                  Description
  //          Numeric                                      name                               Code
  'columns':                                               ['cols',                               'co'], //                                number of columns in a line
  'init_tabs':                                             ['it',                                 'it'], //                                tabs initially every # spaces
  'label_height':                                          ['lh',                                 'lh'], //                                rows in each label
  'label_width':                                           ['lw',                                 'lw'], //                                columns in each label
  'lines':                                                 ['lines',                              'li'], //                                number of lines on screen or page
  'lines_of_memory':                                       ['lm',                                 'lm'], //                                lines of memory if > line. 0 means varies
  'magic_cookie_glitch':                                   ['xmc',                                'sg'], //                                number of blank characters left by smso or rmso
  'max_attributes':                                        ['ma',                                 'ma'], //                                maximum combined attributes terminal can handle
  'max_colors':                                            ['colors',                             'Co'], //                                maximum number of colors on screen
  'max_pairs':                                             ['pairs',                              'pa'], //                                maximum number of color-pairs on the screen
  'maximum_windows':                                       ['wnum',                               'MW'], //                                maximum number of defineable windows
  'no_color_video':                                        ['ncv',                                'NC'], //                                video attributes that cannot be used with colors
  'num_labels':                                            ['nlab',                               'Nl'], //                                number of labels on screen
  'padding_baud_rate':                                     ['pb',                                 'pb'], //                                lowest baud rate where padding needed
  'virtual_terminal':                                      ['vt',                                 'vt'], //                                virtual terminal number (CB/unix)
  'width_status_line':                                     ['wsl',                                'ws'], //                                number of columns in status line

  // The  following  numeric  capabilities  are present in the SVr4.0 term structure, but are not yet documented in the man page.  They came in with
  // SVr4's printer support.


  //         Variable                                      Cap-                               TCap                                  Description
  //          Numeric                                      name                               Code
  'bit_image_entwining':                                   ['bitwin',                             'Yo'], //                                number of passes for each bit-image row
  'bit_image_type':                                        ['bitype',                             'Yp'], //                                type of bit-image device
  'buffer_capacity':                                       ['bufsz',                              'Ya'], //                                numbers of bytes buffered before printing
  'buttons':                                               ['btns',                               'BT'], //                                number of buttons on mouse
  'dot_horz_spacing':                                      ['spinh',                              'Yc'], //                                spacing of dots hor‐ izontally in dots per inch
  'dot_vert_spacing':                                      ['spinv',                              'Yb'], //                                spacing of pins ver‐ tically in pins per inch
  'max_micro_address':                                     ['maddr',                              'Yd'], //                                maximum value in micro_..._address
  'max_micro_jump':                                        ['mjump',                              'Ye'], //                                maximum value in parm_..._micro
  'micro_col_size':                                        ['mcs',                                'Yf'], //                                character step size when in micro mode
  'micro_line_size':                                       ['mls',                                'Yg'], //                                line step size when in micro mode
  'number_of_pins':                                        ['npins',                              'Yh'], //                                numbers of pins in print-head
  'output_res_char':                                       ['orc',                                'Yi'], //                                horizontal resolu‐ tion in units per line
  'output_res_horz_inch':                                  ['orhi',                               'Yk'], //                                horizontal resolu‐ tion in units per inch
  'output_res_line':                                       ['orl',                                'Yj'], //                                vertical resolution in units per line
  'output_res_vert_inch':                                  ['orvi',                               'Yl'], //                                vertical resolution in units per inch
  'print_rate':                                            ['cps',                                'Ym'], //                                print rate in char‐ acters per second
  'wide_char_size':                                        ['widcs',                              'Yn']  //                                character step size when in double wide mode
};

// These are the string capabilities:
alias.strings = {
  //         Variable                                    Cap-                             TCap                                   Description
  //          String                                     name                             Code
  'acs_chars':                                           ['acsc',                             'ac'], //                              graphics charset pairs, based on vt100
  'back_tab':                                            ['cbt',                              'bt'], //                              back tab (P)
  'bell':                                                ['bel',                              'bl'], //                              audible signal (bell) (P)
  'carriage_return':                                     ['cr',                               'cr'], //                              carriage return (P*) (P*)
  'change_char_pitch':                                   ['cpi',                              'ZA'], //                              Change number of characters per inch to #1
  'change_line_pitch':                                   ['lpi',                              'ZB'], //                              Change number of lines per inch to #1
  'change_res_horz':                                     ['chr',                              'ZC'], //                              Change horizontal resolution to #1
  'change_res_vert':                                     ['cvr',                              'ZD'], //                              Change vertical res‐ olution to #1
  'change_scroll_region':                                ['csr',                              'cs'], //                              change region to line #1 to line #2 (P)
  'char_padding':                                        ['rmp',                              'rP'], //                              like ip but when in insert mode
  'clear_all_tabs':                                      ['tbc',                              'ct'], //                              clear all tab stops (P)
  'clear_margins':                                       ['mgc',                              'MC'], //                              clear right and left soft margins
  'clear_screen':                                        ['clear',                            'cl'], //                              clear screen and home cursor (P*)
  'clr_bol':                                             ['el1',                              'cb'], //                              Clear to beginning of line
  'clr_eol':                                             ['el',                               'ce'], //                              clear to end of line (P)
  'clr_eos':                                             ['ed',                               'cd'], //                              clear to end of screen (P*)
  'column_address':                                      ['hpa',                              'ch'], //                              horizontal position #1, absolute (P)
  'command_character':                                   ['cmdch',                            'CC'], //                              terminal settable cmd character in prototype !?
  'create_window':                                       ['cwin',                             'CW'], //                              define a window #1 from #2,#3 to #4,#5
  'cursor_address':                                      ['cup',                              'cm'], //                              move to row #1 col‐ umns #2
  'cursor_down':                                         ['cud1',                             'do'], //                              down one line
  'cursor_home':                                         ['home',                             'ho'], //                              home cursor (if no cup)
  'cursor_invisible':                                    ['civis',                            'vi'], //                              make cursor invisi‐ ble
  'cursor_left':                                         ['cub1',                             'le'], //                              move left one space
  'cursor_mem_address':                                  ['mrcup',                            'CM'], //                              memory relative cur‐ sor addressing, move to row #1 columns #2
  'cursor_normal':                                       ['cnorm',                            've'], //                              make cursor appear normal (undo civis/cvvis)
  'cursor_right':                                        ['cuf1',                             'nd'], //                              non-destructive space (move right one space)
  'cursor_to_ll':                                        ['ll',                               'll'], //                              last line, first column (if no cup)
  'cursor_up':                                           ['cuu1',                             'up'], //                              up one line
  'cursor_visible':                                      ['cvvis',                            'vs'], //                              make cursor very visible
  'define_char':                                         ['defc',                             'ZE'], //                              Define a character #1, #2 dots wide, descender #3
  'delete_character':                                    ['dch1',                             'dc'], //                              delete character (P*)
  'delete_line':                                         ['dl1',                              'dl'], //                              delete line (P*)
  'dial_phone':                                          ['dial',                             'DI'], //                              dial number #1
  'dis_status_line':                                     ['dsl',                              'ds'], //                              disable status line
  'display_clock':                                       ['dclk',                             'DK'], //                              display clock
  'down_half_line':                                      ['hd',                               'hd'], //                              half a line down
  'ena_acs':                                             ['enacs',                            'eA'], //                              enable alternate char set
  'enter_alt_charset_mode':                              ['smacs',                            'as'], //                              start alternate character set (P)
  'enter_am_mode':                                       ['smam',                             'SA'], //                              turn on automatic margins
  'enter_blink_mode':                                    ['blink',                            'mb'], //                              turn on blinking
  'enter_bold_mode':                                     ['bold',                             'md'], //                              turn on bold (extra bright) mode
  'enter_ca_mode':                                       ['smcup',                            'ti'], //                              string to start pro‐ grams using cup
  'enter_delete_mode':                                   ['smdc',                             'dm'], //                              enter delete mode
  'enter_dim_mode':                                      ['dim',                              'mh'], //                              turn on half-bright mode
  'enter_doublewide_mode':                               ['swidm',                            'ZF'], //                              Enter double-wide mode
  'enter_draft_quality':                                 ['sdrfq',                            'ZG'], //                              Enter draft-quality mode
  'enter_insert_mode':                                   ['smir',                             'im'], //                              enter insert mode
  'enter_italics_mode':                                  ['sitm',                             'ZH'], //                              Enter italic mode
  'enter_leftward_mode':                                 ['slm',                              'ZI'], //                              Start leftward car‐ riage motion
  'enter_micro_mode':                                    ['smicm',                            'ZJ'], //                              Start micro-motion mode
  'enter_near_letter_quality':                           ['snlq',                             'ZK'], //                              Enter NLQ mode
  'enter_normal_quality':                                ['snrmq',                            'ZL'], //                              Enter normal-quality mode
  'enter_protected_mode':                                ['prot',                             'mp'], //                              turn on protected mode
  'enter_reverse_mode':                                  ['rev',                              'mr'], //                              turn on reverse video mode
  'enter_secure_mode':                                   ['invis',                            'mk'], //                              turn on blank mode (characters invisi‐ ble)
  'enter_shadow_mode':                                   ['sshm',                             'ZM'], //                              Enter shadow-print mode
  'enter_standout_mode':                                 ['smso',                             'so'], //                              begin standout mode
  'enter_subscript_mode':                                ['ssubm',                            'ZN'], //                              Enter subscript mode
  'enter_superscript_mode':                              ['ssupm',                            'ZO'], //                              Enter superscript mode
  'enter_underline_mode':                                ['smul',                             'us'], //                              begin underline mode
  'enter_upward_mode':                                   ['sum',                              'ZP'], //                              Start upward car‐ riage motion
  'enter_xon_mode':                                      ['smxon',                            'SX'], //                              turn on xon/xoff handshaking
  'erase_chars':                                         ['ech',                              'ec'], //                              erase #1 characters (P)
  'exit_alt_charset_mode':                               ['rmacs',                            'ae'], //                              end alternate char‐ acter set (P)
  'exit_am_mode':                                        ['rmam',                             'RA'], //                              turn off automatic margins
  'exit_attribute_mode':                                 ['sgr0',                             'me'], //                              turn off all attributes
  'exit_ca_mode':                                        ['rmcup',                            'te'], //                              strings to end pro‐ grams using cup
  'exit_delete_mode':                                    ['rmdc',                             'ed'], //                              end delete mode
  'exit_doublewide_mode':                                ['rwidm',                            'ZQ'], //                              End double-wide mode
  'exit_insert_mode':                                    ['rmir',                             'ei'], //                              exit insert mode
  'exit_italics_mode':                                   ['ritm',                             'ZR'], //                              End italic mode
  'exit_leftward_mode':                                  ['rlm',                              'ZS'], //                              End left-motion mode


  'exit_micro_mode':                                     ['rmicm',                            'ZT'], //                              End micro-motion mode
  'exit_shadow_mode':                                    ['rshm',                             'ZU'], //                              End shadow-print mode
  'exit_standout_mode':                                  ['rmso',                             'se'], //                              exit standout mode
  'exit_subscript_mode':                                 ['rsubm',                            'ZV'], //                              End subscript mode
  'exit_superscript_mode':                               ['rsupm',                            'ZW'], //                              End superscript mode
  'exit_underline_mode':                                 ['rmul',                             'ue'], //                              exit underline mode
  'exit_upward_mode':                                    ['rum',                              'ZX'], //                              End reverse charac‐ ter motion
  'exit_xon_mode':                                       ['rmxon',                            'RX'], //                              turn off xon/xoff handshaking
  'fixed_pause':                                         ['pause',                            'PA'], //                              pause for 2-3 sec‐ onds
  'flash_hook':                                          ['hook',                             'fh'], //                              flash switch hook
  'flash_screen':                                        ['flash',                            'vb'], //                              visible bell (may not move cursor)
  'form_feed':                                           ['ff',                               'ff'], //                              hardcopy terminal page eject (P*)
  'from_status_line':                                    ['fsl',                              'fs'], //                              return from status line
  'goto_window':                                         ['wingo',                            'WG'], //                              go to window #1
  'hangup':                                              ['hup',                              'HU'], //                              hang-up phone
  'init_1string':                                        ['is1',                              'i1'], //                              initialization string
  'init_2string':                                        ['is2',                              'is'], //                              initialization string
  'init_3string':                                        ['is3',                              'i3'], //                              initialization string
  'init_file':                                           ['if',                               'if'], //                              name of initializa‐ tion file
  'init_prog':                                           ['iprog',                            'iP'], //                              path name of program for initialization
  'initialize_color':                                    ['initc',                            'Ic'], //                              initialize color #1 to (#2,#3,#4)
  'initialize_pair':                                     ['initp',                            'Ip'], //                              Initialize color pair #1 to fg=(#2,#3,#4), bg=(#5,#6,#7)
  'insert_character':                                    ['ich1',                             'ic'], //                              insert character (P)
  'insert_line':                                         ['il1',                              'al'], //                              insert line (P*)
  'insert_padding':                                      ['ip',                               'ip'], //                              insert padding after inserted character
  'key_a1':                                              ['ka1',                              'K1'], //                              upper left of keypad
  'key_a3':                                              ['ka3',                              'K3'], //                              upper right of key‐ pad
  'key_b2':                                              ['kb2',                              'K2'], //                              center of keypad
  'key_backspace':                                       ['kbs',                              'kb'], //                              backspace key
  'key_beg':                                             ['kbeg',                             '@1'], //                              begin key
  'key_btab':                                            ['kcbt',                             'kB'], //                              back-tab key
  'key_c1':                                              ['kc1',                              'K4'], //                              lower left of keypad
  'key_c3':                                              ['kc3',                              'K5'], //                              lower right of key‐ pad
  'key_cancel':                                          ['kcan',                             '@2'], //                              cancel key
  'key_catab':                                           ['ktbc',                             'ka'], //                              clear-all-tabs key
  'key_clear':                                           ['kclr',                             'kC'], //                              clear-screen or erase key
  'key_close':                                           ['kclo',                             '@3'], //                              close key
  'key_command':                                         ['kcmd',                             '@4'], //                              command key
  'key_copy':                                            ['kcpy',                             '@5'], //                              copy key
  'key_create':                                          ['kcrt',                             '@6'], //                              create key
  'key_ctab':                                            ['kctab',                            'kt'], //                              clear-tab key
  'key_dc':                                              ['kdch1',                            'kD'], //                              delete-character key
  'key_dl':                                              ['kdl1',                             'kL'], //                              delete-line key
  'key_down':                                            ['kcud1',                            'kd'], //                              down-arrow key

  'key_eic':                                             ['krmir',                            'kM'], //                              sent by rmir or smir in insert mode
  'key_end':                                             ['kend',                             '@7'], //                              end key
  'key_enter':                                           ['kent',                             '@8'], //                              enter/send key
  'key_eol':                                             ['kel',                              'kE'], //                              clear-to-end-of-line key
  'key_eos':                                             ['ked',                              'kS'], //                              clear-to-end-of- screen key
  'key_exit':                                            ['kext',                             '@9'], //                              exit key
  'key_f0':                                              ['kf0',                              'k0'], //                              F0 function key
  'key_f1':                                              ['kf1',                              'k1'], //                              F1 function key
  'key_f10':                                             ['kf10',                             'k;'], //                              F10 function key
  'key_f11':                                             ['kf11',                             'F1'], //                              F11 function key
  'key_f12':                                             ['kf12',                             'F2'], //                              F12 function key
  'key_f13':                                             ['kf13',                             'F3'], //                              F13 function key
  'key_f14':                                             ['kf14',                             'F4'], //                              F14 function key
  'key_f15':                                             ['kf15',                             'F5'], //                              F15 function key
  'key_f16':                                             ['kf16',                             'F6'], //                              F16 function key
  'key_f17':                                             ['kf17',                             'F7'], //                              F17 function key
  'key_f18':                                             ['kf18',                             'F8'], //                              F18 function key
  'key_f19':                                             ['kf19',                             'F9'], //                              F19 function key
  'key_f2':                                              ['kf2',                              'k2'], //                              F2 function key
  'key_f20':                                             ['kf20',                             'FA'], //                              F20 function key
  'key_f21':                                             ['kf21',                             'FB'], //                              F21 function key
  'key_f22':                                             ['kf22',                             'FC'], //                              F22 function key
  'key_f23':                                             ['kf23',                             'FD'], //                              F23 function key
  'key_f24':                                             ['kf24',                             'FE'], //                              F24 function key
  'key_f25':                                             ['kf25',                             'FF'], //                              F25 function key
  'key_f26':                                             ['kf26',                             'FG'], //                              F26 function key
  'key_f27':                                             ['kf27',                             'FH'], //                              F27 function key
  'key_f28':                                             ['kf28',                             'FI'], //                              F28 function key
  'key_f29':                                             ['kf29',                             'FJ'], //                              F29 function key
  'key_f3':                                              ['kf3',                              'k3'], //                              F3 function key
  'key_f30':                                             ['kf30',                             'FK'], //                              F30 function key
  'key_f31':                                             ['kf31',                             'FL'], //                              F31 function key
  'key_f32':                                             ['kf32',                             'FM'], //                              F32 function key
  'key_f33':                                             ['kf33',                             'FN'], //                              F33 function key
  'key_f34':                                             ['kf34',                             'FO'], //                              F34 function key
  'key_f35':                                             ['kf35',                             'FP'], //                              F35 function key
  'key_f36':                                             ['kf36',                             'FQ'], //                              F36 function key
  'key_f37':                                             ['kf37',                             'FR'], //                              F37 function key
  'key_f38':                                             ['kf38',                             'FS'], //                              F38 function key
  'key_f39':                                             ['kf39',                             'FT'], //                              F39 function key
  'key_f4':                                              ['kf4',                              'k4'], //                              F4 function key
  'key_f40':                                             ['kf40',                             'FU'], //                              F40 function key
  'key_f41':                                             ['kf41',                             'FV'], //                              F41 function key
  'key_f42':                                             ['kf42',                             'FW'], //                              F42 function key
  'key_f43':                                             ['kf43',                             'FX'], //                              F43 function key
  'key_f44':                                             ['kf44',                             'FY'], //                              F44 function key
  'key_f45':                                             ['kf45',                             'FZ'], //                              F45 function key
  'key_f46':                                             ['kf46',                             'Fa'], //                              F46 function key
  'key_f47':                                             ['kf47',                             'Fb'], //                              F47 function key
  'key_f48':                                             ['kf48',                             'Fc'], //                              F48 function key
  'key_f49':                                             ['kf49',                             'Fd'], //                              F49 function key
  'key_f5':                                              ['kf5',                              'k5'], //                              F5 function key
  'key_f50':                                             ['kf50',                             'Fe'], //                              F50 function key
  'key_f51':                                             ['kf51',                             'Ff'], //                              F51 function key
  'key_f52':                                             ['kf52',                             'Fg'], //                              F52 function key
  'key_f53':                                             ['kf53',                             'Fh'], //                              F53 function key
  'key_f54':                                             ['kf54',                             'Fi'], //                              F54 function key
  'key_f55':                                             ['kf55',                             'Fj'], //                              F55 function key
  'key_f56':                                             ['kf56',                             'Fk'], //                              F56 function key
  'key_f57':                                             ['kf57',                             'Fl'], //                              F57 function key
  'key_f58':                                             ['kf58',                             'Fm'], //                              F58 function key
  'key_f59':                                             ['kf59',                             'Fn'], //                              F59 function key

  'key_f6':                                              ['kf6',                              'k6'], //                              F6 function key
  'key_f60':                                             ['kf60',                             'Fo'], //                              F60 function key
  'key_f61':                                             ['kf61',                             'Fp'], //                              F61 function key
  'key_f62':                                             ['kf62',                             'Fq'], //                              F62 function key
  'key_f63':                                             ['kf63',                             'Fr'], //                              F63 function key
  'key_f7':                                              ['kf7',                              'k7'], //                              F7 function key
  'key_f8':                                              ['kf8',                              'k8'], //                              F8 function key
  'key_f9':                                              ['kf9',                              'k9'], //                              F9 function key
  'key_find':                                            ['kfnd',                             '@0'], //                              find key
  'key_help':                                            ['khlp',                             '%1'], //                              help key
  'key_home':                                            ['khome',                            'kh'], //                              home key
  'key_ic':                                              ['kich1',                            'kI'], //                              insert-character key
  'key_il':                                              ['kil1',                             'kA'], //                              insert-line key
  'key_left':                                            ['kcub1',                            'kl'], //                              left-arrow key
  'key_ll':                                              ['kll',                              'kH'], //                              lower-left key (home down)
  'key_mark':                                            ['kmrk',                             '%2'], //                              mark key
  'key_message':                                         ['kmsg',                             '%3'], //                              message key
  'key_move':                                            ['kmov',                             '%4'], //                              move key
  'key_next':                                            ['knxt',                             '%5'], //                              next key
  'key_npage':                                           ['knp',                              'kN'], //                              next-page key
  'key_open':                                            ['kopn',                             '%6'], //                              open key
  'key_options':                                         ['kopt',                             '%7'], //                              options key
  'key_ppage':                                           ['kpp',                              'kP'], //                              previous-page key
  'key_previous':                                        ['kprv',                             '%8'], //                              previous key
  'key_print':                                           ['kprt',                             '%9'], //                              print key
  'key_redo':                                            ['krdo',                             '%0'], //                              redo key
  'key_reference':                                       ['kref',                             '&1'], //                              reference key
  'key_refresh':                                         ['krfr',                             '&2'], //                              refresh key
  'key_replace':                                         ['krpl',                             '&3'], //                              replace key
  'key_restart':                                         ['krst',                             '&4'], //                              restart key
  'key_resume':                                          ['kres',                             '&5'], //                              resume key
  'key_right':                                           ['kcuf1',                            'kr'], //                              right-arrow key
  'key_save':                                            ['ksav',                             '&6'], //                              save key
  'key_sbeg':                                            ['kBEG',                             '&9'], //                              shifted begin key
  'key_scancel':                                         ['kCAN',                             '&0'], //                              shifted cancel key
  'key_scommand':                                        ['kCMD',                             '*1'], //                              shifted command key
  'key_scopy':                                           ['kCPY',                             '*2'], //                              shifted copy key
  'key_screate':                                         ['kCRT',                             '*3'], //                              shifted create key
  'key_sdc':                                             ['kDC',                              '*4'], //                              shifted delete-char‐ acter key
  'key_sdl':                                             ['kDL',                              '*5'], //                              shifted delete-line key
  'key_select':                                          ['kslt',                             '*6'], //                              select key
  'key_send':                                            ['kEND',                             '*7'], //                              shifted end key
  'key_seol':                                            ['kEOL',                             '*8'], //                              shifted clear-to- end-of-line key
  'key_sexit':                                           ['kEXT',                             '*9'], //                              shifted exit key
  'key_sf':                                              ['kind',                             'kF'], //                              scroll-forward key
  'key_sfind':                                           ['kFND',                             '*0'], //                              shifted find key
  'key_shelp':                                           ['kHLP',                             '#1'], //                              shifted help key
  'key_shome':                                           ['kHOM',                             '#2'], //                              shifted home key
  'key_sic':                                             ['kIC',                              '#3'], //                              shifted insert-char‐ acter key
  'key_sleft':                                           ['kLFT',                             '#4'], //                              shifted left-arrow key
  'key_smessage':                                        ['kMSG',                             '%a'], //                              shifted message key
  'key_smove':                                           ['kMOV',                             '%b'], //                              shifted move key
  'key_snext':                                           ['kNXT',                             '%c'], //                              shifted next key
  'key_soptions':                                        ['kOPT',                             '%d'], //                              shifted options key
  'key_sprevious':                                       ['kPRV',                             '%e'], //                              shifted previous key
  'key_sprint':                                          ['kPRT',                             '%f'], //                              shifted print key
  'key_sr':                                              ['kri',                              'kR'], //                              scroll-backward key
  'key_sredo':                                           ['kRDO',                             '%g'], //                              shifted redo key
  'key_sreplace':                                        ['kRPL',                             '%h'], //                              shifted replace key

  'key_sright':                                          ['kRIT',                             '%i'], //                              shifted right-arrow key
  'key_srsume':                                          ['kRES',                             '%j'], //                              shifted resume key
  'key_ssave':                                           ['kSAV',                             '!1'], //                              shifted save key
  'key_ssuspend':                                        ['kSPD',                             '!2'], //                              shifted suspend key
  'key_stab':                                            ['khts',                             'kT'], //                              set-tab key
  'key_sundo':                                           ['kUND',                             '!3'], //                              shifted undo key
  'key_suspend':                                         ['kspd',                             '&7'], //                              suspend key
  'key_undo':                                            ['kund',                             '&8'], //                              undo key
  'key_up':                                              ['kcuu1',                            'ku'], //                              up-arrow key
  'keypad_local':                                        ['rmkx',                             'ke'], //                              leave 'key‐ board_transmit' mode
  'keypad_xmit':                                         ['smkx',                             'ks'], //                              enter 'key‐ board_transmit' mode
  'lab_f0':                                              ['lf0',                              'l0'], //                              label on function key f0 if not f0
  'lab_f1':                                              ['lf1',                              'l1'], //                              label on function key f1 if not f1
  'lab_f10':                                             ['lf10',                             'la'], //                              label on function key f10 if not f10
  'lab_f2':                                              ['lf2',                              'l2'], //                              label on function key f2 if not f2
  'lab_f3':                                              ['lf3',                              'l3'], //                              label on function key f3 if not f3
  'lab_f4':                                              ['lf4',                              'l4'], //                              label on function key f4 if not f4
  'lab_f5':                                              ['lf5',                              'l5'], //                              label on function key f5 if not f5
  'lab_f6':                                              ['lf6',                              'l6'], //                              label on function key f6 if not f6
  'lab_f7':                                              ['lf7',                              'l7'], //                              label on function key f7 if not f7
  'lab_f8':                                              ['lf8',                              'l8'], //                              label on function key f8 if not f8
  'lab_f9':                                              ['lf9',                              'l9'], //                              label on function key f9 if not f9
  'label_format':                                        ['fln',                              'Lf'], //                              label format
  'label_off':                                           ['rmln',                             'LF'], //                              turn off soft labels
  'label_on':                                            ['smln',                             'LO'], //                              turn on soft labels
  'meta_off':                                            ['rmm',                              'mo'], //                              turn off meta mode
  'meta_on':                                             ['smm',                              'mm'], //                              turn on meta mode (8th-bit on)
  'micro_column_address':                                ['mhpa',                             'ZY'], //                              Like column_address in micro mode
  'micro_down':                                          ['mcud1',                            'ZZ'], //                              Like cursor_down in micro mode
  'micro_left':                                          ['mcub1',                            'Za'], //                              Like cursor_left in micro mode
  'micro_right':                                         ['mcuf1',                            'Zb'], //                              Like cursor_right in micro mode
  'micro_row_address':                                   ['mvpa',                             'Zc'], //                              Like row_address #1 in micro mode
  'micro_up':                                            ['mcuu1',                            'Zd'], //                              Like cursor_up in micro mode
  'newline':                                             ['nel',                              'nw'], //                              newline (behave like cr followed by lf)
  'order_of_pins':                                       ['porder',                           'Ze'], //                              Match software bits to print-head pins
  'orig_colors':                                         ['oc',                               'oc'], //                              Set all color pairs to the original ones
  'orig_pair':                                           ['op',                               'op'], //                              Set default pair to its original value
  'pad_char':                                            ['pad',                              'pc'], //                              padding char (instead of null)


  'parm_dch':                                            ['dch',                              'DC'], //                              delete #1 characters (P*)
  'parm_delete_line':                                    ['dl',                               'DL'], //                              delete #1 lines (P*)
  'parm_down_cursor':                                    ['cud',                              'DO'], //                              down #1 lines (P*)
  'parm_down_micro':                                     ['mcud',                             'Zf'], //                              Like parm_down_cur‐ sor in micro mode
  'parm_ich':                                            ['ich',                              'IC'], //                              insert #1 characters (P*)
  'parm_index':                                          ['indn',                             'SF'], //                              scroll forward #1 lines (P)
  'parm_insert_line':                                    ['il',                               'AL'], //                              insert #1 lines (P*)
  'parm_left_cursor':                                    ['cub',                              'LE'], //                              move #1 characters to the left (P)
  'parm_left_micro':                                     ['mcub',                             'Zg'], //                              Like parm_left_cur‐ sor in micro mode
  'parm_right_cursor':                                   ['cuf',                              'RI'], //                              move #1 characters to the right (P*)
  'parm_right_micro':                                    ['mcuf',                             'Zh'], //                              Like parm_right_cur‐ sor in micro mode
  'parm_rindex':                                         ['rin',                              'SR'], //                              scroll back #1 lines (P)
  'parm_up_cursor':                                      ['cuu',                              'UP'], //                              up #1 lines (P*)
  'parm_up_micro':                                       ['mcuu',                             'Zi'], //                              Like parm_up_cursor in micro mode
  'pkey_key':                                            ['pfkey',                            'pk'], //                              program function key #1 to type string #2
  'pkey_local':                                          ['pfloc',                            'pl'], //                              program function key #1 to execute string #2
  'pkey_xmit':                                           ['pfx',                              'px'], //                              program function key #1 to transmit string #2
  'plab_norm':                                           ['pln',                              'pn'], //                              program label #1 to show string #2
  'print_screen':                                        ['mc0',                              'ps'], //                              print contents of screen
  'prtr_non':                                            ['mc5p',                             'pO'], //                              turn on printer for #1 bytes
  'prtr_off':                                            ['mc4',                              'pf'], //                              turn off printer
  'prtr_on':                                             ['mc5',                              'po'], //                              turn on printer
  'pulse':                                               ['pulse',                            'PU'], //                              select pulse dialing
  'quick_dial':                                          ['qdial',                            'QD'], //                              dial number #1 with‐ out checking
  'remove_clock':                                        ['rmclk',                            'RC'], //                              remove clock
  'repeat_char':                                         ['rep',                              'rp'], //                              repeat char #1 #2 times (P*)
  'req_for_input':                                       ['rfi',                              'RF'], //                              send next input char (for ptys)
  'reset_1string':                                       ['rs1',                              'r1'], //                              reset string
  'reset_2string':                                       ['rs2',                              'r2'], //                              reset string
  'reset_3string':                                       ['rs3',                              'r3'], //                              reset string
  'reset_file':                                          ['rf',                               'rf'], //                              name of reset file
  'restore_cursor':                                      ['rc',                               'rc'], //                              restore cursor to position of last save_cursor
  'row_address':                                         ['vpa',                              'cv'], //                              vertical position #1 absolute (P)
  'save_cursor':                                         ['sc',                               'sc'], //                              save current cursor position (P)
  'scroll_forward':                                      ['ind',                              'sf'], //                              scroll text up (P)
  'scroll_reverse':                                      ['ri',                               'sr'], //                              scroll text down (P)
  'select_char_set':                                     ['scs',                              'Zj'], //                              Select character set, #1



  'set_attributes':                                      ['sgr',                              'sa'], //                              define video attributes #1-#9 (PG9)
  'set_background':                                      ['setb',                             'Sb'], //                              Set background color #1
  'set_bottom_margin':                                   ['smgb',                             'Zk'], //                              Set bottom margin at current line
  'set_bottom_margin_parm':                              ['smgbp',                            'Zl'], //                              Set bottom margin at line #1 or (if smgtp is not given) #2 lines from bottom
  'set_clock':                                           ['sclk',                             'SC'], //                              set clock, #1 hrs #2 mins #3 secs
  'set_color_pair':                                      ['scp',                              'sp'], //                              Set current color pair to #1
  'set_foreground':                                      ['setf',                             'Sf'], //                              Set foreground color #1
  'set_left_margin':                                     ['smgl',                             'ML'], //                              set left soft margin at current col‐ umn.  See smgl. (ML is not in BSD termcap).
  'set_left_margin_parm':                                ['smglp',                            'Zm'], //                              Set left (right) margin at column #1
  'set_right_margin':                                    ['smgr',                             'MR'], //                              set right soft margin at current column
  'set_right_margin_parm':                               ['smgrp',                            'Zn'], //                              Set right margin at column #1
  'set_tab':                                             ['hts',                              'st'], //                              set a tab in every row, current columns
  'set_top_margin':                                      ['smgt',                             'Zo'], //                              Set top margin at current line
  'set_top_margin_parm':                                 ['smgtp',                            'Zp'], //                              Set top (bottom) margin at row #1
  'set_window':                                          ['wind',                             'wi'], //                              current window is lines #1-#2 cols #3-#4
  'start_bit_image':                                     ['sbim',                             'Zq'], //                              Start printing bit image graphics
  'start_char_set_def':                                  ['scsd',                             'Zr'], //                              Start character set defi‐ nition #1, with #2 charac‐ ters in the set
  'stop_bit_image':                                      ['rbim',                             'Zs'], //                              Stop printing bit image graphics
  'stop_char_set_def':                                   ['rcsd',                             'Zt'], //                              End definition of charac‐ ter set #1
  'subscript_characters':                                ['subcs',                            'Zu'], //                              List of subscriptable characters
  'superscript_characters':                              ['supcs',                            'Zv'], //                              List of superscriptable characters
  'tab':                                                 ['ht',                               'ta'], //                              tab to next 8-space hard‐ ware tab stop
  'these_cause_cr':                                      ['docr',                             'Zw'], //                              Printing any of these characters causes CR
  'to_status_line':                                      ['tsl',                              'ts'], //                              move to status line, col‐ umn #1
  'tone':                                                ['tone',                             'TO'], //                              select touch tone dialing
  'underline_char':                                      ['uc',                               'uc'], //                              underline char and move past it
  'up_half_line':                                        ['hu',                               'hu'], //                              half a line up
  'user0':                                               ['u0',                               'u0'], //                              User string #0
  'user1':                                               ['u1',                               'u1'], //                              User string #1
  'user2':                                               ['u2',                               'u2'], //                              User string #2
  'user3':                                               ['u3',                               'u3'], //                              User string #3
  'user4':                                               ['u4',                               'u4'], //                              User string #4
  'user5':                                               ['u5',                               'u5'], //                              User string #5

  'user6':                                               ['u6',                               'u6'], //                              User string #6
  'user7':                                               ['u7',                               'u7'], //                              User string #7
  'user8':                                               ['u8',                               'u8'], //                              User string #8
  'user9':                                               ['u9',                               'u9'], //                              User string #9
  'wait_tone':                                           ['wait',                             'WA'], //                              wait for dial-tone
  'xoff_character':                                      ['xoffc',                            'XF'], //                              XOFF character
  'xon_character':                                       ['xonc',                             'XN'], //                              XON character
  'zero_motion':                                         ['zerom',                            'Zx'], //                              No motion for subsequent character

  // The following string capabilities are present in the SVr4.0 term structure, but were originally not documented in the man page.


  //         Variable                                      Cap-                                 TCap                                 Description
  //          String                                       name                                 Code
  'alt_scancode_esc':                                      ['scesa',                                'S8'], //                                Alternate escape for scancode emu‐ lation
  'bit_image_carriage_return':                             ['bicr',                                 'Yv'], //                                Move to beginning of same row
  'bit_image_newline':                                     ['binel',                                'Zz'], //                                Move to next row of the bit image
  'bit_image_repeat':                                      ['birep',                                'Xy'], //                                Repeat bit image cell #1 #2 times
  'char_set_names':                                        ['csnm',                                 'Zy'], //                                Produce #1'th item from list of char‐ acter set names
  'code_set_init':                                         ['csin',                                 'ci'], //                                Init sequence for multiple codesets
  'color_names':                                           ['colornm',                              'Yw'], //                                Give name for color #1
  'define_bit_image_region':                               ['defbi',                                'Yx'], //                                Define rectan‐ gualar bit image region
  'device_type':                                           ['devt',                                 'dv'], //                                Indicate lan‐ guage/codeset sup‐ port
  'display_pc_char':                                       ['dispc',                                'S1'], //                                Display PC charac‐ ter #1
  'end_bit_image_region':                                  ['endbi',                                'Yy'], //                                End a bit-image region
  'enter_pc_charset_mode':                                 ['smpch',                                'S2'], //                                Enter PC character display mode
  'enter_scancode_mode':                                   ['smsc',                                 'S4'], //                                Enter PC scancode mode
  'exit_pc_charset_mode':                                  ['rmpch',                                'S3'], //                                Exit PC character display mode
  'exit_scancode_mode':                                    ['rmsc',                                 'S5'], //                                Exit PC scancode mode
  'get_mouse':                                             ['getm',                                 'Gm'], //                                Curses should get button events, parameter #1 not documented.
  'key_mouse':                                             ['kmous',                                'Km'], //                                Mouse event has occurred
  'mouse_info':                                            ['minfo',                                'Mi'], //                                Mouse status information
  'pc_term_options':                                       ['pctrm',                                'S6'], //                                PC terminal options
  'pkey_plab':                                             ['pfxl',                                 'xl'], //                                Program function key #1 to type string #2 and show string #3
  'req_mouse_pos':                                         ['reqmp',                                'RQ'], //                                Request mouse position

  'scancode_escape':                                       ['scesc',                                'S7'], //                                Escape for scan‐ code emulation
  'set0_des_seq':                                          ['s0ds',                                 's0'], //                                Shift to codeset 0 (EUC set 0, ASCII)
  'set1_des_seq':                                          ['s1ds',                                 's1'], //                                Shift to codeset 1
  'set2_des_seq':                                          ['s2ds',                                 's2'], //                                Shift to codeset 2
  'set3_des_seq':                                          ['s3ds',                                 's3'], //                                Shift to codeset 3
  'set_a_background':                                      ['setab',                                'AB'], //                                Set background color to #1, using ANSI escape
  'set_a_foreground':                                      ['setaf',                                'AF'], //                                Set foreground color to #1, using ANSI escape
  'set_color_band':                                        ['setcolor',                             'Yz'], //                                Change to ribbon color #1
  'set_lr_margin':                                         ['smglr',                                'ML'], //                                Set both left and right margins to #1, #2.  (ML is not in BSD term‐ cap).
  'set_page_length':                                       ['slines',                               'YZ'], //                                Set page length to #1 lines
  'set_tb_margin':                                         ['smgtb',                                'MT'], //                                Sets both top and bottom margins to #1, #2

  // The XSI Curses standard added these.  They are some post-4.1 versions of System V curses, e.g., Solaris 2.5 and IRIX 6.x.  The ncurses termcap
  // names for them are invented; according to the XSI Curses standard, they have no termcap names.  If your compiled terminfo entries  use  these,
  // they may not be binary-compatible with System V terminfo entries after SVr4.1; beware!


  //         Variable                                      Cap-                               TCap                                 Description
  //          String                                       name                               Code
  'enter_horizontal_hl_mode':                              ['ehhlm',                              'Xh'], //                               Enter horizontal highlight mode
  'enter_left_hl_mode':                                    ['elhlm',                              'Xl'], //                               Enter left highlight mode
  'enter_low_hl_mode':                                     ['elohlm',                             'Xo'], //                               Enter low highlight mode
  'enter_right_hl_mode':                                   ['erhlm',                              'Xr'], //                               Enter right high‐ light mode
  'enter_top_hl_mode':                                     ['ethlm',                              'Xt'], //                               Enter top highlight mode
  'enter_vertical_hl_mode':                                ['evhlm',                              'Xv'], //                               Enter vertical high‐ light mode
  'set_a_attributes':                                      ['sgr1',                               'sA'], //                               Define second set of video attributes #1-#6
  'set_pglen_inch':                                        ['slength',                            'sL']  //                               YI Set page length to #1 hundredth of an inch
};
};
BundleModuleCode['term/colors']=function (module,exports){
/**
 * colors.js - color-related functions for blessed.
 * Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License).
 * https://github.com/chjj/blessed
 */

exports.match = function(r1, g1, b1) {
  if (typeof r1 === 'string') {
    var hex = r1;
    if (hex[0] !== '#') {
      return -1;
    }
    hex = exports.hexToRGB(hex);
    r1 = hex[0], g1 = hex[1], b1 = hex[2];
  } else if (Array.isArray(r1)) {
    b1 = r1[2], g1 = r1[1], r1 = r1[0];
  }

  var hash = (r1 << 16) | (g1 << 8) | b1;

  if (exports._cache[hash] != null) {
    return exports._cache[hash];
  }

  var ldiff = Infinity
    , li = -1
    , i = 0
    , c
    , r2
    , g2
    , b2
    , diff;

  for (; i < exports.vcolors.length; i++) {
    c = exports.vcolors[i];
    r2 = c[0];
    g2 = c[1];
    b2 = c[2];

    diff = colorDistance(r1, g1, b1, r2, g2, b2);

    if (diff === 0) {
      li = i;
      break;
    }

    if (diff < ldiff) {
      ldiff = diff;
      li = i;
    }
  }

  return exports._cache[hash] = li;
};

exports.RGBToHex = function(r, g, b) {
  if (Array.isArray(r)) {
    b = r[2], g = r[1], r = r[0];
  }

  function hex(n) {
    n = n.toString(16);
    if (n.length < 2) n = '0' + n;
    return n;
  }

  return '#' + hex(r) + hex(g) + hex(b);
};

exports.hexToRGB = function(hex) {
  if (hex.length === 4) {
    hex = hex[0]
      + hex[1] + hex[1]
      + hex[2] + hex[2]
      + hex[3] + hex[3];
  }

  var col = parseInt(hex.substring(1), 16)
    , r = (col >> 16) & 0xff
    , g = (col >> 8) & 0xff
    , b = col & 0xff;

  return [r, g, b];
};

// As it happens, comparing how similar two colors are is really hard. Here is
// one of the simplest solutions, which doesn't require conversion to another
// color space, posted on stackoverflow[1]. Maybe someone better at math can
// propose a superior solution.
// [1] http://stackoverflow.com/questions/1633828

function colorDistance(r1, g1, b1, r2, g2, b2) {
  return Math.pow(30 * (r1 - r2), 2)
    + Math.pow(59 * (g1 - g2), 2)
    + Math.pow(11 * (b1 - b2), 2);
}

// This might work well enough for a terminal's colors: treat RGB as XYZ in a
// 3-dimensional space and go midway between the two points.
exports.mixColors = function(c1, c2, alpha) {
  // if (c1 === 0x1ff) return c1;
  // if (c2 === 0x1ff) return c1;
  if (c1 === 0x1ff) c1 = 0;
  if (c2 === 0x1ff) c2 = 0;
  if (alpha == null) alpha = 0.5;

  c1 = exports.vcolors[c1];
  var r1 = c1[0];
  var g1 = c1[1];
  var b1 = c1[2];

  c2 = exports.vcolors[c2];
  var r2 = c2[0];
  var g2 = c2[1];
  var b2 = c2[2];

  r1 += (r2 - r1) * alpha | 0;
  g1 += (g2 - g1) * alpha | 0;
  b1 += (b2 - b1) * alpha | 0;

  return exports.match([r1, g1, b1]);
};

exports.blend = function blend(attr, attr2, alpha) {
  var name, i, c, nc;

  var bg = attr & 0x1ff;
  if (attr2 != null) {
    var bg2 = attr2 & 0x1ff;
    if (bg === 0x1ff) bg = 0;
    if (bg2 === 0x1ff) bg2 = 0;
    bg = exports.mixColors(bg, bg2, alpha);
  } else {
    if (blend._cache[bg] != null) {
      bg = blend._cache[bg];
    // } else if (bg < 8) {
    //   bg += 8;
    } else if (bg >= 8 && bg <= 15) {
      bg -= 8;
    } else {
      name = exports.ncolors[bg];
      if (name) {
        for (i = 0; i < exports.ncolors.length; i++) {
          if (name === exports.ncolors[i] && i !== bg) {
            c = exports.vcolors[bg];
            nc = exports.vcolors[i];
            if (nc[0] + nc[1] + nc[2] < c[0] + c[1] + c[2]) {
              blend._cache[bg] = i;
              bg = i;
              break;
            }
          }
        }
      }
    }
  }

  attr &= ~0x1ff;
  attr |= bg;

  var fg = (attr >> 9) & 0x1ff;
  if (attr2 != null) {
    var fg2 = (attr2 >> 9) & 0x1ff;
    // 0, 7, 188, 231, 251
    if (fg === 0x1ff) {
      // XXX workaround
      fg = 248;
    } else {
      if (fg === 0x1ff) fg = 7;
      if (fg2 === 0x1ff) fg2 = 7;
      fg = exports.mixColors(fg, fg2, alpha);
    }
  } else {
    if (blend._cache[fg] != null) {
      fg = blend._cache[fg];
    // } else if (fg < 8) {
    //   fg += 8;
    } else if (fg >= 8 && fg <= 15) {
      fg -= 8;
    } else {
      name = exports.ncolors[fg];
      if (name) {
        for (i = 0; i < exports.ncolors.length; i++) {
          if (name === exports.ncolors[i] && i !== fg) {
            c = exports.vcolors[fg];
            nc = exports.vcolors[i];
            if (nc[0] + nc[1] + nc[2] < c[0] + c[1] + c[2]) {
              blend._cache[fg] = i;
              fg = i;
              break;
            }
          }
        }
      }
    }
  }

  attr &= ~(0x1ff << 9);
  attr |= fg << 9;

  return attr;
};

exports.blend._cache = {};

exports._cache = {};

exports.reduce = function(color, total) {
  if (color >= 16 && total <= 16) {
    color = exports.ccolors[color];
  } else if (color >= 8 && total <= 8) {
    color -= 8;
  } else if (color >= 2 && total <= 2) {
    color %= 2;
  }
  return color;
};

// XTerm Colors
// These were actually tough to track down. The xterm source only uses color
// keywords. The X11 source needed to be examined to find the actual values.
// They then had to be mapped to rgb values and then converted to hex values.
exports.xterm = [
  '#000000', // black
  '#cd0000', // red3
  '#00cd00', // green3
  '#cdcd00', // yellow3
  '#0000ee', // blue2
  '#cd00cd', // magenta3
  '#00cdcd', // cyan3
  '#e5e5e5', // gray90
  '#7f7f7f', // gray50
  '#ff0000', // red
  '#00ff00', // green
  '#ffff00', // yellow
  '#5c5cff', // rgb:5c/5c/ff
  '#ff00ff', // magenta
  '#00ffff', // cyan
  '#ffffff'  // white
];

// Seed all 256 colors. Assume xterm defaults.
// Ported from the xterm color generation script.
exports.colors = (function() {
  var cols = exports.colors = []
    , _cols = exports.vcolors = []
    , r
    , g
    , b
    , i
    , l;

  function hex(n) {
    n = n.toString(16);
    if (n.length < 2) n = '0' + n;
    return n;
  }

  function push(i, r, g, b) {
    cols[i] = '#' + hex(r) + hex(g) + hex(b);
    _cols[i] = [r, g, b];
  }

  // 0 - 15
  exports.xterm.forEach(function(c, i) {
    c = parseInt(c.substring(1), 16);
    push(i, (c >> 16) & 0xff, (c >> 8) & 0xff, c & 0xff);
  });

  // 16 - 231
  for (r = 0; r < 6; r++) {
    for (g = 0; g < 6; g++) {
      for (b = 0; b < 6; b++) {
        i = 16 + (r * 36) + (g * 6) + b;
        push(i,
          r ? (r * 40 + 55) : 0,
          g ? (g * 40 + 55) : 0,
          b ? (b * 40 + 55) : 0);
      }
    }
  }

  // 232 - 255 are grey.
  for (g = 0; g < 24; g++) {
    l = (g * 10) + 8;
    i = 232 + g;
    push(i, l, l, l);
  }

  return cols;
})();

// Map higher colors to the first 8 colors.
// This allows translation of high colors to low colors on 8-color terminals.
exports.ccolors = (function() {
  var _cols = exports.vcolors.slice()
    , cols = exports.colors.slice()
    , out;

  exports.vcolors = exports.vcolors.slice(0, 8);
  exports.colors = exports.colors.slice(0, 8);

  out = cols.map(exports.match);

  exports.colors = cols;
  exports.vcolors = _cols;
  exports.ccolors = out;

  return out;
})();

var colorNames = exports.colorNames = {
  // special
  default: -1,
  normal: -1,
  bg: -1,
  fg: -1,
  // normal
  black: 0,
  red: 1,
  green: 2,
  yellow: 3,
  blue: 4,
  magenta: 5,
  cyan: 6,
  white: 7,
  // light
  lightblack: 8,
  lightred: 9,
  lightgreen: 10,
  lightyellow: 11,
  lightblue: 12,
  lightmagenta: 13,
  lightcyan: 14,
  lightwhite: 15,
  // bright
  brightblack: 8,
  brightred: 9,
  brightgreen: 10,
  brightyellow: 11,
  brightblue: 12,
  brightmagenta: 13,
  brightcyan: 14,
  brightwhite: 15,
  // alternate spellings
  grey: 8,
  gray: 8,
  lightgrey: 7,
  lightgray: 7,
  brightgrey: 7,
  brightgray: 7
};

exports.convert = function(color) {
  if (typeof color === 'number') {
    ;
  } else if (typeof color === 'string') {
    color = color.replace(/[\- ]/g, '');
    if (colorNames[color] != null) {
      color = colorNames[color];
    } else {
      color = exports.match(color);
    }
  } else if (Array.isArray(color)) {
    color = exports.match(color);
  } else {
    color = -1;
  }
  return color !== -1 ? color : 0x1ff;
};

// Map higher colors to the first 8 colors.
// This allows translation of high colors to low colors on 8-color terminals.
// Why the hell did I do this by hand?
exports.ccolors = {
  blue: [
    4,
    12,
    [17, 21],
    [24, 27],
    [31, 33],
    [38, 39],
    45,
    [54, 57],
    [60, 63],
    [67, 69],
    [74, 75],
    81,
    [91, 93],
    [97, 99],
    [103, 105],
    [110, 111],
    117,
    [128, 129],
    [134, 135],
    [140, 141],
    [146, 147],
    153,
    165,
    171,
    177,
    183,
    189
  ],

  green: [
    2,
    10,
    22,
    [28, 29],
    [34, 36],
    [40, 43],
    [46, 50],
    [64, 65],
    [70, 72],
    [76, 79],
    [82, 86],
    [106, 108],
    [112, 115],
    [118, 122],
    [148, 151],
    [154, 158],
    [190, 194]
  ],

  cyan: [
    6,
    14,
    23,
    30,
    37,
    44,
    51,
    66,
    73,
    80,
    87,
    109,
    116,
    123,
    152,
    159,
    195
  ],

  red: [
    1,
    9,
    52,
    [88, 89],
    [94, 95],
    [124, 126],
    [130, 132],
    [136, 138],
    [160, 163],
    [166, 169],
    [172, 175],
    [178, 181],
    [196, 200],
    [202, 206],
    [208, 212],
    [214, 218],
    [220, 224]
  ],

  magenta: [
    5,
    13,
    53,
    90,
    96,
    127,
    133,
    139,
    164,
    170,
    176,
    182,
    201,
    207,
    213,
    219,
    225
  ],

  yellow: [
    3,
    11,
    58,
    [100, 101],
    [142, 144],
    [184, 187],
    [226, 230]
  ],

  black: [
    0,
    8,
    16,
    59,
    102,
    [232, 243]
  ],

  white: [
    7,
    15,
    145,
    188,
    231,
    [244, 255]
  ]
};

exports.ncolors = [];

Object.keys(exports.ccolors).forEach(function(name) {
  exports.ccolors[name].forEach(function(offset) {
    if (typeof offset === 'number') {
      exports.ncolors[offset] = name;
      exports.ccolors[offset] = exports.colorNames[name];
      return;
    }
    for (var i = offset[0], l = offset[1]; i <= l; i++) {
      exports.ncolors[i] = name;
      exports.ccolors[i] = exports.colorNames[name];
    }
  });
  delete exports.ccolors[name];
});
};
BundleModuleCode['term/keys']=function (module,exports){
/**
 * keys.js - emit key presses
 * Copyright (c) 2010-2015, Joyent, Inc. and other contributors (MIT License)
 * https://github.com/chjj/blessed
 */

// Originally taken from the node.js tree:
//
// Copyright Joyent, Inc. and other Node contributors. All rights reserved.
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.

var EventEmitter = Require('events').EventEmitter;

// NOTE: node <=v0.8.x has no EventEmitter.listenerCount
function listenerCount(stream, event) {
  return EventEmitter.listenerCount
    ? EventEmitter.listenerCount(stream, event)
    : stream.listeners(event).length;
}

/**
 * accepts a readable Stream instance and makes it emit "keypress" events
 */

function emitKeypressEvents(stream) {
  if (stream._keypressDecoder) return;
  var StringDecoder = Require('string_decoder').StringDecoder; // lazy load
  stream._keypressDecoder = new StringDecoder('utf8');

  function onData(b) {
    if (listenerCount(stream, 'keypress') > 0) {
      var r = stream._keypressDecoder.write(b);
      if (r) emitKeys(stream, r);
    } else {
      // Nobody's watching anyway
      stream.removeListener('data', onData);
      stream.on('newListener', onNewListener);
    }
  }

  function onNewListener(event) {
    if (event === 'keypress') {
      stream.on('data', onData);
      stream.removeListener('newListener', onNewListener);
    }
  }

  if (listenerCount(stream, 'keypress') > 0) {
    stream.on('data', onData);
  } else {
    stream.on('newListener', onNewListener);
  }
}
exports.emitKeypressEvents = emitKeypressEvents;

/*
  Some patterns seen in terminal key escape codes, derived from combos seen
  at http://www.midnight-commander.org/browser/lib/tty/key.c

  ESC letter
  ESC [ letter
  ESC [ modifier letter
  ESC [ 1 ; modifier letter
  ESC [ num char
  ESC [ num ; modifier char
  ESC O letter
  ESC O modifier letter
  ESC O 1 ; modifier letter
  ESC N letter
  ESC [ [ num ; modifier char
  ESC [ [ 1 ; modifier letter
  ESC ESC [ num char
  ESC ESC O letter

  - char is usually ~ but $ and ^ also happen with rxvt
  - modifier is 1 +
                (shift     * 1) +
                (left_alt  * 2) +
                (ctrl      * 4) +
                (right_alt * 8)
  - two leading ESCs apparently mean the same as one leading ESC
*/

// Regexes used for ansi escape code splitting
var metaKeyCodeReAnywhere = /(?:\x1b)([a-zA-Z0-9])/;
var metaKeyCodeRe = new RegExp('^' + metaKeyCodeReAnywhere.source + '$');
var functionKeyCodeReAnywhere = new RegExp('(?:\x1b+)(O|N|\\[|\\[\\[)(?:' + [
  '(\\d+)(?:;(\\d+))?([~^$])',
  '(?:M([@ #!a`])(.)(.))', // mouse
  '(?:1;)?(\\d+)?([a-zA-Z])'
].join('|') + ')');
var functionKeyCodeRe = new RegExp('^' + functionKeyCodeReAnywhere.source);
var escapeCodeReAnywhere = new RegExp([
  functionKeyCodeReAnywhere.source, metaKeyCodeReAnywhere.source, /\x1b./.source
].join('|'));

function emitKeys(stream, s) {
  if (Buffer.isBuffer(s)) {
    if (s[0] > 127 && s[1] === undefined) {
      s[0] -= 128;
      s = '\x1b' + s.toString(stream.encoding || 'utf-8');
    } else {
      s = s.toString(stream.encoding || 'utf-8');
    }
  }

  if (isMouse(s)) return;

  var buffer = [];
  var match;
  while (match = escapeCodeReAnywhere.exec(s)) {
    buffer = buffer.concat(s.slice(0, match.index).split(''));
    buffer.push(match[0]);
    s = s.slice(match.index + match[0].length);
  }
  buffer = buffer.concat(s.split(''));

  buffer.forEach(function(s) {
    var ch,
        key = {
          sequence: s,
          name: undefined,
          ctrl: false,
          meta: false,
          shift: false
        },
        parts;

    if (s === '\r') {
      // carriage return
      key.name = 'return';

    } else if (s === '\n') {
      // enter, should have been called linefeed
      key.name = 'enter';
      // linefeed
      // key.name = 'linefeed';

    } else if (s === '\t') {
      // tab
      key.name = 'tab';

    } else if (s === '\b' || s === '\x7f' ||
               s === '\x1b\x7f' || s === '\x1b\b') {
      // backspace or ctrl+h
      key.name = 'backspace';
      key.meta = (s.charAt(0) === '\x1b');

    } else if (s === '\x1b' || s === '\x1b\x1b') {
      // escape key
      key.name = 'escape';
      key.meta = (s.length === 2);

    } else if (s === ' ' || s === '\x1b ') {
      key.name = 'space';
      key.meta = (s.length === 2);

    } else if (s.length === 1 && s <= '\x1a') {
      // ctrl+letter
      key.name = String.fromCharCode(s.charCodeAt(0) + 'a'.charCodeAt(0) - 1);
      key.ctrl = true;

    } else if (s.length === 1 && s >= 'a' && s <= 'z') {
      // lowercase letter
      key.name = s;

    } else if (s.length === 1 && s >= 'A' && s <= 'Z') {
      // shift+letter
      key.name = s.toLowerCase();
      key.shift = true;

    } else if (parts = metaKeyCodeRe.exec(s)) {
      // meta+character key
      key.name = parts[1].toLowerCase();
      key.meta = true;
      key.shift = /^[A-Z]$/.test(parts[1]);

    } else if (parts = functionKeyCodeRe.exec(s)) {
      // ansi escape sequence

      // reassemble the key code leaving out leading \x1b's,
      // the modifier key bitflag and any meaningless "1;" sequence
      var code = (parts[1] || '') + (parts[2] || '') +
                 (parts[4] || '') + (parts[9] || ''),
          modifier = (parts[3] || parts[8] || 1) - 1;

      // Parse the key modifier
      key.ctrl = !!(modifier & 4);
      key.meta = !!(modifier & 10);
      key.shift = !!(modifier & 1);
      key.code = code;

      // Parse the key itself
      switch (code) {
        /* xterm/gnome ESC O letter */
        case 'OP': key.name = 'f1'; break;
        case 'OQ': key.name = 'f2'; break;
        case 'OR': key.name = 'f3'; break;
        case 'OS': key.name = 'f4'; break;

        /* xterm/rxvt ESC [ number ~ */
        case '[11~': key.name = 'f1'; break;
        case '[12~': key.name = 'f2'; break;
        case '[13~': key.name = 'f3'; break;
        case '[14~': key.name = 'f4'; break;

        /* from Cygwin and used in libuv */
        case '[[A': key.name = 'f1'; break;
        case '[[B': key.name = 'f2'; break;
        case '[[C': key.name = 'f3'; break;
        case '[[D': key.name = 'f4'; break;
        case '[[E': key.name = 'f5'; break;

        /* common */
        case '[15~': key.name = 'f5'; break;
        case '[17~': key.name = 'f6'; break;
        case '[18~': key.name = 'f7'; break;
        case '[19~': key.name = 'f8'; break;
        case '[20~': key.name = 'f9'; break;
        case '[21~': key.name = 'f10'; break;
        case '[23~': key.name = 'f11'; break;
        case '[24~': key.name = 'f12'; break;

        /* xterm ESC [ letter */
        case '[A': key.name = 'up'; break;
        case '[B': key.name = 'down'; break;
        case '[C': key.name = 'right'; break;
        case '[D': key.name = 'left'; break;
        case '[E': key.name = 'clear'; break;
        case '[F': key.name = 'end'; break;
        case '[H': key.name = 'home'; break;

        /* xterm/gnome ESC O letter */
        case 'OA': key.name = 'up'; break;
        case 'OB': key.name = 'down'; break;
        case 'OC': key.name = 'right'; break;
        case 'OD': key.name = 'left'; break;
        case 'OE': key.name = 'clear'; break;
        case 'OF': key.name = 'end'; break;
        case 'OH': key.name = 'home'; break;

        /* xterm/rxvt ESC [ number ~ */
        case '[1~': key.name = 'home'; break;
        case '[2~': key.name = 'insert'; break;
        case '[3~': key.name = 'delete'; break;
        case '[4~': key.name = 'end'; break;
        case '[5~': key.name = 'pageup'; break;
        case '[6~': key.name = 'pagedown'; break;

        /* putty */
        case '[[5~': key.name = 'pageup'; break;
        case '[[6~': key.name = 'pagedown'; break;

        /* rxvt */
        case '[7~': key.name = 'home'; break;
        case '[8~': key.name = 'end'; break;

        /* rxvt keys with modifiers */
        case '[a': key.name = 'up'; key.shift = true; break;
        case '[b': key.name = 'down'; key.shift = true; break;
        case '[c': key.name = 'right'; key.shift = true; break;
        case '[d': key.name = 'left'; key.shift = true; break;
        case '[e': key.name = 'clear'; key.shift = true; break;

        case '[2$': key.name = 'insert'; key.shift = true; break;
        case '[3$': key.name = 'delete'; key.shift = true; break;
        case '[5$': key.name = 'pageup'; key.shift = true; break;
        case '[6$': key.name = 'pagedown'; key.shift = true; break;
        case '[7$': key.name = 'home'; key.shift = true; break;
        case '[8$': key.name = 'end'; key.shift = true; break;

        case 'Oa': key.name = 'up'; key.ctrl = true; break;
        case 'Ob': key.name = 'down'; key.ctrl = true; break;
        case 'Oc': key.name = 'right'; key.ctrl = true; break;
        case 'Od': key.name = 'left'; key.ctrl = true; break;
        case 'Oe': key.name = 'clear'; key.ctrl = true; break;

        case '[2^': key.name = 'insert'; key.ctrl = true; break;
        case '[3^': key.name = 'delete'; key.ctrl = true; break;
        case '[5^': key.name = 'pageup'; key.ctrl = true; break;
        case '[6^': key.name = 'pagedown'; key.ctrl = true; break;
        case '[7^': key.name = 'home'; key.ctrl = true; break;
        case '[8^': key.name = 'end'; key.ctrl = true; break;

        /* misc. */
        case '[Z': key.name = 'tab'; key.shift = true; break;
        default: key.name = 'undefined'; break;

      }
    }

    // Don't emit a key if no name was found
    if (key.name === undefined) {
      key = undefined;
    }

    if (s.length === 1) {
      ch = s;
    }

    if (key || ch) {
      stream.emit('keypress', ch, key);
      // if (key && key.name === 'return') {
      //   var nkey = {};
      //   Object.keys(key).forEach(function(k) {
      //     nkey[k] = key[k];
      //   });
      //   nkey.name = 'enter';
      //   stream.emit('keypress', ch, nkey);
      // }
    }
  });
}

function isMouse(s) {
  return /\x1b\[M/.test(s)
    || /\x1b\[M([\x00\u0020-\ufffe]{3})/.test(s)
    || /\x1b\[(\d+;\d+;\d+)M/.test(s)
    || /\x1b\[<(\d+;\d+;\d+)([mM])/.test(s)
    || /\x1b\[<(\d+;\d+;\d+;\d+)&w/.test(s)
    || /\x1b\[24([0135])~\[(\d+),(\d+)\]\r/.test(s)
    || /\x1b\[(O|I)/.test(s);
}
};
BundleModuleCode['term/widget']=function (module,exports){
/**
 * widget.js - high-level interface for blessed
 * Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License).
 * https://github.com/chjj/blessed
 */

var widget = exports;

widget.classes = [
  'Node',
  'Screen',
  'Element',
  'Box',
  'Chat',
  'Text',
  'Line',
  'ScrollableBox',
  'ScrollableText',
  'BigText',
  'List',
  'Form',
  'Input',
  'Textarea',
  'Textbox',
  'Button',
  'ProgressBar',
  'FileManager',
  'Checkbox',
  'RadioSet',
  'RadioButton',
  'Prompt',
  'Question',
  'Message',
  'Keyboard',
  'Loading',
  'Listbar',
  'Log',
  'Table',
  'ListTable',
  'Terminal',
  'Image',
  'ANSIImage',
  'OverlayImage',
  'Video',
  'Layout',
  'Log',
  'Tree'
];

widget.classes.forEach(function(name) {
  var file = name.toLowerCase();
  widget[name] = widget[file] = Require('term/widgets/' + file);
});

widget.aliases = {
  'ListBar': 'Listbar',
  'PNG': 'ANSIImage'
};

Object.keys(widget.aliases).forEach(function(key) {
  var name = widget.aliases[key];
  widget[key] = widget[name];
  widget[key.toLowerCase()] = widget[name];
});
};
BundleModuleCode['term/widgets/node']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Christopher Jeffrey and contributors, Stefan Bosse
 **    $INITIAL:     (C) 2013-2015, Christopher Jeffrey and contributors
 **    $MODIFIED:    sbosse (2017-2021).
 **    $VERSION:     1.2.1
 **
 **    $INFO:
 *
 * node.js - base abstract node for blessed
 *
 **    $ENDOFINFO
 */

/**
 * Modules
 */
var Comp = Require('com/compat');

var EventEmitter = Require('term/events').EventEmitter;

/**
 * Node
 */

function Node(options) {
  var self = this;
  var Screen = Require('term/widgets/screen');

  if (!instanceOf(this,Node)) {
    return new Node(options);
  }

  EventEmitter.call(this);

  options = options || {};
  this.options = options;

  this.screen = this.screen || options.screen;

  if (!this.screen) {
    if (this.type === 'screen') {
      this.screen = this;
    } else if (Screen.total === 1) {
      this.screen = Screen.global;
    } else if (options.parent) {
      this.screen = options.parent;
      while (this.screen && this.screen.type !== 'screen') {
        this.screen = this.screen.parent;
      }
    } else if (Screen.total) {
      // This _should_ work in most cases as long as the element is appended
      // synchronously after the screen's creation. Throw error if not.
      this.screen = Screen.instances[Screen.instances.length - 1];
      process.nextTick(function() {
        if (!self.parent) {
          throw new Error('Element (' + self.type + ')'
            + ' was not appended synchronously after the'
            + ' screen\'s creation. Please set a `parent`'
            + ' or `screen` option in the element\'s constructor'
            + ' if you are going to use multiple screens and'
            + ' append the element later.');
        }
      });
    } else {
      throw new Error('No active screen.');
    }
  }

  this.parent = options.parent || null;
  this.children = [];
  this.$ = this._ = this.data = {};
  this.uid = Node.uid++;
  this.index = this.index != null ? this.index : -1;

  if (this.type !== 'screen') {
    this.detached = true;
  }

  if (this.parent) {
    this.parent.append(this);
  }

  (options.children || []).forEach(this.append.bind(this));
}

Node.uid = 0;

//Node.prototype.__proto__ = EventEmitter.prototype;
inheritPrototype(Node,EventEmitter);

Node.prototype.type = 'node';

Node.prototype.insert = function(element, i) {
  var self = this;

  if (element.screen && element.screen !== this.screen) {
    throw new Error('Cannot switch a node\'s screen.');
  }

  element.detach();
  element.parent = this;
  element.screen = this.screen;

  if (i === 0) {
    this.children.unshift(element);
  } else if (i === this.children.length) {
    this.children.push(element);
  } else {
    this.children.splice(i, 0, element);
  }

  element.emit('reparent', this);
  this.emit('adopt', element);

  (function emit(el) {
    var n = el.detached !== self.detached;
    el.detached = self.detached;
    if (n) el.emit('attach');
    el.children.forEach(emit);
  })(element);

  if (!this.screen.focused) {
    this.screen.focused = element;
  }
};

Node.prototype.prepend = function(element) {
  this.insert(element, 0);
};

Node.prototype.append = function(element) {
  this.insert(element, this.children.length);
};

Node.prototype.insertBefore = function(element, other) {
  var i = this.children.indexOf(other);
  if (~i) this.insert(element, i);
};

Node.prototype.insertAfter = function(element, other) {
  var i = this.children.indexOf(other);
  if (~i) this.insert(element, i + 1);
};

Node.prototype.remove = function(element) {
  if (element.parent !== this) return;

  var i = this.children.indexOf(element);
  if (!~i) return;

  element.clearPos();

  element.parent = null;

  this.children.splice(i, 1);

  i = this.screen.clickable.indexOf(element);
  if (~i) this.screen.clickable.splice(i, 1);
  i = this.screen.keyable.indexOf(element);
  if (~i) this.screen.keyable.splice(i, 1);

  element.emit('reparent', null);
  this.emit('remove', element);

  (function emit(el) {
    var n = el.detached !== true;
    el.detached = true;
    if (n) el.emit('detach');
    el.children.forEach(emit);
  })(element);

  if (this.screen.focused === element) {
    this.screen.rewindFocus();
  }
};

Node.prototype.detach = function() {
  if (this.parent) this.parent.remove(this);
};

Node.prototype.free = function() {
  return;
};

Node.prototype.destroy = function() {
  this.detach();
  this.forDescendants(function(el) {
    el.free();
    el.destroyed = true;
    el.emit('destroy');
  }, this);
};

Node.prototype.forDescendants = function(iter, s) {
  if (s) iter(this);
  this.children.forEach(function emit(el) {
    iter(el);
    el.children.forEach(emit);
  });
};

Node.prototype.forAncestors = function(iter, s) {
  var el = this;
  if (s) iter(this);
  while (el = el.parent) {
    iter(el);
  }
};

Node.prototype.collectDescendants = function(s) {
  var out = [];
  this.forDescendants(function(el) {
    out.push(el);
  }, s);
  return out;
};

Node.prototype.collectAncestors = function(s) {
  var out = [];
  this.forAncestors(function(el) {
    out.push(el);
  }, s);
  return out;
};

Node.prototype.emitDescendants = function() {
  var args = Array.prototype.slice(arguments)
    , iter;

  if (typeof args[args.length - 1] === 'function') {
    iter = args.pop();
  }

  return this.forDescendants(function(el) {
    if (iter) iter(el);
    el.emit.apply(el, args);
  }, true);
};

Node.prototype.emitAncestors = function() {
  var args = Array.prototype.slice(arguments)
    , iter;

  if (typeof args[args.length - 1] === 'function') {
    iter = args.pop();
  }

  return this.forAncestors(function(el) {
    if (iter) iter(el);
    el.emit.apply(el, args);
  }, true);
};

Node.prototype.hasDescendant = function(target) {
  return (function find(el) {
    for (var i = 0; i < el.children.length; i++) {
      if (el.children[i] === target) {
        return true;
      }
      if (find(el.children[i]) === true) {
        return true;
      }
    }
    return false;
  })(this);
};

Node.prototype.hasAncestor = function(target) {
  var el = this;
  while (el = el.parent) {
    if (el === target) return true;
  }
  return false;
};

Node.prototype.get = function(name, value) {
  if (this.data.hasOwnProperty(name)) {
    return this.data[name];
  }
  return value;
};

Node.prototype.set = function(name, value) {
  return this.data[name] = value;
};

/**
 * Expose
 */

module.exports = Node;
};
BundleModuleCode['term/events']=function (module,exports){
/**
 * Version 1.1.2
 * events.js - event emitter for blessed
 * Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License).
 * https://github.com/chjj/blessed
 */

var slice = Array.prototype.slice;

/**
 * EventEmitter
 */

function EventEmitter() {
  if (!this._events) this._events = {};
}

EventEmitter.prototype.setMaxListeners = function(n) {
  this._maxListeners = n;
};

EventEmitter.prototype.addListener = function(type, listener) {
  if (!this._events[type]) {
    this._events[type] = listener;
  } else if (typeof this._events[type] === 'function') {
    this._events[type] = [this._events[type], listener];
  } else {
    this._events[type].push(listener);
  }
  this._emit('newListener', [type, listener]);
};

EventEmitter.prototype.on = EventEmitter.prototype.addListener;

EventEmitter.prototype.removeListener = function(type, listener) {
  var handler = this._events[type];
  if (!handler) return;

  if (typeof handler === 'function' ) {
    if (!(handler === listener)) return;
    delete this._events[type];
    this._emit('removeListener', [type, listener]);
    return;
  }
  if (handler.length === 1) {
    if (!(handler[0] === listener)) return;    
    delete this._events[type];
    this._emit('removeListener', [type, listener]);
    return;
  }

  for (var i = 0; i < handler.length; i++) {
    if (handler[i] === listener || handler[i].listener === listener) {
      handler.splice(i, 1);
      this._emit('removeListener', [type, listener]);
      return;
    }
  }
};

EventEmitter.prototype.off = EventEmitter.prototype.removeListener;

EventEmitter.prototype.removeAllListeners = function(type) {
  if (type) {
    delete this._events[type];
  } else {
    this._events = {};
  }
};

EventEmitter.prototype.once = function(type, listener) {
  function on() {
    this.removeListener(type, on);
    return listener.apply(this, arguments);
  }
  on.listener = listener;
  return this.on(type, on);
};

EventEmitter.prototype.listeners = function(type) {
  return typeof this._events[type] === 'function'
    ? [this._events[type]]
    : this._events[type] || [];
};

EventEmitter.prototype._emit = function(type, args) {
  var handler = this._events[type]
    , ret;

  // if (type !== 'event') {
  //   this._emit('event', [type.replace(/^element /, '')].concat(args));
  // }

  if (!handler) {
    if (type === 'error') {
      throw new args[0];
    }
    return;
  }

  if (typeof handler === 'function') {
    return handler.apply(this, args);
  }

  for (var i = 0; i < handler.length; i++) {
    if (handler[i].apply(this, args) === false) {
      ret = false;
    }
  }

  return ret !== false;
};

EventEmitter.prototype.emit = function(type) {
  var args = slice.call(arguments, 1)
    , params = slice.call(arguments)
    , el = this;

  this._emit('event', params);

  if (this.type === 'screen') {
    return this._emit(type, args);
  }

  if (this._emit(type, args) === false) {
    return false;
  }

  type = 'element ' + type;
  args.unshift(this);
  // `element` prefix
  // params = [type].concat(args);
  // no `element` prefix
  // params.splice(1, 0, this);

  do {
    // el._emit('event', params);
    if (!el._events[type]) continue;
    if (el._emit(type, args) === false) {
      return false;
    }
  } while (el = el.parent);

  return true;
};

// For hooking into the main EventEmitter if we want to.
// Might be better to do things this way being that it
// will always be compatible with node, not to mention
// it gives us domain support as well.
// Node.prototype._emit = Node.prototype.emit;
// Node.prototype.emit = function(type) {
//   var args, el;
//
//   if (this.type === 'screen') {
//     return this._emit.apply(this, arguments);
//   }
//
//   this._emit.apply(this, arguments);
//   if (this._bubbleStopped) return false;
//
//   args = slice.call(arguments, 1);
//   el = this;
//
//   args.unshift('element ' + type, this);
//   this._bubbleStopped = false;
//   //args.push(stopBubble);
//
//   do {
//     if (!el._events || !el._events[type]) continue;
//     el._emit.apply(el, args);
//     if (this._bubbleStopped) return false;
//   } while (el = el.parent);
//
//   return true;
// };
//
// Node.prototype._addListener = Node.prototype.addListener;
// Node.prototype.on =
// Node.prototype.addListener = function(type, listener) {
//   function on() {
//     if (listener.apply(this, arguments) === false) {
//       this._bubbleStopped = true;
//     }
//   }
//   on.listener = listener;
//   return this._addListener(type, on);
// };

/**
 * Expose
 */

exports = EventEmitter;
exports.EventEmitter = EventEmitter;

module.exports = exports;
};
BundleModuleCode['term/widgets/screen']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Christopher Jeffrey and contributors, Stefan Bosse
 **    $INITIAL:     (C) 2013-2015, Christopher Jeffrey and contributors
 **    $MODIFIED:    by sbosse (2016-2017)
 **    $REVESIO:     1.1.6
 **
 **    $INFO:
 **
 **    screen.js - screen node for blessed
 **
 **     Added:
 **       - Round-Robin focus shift on tabulator key press event. Requires <box>.options.focus=true
 **         setting for selectable boxes.
 **
 **    $ENDOFINFO
 */

/**
 * Modules
 */
var Comp = Require('com/compat');

var path = Require('path')
  , fs = Require('fs')
  , cp = Require('child_process');

var colors = Require('term/colors')
  , program = Require('term/program')
  , unicode = Require('term/unicode');

var nextTick = global.setImmediate || process.nextTick.bind(process);

var helpers = Require('term/helpers');

var Node = Require('term/widgets/node');
var Log = Require('term/widgets/log');
var Element = Require('term/widgets/element');
var Box = Require('term/widgets/box');

/**
 * Screen
 */

function Screen(options) {
  var self = this;

  if (!instanceOf(this,Node)) {
    return new Screen(options);
  }

  Screen.bind(this);

  options = options || {};
  if (options.rsety && options.listen) {
    options = { program: options };
  }

  this.program = options.program;
  if (!this.program) {
    this.program = program({
      input: options.input,
      output: options.output,
      log: options.log,
      debug: options.debug,
      dump: options.dump,
      terminal: options.terminal || options.term,
      resizeTimeout: options.resizeTimeout,
      forceUnicode: options.forceUnicode,
      tput: true,
      buffer: true,
      zero: true
    });
  } else {
    this.program.setupTput();
    this.program.useBuffer = true;
    this.program.zero = true;
    this.program.options.resizeTimeout = options.resizeTimeout;
    if (options.forceUnicode != null) {
      this.program.tput.features.unicode = options.forceUnicode;
      this.program.tput.unicode = options.forceUnicode;
    }
  }

  
  this.tput = this.program.tput;

  Node.call(this, options);

  this.autoPadding = options.autoPadding !== false;
  this.tabc = Array((options.tabSize || 4) + 1).join(' ');
  this.dockBorders = options.dockBorders;
  // use terminal reset code to hide cursor
  this.forceCursor = options.forceCursor;
  
  this.ignoreLocked = options.ignoreLocked || [];

  this._unicode = this.tput.unicode || this.tput.numbers.U8 === 1;
  this.fullUnicode = this.options.fullUnicode && this._unicode;

  this.dattr = ((0 << 18) | (0x1ff << 9)) | 0x1ff;

  this.renders = 0;
  this.position = {
    left: this.left = this.aleft = this.rleft = 0,
    right: this.right = this.aright = this.rright = 0,
    top: this.top = this.atop = this.rtop = 0,
    bottom: this.bottom = this.abottom = this.rbottom = 0,
    get height() { return self.height; },
    get width() { return self.width; }
  };

  this.ileft = 0;
  this.itop = 0;
  this.iright = 0;
  this.ibottom = 0;
  this.iheight = 0;
  this.iwidth = 0;

  this.padding = {
    left: 0,
    top: 0,
    right: 0,
    bottom: 0
  };

  this.hover = null;
  this.history = [];
  this.clickable = [];
  this.keyable = [];
  this.grabKeys = false;
  this.lockKeys = false;
  this.focused;
  this._buf = '';

  this._ci = -1;

  if (options.title) {
    this.title = options.title;
  }

  options.cursor = options.cursor || {
    artificial: options.artificialCursor,
    shape: options.cursorShape,
    blink: options.cursorBlink,
    color: options.cursorColor
  };

  this.cursor = {
    artificial: options.cursor.artificial || false,
    shape: options.cursor.shape || 'block',
    blink: options.cursor.blink || false,
    color: options.cursor.color || null,
    _set: false,
    _state: 1,
    _hidden: true
  };

  this.program.on('resize', function() {
    self.alloc();
    self.render();
    (function emit(el) {
      el.emit('resize');
      el.children.forEach(emit);
    })(self);
  });

  this.program.on('focus', function() {
    self.emit('focus');
  });

  this.program.on('blur', function() {
    self.emit('blur');
  });

  this.program.on('warning', function(text) {
    self.emit('warning', text);
  });

  this.on('newListener', function fn(type) {
    if (type === 'keypress' || type.indexOf('key ') === 0 || type === 'mouse') {
      if (type === 'keypress' || type.indexOf('key ') === 0) self._listenKeys();
      if (type === 'mouse') self._listenMouse();
    }
    if (type === 'mouse'
      || type === 'click'
      || type === 'mouseover'
      || type === 'mouseout'
      || type === 'mousedown'
      || type === 'mouseup'
      || type === 'mousewheel'
      || type === 'wheeldown'
      || type === 'wheelup'
      || type === 'mousemove') {
      self._listenMouse();
    }
  });

  this.setMaxListeners(Infinity);

  this.enter();

  this.postEnter();
}

Screen.global = null;

Screen.total = 0;

Screen.instances = [];

Screen.bind = function(screen) {
  if (!Screen.global) {
    Screen.global = screen;
  }

  if (!~Screen.instances.indexOf(screen)) {
    Screen.instances.push(screen);
    screen.index = Screen.total;
    Screen.total++;
  }

  if (Screen._bound) return;
  Screen._bound = true;

  process.on('uncaughtException', Screen._exceptionHandler = function(err) {
    if (process.listeners('uncaughtException').length > 1) {
      return;
    }
    Screen.instances.slice().forEach(function(screen) {
      screen.destroy();
    });
    err = err || new Error('Uncaught Exception.');
    console.error(err.stack ? err.stack + '' : err + '');
    nextTick(function() {
      process.exit(1);
    });
  });

  ['SIGTERM', 'SIGINT', 'SIGQUIT'].forEach(function(signal) {
    var name = '_' + signal.toLowerCase() + 'Handler';
    process.on(signal, Screen[name] = function() {
      if (process.listeners(signal).length > 1) {
        return;
      }
      nextTick(function() {
        process.exit(0);
      });
    });
  });

  process.on('exit', Screen._exitHandler = function() {
    Screen.instances.slice().forEach(function(screen) {
      screen.destroy();
    });
  });
};

//Screen.prototype.__proto__ = Node.prototype;
inheritPrototype(Screen,Node);

Screen.prototype.type = 'screen';
/* Depricated
Screen.prototype.__defineGetter__('title', function() {
  return this.program.title;
});

Screen.prototype.__defineSetter__('title', function(title) {
  return this.program.title = title;
});

Screen.prototype.__defineGetter__('terminal', function() {
  return this.program.terminal;
});

Screen.prototype.__defineSetter__('terminal', function(terminal) {
  this.setTerminal(terminal);
  return this.program.terminal;
});
*/
defineGetter(Screen,'title', function() {
  return this.program.title;
});
defineSetter(Screen,'title', function(title) {
  return this.program.title = title;
});

defineGetter(Screen,'terminal', function() {
  return this.program.terminal;
});
defineSetter(Screen,'terminal', function(terminal) {
  this.setTerminal(terminal);
  return this.program.terminal;
});
/*

Object.defineProperty(Screen.prototype,'title',{
  get: function () {
    return this.program.title;  
  },
  set: function (title) {
    return this.program.title = title;
  }
});
Object.defineProperty(Screen.prototype,'terminal',{
  get: function () {
    return this.program.terminal;  
  },
  set: function (terminal) {
    this.setTerminal(terminal);
    return this.program.terminal;
  }
});
*/
Screen.prototype.setTerminal = function(terminal) {
  var entered = !!this.program.isAlt;
  if (entered) {
    this._buf = '';
    this.program._buf = '';
    this.leave();
  }
  this.program.setTerminal(terminal);
  this.tput = this.program.tput;
  if (entered) {
    this.enter();
  }
};


Screen.prototype.enter = function() {
  if (this.program.isAlt) return;
  if (!this.cursor._set) {
    if (this.options.cursor.shape) {
      this.cursorShape(this.cursor.shape, this.cursor.blink);
    }
    if (this.options.cursor.color) {
      this.cursorColor(this.cursor.color);
    }
  }
  if (process.platform === 'win32') {
    try {
      cp.execSync('cls', { stdio: 'ignore', timeout: 1000 });
    } catch (e) {
      ;
    }
  }
  this.program.alternateBuffer();
  this.program.put.keypad_xmit();
  this.program.csr(0, this.height - 1);
  this.program.hideCursor();
  this.program.cup(0, 0);
  // We need this for tmux now:
  if (this.tput.strings.ena_acs) {
    this.program._write(this.tput.enacs());
  }
  this.alloc();
};

Screen.prototype.leave = function() {
  if (!this.program.isAlt) return;
  this.program.put.keypad_local();
  if (this.program.scrollTop !== 0
      || this.program.scrollBottom !== this.rows - 1) {
    this.program.csr(0, this.height - 1);
  }
  // XXX For some reason if alloc/clear() is before this
  // line, it doesn't work on linux console.
  this.program.showCursor();
  this.alloc();
  if (this._listenedMouse) {
    this.program.disableMouse();
  }
  this.program.normalBuffer();
  if (this.cursor._set) this.cursorReset();
  this.program.flush();
  if (process.platform === 'win32') {
    try {
      cp.execSync('cls', { stdio: 'ignore', timeout: 1000 });
    } catch (e) {
      ;
    }
  }
};

Screen.prototype.postEnter = function() {
  var self = this;
  if (this.options.debug) {
    this.debugLog = new Log({
      screen: this,
      parent: this,
      hidden: true,
      draggable: true,
      left: 'center',
      top: 'center',
      width: '30%',
      height: '30%',
      border: 'line',
      label: ' {bold}Debug Log{/bold} ',
      tags: true,
      keys: true,
      vi: true,
      mouse: true,
      scrollbar: {
        ch: ' ',
        track: {
          bg: 'yellow'
        },
        style: {
          inverse: true
        }
      }
    });

    this.debugLog.toggle = function() {
      if (self.debugLog.hidden) {
        self.saveFocus();
        self.debugLog.show();
        self.debugLog.setFront();
        self.debugLog.focus();
      } else {
        self.debugLog.hide();
        self.restoreFocus();
      }
      self.render();
    };

    this.debugLog.key(['q', 'escape'], self.debugLog.toggle);
    this.key('f12', self.debugLog.toggle);
  }

  if (this.options.warnings) {
    this.on('warning', function(text) {
      var warning = new Box({
        screen: self,
        parent: self,
        left: 'center',
        top: 'center',
        width: 'shrink',
        padding: 1,
        height: 'shrink',
        align: 'center',
        valign: 'middle',
        border: 'line',
        label: ' {red-fg}{bold}WARNING{/} ',
        content: '{bold}' + text + '{/bold}',
        tags: true
      });
      self.render();
      var timeout = setTimeout(function() {
        warning.destroy();
        self.render();
      }, 1500);
      if (timeout.unref) {
        timeout.unref();
      }
    });
  }
};

Screen.prototype._destroy = Screen.prototype.destroy;
Screen.prototype.destroy = function() {
  if (!this.program) return;
  this.leave();

  var index = Screen.instances.indexOf(this);
  if (~index) {
    Screen.instances.splice(index, 1);
    Screen.total--;

    Screen.global = Screen.instances[0];

    if (Screen.total === 0) {
      Screen.global = null;

      process.removeListener('uncaughtException', Screen._exceptionHandler);
      process.removeListener('SIGTERM', Screen._sigtermHandler);
      process.removeListener('SIGINT', Screen._sigintHandler);
      process.removeListener('SIGQUIT', Screen._sigquitHandler);
      process.removeListener('exit', Screen._exitHandler);
      delete Screen._exceptionHandler;
      delete Screen._sigtermHandler;
      delete Screen._sigintHandler;
      delete Screen._sigquitHandler;
      delete Screen._exitHandler;

      delete Screen._bound;
    }

    this.destroyed = true;
    this.emit('destroy');
    this._destroy();
  }

  this.program.destroy();
};

Screen.prototype.log = function() {
  return this.program.log.apply(this.program, arguments);
};

Screen.prototype.debug = function() {
  if (this.debugLog) {
    this.debugLog.log.apply(this.debugLog, arguments);
  }
  return this.program.debug.apply(this.program, arguments);
};

Screen.prototype._listenMouse = function(el) {
  var self = this;

  if (el && !~this.clickable.indexOf(el)) {
    el.clickable = true;
    this.clickable.push(el);
  }

  if (this._listenedMouse) return;
  this._listenedMouse = true;

  this.program.enableMouse();
  if (this.options.sendFocus) {
    this.program.setMouse({ sendFocus: true }, true);
  }

  this.on('render', function() {
    self._needsClickableSort = true;
  });

  this.program.on('mouse', function(data) {
    if (self.lockKeys) return;

    if (self._needsClickableSort) {
      self.clickable = helpers.hsort(self.clickable);
      self._needsClickableSort = false;
    }

    var i = 0
      , el
      , set
      , pos;

    for (; i < self.clickable.length; i++) {
      el = self.clickable[i];

      if (el.detached || !el.visible) {
        continue;
      }

      // if (self.grabMouse && self.focused !== el
      //     && !el.hasAncestor(self.focused)) continue;

      pos = el.lpos;
      if (!pos) continue;

      if (data.x >= pos.xi && data.x < pos.xl
          && data.y >= pos.yi && data.y < pos.yl) {
        el.emit('mouse', data);
        if (data.action === 'mousedown') {
          self.mouseDown = el;
        } else if (data.action === 'mouseup') {
          (self.mouseDown || el).emit('click', data);
          self.mouseDown = null;
        } else if (data.action === 'mousemove') {
          if (self.hover && el.index > self.hover.index) {
            set = false;
          }
          if (self.hover !== el && !set) {
            if (self.hover) {
              self.hover.emit('mouseout', data);
            }
            el.emit('mouseover', data);
            self.hover = el;
          }
          set = true;
        }
        el.emit(data.action, data);
        break;
      }
    }

    // Just mouseover?
    if ((data.action === 'mousemove'
        || data.action === 'mousedown'
        || data.action === 'mouseup')
        && self.hover
        && !set) {
      self.hover.emit('mouseout', data);
      self.hover = null;
    }

    self.emit('mouse', data);
    self.emit(data.action, data);
  });

  // Autofocus highest element.
  // this.on('element click', function(el, data) {
  //   var target;
  //   do {
  //     if (el.clickable === true && el.options.autoFocus !== false) {
  //       target = el;
  //     }
  //   } while (el = el.parent);
  //   if (target) target.focus();
  // });

  // Autofocus elements with the appropriate option.
  this.on('element click', function(el) {
    if (el.clickable === true && el.options.autoFocus !== false) {
      el.focus();
    }
  });
};

Screen.prototype.enableMouse = function(el) {
  this._listenMouse(el);
};

Screen.prototype._listenKeys = function(el) {
  var self = this;

  if (el && !~this.keyable.indexOf(el)) {
    el.keyable = true;
    this.keyable.push(el);
  }

  if (this._listenedKeys) return;
  this._listenedKeys = true;

  // NOTE: The event emissions used to be reversed:
  // element + screen
  // They are now:
  // screen + element
  // After the first keypress emitted, the handler
  // checks to make sure grabKeys, lockKeys, and focused
  // weren't changed, and handles those situations appropriately.
  this.program.on('keypress', function(ch, key) {
    if (key.name == 'tab') {
      /* Round-Robin focus shift of top-level boxes -- find
      ** currently focussed box by iterating over children list.
      ** Maybe it is a child of a top-level box itself!
      */ 
      //console.log(self.focused.type)
      self.program.hideCursor(self.forceCursor);
      var last=self.focused;
      var first=undefined;
      var next=undefined;
      for (var c in self.children) {
        var child=self.children[c];
        if (first==undefined) first=child;
        if (last==undefined && next==undefined && child && child.options && child.options.focus) next=child;
        if (last && child==last) last=undefined;
        if (last && child[last.type]==last) last=undefined;
      }
      if (!next) next=first;
      if (next) next.focus();
      self.render();
      return;
    }
    if (self.lockKeys && !~self.ignoreLocked.indexOf(key.full)) {
      return;
    }

    var focused = self.focused
      , grabKeys = self.grabKeys;

    if (!grabKeys || ~self.ignoreLocked.indexOf(key.full)) {
      self.emit('keypress', ch, key);
      self.emit('key ' + key.full, ch, key);
    }

    // If something changed from the screen key handler, stop.
    if (self.grabKeys !== grabKeys || self.lockKeys) {
      return;
    }

    if (focused && focused.keyable) {
      focused.emit('keypress', ch, key);
      focused.emit('key ' + key.full, ch, key);
    }
  });
};

Screen.prototype.enableKeys = function(el) {
  this._listenKeys(el);
};

Screen.prototype.enableInput = function(el) {
  this._listenMouse(el);
  this._listenKeys(el);
};

Screen.prototype._initHover = function() {
  var self = this;

  if (this._hoverText) {
    return;
  }

  this._hoverText = new Box({
    screen: this,
    left: 0,
    top: 0,
    tags: false,
    height: 'shrink',
    width: 'shrink',
    border: 'line',
    style: {
      border: {
        fg: 'default'
      },
      bg: 'default',
      fg: 'default'
    }
  });

  this.on('mousemove', function(data) {
    if (self._hoverText.detached) return;
    self._hoverText.rleft = data.x + 1;
    self._hoverText.rtop = data.y;
    self.render();
  });

  this.on('element mouseover', function(el, data) {
    if (!el._hoverOptions) return;
    self._hoverText.parseTags = el.parseTags;
    self._hoverText.setContent(el._hoverOptions.text);
    self.append(self._hoverText);
    self._hoverText.rleft = data.x + 1;
    self._hoverText.rtop = data.y;
    self.render();
  });

  this.on('element mouseout', function() {
    if (self._hoverText.detached) return;
    self._hoverText.detach();
    self.render();
  });

  // XXX This can cause problems if the
  // terminal does not support allMotion.
  // Workaround: check to see if content is set.
  this.on('element mouseup', function(el) {
    if (!self._hoverText.getContent()) return;
    if (!el._hoverOptions) return;
    self.append(self._hoverText);
    self.render();
  });
};

/* Depricated
Screen.prototype.__defineGetter__('cols', function() {
  return this.program.cols;
});

Screen.prototype.__defineGetter__('rows', function() {
  return this.program.rows;
});

Screen.prototype.__defineGetter__('width', function() {
  return this.program.cols;
});

Screen.prototype.__defineGetter__('height', function() {
  return this.program.rows;
});
*/
Object.defineProperty(Screen.prototype,'cols',{
  get: function () {
    return this.program.cols;
  },
});
Object.defineProperty(Screen.prototype,'rows',{
  get: function () {
    return this.program.rows;
  },
});
Object.defineProperty(Screen.prototype,'width',{
  get: function () {
    return this.program.cols;
  },
});
Object.defineProperty(Screen.prototype,'height',{
  get: function () {
    return this.program.rows;
  },
});

Screen.prototype.alloc = function(dirty) {
  var x, y;

  this.lines = [];
  for (y = 0; y < this.rows; y++) {
    this.lines[y] = [];
    for (x = 0; x < this.cols; x++) {
      this.lines[y][x] = [this.dattr, ' '];
    }
    this.lines[y].dirty = !!dirty;
  }

  this.olines = [];
  for (y = 0; y < this.rows; y++) {
    this.olines[y] = [];
    for (x = 0; x < this.cols; x++) {
      this.olines[y][x] = [this.dattr, ' '];
    }
  }

  this.program.clear();
};

Screen.prototype.realloc = function() {
  return this.alloc(true);
};

Screen.prototype.render = function() {
  var self = this;

  if (this.destroyed) return;

  this.emit('prerender');

  this._borderStops = {};

  // TODO: Possibly get rid of .dirty altogether.
  // TODO: Could possibly drop .dirty and just clear the `lines` buffer every
  // time before a screen.render. This way clearRegion doesn't have to be
  // called in arbitrary places for the sake of clearing a spot where an
  // element used to be (e.g. when an element moves or is hidden). There could
  // be some overhead though.
  // this.screen.clearRegion(0, this.cols, 0, this.rows);
  this._ci = 0;
  this.children.forEach(function(el) {
    el.index = self._ci++;
    //el._rendering = true;
    el.render();
    //el._rendering = false;
  });
  this._ci = -1;

  if (this.screen.dockBorders) {
    this._dockBorders();
  }

  this.draw(0, this.lines.length - 1);

  // XXX Workaround to deal with cursor pos before the screen has rendered and
  // lpos is not reliable (stale).
  if (this.focused && this.focused._updateCursor) {
    this.focused._updateCursor(true);
  }

  this.renders++;

  this.emit('render');
};

Screen.prototype.blankLine = function(ch, dirty) {
  var out = [];
  for (var x = 0; x < this.cols; x++) {
    out[x] = [this.dattr, ch || ' '];
  }
  out.dirty = dirty;
  return out;
};

Screen.prototype.insertLine = function(n, y, top, bottom) {
  // if (y === top) return this.insertLineNC(n, y, top, bottom);

  if (!this.tput.strings.change_scroll_region
      || !this.tput.strings.delete_line
      || !this.tput.strings.insert_line) return;

  this._buf += this.tput.csr(top, bottom);
  this._buf += this.tput.cup(y, 0);
  this._buf += this.tput.il(n);
  this._buf += this.tput.csr(0, this.height - 1);

  var j = bottom + 1;

  while (n--) {
    this.lines.splice(y, 0, this.blankLine());
    this.lines.splice(j, 1);
    this.olines.splice(y, 0, this.blankLine());
    this.olines.splice(j, 1);
  }
};

Screen.prototype.deleteLine = function(n, y, top, bottom) {
  // if (y === top) return this.deleteLineNC(n, y, top, bottom);

  if (!this.tput.strings.change_scroll_region
      || !this.tput.strings.delete_line
      || !this.tput.strings.insert_line) return;

  this._buf += this.tput.csr(top, bottom);
  this._buf += this.tput.cup(y, 0);
  this._buf += this.tput.dl(n);
  this._buf += this.tput.csr(0, this.height - 1);

  var j = bottom + 1;

  while (n--) {
    this.lines.splice(j, 0, this.blankLine());
    this.lines.splice(y, 1);
    this.olines.splice(j, 0, this.blankLine());
    this.olines.splice(y, 1);
  }
};

// This is how ncurses does it.
// Scroll down (up cursor-wise).
// This will only work for top line deletion as opposed to arbitrary lines.
Screen.prototype.insertLineNC = function(n, y, top, bottom) {
  if (!this.tput.strings.change_scroll_region
      || !this.tput.strings.delete_line) return;

  this._buf += this.tput.csr(top, bottom);
  this._buf += this.tput.cup(top, 0);
  this._buf += this.tput.dl(n);
  this._buf += this.tput.csr(0, this.height - 1);

  var j = bottom + 1;

  while (n--) {
    this.lines.splice(j, 0, this.blankLine());
    this.lines.splice(y, 1);
    this.olines.splice(j, 0, this.blankLine());
    this.olines.splice(y, 1);
  }
};

// This is how ncurses does it.
// Scroll up (down cursor-wise).
// This will only work for bottom line deletion as opposed to arbitrary lines.
Screen.prototype.deleteLineNC = function(n, y, top, bottom) {
  if (!this.tput.strings.change_scroll_region
      || !this.tput.strings.delete_line) return;

  this._buf += this.tput.csr(top, bottom);
  this._buf += this.tput.cup(bottom, 0);
  this._buf += Array(n + 1).join('\n');
  this._buf += this.tput.csr(0, this.height - 1);

  var j = bottom + 1;

  while (n--) {
    this.lines.splice(j, 0, this.blankLine());
    this.lines.splice(y, 1);
    this.olines.splice(j, 0, this.blankLine());
    this.olines.splice(y, 1);
  }
};

Screen.prototype.insertBottom = function(top, bottom) {
  return this.deleteLine(1, top, top, bottom);
};

Screen.prototype.insertTop = function(top, bottom) {
  return this.insertLine(1, top, top, bottom);
};

Screen.prototype.deleteBottom = function(top, bottom) {
  return this.clearRegion(0, this.width, bottom, bottom);
};

Screen.prototype.deleteTop = function(top, bottom) {
  // Same as: return this.insertBottom(top, bottom);
  return this.deleteLine(1, top, top, bottom);
};

// Parse the sides of an element to determine
// whether an element has uniform cells on
// both sides. If it does, we can use CSR to
// optimize scrolling on a scrollable element.
// Not exactly sure how worthwile this is.
// This will cause a performance/cpu-usage hit,
// but will it be less or greater than the
// performance hit of slow-rendering scrollable
// boxes with clean sides?
Screen.prototype.cleanSides = function(el) {
  var pos = el.lpos;

  if (!pos) {
    return false;
  }

  if (pos._cleanSides != null) {
    return pos._cleanSides;
  }

  if (pos.xi <= 0 && pos.xl >= this.width) {
    return pos._cleanSides = true;
  }

  if (this.options.fastCSR) {
    // Maybe just do this instead of parsing.
    if (pos.yi < 0) return pos._cleanSides = false;
    if (pos.yl > this.height) return pos._cleanSides = false;
    if (this.width - (pos.xl - pos.xi) < 40) {
      return pos._cleanSides = true;
    }
    return pos._cleanSides = false;
  }

  if (!this.options.smartCSR) {
    return false;
  }

  // The scrollbar can't update properly, and there's also a
  // chance that the scrollbar may get moved around senselessly.
  // NOTE: In pratice, this doesn't seem to be the case.
  // if (this.scrollbar) {
  //   return pos._cleanSides = false;
  // }

  // Doesn't matter if we're only a height of 1.
  // if ((pos.yl - el.ibottom) - (pos.yi + el.itop) <= 1) {
  //   return pos._cleanSides = false;
  // }

  var yi = pos.yi + el.itop
    , yl = pos.yl - el.ibottom
    , first
    , ch
    , x
    , y;

  if (pos.yi < 0) return pos._cleanSides = false;
  if (pos.yl > this.height) return pos._cleanSides = false;
  if (pos.xi - 1 < 0) return pos._cleanSides = true;
  if (pos.xl > this.width) return pos._cleanSides = true;

  for (x = pos.xi - 1; x >= 0; x--) {
    if (!this.olines[yi]) break;
    first = this.olines[yi][x];
    for (y = yi; y < yl; y++) {
      if (!this.olines[y] || !this.olines[y][x]) break;
      ch = this.olines[y][x];
      if (ch[0] !== first[0] || ch[1] !== first[1]) {
        return pos._cleanSides = false;
      }
    }
  }

  for (x = pos.xl; x < this.width; x++) {
    if (!this.olines[yi]) break;
    first = this.olines[yi][x];
    for (y = yi; y < yl; y++) {
      if (!this.olines[y] || !this.olines[y][x]) break;
      ch = this.olines[y][x];
      if (ch[0] !== first[0] || ch[1] !== first[1]) {
        return pos._cleanSides = false;
      }
    }
  }

  return pos._cleanSides = true;
};

Screen.prototype._dockBorders = function() {
  var lines = this.lines
    , stops = this._borderStops
    , i
    , y
    , x
    , ch;

  // var keys, stop;
  //
  // keys = Object.keys(this._borderStops)
  //   .map(function(k) { return +k; })
  //   .sort(function(a, b) { return a - b; });
  //
  // for (i = 0; i < keys.length; i++) {
  //   y = keys[i];
  //   if (!lines[y]) continue;
  //   stop = this._borderStops[y];
  //   for (x = stop.xi; x < stop.xl; x++) {

  stops = Object.keys(stops)
    .map(function(k) { return +k; })
    .sort(function(a, b) { return a - b; });

  for (i = 0; i < stops.length; i++) {
    y = stops[i];
    if (!lines[y]) continue;
    for (x = 0; x < this.width; x++) {
      ch = lines[y][x][1];
      if (angles[ch]) {
        lines[y][x][1] = this._getAngle(lines, x, y);
        lines[y].dirty = true;
      }
    }
  }
};

Screen.prototype._getAngle = function(lines, x, y) {
  var angle = 0
    , attr = lines[y][x][0]
    , ch = lines[y][x][1];

  if (lines[y][x - 1] && langles[lines[y][x - 1][1]]) {
    if (!this.options.ignoreDockContrast) {
      if (lines[y][x - 1][0] !== attr) return ch;
    }
    angle |= 1 << 3;
  }

  if (lines[y - 1] && uangles[lines[y - 1][x][1]]) {
    if (!this.options.ignoreDockContrast) {
      if (lines[y - 1][x][0] !== attr) return ch;
    }
    angle |= 1 << 2;
  }

  if (lines[y][x + 1] && rangles[lines[y][x + 1][1]]) {
    if (!this.options.ignoreDockContrast) {
      if (lines[y][x + 1][0] !== attr) return ch;
    }
    angle |= 1 << 1;
  }

  if (lines[y + 1] && dangles[lines[y + 1][x][1]]) {
    if (!this.options.ignoreDockContrast) {
      if (lines[y + 1][x][0] !== attr) return ch;
    }
    angle |= 1 << 0;
  }

  // Experimental: fixes this situation:
  // +----------+
  //            | <-- empty space here, should be a T angle
  // +-------+  |
  // |       |  |
  // +-------+  |
  // |          |
  // +----------+
  // if (uangles[lines[y][x][1]]) {
  //   if (lines[y + 1] && cdangles[lines[y + 1][x][1]]) {
  //     if (!this.options.ignoreDockContrast) {
  //       if (lines[y + 1][x][0] !== attr) return ch;
  //     }
  //     angle |= 1 << 0;
  //   }
  // }

  return angleTable[angle] || ch;
};

Screen.prototype.draw = function(start, end) {
  // this.emit('predraw');

  var x
    , y
    , line
    , out
    , ch
    , data
    , attr
    , fg
    , bg
    , flags;

  var main = ''
    , pre
    , post;

  var clr
    , neq
    , xx;

  var lx = -1
    , ly = -1
    , o;

  var acs;

  if (this._buf) {
    main += this._buf;
    this._buf = '';
  }

  for (y = start; y <= end; y++) {
    line = this.lines[y];
    o = this.olines[y];

    if (!line.dirty && !(this.cursor.artificial && y === this.program.y)) {
      continue;
    }
    line.dirty = false;

    out = '';
    attr = this.dattr;

    for (x = 0; x < line.length; x++) {
      data = line[x][0];
      ch = line[x][1];

      // Render the artificial cursor.
      if (this.cursor.artificial
          && !this.cursor._hidden
          && this.cursor._state
          && x === this.program.x
          && y === this.program.y) {
        var cattr = this._cursorAttr(this.cursor, data);
        if (cattr.ch) ch = cattr.ch;
        data = cattr.attr;
      }

      // Take advantage of xterm's back_color_erase feature by using a
      // lookahead. Stop spitting out so many damn spaces. NOTE: Is checking
      // the bg for non BCE terminals worth the overhead?
      if (this.options.useBCE
          && ch === ' '
          && (this.tput.bools.back_color_erase
          || (data & 0x1ff) === (this.dattr & 0x1ff))
          && ((data >> 18) & 8) === ((this.dattr >> 18) & 8)) {
        clr = true;
        neq = false;

        for (xx = x; xx < line.length; xx++) {
          if (line[xx][0] !== data || line[xx][1] !== ' ') {
            clr = false;
            break;
          }
          if (line[xx][0] !== o[xx][0] || line[xx][1] !== o[xx][1]) {
            neq = true;
          }
        }

        if (clr && neq) {
          lx = -1, ly = -1;
          if (data !== attr) {
            out += this.codeAttr(data);
            attr = data;
          }
          out += this.tput.cup(y, x);
          out += this.tput.el();
          for (xx = x; xx < line.length; xx++) {
            o[xx][0] = data;
            o[xx][1] = ' ';
          }
          break;
        }

        // If there's more than 10 spaces, use EL regardless
        // and start over drawing the rest of line. Might
        // not be worth it. Try to use ECH if the terminal
        // supports it. Maybe only try to use ECH here.
        // //if (this.tput.strings.erase_chars)
        // if (!clr && neq && (xx - x) > 10) {
        //   lx = -1, ly = -1;
        //   if (data !== attr) {
        //     out += this.codeAttr(data);
        //     attr = data;
        //   }
        //   out += this.tput.cup(y, x);
        //   if (this.tput.strings.erase_chars) {
        //     // Use erase_chars to avoid erasing the whole line.
        //     out += this.tput.ech(xx - x);
        //   } else {
        //     out += this.tput.el();
        //   }
        //   if (this.tput.strings.parm_right_cursor) {
        //     out += this.tput.cuf(xx - x);
        //   } else {
        //     out += this.tput.cup(y, xx);
        //   }
        //   this.fillRegion(data, ' ',
        //     x, this.tput.strings.erase_chars ? xx : line.length,
        //     y, y + 1);
        //   x = xx - 1;
        //   continue;
        // }

        // Skip to the next line if the
        // rest of the line is already drawn.
        // if (!neq) {
        //   for (; xx < line.length; xx++) {
        //     if (line[xx][0] !== o[xx][0] || line[xx][1] !== o[xx][1]) {
        //       neq = true;
        //       break;
        //     }
        //   }
        //   if (!neq) {
        //     attr = data;
        //     break;
        //   }
        // }
      }

      // Optimize by comparing the real output
      // buffer to the pending output buffer.
      if (data === o[x][0] && ch === o[x][1]) {
        if (lx === -1) {
          lx = x;
          ly = y;
        }
        continue;
      } else if (lx !== -1) {
        if (this.tput.strings.parm_right_cursor) {
          out += y === ly
            ? this.tput.cuf(x - lx)
            : this.tput.cup(y, x);
        } else {
          out += this.tput.cup(y, x);
        }
        lx = -1, ly = -1;
      }
      o[x][0] = data;
      o[x][1] = ch;

      if (data !== attr) {
        if (attr !== this.dattr) {
          out += '\x1b[m';
        }
        if (data !== this.dattr) {
          out += '\x1b[';

          bg = data & 0x1ff;
          fg = (data >> 9) & 0x1ff;
          flags = data >> 18;

          // bold
          if (flags & 1) {
            out += '1;';
          }

          // underline
          if (flags & 2) {
            out += '4;';
          }

          // blink
          if (flags & 4) {
            out += '5;';
          }

          // inverse
          if (flags & 8) {
            out += '7;';
          }

          // invisible
          if (flags & 16) {
            out += '8;';
          }

          if (bg !== 0x1ff) {
            bg = this._reduceColor(bg);
            if (bg < 16) {
              if (bg < 8) {
                bg += 40;
              } else if (bg < 16) {
                bg -= 8;
                bg += 100;
              }
              out += bg + ';';
            } else {
              out += '48;5;' + bg + ';';
            }
          }

          if (fg !== 0x1ff) {
            fg = this._reduceColor(fg);
            if (fg < 16) {
              if (fg < 8) {
                fg += 30;
              } else if (fg < 16) {
                fg -= 8;
                fg += 90;
              }
              out += fg + ';';
            } else {
              out += '38;5;' + fg + ';';
            }
          }

          if (out[out.length - 1] === ';') out = out.slice(0, -1);

          out += 'm';
        }
      }

      // If we find a double-width char, eat the next character which should be
      // a space due to parseContent's behavior.
      if (this.fullUnicode) {
        // If this is a surrogate pair double-width char, we can ignore it
        // because parseContent already counted it as length=2.
        if (unicode.charWidth(line[x][1]) === 2) {
          // NOTE: At cols=44, the bug that is avoided
          // by the angles check occurs in widget-unicode:
          // Might also need: `line[x + 1][0] !== line[x][0]`
          // for borderless boxes?
          if (x === line.length - 1 || angles[line[x + 1][1]]) {
            // If we're at the end, we don't have enough space for a
            // double-width. Overwrite it with a space and ignore.
            ch = ' ';
            o[x][1] = '\0';
          } else {
            // ALWAYS refresh double-width chars because this special cursor
            // behavior is needed. There may be a more efficient way of doing
            // this. See above.
            o[x][1] = '\0';
            // Eat the next character by moving forward and marking as a
            // space (which it is).
            o[++x][1] = '\0';
          }
        }
      }

      // Attempt to use ACS for supported characters.
      // This is not ideal, but it's how ncurses works.
      // There are a lot of terminals that support ACS
      // *and UTF8, but do not declare U8. So ACS ends
      // up being used (slower than utf8). Terminals
      // that do not support ACS and do not explicitly
      // support UTF8 get their unicode characters
      // replaced with really ugly ascii characters.
      // It is possible there is a terminal out there
      // somewhere that does not support ACS, but
      // supports UTF8, but I imagine it's unlikely.
      // Maybe remove !this.tput.unicode check, however,
      // this seems to be the way ncurses does it.
      if (this.tput.strings.enter_alt_charset_mode
          && !this.tput.brokenACS && (this.tput.acscr[ch] || acs)) {
        // Fun fact: even if this.tput.brokenACS wasn't checked here,
        // the linux console would still work fine because the acs
        // table would fail the check of: this.tput.acscr[ch]
        if (this.tput.acscr[ch]) {
          if (acs) {
            ch = this.tput.acscr[ch];
          } else {
            ch = this.tput.smacs()
              + this.tput.acscr[ch];
            acs = true;
          }
        } else if (acs) {
          ch = this.tput.rmacs() + ch;
          acs = false;
        }
      } else {
        // U8 is not consistently correct. Some terminfo's
        // terminals that do not declare it may actually
        // support utf8 (e.g. urxvt), but if the terminal
        // does not declare support for ACS (and U8), chances
        // are it does not support UTF8. This is probably
        // the "safest" way to do this. Should fix things
        // like sun-color.
        // NOTE: It could be the case that the $LANG
        // is all that matters in some cases:
        // if (!this.tput.unicode && ch > '~') {
        if (!this.tput.unicode && this.tput.numbers.U8 !== 1 && ch > '~') {
          ch = this.tput.utoa[ch] || '?';
        }
      }

      out += ch;
      attr = data;
    }

    if (attr !== this.dattr) {
      out += '\x1b[m';
    }

    if (out) {
      main += this.tput.cup(y, 0) + out;
    }
  }

  if (acs) {
    main += this.tput.rmacs();
    acs = false;
  }

  if (main) {
    pre = '';
    post = '';

    pre += this.tput.sc();
    post += this.tput.rc();

    if (!this.program.cursorHidden) {
      pre += this.tput.civis();
      post += this.tput.cnorm();
    }

    // this.program.flush();
    // this.program._owrite(pre + main + post);
    this.program._write(pre + main + post);
  }

  // this.emit('draw');
};

Screen.prototype._reduceColor = function(color) {
  return colors.reduce(color, this.tput.colors);
};

// Convert an SGR string to our own attribute format.
Screen.prototype.attrCode = function(code, cur, def) {
  var flags = (cur >> 18) & 0x1ff
    , fg = (cur >> 9) & 0x1ff
    , bg = cur & 0x1ff
    , c
    , i;

  code = code.slice(2, -1).split(';');
  if (!code[0]) code[0] = '0';

  for (i = 0; i < code.length; i++) {
    c = +code[i] || 0;
    switch (c) {
      case 0: // normal
        bg = def & 0x1ff;
        fg = (def >> 9) & 0x1ff;
        flags = (def >> 18) & 0x1ff;
        break;
      case 1: // bold
        flags |= 1;
        break;
      case 22:
        flags = (def >> 18) & 0x1ff;
        break;
      case 4: // underline
        flags |= 2;
        break;
      case 24:
        flags = (def >> 18) & 0x1ff;
        break;
      case 5: // blink
        flags |= 4;
        break;
      case 25:
        flags = (def >> 18) & 0x1ff;
        break;
      case 7: // inverse
        flags |= 8;
        break;
      case 27:
        flags = (def >> 18) & 0x1ff;
        break;
      case 8: // invisible
        flags |= 16;
        break;
      case 28:
        flags = (def >> 18) & 0x1ff;
        break;
      case 39: // default fg
        fg = (def >> 9) & 0x1ff;
        break;
      case 49: // default bg
        bg = def & 0x1ff;
        break;
      case 100: // default fg/bg
        fg = (def >> 9) & 0x1ff;
        bg = def & 0x1ff;
        break;
      default: // color
        if (c === 48 && +code[i+1] === 5) {
          i += 2;
          bg = +code[i];
          break;
        } else if (c === 48 && +code[i+1] === 2) {
          i += 2;
          bg = colors.match(+code[i], +code[i+1], +code[i+2]);
          if (bg === -1) bg = def & 0x1ff;
          i += 2;
          break;
        } else if (c === 38 && +code[i+1] === 5) {
          i += 2;
          fg = +code[i];
          break;
        } else if (c === 38 && +code[i+1] === 2) {
          i += 2;
          fg = colors.match(+code[i], +code[i+1], +code[i+2]);
          if (fg === -1) fg = (def >> 9) & 0x1ff;
          i += 2;
          break;
        }
        if (c >= 40 && c <= 47) {
          bg = c - 40;
        } else if (c >= 100 && c <= 107) {
          bg = c - 100;
          bg += 8;
        } else if (c === 49) {
          bg = def & 0x1ff;
        } else if (c >= 30 && c <= 37) {
          fg = c - 30;
        } else if (c >= 90 && c <= 97) {
          fg = c - 90;
          fg += 8;
        } else if (c === 39) {
          fg = (def >> 9) & 0x1ff;
        } else if (c === 100) {
          fg = (def >> 9) & 0x1ff;
          bg = def & 0x1ff;
        }
        break;
    }
  }

  return (flags << 18) | (fg << 9) | bg;
};

// Convert our own attribute format to an SGR string.
Screen.prototype.codeAttr = function(code) {
  var flags = (code >> 18) & 0x1ff
    , fg = (code >> 9) & 0x1ff
    , bg = code & 0x1ff
    , out = '';

  // bold
  if (flags & 1) {
    out += '1;';
  }

  // underline
  if (flags & 2) {
    out += '4;';
  }

  // blink
  if (flags & 4) {
    out += '5;';
  }

  // inverse
  if (flags & 8) {
    out += '7;';
  }

  // invisible
  if (flags & 16) {
    out += '8;';
  }

  if (bg !== 0x1ff) {
    bg = this._reduceColor(bg);
    if (bg < 16) {
      if (bg < 8) {
        bg += 40;
      } else if (bg < 16) {
        bg -= 8;
        bg += 100;
      }
      out += bg + ';';
    } else {
      out += '48;5;' + bg + ';';
    }
  }

  if (fg !== 0x1ff) {
    fg = this._reduceColor(fg);
    if (fg < 16) {
      if (fg < 8) {
        fg += 30;
      } else if (fg < 16) {
        fg -= 8;
        fg += 90;
      }
      out += fg + ';';
    } else {
      out += '38;5;' + fg + ';';
    }
  }

  if (out[out.length - 1] === ';') out = out.slice(0, -1);

  return '\x1b[' + out + 'm';
};

Screen.prototype.focusOffset = function(offset) {
  var shown = this.keyable.filter(function(el) {
    return !el.detached && el.visible;
  }).length;

  if (!shown || !offset) {
    return;
  }

  var i = this.keyable.indexOf(this.focused);
  if (!~i) return;

  if (offset > 0) {
    while (offset--) {
      if (++i > this.keyable.length - 1) i = 0;
      if (this.keyable[i].detached || !this.keyable[i].visible) offset++;
    }
  } else {
    offset = -offset;
    while (offset--) {
      if (--i < 0) i = this.keyable.length - 1;
      if (this.keyable[i].detached || !this.keyable[i].visible) offset++;
    }
  }

  return this.keyable[i].focus();
};

Screen.prototype.focusPrev =
Screen.prototype.focusPrevious = function() {
  return this.focusOffset(-1);
};

Screen.prototype.focusNext = function() {
  return this.focusOffset(1);
};

Screen.prototype.focusPush = function(el) {
  if (!el) return;
  var old = this.history[this.history.length - 1];
  if (this.history.length === 10) {
    this.history.shift();
  }
  this.history.push(el);
  this._focus(el, old);
};

Screen.prototype.focusPop = function() {
  var old = this.history.pop();
  if (this.history.length) {
    this._focus(this.history[this.history.length - 1], old);
  }
  return old;
};

Screen.prototype.saveFocus = function() {
  return this._savedFocus = this.focused;
};

Screen.prototype.restoreFocus = function() {
  if (!this._savedFocus) return;
  this._savedFocus.focus();
  delete this._savedFocus;
  return this.focused;
};

Screen.prototype.rewindFocus = function() {
  var old = this.history.pop()
    , el;

  while (this.history.length) {
    el = this.history.pop();
    if (!el.detached && el.visible) {
      this.history.push(el);
      this._focus(el, old);
      return el;
    }
  }

  if (old) {
    old.emit('blur');
  }
};

Screen.prototype._focus = function(self, old) {
  // Find a scrollable ancestor if we have one.
  var el = self;
  while (el = el.parent) {
    if (el.scrollable) break;
  }

  // If we're in a scrollable element,
  // automatically scroll to the focused element.
  if (el && !el.detached) {
    // NOTE: This is different from the other "visible" values - it needs the
    // visible height of the scrolling element itself, not the element within
    // it.
    var visible = self.screen.height - el.atop - el.itop - el.abottom - el.ibottom;
    if (self.rtop < el.childBase) {
      el.scrollTo(self.rtop);
      self.screen.render();
    } else if (self.rtop + self.height - self.ibottom > el.childBase + visible) {
      // Explanation for el.itop here: takes into account scrollable elements
      // with borders otherwise the element gets covered by the bottom border:
      el.scrollTo(self.rtop - (el.height - self.height) + el.itop, true);
      self.screen.render();
    }
  }

  if (old) {
    old.emit('blur', self);
  }

  self.emit('focus', old);
};

/* Depricated
Screen.prototype.__defineGetter__('focused', function() {
  return this.history[this.history.length - 1];
});

Screen.prototype.__defineSetter__('focused', function(el) {
  return this.focusPush(el);
});
*/
Object.defineProperty(Screen.prototype,'focused',{
  get: function () {return this.history[this.history.length - 1];},
  set: function (el) {
    return this.focusPush(el);  
  }
});

Screen.prototype.clearRegion = function(xi, xl, yi, yl, override) {
  return this.fillRegion(this.dattr, ' ', xi, xl, yi, yl, override);
};

Screen.prototype.fillRegion = function(attr, ch, xi, xl, yi, yl, override) {
  var lines = this.lines
    , cell
    , xx;

  if (xi < 0) xi = 0;
  if (yi < 0) yi = 0;

  for (; yi < yl; yi++) {
    if (!lines[yi]) break;
    for (xx = xi; xx < xl; xx++) {
      cell = lines[yi][xx];
      if (!cell) break;
      if (override || attr !== cell[0] || ch !== cell[1]) {
        lines[yi][xx][0] = attr;
        lines[yi][xx][1] = ch;
        lines[yi].dirty = true;
      }
    }
  }
};

Screen.prototype.key = function() {
  return this.program.key.apply(this, arguments);
};

Screen.prototype.onceKey = function() {
  return this.program.onceKey.apply(this, arguments);
};

Screen.prototype.unkey =
Screen.prototype.removeKey = function() {
  return this.program.unkey.apply(this, arguments);
};

Screen.prototype.spawn = function(file, args, options) {
  if (!Array.isArray(args)) {
    options = args;
    args = [];
  }

  var screen = this
    , program = screen.program
    , spawn = require('child_process').spawn
    , mouse = program.mouseEnabled
    , ps;

  options = options || {};

  options.stdio = options.stdio || 'inherit';

  program.lsaveCursor('spawn');
  // program.csr(0, program.rows - 1);
  program.normalBuffer();
  program.showCursor();
  if (mouse) program.disableMouse();

  var write = program.output.write;
  program.output.write = function() {};
  program.input.pause();
  if (program.input.setRawMode) {
    program.input.setRawMode(false);
  }

  var resume = function() {
    if (resume.done) return;
    resume.done = true;

    if (program.input.setRawMode) {
      program.input.setRawMode(true);
    }
    program.input.resume();
    program.output.write = write;

    program.alternateBuffer();
    // program.csr(0, program.rows - 1);
    if (mouse) {
      program.enableMouse();
      if (screen.options.sendFocus) {
        screen.program.setMouse({ sendFocus: true }, true);
      }
    }

    screen.alloc();
    screen.render();

    screen.program.lrestoreCursor('spawn', true);
  };

  ps = spawn(file, args, options);

  ps.on('error', resume);

  ps.on('exit', resume);

  return ps;
};

Screen.prototype.exec = function(file, args, options, callback) {
  var ps = this.spawn(file, args, options);

  ps.on('error', function(err) {
    if (!callback) return;
    return callback(err, false);
  });

  ps.on('exit', function(code) {
    if (!callback) return;
    return callback(null, code === 0);
  });

  return ps;
};

Screen.prototype.readEditor = function(options, callback) {
  if (typeof options === 'string') {
    options = { editor: options };
  }

  if (!callback) {
    callback = options;
    options = null;
  }

  if (!callback) {
    callback = function() {};
  }

  options = options || {};

  var self = this
    , editor = options.editor || process.env.EDITOR || 'vi'
    , name = options.name || process.title || 'blessed'
    , rnd = Math.random().toString(36).split('.').pop()
    , file = '/tmp/' + name + '.' + rnd
    , args = [file]
    , opt;

  opt = {
    stdio: 'inherit',
    env: process.env,
    cwd: process.env.HOME
  };

  function writeFile(callback) {
    if (!options.value) return callback();
    return fs.writeFile(file, options.value, callback);
  }

  return writeFile(function(err) {
    if (err) return callback(err);
    return self.exec(editor, args, opt, function(err, success) {
      if (err) return callback(err);
      return fs.readFile(file, 'utf8', function(err, data) {
        return fs.unlink(file, function() {
          if (!success) return callback(new Error('Unsuccessful.'));
          if (err) return callback(err);
          return callback(null, data);
        });
      });
    });
  });
};

Screen.prototype.displayImage = function(file, callback) {
  if (!file) {
    if (!callback) return;
    return callback(new Error('No image.'));
  }

  file = path.resolve(process.cwd(), file);

  if (!~file.indexOf('://')) {
    file = 'file://' + file;
  }

  var args = ['w3m', '-T', 'text/html'];

  var input = '<title>press q to exit</title>'
    + '<img align="center" src="' + file + '">';

  var opt = {
    stdio: ['pipe', 1, 2],
    env: process.env,
    cwd: process.env.HOME
  };

  var ps = this.spawn(args[0], args.slice(1), opt);

  ps.on('error', function(err) {
    if (!callback) return;
    return callback(err);
  });

  ps.on('exit', function(code) {
    if (!callback) return;
    if (code !== 0) return callback(new Error('Exit Code: ' + code));
    return callback(null, code === 0);
  });

  ps.stdin.write(input + '\n');
  ps.stdin.end();
};

Screen.prototype.setEffects = function(el, fel, over, out, effects, temp) {
  if (!effects) return;

  var tmp = {};
  if (temp) el[temp] = tmp;

  if (typeof el !== 'function') {
    var _el = el;
    el = function() { return _el; };
  }

  fel.on(over, function() {
    var element = el();
    Object.keys(effects).forEach(function(key) {
      var val = effects[key];
      if (val !== null && typeof val === 'object') {
        tmp[key] = tmp[key] || {};
        // element.style[key] = element.style[key] || {};
        Object.keys(val).forEach(function(k) {
          var v = val[k];
          tmp[key][k] = element.style[key][k];
          element.style[key][k] = v;
        });
        return;
      }
      tmp[key] = element.style[key];
      element.style[key] = val;
    });
    element.screen.render();
  });

  fel.on(out, function() {
    var element = el();
    Object.keys(effects).forEach(function(key) {
      var val = effects[key];
      if (val !== null && typeof val === 'object') {
        tmp[key] = tmp[key] || {};
        // element.style[key] = element.style[key] || {};
        Object.keys(val).forEach(function(k) {
          if (tmp[key].hasOwnProperty(k)) {
            element.style[key][k] = tmp[key][k];
          }
        });
        return;
      }
      if (tmp.hasOwnProperty(key)) {
        element.style[key] = tmp[key];
      }
    });
    element.screen.render();
  });
};

Screen.prototype.sigtstp = function(callback) {
  var self = this;
  this.program.sigtstp(function() {
    self.alloc();
    self.render();
    self.program.lrestoreCursor('pause', true);
    if (callback) callback();
  });
};

Screen.prototype.copyToClipboard = function(text) {
  return this.program.copyToClipboard(text);
};

Screen.prototype.cursorShape = function(shape, blink) {
  var self = this;

  this.cursor.shape = shape || 'block';
  this.cursor.blink = blink || false;
  this.cursor._set = true;

  if (this.cursor.artificial) {
    if (!this.program.hideCursor_old) {
      var hideCursor = this.program.hideCursor;
      this.program.hideCursor_old = this.program.hideCursor;
      this.program.hideCursor = function() {
        hideCursor.call(self.program);
        self.cursor._hidden = true;
        if (self.renders) self.render();
      };
    }
    if (!this.program.showCursor_old) {
      var showCursor = this.program.showCursor;
      this.program.showCursor_old = this.program.showCursor;
      this.program.showCursor = function() {
        self.cursor._hidden = false;
        if (self.program._exiting) showCursor.call(self.program);
        if (self.renders) self.render();
      };
    }
    if (!this._cursorBlink) {
      this._cursorBlink = setInterval(function() {
        if (!self.cursor.blink) return;
        self.cursor._state ^= 1;
        if (self.renders) self.render();
      }, 500);
      if (this._cursorBlink.unref) {
        this._cursorBlink.unref();
      }
    }
    return true;
  }

  return this.program.cursorShape(this.cursor.shape, this.cursor.blink);
};

Screen.prototype.cursorColor = function(color) {
  this.cursor.color = color != null
    ? colors.convert(color)
    : null;
  this.cursor._set = true;

  if (this.cursor.artificial) {
    return true;
  }

  return this.program.cursorColor(colors.ncolors[this.cursor.color]);
};

Screen.prototype.cursorReset =
Screen.prototype.resetCursor = function() {
  this.cursor.shape = 'block';
  this.cursor.blink = false;
  this.cursor.color = null;
  this.cursor._set = false;

  if (this.cursor.artificial) {
    this.cursor.artificial = false;
    if (this.program.hideCursor_old) {
      this.program.hideCursor = this.program.hideCursor_old;
      delete this.program.hideCursor_old;
    }
    if (this.program.showCursor_old) {
      this.program.showCursor = this.program.showCursor_old;
      delete this.program.showCursor_old;
    }
    if (this._cursorBlink) {
      clearInterval(this._cursorBlink);
      delete this._cursorBlink;
    }
    return true;
  }

  return this.program.cursorReset();
};

Screen.prototype._cursorAttr = function(cursor, dattr) {
  var attr = dattr || this.dattr
    , cattr
    , ch;

  if (cursor.shape === 'line') {
    attr &= ~(0x1ff << 9);
    attr |= 7 << 9;
    ch = '\u2502';
  } else if (cursor.shape === 'underline') {
    attr &= ~(0x1ff << 9);
    attr |= 7 << 9;
    attr |= 2 << 18;
  } else if (cursor.shape === 'block') {
    attr &= ~(0x1ff << 9);
    attr |= 7 << 9;
    attr |= 8 << 18;
  } else if (typeof cursor.shape === 'object' && cursor.shape) {
    cattr = Element.prototype.sattr.call(cursor, cursor.shape);

    if (cursor.shape.bold || cursor.shape.underline
        || cursor.shape.blink || cursor.shape.inverse
        || cursor.shape.invisible) {
      attr &= ~(0x1ff << 18);
      attr |= ((cattr >> 18) & 0x1ff) << 18;
    }

    if (cursor.shape.fg) {
      attr &= ~(0x1ff << 9);
      attr |= ((cattr >> 9) & 0x1ff) << 9;
    }

    if (cursor.shape.bg) {
      attr &= ~(0x1ff << 0);
      attr |= cattr & 0x1ff;
    }

    if (cursor.shape.ch) {
      ch = cursor.shape.ch;
    }
  }

  if (cursor.color != null) {
    attr &= ~(0x1ff << 9);
    attr |= cursor.color << 9;
  }

  return {
    ch: ch,
    attr: attr
  };
};

Screen.prototype.screenshot = function(xi, xl, yi, yl, term) {
  if (xi == null) xi = 0;
  if (xl == null) xl = this.cols;
  if (yi == null) yi = 0;
  if (yl == null) yl = this.rows;

  if (xi < 0) xi = 0;
  if (yi < 0) yi = 0;

  var x
    , y
    , line
    , out
    , ch
    , data
    , attr;

  var sdattr = this.dattr;

  if (term) {
    this.dattr = term.defAttr;
  }

  var main = '';

  for (y = yi; y < yl; y++) {
    line = term
      ? term.lines[y]
      : this.lines[y];

    if (!line) break;

    out = '';
    attr = this.dattr;

    for (x = xi; x < xl; x++) {
      if (!line[x]) break;

      data = line[x][0];
      ch = line[x][1];

      if (data !== attr) {
        if (attr !== this.dattr) {
          out += '\x1b[m';
        }
        if (data !== this.dattr) {
          var _data = data;
          if (term) {
            if (((_data >> 9) & 0x1ff) === 257) _data |= 0x1ff << 9;
            if ((_data & 0x1ff) === 256) _data |= 0x1ff;
          }
          out += this.codeAttr(_data);
        }
      }

      if (this.fullUnicode) {
        if (unicode.charWidth(line[x][1]) === 2) {
          if (x === xl - 1) {
            ch = ' ';
          } else {
            x++;
          }
        }
      }

      out += ch;
      attr = data;
    }

    if (attr !== this.dattr) {
      out += '\x1b[m';
    }

    if (out) {
      main += (y > 0 ? '\n' : '') + out;
    }
  }

  main = main.replace(/(?:\s*\x1b\[40m\s*\x1b\[m\s*)*$/, '') + '\n';

  if (term) {
    this.dattr = sdattr;
  }

  return main;
};

/**
 * Positioning
 */

Screen.prototype._getPos = function() {
  return this;
};

/**
 * Angle Table
 */

var angles = {
  '\u2518': true, // '┘'
  '\u2510': true, // '┐'
  '\u250c': true, // '┌'
  '\u2514': true, // '└'
  '\u253c': true, // '┼'
  '\u251c': true, // '├'
  '\u2524': true, // '┤'
  '\u2534': true, // '┴'
  '\u252c': true, // '┬'
  '\u2502': true, // '│'
  '\u2500': true  // '─'
};

var langles = {
  '\u250c': true, // '┌'
  '\u2514': true, // '└'
  '\u253c': true, // '┼'
  '\u251c': true, // '├'
  '\u2534': true, // '┴'
  '\u252c': true, // '┬'
  '\u2500': true  // '─'
};

var uangles = {
  '\u2510': true, // '┐'
  '\u250c': true, // '┌'
  '\u253c': true, // '┼'
  '\u251c': true, // '├'
  '\u2524': true, // '┤'
  '\u252c': true, // '┬'
  '\u2502': true  // '│'
};

var rangles = {
  '\u2518': true, // '┘'
  '\u2510': true, // '┐'
  '\u253c': true, // '┼'
  '\u2524': true, // '┤'
  '\u2534': true, // '┴'
  '\u252c': true, // '┬'
  '\u2500': true  // '─'
};

var dangles = {
  '\u2518': true, // '┘'
  '\u2514': true, // '└'
  '\u253c': true, // '┼'
  '\u251c': true, // '├'
  '\u2524': true, // '┤'
  '\u2534': true, // '┴'
  '\u2502': true  // '│'
};

// var cdangles = {
//   '\u250c': true  // '┌'
// };

// Every ACS angle character can be
// represented by 4 bits ordered like this:
// [langle][uangle][rangle][dangle]
var angleTable = {
  '0000': '', // ?
  '0001': '\u2502', // '│' // ?
  '0010': '\u2500', // '─' // ??
  '0011': '\u250c', // '┌'
  '0100': '\u2502', // '│' // ?
  '0101': '\u2502', // '│'
  '0110': '\u2514', // '└'
  '0111': '\u251c', // '├'
  '1000': '\u2500', // '─' // ??
  '1001': '\u2510', // '┐'
  '1010': '\u2500', // '─' // ??
  '1011': '\u252c', // '┬'
  '1100': '\u2518', // '┘'
  '1101': '\u2524', // '┤'
  '1110': '\u2534', // '┴'
  '1111': '\u253c'  // '┼'
};

Object.keys(angleTable).forEach(function(key) {
  angleTable[parseInt(key, 2)] = angleTable[key];
  delete angleTable[key];
});

/**
 * Expose
 */

module.exports = Screen;
};
BundleModuleCode['term/unicode']=function (module,exports){
/**
 * unicode.js - east asian width and surrogate pairs
 * Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License).
 * https://github.com/chjj/blessed
 * Borrowed from vangie/east-asian-width, komagata/eastasianwidth,
 * and mathiasbynens/String.prototype.codePointAt. Licenses below.
 */

// east-asian-width
//
// Copyright (c) 2015 Vangie Du
// https://github.com/vangie/east-asian-width
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.

// eastasianwidth
//
// Copyright (c) 2013, Masaki Komagata
// https://github.com/komagata/eastasianwidth
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

// String.prototype.codePointAt
//
// Copyright Mathias Bynens <https://mathiasbynens.be/>
// https://github.com/mathiasbynens/String.prototype.codePointAt
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

// String.fromCodePoint
//
// Copyright Mathias Bynens <https://mathiasbynens.be/>
// https://github.com/mathiasbynens/String.fromCodePoint
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

var stringFromCharCode = String.fromCharCode;
var floor = Math.floor;

/**
 * Wide, Surrogates, and Combining
 */

exports.charWidth = function(str, i) {
  var point = typeof str !== 'number'
    ? exports.codePointAt(str, i || 0)
    : str;

  // nul
  if (point === 0) return 0;

  // tab
  if (point === 0x09) {
    if (!exports.blessed) {
      // TODO? ??? exports.blessed = require('../');
    }
    return exports.blessed.screen.global
      ? exports.blessed.screen.global.tabc.length
      : 8;
  }

  // 8-bit control characters (2-width according to unicode??)
  if (point < 32 || (point >= 0x7f && point < 0xa0)) {
    return 0;
  }

  // search table of non-spacing characters
  // is ucs combining or C0/C1 control character
  if (exports.combining[point]) {
    return 0;
  }

  // check for double-wide
  // if (point >= 0x1100
  //     && (point <= 0x115f // Hangul Jamo init. consonants
  //     || point === 0x2329 || point === 0x232a
  //     || (point >= 0x2e80 && point <= 0xa4cf
  //     && point !== 0x303f) // CJK ... Yi
  //     || (point >= 0xac00 && point <= 0xd7a3) // Hangul Syllables
  //     || (point >= 0xf900 && point <= 0xfaff) // CJK Compatibility Ideographs
  //     || (point >= 0xfe10 && point <= 0xfe19) // Vertical forms
  //     || (point >= 0xfe30 && point <= 0xfe6f) // CJK Compatibility Forms
  //     || (point >= 0xff00 && point <= 0xff60) // Fullwidth Forms
  //     || (point >= 0xffe0 && point <= 0xffe6)
  //     || (point >= 0x20000 && point <= 0x2fffd)
  //     || (point >= 0x30000 && point <= 0x3fffd))) {
  //   return 2;
  // }

  // check for double-wide
  if ((0x3000 === point)
      || (0xFF01 <= point && point <= 0xFF60)
      || (0xFFE0 <= point && point <= 0xFFE6)) {
    return 2;
  }

  if ((0x1100 <= point && point <= 0x115F)
      || (0x11A3 <= point && point <= 0x11A7)
      || (0x11FA <= point && point <= 0x11FF)
      || (0x2329 <= point && point <= 0x232A)
      || (0x2E80 <= point && point <= 0x2E99)
      || (0x2E9B <= point && point <= 0x2EF3)
      || (0x2F00 <= point && point <= 0x2FD5)
      || (0x2FF0 <= point && point <= 0x2FFB)
      || (0x3001 <= point && point <= 0x303E)
      || (0x3041 <= point && point <= 0x3096)
      || (0x3099 <= point && point <= 0x30FF)
      || (0x3105 <= point && point <= 0x312D)
      || (0x3131 <= point && point <= 0x318E)
      || (0x3190 <= point && point <= 0x31BA)
      || (0x31C0 <= point && point <= 0x31E3)
      || (0x31F0 <= point && point <= 0x321E)
      || (0x3220 <= point && point <= 0x3247)
      || (0x3250 <= point && point <= 0x32FE)
      || (0x3300 <= point && point <= 0x4DBF)
      || (0x4E00 <= point && point <= 0xA48C)
      || (0xA490 <= point && point <= 0xA4C6)
      || (0xA960 <= point && point <= 0xA97C)
      || (0xAC00 <= point && point <= 0xD7A3)
      || (0xD7B0 <= point && point <= 0xD7C6)
      || (0xD7CB <= point && point <= 0xD7FB)
      || (0xF900 <= point && point <= 0xFAFF)
      || (0xFE10 <= point && point <= 0xFE19)
      || (0xFE30 <= point && point <= 0xFE52)
      || (0xFE54 <= point && point <= 0xFE66)
      || (0xFE68 <= point && point <= 0xFE6B)
      || (0x1B000 <= point && point <= 0x1B001)
      || (0x1F200 <= point && point <= 0x1F202)
      || (0x1F210 <= point && point <= 0x1F23A)
      || (0x1F240 <= point && point <= 0x1F248)
      || (0x1F250 <= point && point <= 0x1F251)
      || (0x20000 <= point && point <= 0x2F73F)
      || (0x2B740 <= point && point <= 0x2FFFD)
      || (0x30000 <= point && point <= 0x3FFFD)) {
    return 2;
  }

  // CJK Ambiguous
  // http://www.unicode.org/reports/tr11/
  // http://www.unicode.org/reports/tr11/#Ambiguous
  if (process.env.NCURSES_CJK_WIDTH) {
    if ((0x00A1 === point)
        || (0x00A4 === point)
        || (0x00A7 <= point && point <= 0x00A8)
        || (0x00AA === point)
        || (0x00AD <= point && point <= 0x00AE)
        || (0x00B0 <= point && point <= 0x00B4)
        || (0x00B6 <= point && point <= 0x00BA)
        || (0x00BC <= point && point <= 0x00BF)
        || (0x00C6 === point)
        || (0x00D0 === point)
        || (0x00D7 <= point && point <= 0x00D8)
        || (0x00DE <= point && point <= 0x00E1)
        || (0x00E6 === point)
        || (0x00E8 <= point && point <= 0x00EA)
        || (0x00EC <= point && point <= 0x00ED)
        || (0x00F0 === point)
        || (0x00F2 <= point && point <= 0x00F3)
        || (0x00F7 <= point && point <= 0x00FA)
        || (0x00FC === point)
        || (0x00FE === point)
        || (0x0101 === point)
        || (0x0111 === point)
        || (0x0113 === point)
        || (0x011B === point)
        || (0x0126 <= point && point <= 0x0127)
        || (0x012B === point)
        || (0x0131 <= point && point <= 0x0133)
        || (0x0138 === point)
        || (0x013F <= point && point <= 0x0142)
        || (0x0144 === point)
        || (0x0148 <= point && point <= 0x014B)
        || (0x014D === point)
        || (0x0152 <= point && point <= 0x0153)
        || (0x0166 <= point && point <= 0x0167)
        || (0x016B === point)
        || (0x01CE === point)
        || (0x01D0 === point)
        || (0x01D2 === point)
        || (0x01D4 === point)
        || (0x01D6 === point)
        || (0x01D8 === point)
        || (0x01DA === point)
        || (0x01DC === point)
        || (0x0251 === point)
        || (0x0261 === point)
        || (0x02C4 === point)
        || (0x02C7 === point)
        || (0x02C9 <= point && point <= 0x02CB)
        || (0x02CD === point)
        || (0x02D0 === point)
        || (0x02D8 <= point && point <= 0x02DB)
        || (0x02DD === point)
        || (0x02DF === point)
        || (0x0300 <= point && point <= 0x036F)
        || (0x0391 <= point && point <= 0x03A1)
        || (0x03A3 <= point && point <= 0x03A9)
        || (0x03B1 <= point && point <= 0x03C1)
        || (0x03C3 <= point && point <= 0x03C9)
        || (0x0401 === point)
        || (0x0410 <= point && point <= 0x044F)
        || (0x0451 === point)
        || (0x2010 === point)
        || (0x2013 <= point && point <= 0x2016)
        || (0x2018 <= point && point <= 0x2019)
        || (0x201C <= point && point <= 0x201D)
        || (0x2020 <= point && point <= 0x2022)
        || (0x2024 <= point && point <= 0x2027)
        || (0x2030 === point)
        || (0x2032 <= point && point <= 0x2033)
        || (0x2035 === point)
        || (0x203B === point)
        || (0x203E === point)
        || (0x2074 === point)
        || (0x207F === point)
        || (0x2081 <= point && point <= 0x2084)
        || (0x20AC === point)
        || (0x2103 === point)
        || (0x2105 === point)
        || (0x2109 === point)
        || (0x2113 === point)
        || (0x2116 === point)
        || (0x2121 <= point && point <= 0x2122)
        || (0x2126 === point)
        || (0x212B === point)
        || (0x2153 <= point && point <= 0x2154)
        || (0x215B <= point && point <= 0x215E)
        || (0x2160 <= point && point <= 0x216B)
        || (0x2170 <= point && point <= 0x2179)
        || (0x2189 === point)
        || (0x2190 <= point && point <= 0x2199)
        || (0x21B8 <= point && point <= 0x21B9)
        || (0x21D2 === point)
        || (0x21D4 === point)
        || (0x21E7 === point)
        || (0x2200 === point)
        || (0x2202 <= point && point <= 0x2203)
        || (0x2207 <= point && point <= 0x2208)
        || (0x220B === point)
        || (0x220F === point)
        || (0x2211 === point)
        || (0x2215 === point)
        || (0x221A === point)
        || (0x221D <= point && point <= 0x2220)
        || (0x2223 === point)
        || (0x2225 === point)
        || (0x2227 <= point && point <= 0x222C)
        || (0x222E === point)
        || (0x2234 <= point && point <= 0x2237)
        || (0x223C <= point && point <= 0x223D)
        || (0x2248 === point)
        || (0x224C === point)
        || (0x2252 === point)
        || (0x2260 <= point && point <= 0x2261)
        || (0x2264 <= point && point <= 0x2267)
        || (0x226A <= point && point <= 0x226B)
        || (0x226E <= point && point <= 0x226F)
        || (0x2282 <= point && point <= 0x2283)
        || (0x2286 <= point && point <= 0x2287)
        || (0x2295 === point)
        || (0x2299 === point)
        || (0x22A5 === point)
        || (0x22BF === point)
        || (0x2312 === point)
        || (0x2460 <= point && point <= 0x24E9)
        || (0x24EB <= point && point <= 0x254B)
        || (0x2550 <= point && point <= 0x2573)
        || (0x2580 <= point && point <= 0x258F)
        || (0x2592 <= point && point <= 0x2595)
        || (0x25A0 <= point && point <= 0x25A1)
        || (0x25A3 <= point && point <= 0x25A9)
        || (0x25B2 <= point && point <= 0x25B3)
        || (0x25B6 <= point && point <= 0x25B7)
        || (0x25BC <= point && point <= 0x25BD)
        || (0x25C0 <= point && point <= 0x25C1)
        || (0x25C6 <= point && point <= 0x25C8)
        || (0x25CB === point)
        || (0x25CE <= point && point <= 0x25D1)
        || (0x25E2 <= point && point <= 0x25E5)
        || (0x25EF === point)
        || (0x2605 <= point && point <= 0x2606)
        || (0x2609 === point)
        || (0x260E <= point && point <= 0x260F)
        || (0x2614 <= point && point <= 0x2615)
        || (0x261C === point)
        || (0x261E === point)
        || (0x2640 === point)
        || (0x2642 === point)
        || (0x2660 <= point && point <= 0x2661)
        || (0x2663 <= point && point <= 0x2665)
        || (0x2667 <= point && point <= 0x266A)
        || (0x266C <= point && point <= 0x266D)
        || (0x266F === point)
        || (0x269E <= point && point <= 0x269F)
        || (0x26BE <= point && point <= 0x26BF)
        || (0x26C4 <= point && point <= 0x26CD)
        || (0x26CF <= point && point <= 0x26E1)
        || (0x26E3 === point)
        || (0x26E8 <= point && point <= 0x26FF)
        || (0x273D === point)
        || (0x2757 === point)
        || (0x2776 <= point && point <= 0x277F)
        || (0x2B55 <= point && point <= 0x2B59)
        || (0x3248 <= point && point <= 0x324F)
        || (0xE000 <= point && point <= 0xF8FF)
        || (0xFE00 <= point && point <= 0xFE0F)
        || (0xFFFD === point)
        || (0x1F100 <= point && point <= 0x1F10A)
        || (0x1F110 <= point && point <= 0x1F12D)
        || (0x1F130 <= point && point <= 0x1F169)
        || (0x1F170 <= point && point <= 0x1F19A)
        || (0xE0100 <= point && point <= 0xE01EF)
        || (0xF0000 <= point && point <= 0xFFFFD)
        || (0x100000 <= point && point <= 0x10FFFD)) {
      return +process.env.NCURSES_CJK_WIDTH || 1;
    }
  }

  return 1;
};

exports.strWidth = function(str) {
  var width = 0;
  for (var i = 0; i < str.length; i++) {
    width += exports.charWidth(str, i);
    if (exports.isSurrogate(str, i)) i++;
  }
  return width;
};

exports.isSurrogate = function(str, i) {
  var point = typeof str !== 'number'
    ? exports.codePointAt(str, i || 0)
    : str;
  return point > 0x00ffff;
};

exports.combiningTable = [
  [0x0300, 0x036F],   [0x0483, 0x0486],   [0x0488, 0x0489],
  [0x0591, 0x05BD],   [0x05BF, 0x05BF],   [0x05C1, 0x05C2],
  [0x05C4, 0x05C5],   [0x05C7, 0x05C7],   [0x0600, 0x0603],
  [0x0610, 0x0615],   [0x064B, 0x065E],   [0x0670, 0x0670],
  [0x06D6, 0x06E4],   [0x06E7, 0x06E8],   [0x06EA, 0x06ED],
  [0x070F, 0x070F],   [0x0711, 0x0711],   [0x0730, 0x074A],
  [0x07A6, 0x07B0],   [0x07EB, 0x07F3],   [0x0901, 0x0902],
  [0x093C, 0x093C],   [0x0941, 0x0948],   [0x094D, 0x094D],
  [0x0951, 0x0954],   [0x0962, 0x0963],   [0x0981, 0x0981],
  [0x09BC, 0x09BC],   [0x09C1, 0x09C4],   [0x09CD, 0x09CD],
  [0x09E2, 0x09E3],   [0x0A01, 0x0A02],   [0x0A3C, 0x0A3C],
  [0x0A41, 0x0A42],   [0x0A47, 0x0A48],   [0x0A4B, 0x0A4D],
  [0x0A70, 0x0A71],   [0x0A81, 0x0A82],   [0x0ABC, 0x0ABC],
  [0x0AC1, 0x0AC5],   [0x0AC7, 0x0AC8],   [0x0ACD, 0x0ACD],
  [0x0AE2, 0x0AE3],   [0x0B01, 0x0B01],   [0x0B3C, 0x0B3C],
  [0x0B3F, 0x0B3F],   [0x0B41, 0x0B43],   [0x0B4D, 0x0B4D],
  [0x0B56, 0x0B56],   [0x0B82, 0x0B82],   [0x0BC0, 0x0BC0],
  [0x0BCD, 0x0BCD],   [0x0C3E, 0x0C40],   [0x0C46, 0x0C48],
  [0x0C4A, 0x0C4D],   [0x0C55, 0x0C56],   [0x0CBC, 0x0CBC],
  [0x0CBF, 0x0CBF],   [0x0CC6, 0x0CC6],   [0x0CCC, 0x0CCD],
  [0x0CE2, 0x0CE3],   [0x0D41, 0x0D43],   [0x0D4D, 0x0D4D],
  [0x0DCA, 0x0DCA],   [0x0DD2, 0x0DD4],   [0x0DD6, 0x0DD6],
  [0x0E31, 0x0E31],   [0x0E34, 0x0E3A],   [0x0E47, 0x0E4E],
  [0x0EB1, 0x0EB1],   [0x0EB4, 0x0EB9],   [0x0EBB, 0x0EBC],
  [0x0EC8, 0x0ECD],   [0x0F18, 0x0F19],   [0x0F35, 0x0F35],
  [0x0F37, 0x0F37],   [0x0F39, 0x0F39],   [0x0F71, 0x0F7E],
  [0x0F80, 0x0F84],   [0x0F86, 0x0F87],   [0x0F90, 0x0F97],
  [0x0F99, 0x0FBC],   [0x0FC6, 0x0FC6],   [0x102D, 0x1030],
  [0x1032, 0x1032],   [0x1036, 0x1037],   [0x1039, 0x1039],
  [0x1058, 0x1059],   [0x1160, 0x11FF],   [0x135F, 0x135F],
  [0x1712, 0x1714],   [0x1732, 0x1734],   [0x1752, 0x1753],
  [0x1772, 0x1773],   [0x17B4, 0x17B5],   [0x17B7, 0x17BD],
  [0x17C6, 0x17C6],   [0x17C9, 0x17D3],   [0x17DD, 0x17DD],
  [0x180B, 0x180D],   [0x18A9, 0x18A9],   [0x1920, 0x1922],
  [0x1927, 0x1928],   [0x1932, 0x1932],   [0x1939, 0x193B],
  [0x1A17, 0x1A18],   [0x1B00, 0x1B03],   [0x1B34, 0x1B34],
  [0x1B36, 0x1B3A],   [0x1B3C, 0x1B3C],   [0x1B42, 0x1B42],
  [0x1B6B, 0x1B73],   [0x1DC0, 0x1DCA],   [0x1DFE, 0x1DFF],
  [0x200B, 0x200F],   [0x202A, 0x202E],   [0x2060, 0x2063],
  [0x206A, 0x206F],   [0x20D0, 0x20EF],   [0x302A, 0x302F],
  [0x3099, 0x309A],   [0xA806, 0xA806],   [0xA80B, 0xA80B],
  [0xA825, 0xA826],   [0xFB1E, 0xFB1E],   [0xFE00, 0xFE0F],
  [0xFE20, 0xFE23],   [0xFEFF, 0xFEFF],   [0xFFF9, 0xFFFB],
  [0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F],
  [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x1D167, 0x1D169],
  [0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD],
  [0x1D242, 0x1D244], [0xE0001, 0xE0001], [0xE0020, 0xE007F],
  [0xE0100, 0xE01EF]
];

exports.combining = exports.combiningTable.reduce(function(out, row) {
  for (var i = row[0]; i <= row[1]; i++) {
    out[i] = true;
  }
  return out;
}, {});

exports.isCombining = function(str, i) {
  var point = typeof str !== 'number'
    ? exports.codePointAt(str, i || 0)
    : str;
  return exports.combining[point] === true;
};

/**
 * Code Point Helpers
 */

exports.codePointAt = function(str, position) {
  if (str == null) {
    throw TypeError();
  }
  var string = String(str);
  if (string.codePointAt) {
    return string.codePointAt(position);
  }
  var size = string.length;
  // `ToInteger`
  var index = position ? Number(position) : 0;
  if (index !== index) { // better `isNaN`
    index = 0;
  }
  // Account for out-of-bounds indices:
  if (index < 0 || index >= size) {
    return undefined;
  }
  // Get the first code unit
  var first = string.charCodeAt(index);
  var second;
  if ( // check if it’s the start of a surrogate pair
    first >= 0xD800 && first <= 0xDBFF && // high surrogate
    size > index + 1 // there is a next code unit
  ) {
    second = string.charCodeAt(index + 1);
    if (second >= 0xDC00 && second <= 0xDFFF) { // low surrogate
      // http://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
      return (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000;
    }
  }
  return first;
};

// exports.codePointAt = function(str, position) {
//   position = +position || 0;
//   var x = str.charCodeAt(position);
//   var y = str.length > 1 ? str.charCodeAt(position + 1) : 0;
//   var point = x;
//   if ((0xD800 <= x && x <= 0xDBFF) && (0xDC00 <= y && y <= 0xDFFF)) {
//     x &= 0x3FF;
//     y &= 0x3FF;
//     point = (x << 10) | y;
//     point += 0x10000;
//   }
//   return point;
// };

exports.fromCodePoint = function() {
  if (String.fromCodePoint) {
    return String.fromCodePoint.apply(String, arguments);
  }
  var MAX_SIZE = 0x4000;
  var codeUnits = [];
  var highSurrogate;
  var lowSurrogate;
  var index = -1;
  var length = arguments.length;
  if (!length) {
    return '';
  }
  var result = '';
  while (++index < length) {
    var codePoint = Number(arguments[index]);
    if (
      !isFinite(codePoint) ||       // `NaN`, `+Infinity`, or `-Infinity`
      codePoint < 0 ||              // not a valid Unicode code point
      codePoint > 0x10FFFF ||       // not a valid Unicode code point
      floor(codePoint) !== codePoint // not an integer
    ) {
      throw RangeError('Invalid code point: ' + codePoint);
    }
    if (codePoint <= 0xFFFF) { // BMP code point
      codeUnits.push(codePoint);
    } else { // Astral code point; split in surrogate halves
      // http://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
      codePoint -= 0x10000;
      highSurrogate = (codePoint >> 10) + 0xD800;
      lowSurrogate = (codePoint % 0x400) + 0xDC00;
      codeUnits.push(highSurrogate, lowSurrogate);
    }
    if (index + 1 === length || codeUnits.length > MAX_SIZE) {
      result += stringFromCharCode.apply(null, codeUnits);
      codeUnits.length = 0;
    }
  }
  return result;
};

/**
 * Regexes
 */

exports.chars = {};

// Double width characters that are _not_ surrogate pairs.
// NOTE: 0x20000 - 0x2fffd and 0x30000 - 0x3fffd are not necessary for this
// regex anyway. This regex is used to put a blank char after wide chars to
// be eaten, however, if this is a surrogate pair, parseContent already adds
// the extra one char because its length equals 2 instead of 1.
exports.chars.wide = new RegExp('(['
  + '\\u1100-\\u115f' // Hangul Jamo init. consonants
  + '\\u2329\\u232a'
  + '\\u2e80-\\u303e\\u3040-\\ua4cf' // CJK ... Yi
  + '\\uac00-\\ud7a3' // Hangul Syllables
  + '\\uf900-\\ufaff' // CJK Compatibility Ideographs
  + '\\ufe10-\\ufe19' // Vertical forms
  + '\\ufe30-\\ufe6f' // CJK Compatibility Forms
  + '\\uff00-\\uff60' // Fullwidth Forms
  + '\\uffe0-\\uffe6'
  + '])', 'g');

// All surrogate pair wide chars.
exports.chars.swide = new RegExp('('
  // 0x20000 - 0x2fffd:
  + '[\\ud840-\\ud87f][\\udc00-\\udffd]'
  + '|'
  // 0x30000 - 0x3fffd:
  + '[\\ud880-\\ud8bf][\\udc00-\\udffd]'
  + ')', 'g');

// All wide chars including surrogate pairs.
exports.chars.all = new RegExp('('
  + exports.chars.swide.source.slice(1, -1)
  + '|'
  + exports.chars.wide.source.slice(1, -1)
  + ')', 'g');

// Regex to detect a surrogate pair.
exports.chars.surrogate = /[\ud800-\udbff][\udc00-\udfff]/g;

// Regex to find combining characters.
exports.chars.combining = exports.combiningTable.reduce(function(out, row) {
  var low, high, range;
  if (row[0] > 0x00ffff) {
    low = exports.fromCodePoint(row[0]);
    low = [
      hexify(low.charCodeAt(0)),
      hexify(low.charCodeAt(1))
    ];
    high = exports.fromCodePoint(row[1]);
    high = [
      hexify(high.charCodeAt(0)),
      hexify(high.charCodeAt(1))
    ];
    range = '[\\u' + low[0] + '-' + '\\u' + high[0] + ']'
          + '[\\u' + low[1] + '-' + '\\u' + high[1] + ']';
    if (!~out.indexOf('|')) out += ']';
    out += '|' + range;
  } else {
    low = hexify(row[0]);
    high = hexify(row[1]);
    low = '\\u' + low;
    high = '\\u' + high;
    out += low + '-' + high;
  }
  return out;
}, '[');

exports.chars.combining = new RegExp(exports.chars.combining, 'g');

function hexify(n) {
  n = n.toString(16);
  while (n.length < 4) n = '0' + n;
  return n;
}

/*
exports.chars.combining = new RegExp(
  '['
  + '\\u0300-\\u036f'
  + '\\u0483-\\u0486'
  + '\\u0488-\\u0489'
  + '\\u0591-\\u05bd'
  + '\\u05bf-\\u05bf'
  + '\\u05c1-\\u05c2'
  + '\\u05c4-\\u05c5'
  + '\\u05c7-\\u05c7'
  + '\\u0600-\\u0603'
  + '\\u0610-\\u0615'
  + '\\u064b-\\u065e'
  + '\\u0670-\\u0670'
  + '\\u06d6-\\u06e4'
  + '\\u06e7-\\u06e8'
  + '\\u06ea-\\u06ed'
  + '\\u070f-\\u070f'
  + '\\u0711-\\u0711'
  + '\\u0730-\\u074a'
  + '\\u07a6-\\u07b0'
  + '\\u07eb-\\u07f3'
  + '\\u0901-\\u0902'
  + '\\u093c-\\u093c'
  + '\\u0941-\\u0948'
  + '\\u094d-\\u094d'
  + '\\u0951-\\u0954'
  + '\\u0962-\\u0963'
  + '\\u0981-\\u0981'
  + '\\u09bc-\\u09bc'
  + '\\u09c1-\\u09c4'
  + '\\u09cd-\\u09cd'
  + '\\u09e2-\\u09e3'
  + '\\u0a01-\\u0a02'
  + '\\u0a3c-\\u0a3c'
  + '\\u0a41-\\u0a42'
  + '\\u0a47-\\u0a48'
  + '\\u0a4b-\\u0a4d'
  + '\\u0a70-\\u0a71'
  + '\\u0a81-\\u0a82'
  + '\\u0abc-\\u0abc'
  + '\\u0ac1-\\u0ac5'
  + '\\u0ac7-\\u0ac8'
  + '\\u0acd-\\u0acd'
  + '\\u0ae2-\\u0ae3'
  + '\\u0b01-\\u0b01'
  + '\\u0b3c-\\u0b3c'
  + '\\u0b3f-\\u0b3f'
  + '\\u0b41-\\u0b43'
  + '\\u0b4d-\\u0b4d'
  + '\\u0b56-\\u0b56'
  + '\\u0b82-\\u0b82'
  + '\\u0bc0-\\u0bc0'
  + '\\u0bcd-\\u0bcd'
  + '\\u0c3e-\\u0c40'
  + '\\u0c46-\\u0c48'
  + '\\u0c4a-\\u0c4d'
  + '\\u0c55-\\u0c56'
  + '\\u0cbc-\\u0cbc'
  + '\\u0cbf-\\u0cbf'
  + '\\u0cc6-\\u0cc6'
  + '\\u0ccc-\\u0ccd'
  + '\\u0ce2-\\u0ce3'
  + '\\u0d41-\\u0d43'
  + '\\u0d4d-\\u0d4d'
  + '\\u0dca-\\u0dca'
  + '\\u0dd2-\\u0dd4'
  + '\\u0dd6-\\u0dd6'
  + '\\u0e31-\\u0e31'
  + '\\u0e34-\\u0e3a'
  + '\\u0e47-\\u0e4e'
  + '\\u0eb1-\\u0eb1'
  + '\\u0eb4-\\u0eb9'
  + '\\u0ebb-\\u0ebc'
  + '\\u0ec8-\\u0ecd'
  + '\\u0f18-\\u0f19'
  + '\\u0f35-\\u0f35'
  + '\\u0f37-\\u0f37'
  + '\\u0f39-\\u0f39'
  + '\\u0f71-\\u0f7e'
  + '\\u0f80-\\u0f84'
  + '\\u0f86-\\u0f87'
  + '\\u0f90-\\u0f97'
  + '\\u0f99-\\u0fbc'
  + '\\u0fc6-\\u0fc6'
  + '\\u102d-\\u1030'
  + '\\u1032-\\u1032'
  + '\\u1036-\\u1037'
  + '\\u1039-\\u1039'
  + '\\u1058-\\u1059'
  + '\\u1160-\\u11ff'
  + '\\u135f-\\u135f'
  + '\\u1712-\\u1714'
  + '\\u1732-\\u1734'
  + '\\u1752-\\u1753'
  + '\\u1772-\\u1773'
  + '\\u17b4-\\u17b5'
  + '\\u17b7-\\u17bd'
  + '\\u17c6-\\u17c6'
  + '\\u17c9-\\u17d3'
  + '\\u17dd-\\u17dd'
  + '\\u180b-\\u180d'
  + '\\u18a9-\\u18a9'
  + '\\u1920-\\u1922'
  + '\\u1927-\\u1928'
  + '\\u1932-\\u1932'
  + '\\u1939-\\u193b'
  + '\\u1a17-\\u1a18'
  + '\\u1b00-\\u1b03'
  + '\\u1b34-\\u1b34'
  + '\\u1b36-\\u1b3a'
  + '\\u1b3c-\\u1b3c'
  + '\\u1b42-\\u1b42'
  + '\\u1b6b-\\u1b73'
  + '\\u1dc0-\\u1dca'
  + '\\u1dfe-\\u1dff'
  + '\\u200b-\\u200f'
  + '\\u202a-\\u202e'
  + '\\u2060-\\u2063'
  + '\\u206a-\\u206f'
  + '\\u20d0-\\u20ef'
  + '\\u302a-\\u302f'
  + '\\u3099-\\u309a'
  + '\\ua806-\\ua806'
  + '\\ua80b-\\ua80b'
  + '\\ua825-\\ua826'
  + '\\ufb1e-\\ufb1e'
  + '\\ufe00-\\ufe0f'
  + '\\ufe20-\\ufe23'
  + '\\ufeff-\\ufeff'
  + '\\ufff9-\\ufffb'
  + ']'
  + '|[\\ud802-\\ud802][\\ude01-\\ude03]'
  + '|[\\ud802-\\ud802][\\ude05-\\ude06]'
  + '|[\\ud802-\\ud802][\\ude0c-\\ude0f]'
  + '|[\\ud802-\\ud802][\\ude38-\\ude3a]'
  + '|[\\ud802-\\ud802][\\ude3f-\\ude3f]'
  + '|[\\ud834-\\ud834][\\udd67-\\udd69]'
  + '|[\\ud834-\\ud834][\\udd73-\\udd82]'
  + '|[\\ud834-\\ud834][\\udd85-\\udd8b]'
  + '|[\\ud834-\\ud834][\\uddaa-\\uddad]'
  + '|[\\ud834-\\ud834][\\ude42-\\ude44]'
  + '|[\\udb40-\\udb40][\\udc01-\\udc01]'
  + '|[\\udb40-\\udb40][\\udc20-\\udc7f]'
  + '|[\\udb40-\\udb40][\\udd00-\\uddef]'
, 'g');
*/
};
BundleModuleCode['term/helpers']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Christopher Jeffrey and contributors, Stefan Bosse
 **    $INITIAL:     (C) 2013-2016, Christopher Jeffrey and contributors
 **    $CREATED:     sbosse on 28-3-15.
 **    $VERSION:     1.9.1
 **
 **    $INFO:
 *
 * helpers.js - helpers for blessed
 *
 **    $ENDOFINFO
 */

/**
 * Modules
 */

var fs = Require('fs');
var Comp = Require('com/compat');

var unicode = Require('term/unicode');

/**
 * Helpers
 */

var helpers = exports;


helpers.asort = function(obj) {
  return obj.sort(function(a, b) {
    a = a.name.toLowerCase();
    b = b.name.toLowerCase();

    if (a[0] === '.' && b[0] === '.') {
      a = a[1];
      b = b[1];
    } else {
      a = a[0];
      b = b[0];
    }

    return a > b ? 1 : (a < b ? -1 : 0);
  });
};

helpers.attrToBinary = function(style, element) {
  return helpers.Element.prototype.sattr.call(element || {}, style);
};

// Compute absolute position, width, height of a box 
// Requires parent object with absolute bbox valies. 
// Commonly parent is screen object.
// Supported formats: number,'half','center','%'
helpers.bbox = function (parent,options) {
  function eval(a,b) {
    if (a.indexOf('%')) return int(Number(a.substring(0,a.length-1))*b/100);
  }
  var bbox={width:options.width,height:options.height,top:options.top,left:options.left,right:options.right};
  if (bbox.width=='half') bbox.width=int(parent.width/2);
  if (typeof bbox.width == 'string' && bbox.width.indexOf('%')!=-1) bbox.width=eval(bbox.width,parent.width);
  if (bbox.height=='half') bbox.height=int(parent.height/2);
  if (typeof bbox.height == 'string' && bbox.height.indexOf('%')!=-1) bbox.height=eval(bbox.height,parent.height);
  if (bbox.left=='center') bbox.left=int((parent.width/2)-(bbox.width/2));
  if (bbox.top=='center') bbox.top=int((parent.height/2)-(bbox.height/2));
  return bbox;  
}

helpers.cleanTags = function(text) {
  return helpers.stripTags(text).trim();
};

helpers.dropUnicode = function(text) {
  if (!text) return '';
  return text
    .replace(unicode.chars.all, '??')
    .replace(unicode.chars.combining, '')
    .replace(unicode.chars.surrogate, '?');
};

helpers.findFile = function(start, target) {
  return (function read(dir) {
    var files, file, stat, out;

    if (dir === '/dev' || dir === '/sys'
        || dir === '/proc' || dir === '/net') {
      return null;
    }

    try {
      files = fs.readdirSync(dir);
    } catch (e) {
      files = [];
    }

    for (var i = 0; i < files.length; i++) {
      file = files[i];

      if (file === target) {
        return (dir === '/' ? '' : dir) + '/' + file;
      }

      try {
        stat = fs.lstatSync((dir === '/' ? '' : dir) + '/' + file);
      } catch (e) {
        stat = null;
      }

      if (stat && stat.isDirectory() && !stat.isSymbolicLink()) {
        out = read((dir === '/' ? '' : dir) + '/' + file);
        if (out) return out;
      }
    }

    return null;
  })(start);
};

// Escape text for tag-enabled elements.
helpers.escape = function(text) {
  return text.replace(/[{}]/g, function(ch) {
    return ch === '{' ? '{open}' : '{close}';
  });
};

helpers.generateTags = function(style, text) {
  var open = ''
    , close = '';

  Object.keys(style || {}).forEach(function(key) {
    var val = style[key];
    if (typeof val === 'string') {
      val = val.replace(/^light(?!-)/, 'light-');
      val = val.replace(/^bright(?!-)/, 'bright-');
      open = '{' + val + '-' + key + '}' + open;
      close += '{/' + val + '-' + key + '}';
    } else {
      if (val === true) {
        open = '{' + key + '}' + open;
        close += '{/' + key + '}';
      }
    }
  });

  if (text != null) {
    return open + text + close;
  }

  return {
    open: open,
    close: close
  };
};

helpers.hsort = function(obj) {
  return obj.sort(function(a, b) {
    return b.index - a.index;
  });
};

helpers.merge = function(a, b) {
  Object.keys(b).forEach(function(key) {
    a[key] = b[key];
  });
  return a;
};

helpers.parseTags = function(text, screen) {
  return helpers.Element.prototype._parseTags.call(
    { parseTags: true, screen: screen || helpers.Screen.global }, text);
};


helpers.stripTags = function(text) {
  if (!text) return '';
  return text
    .replace(/\{(\/?)([\w\-,;!#]*)\}/g, '')
    .replace(/\x1b\[[\d;]*m/g, '');
};

/* Depricated
helpers.__defineGetter__('Screen', function() {
  if (!helpers._screen) {
    helpers._screen = Require('term/widgets/screen');
  }
  return helpers._screen;
});

helpers.__defineGetter__('Element', function() {
  if (!helpers._element) {
    helpers._element = Require('term/widgets/element');
  }
  return helpers._element;
});
*/
Object.defineProperty(helpers,'Screen',{
  get: function () {
    if (!helpers._screen) {
      helpers._screen = Require('term/widgets/screen');
    }
    return helpers._screen;
  }
});
Object.defineProperty(helpers,'Element',{
  get: function () {
    if (!helpers._element) {
      helpers._element = Require('term/widgets/element');
    }
    return helpers._element;
  }
});
};
BundleModuleCode['term/widgets/log']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2013-2016, Christopher Jeffrey and contributors
 **    $MODIFIED:    by sbosse
 **    $REVESIO:     1.3.2
 **
 **    $INFO:
 **
 **    Logging Widget with Scrollbars
 **
 **   options: {
 **       scrollOnInput:boolean -- auto scroll
 **       arrows?: {up:'[-]',down:'[+]',width:3,height:1,fg:'red',bg:'default'}}
 **
 **  Usage:
 
    if (options.top == undefined) options.top=2;
    if (options.left == undefined) options.left=1;
    var obj = blessed.log({
      top: options.top,
      left: options.left,
      width: options.width||(self.screen.width-options.left*2),
      height: options.height||(self.screen.height-options.top-4),
      label: options.label||'Log',
      mouse:true,
      keys:true,
      scrollback:100,
      border: {
        type: 'line'
      },
      scrollbar: {
        ch: ' ',
        track: {
          bg: 'yellow'
        },
        style: {
          fg: 'cyan',
          inverse: true
        }
      },
      alwaysScroll:true,
      scrollOnInput:true,
      style: {
        fg: 'white',
        bg: 'black',
        border: {
          fg: 'green'
        },
        focus: {
          border: {
            fg: 'red'
          }
        }
      }
    });
    screen.append(obj);


 **    $ENDOFINFO
 */

var options = {
  version:'1.3.2'
}
/**
 * Modules
 */
var Comp = Require('com/compat');

var util = require('util');
var Helpers = Require('term/helpers');
var Button = Require('term/widgets/button');
var Arrows = Require('term/widgets/arrows');

var nextTick = global.setImmediate || process.nextTick.bind(process);

var Node = Require('term/widgets/node');
var ScrollableText = Require('term/widgets/scrollabletext');

/**
 * Log
 */

function Log(options) {
  var self = this, bbox;

  if (!instanceOf(this,Node)) {
    return new Log(options);
  }

  options = options || {};

  ScrollableText.call(this, options);

  this.scrollback = options.scrollback != null
    ? options.scrollback
    : Infinity;
  this.scrollOnInput = options.scrollOnInput;
  this._updating=false;

  if (options.arrows) 
    Arrows(
      self,
      options,
      function () { self.scroll(-2)},
      function () { self.scroll(2)}
    );
  
  this.on('set content', function() {
    if (!self._updating && !self._userScrolled && self.scrollOnInput) {
      self._updating=true;
      setTimeout(function() {
        self.setScrollPerc(100);
        self._userScrolled = false;
        self._updating=false;
        self.screen.render();
      },20);
    }
  });
}

//Log.prototype.__proto__ = ScrollableText.prototype;
inheritPrototype(Log,ScrollableText);

Log.prototype.type = 'log';

Log.prototype.log =
Log.prototype.add = function() {
  var args = Array.prototype.slice.call(arguments);
  if (typeof args[0] === 'object') {
    args[0] = util.inspect(args[0], true, 20, true);
  }
  var text = util.format.apply(util, args);
  this.emit('log', text);
  var ret = this.pushLine(text);
 
  //if (this._clines.fake.length > this.scrollback) {
  //  this.shiftLine(0, (this.scrollback / 3) | 0);
  // }
  return ret;
};

Log.prototype.clear = function() {
  if (this._userScrolled) this.setScrollPerc(100);
  this.setContent('');
  this.resetScroll();
}

Log.prototype._scroll = Log.prototype.scroll;
Log.prototype.scroll = function(offset, always) {
  if (offset>this.getScrollHeight() || (this.getScrollHeight()+offset)<0) return;
  if (offset === 0) return this._scroll(offset, always);
  this._userScrolled = true;
  var ret = this._scroll(offset, always);
  if (this.getScrollPerc() === 100) {
    this._userScrolled = false;
  }
  return ret;
};

/**
 * Expose
 */

module.exports = Log;



Log.prototype.logold = function(str) {  
  var i;
  this.logLines.push(str)  
  if (this.logLines.length==1) {
    this.setContent(str);
  }
  else if (this.logLines.length>this.options.bufferLength) {
    this.logLines.shift();
    this.setContent(this.logLines[0]);
    for(i=1;i<this.logLines.length;i++) {
      this.insertLine(i,this.logLines[i]);
    }
    this.scrollBottom();
  } else {
    this.scrollBottom();
    this.insertBottom(str);
    this.scrollBottom();
  }
  // this.setItems(this.logLines)
  // this.scrollTo(this.logLines.length)
}

};
BundleModuleCode['term/widgets/button']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2013-2015, Christopher Jeffrey and contributors
 **    $MODIFIED:    by sbosse
 **    $REVESIO:     1.1.8
 **
 **    $INFO:
 **
 **    button.js - button element for blessed
 **
 **     Options: {mouse:boolean,content,border,style,object}
 **
 **     Events In: click keypress
 **     Events Out: press
 **
 **  Usage:
 
    var width;
    if (Comp.obj.isString(options.width)) {
      // relative value in %!
      width=Comp.pervasives.int_of_string(options.width);
      width=int(self.screen.width*width/100);
    }
    var obj = blessed.button({
      width: options.width||(options.content.length+4),
      left: (options.center?int(self.screen.width/2-width/2):options.left),
      right : options.right,
      top: options.top||0,
      height: 3,
      align: 'center',
      content: options.content||'?',
      mouse:true,
      focus:false,
      border: {
        type: 'line'
      },
      style: {
        fg: 'white',
        bg: options.color||'blue',
        bold:true,
        border: {
          fg: 'black'
        },
        hover: {
          border: {
            fg: 'red'
          }
        }
      }  
    });
    screen.append(obj);
 
 **    $ENDOFINFO
 */

/**
 * Modules
 */
var Comp = Require('com/compat');

var Node = Require('term/widgets/node');
var Input = Require('term/widgets/input');

/**
 * Button
 */

function Button(options) {
  var self = this;

  if (!instanceOf(this,Node)) {
    return new Button(options);
  }

  options = options || {};

  if (options.autoFocus == null) {
    options.autoFocus = false;
  }

  if (!options.style) options.style = {
      fg: 'white',
      bg: 'blue',
      bold:true,
      border: {
        fg: 'black'
      },
      hover: {
        border: {
          fg: 'red'
        }
      },
      focus : {
        border: {
          fg: 'red'
        }      
      }
  }
  if (options.object) this.object=options.object;
  
  Input.call(this, options);

  this.on('keypress', function(ch, key) {
    if (key.name == 'enter' || key.name == 'return') {
      return self.press();
    }
  });

  if (this.options.mouse) {
    this.on('click', function() {
      return self.press();
    });
  }
}

//Button.prototype.__proto__ = Input.prototype;
inheritPrototype(Button,Input);

Button.prototype.type = 'button';

Button.prototype.press = function() {
  this.focus();
  this.value = true;
  var result = this.emit('press');
  delete this.value;
  return result;
};

/**
 * Expose
 */

module.exports = Button;
};
BundleModuleCode['term/widgets/input']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Christopher Jeffrey, Stefan Bosse
 **    $INITIAL:     (C) 2013-2015, Christopher Jeffrey and contributors
 **    $MODIFIED:    by sbosse
 **    $REVESIO:     1.2.2
 **
 **    $INFO:
 **
 **    input.js - abstract input element for blessed
 **
 **     Added:
 **       - Focus handling
 **
 **    Usage:
 **
 **    $ENDOFINFO
 */

/**
 * Modules
 */
var Comp = Require('com/compat');

var Node = Require('term/widgets/node');
var Box = Require('term/widgets/box');

/**
 * Input
 */

function Input(options) {
  if (!instanceOf(this,Node)) {
    return new Input(options);
  }
  options = options || {};
  Box.call(this, options);
}

//Input.prototype.__proto__ = Box.prototype;
inheritPrototype(Input,Box);

Input.prototype.type = 'input';

Input.prototype.focus = function() {
  // Force focus for input field
  this.screen.rewindFocus();
  return this.screen.focused = this;
}

/**
 * Expose
 */

module.exports = Input;
};
BundleModuleCode['term/widgets/box']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Christopher Jeffrey, Stefan Bosse
 **    $INITIAL:     (C) 2013-2015, Christopher Jeffrey and contributors
 **    $MODIFIED:    by sbosse
 **    $REVESIO:     1.2.1
 **
 **    $INFO:
 **
 **    box.js - box element for blessed
 **
 **
 **    $ENDOFINFO
 */

/**
 * Modules
 */
var Comp = Require('com/compat');

var Node = Require('term/widgets/node');
var Element = Require('term/widgets/element');

/**
 * Box
 */

function Box(options) {
  if (!instanceOf(this,Node)) {
    return new Box(options);
  }
  options = options || {};
  Element.call(this, options);
}

//Box.prototype.__proto__ = Element.prototype;
inheritPrototype(Box,Element);

Box.prototype.type = 'box';

/**
 * Expose
 */

module.exports = Box;
};
BundleModuleCode['term/widgets/element']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Christopher Jeffrey and contributors, Stefan Bosse
 **    $INITIAL:     (C) 2013-2016, Christopher Jeffrey and contributors
 **    $MODIFIED:    by sbosse (2016-2021)
 **    $REVESIO:     1.4.3
 **
 **    $INFO:
 **
 **    Base element for blessed
 **
 **    Event Out: change, resize, set content, show, hide
 **
 **     Added:
 **       - 'setStyle({fg:..})' method
 **       - 'getLabel()' method 
 **       - 'isfocus' method
 **       - relative right align attribute right:'60%'
 **
 **   typeof this._clines = string [],rtof:number [], ftor:number [][], fake: value string
 **
 **    $ENDOFINFO
 */

/**
 * Modules
 */
var Comp = Require('com/compat');

var assert = Require('assert');

var colors = Require('term/colors')
  , unicode = Require('term/unicode');

var nextTick = global.setImmediate || process.nextTick.bind(process);

var helpers = Require('term/helpers');

var Node = Require('term/widgets/node');

/**
 * Element
 */

function Element(options) {
  var self = this;

  if (!instanceOf(this,Node)) {
    return new Element(options);
  }

  options = options || {};

  // Workaround to get a `scrollable` option.
  if (options.scrollable && !this._ignore && this.type !== 'scrollable-box') {
    var ScrollableBox = Require('term/widgets/scrollablebox');
    Object.getOwnPropertyNames(ScrollableBox.prototype).forEach(function(key) {
      if (key === 'type') return;
      Object.defineProperty(this, key,
        Object.getOwnPropertyDescriptor(ScrollableBox.prototype, key));
    }, this);
    this._ignore = true;
    ScrollableBox.call(this, options);
    delete this._ignore;
    // Workaround two: catch scrollbar mouse events
    if (options.mouse) {
      this.onScreenEvent('mouse',function (data) { 
        var x = data.x - self.aleft;
        var y = data.y - self.atop;
        if (!self.hidden && data.action=='mousedown' &&
            x === self.width - self.iright - 1) {
          self.focus();  
          var perc = (y - self.itop) / (self.height - self.iheight);
          self.setScrollPerc(perc * 100 | 0);
          self.screen.render();
        } 
      });
    }
    return this;
  }

  Node.call(this, options);

  this.name = options.name;

  options.position = options.position || {
    left: options.left,
    right: options.right,
    top: options.top,
    bottom: options.bottom,
    width: options.width,
    height: options.height
  };

  if (options.position.width === 'shrink'
      || options.position.height === 'shrink') {
    if (options.position.width === 'shrink') {
      delete options.position.width;
    }
    if (options.position.height === 'shrink') {
      delete options.position.height;
    }
    options.shrink = true;
  }

  this.position = options.position;

  this.noOverflow = options.noOverflow;
  this.dockBorders = options.dockBorders;
  this.shadow = options.shadow;

  this.style = options.style;

  if (!this.style) {
    this.style = {};
    this.style.fg = options.fg;
    this.style.bg = options.bg;
    this.style.bold = options.bold;
    this.style.underline = options.underline;
    this.style.blink = options.blink;
    this.style.inverse = options.inverse;
    this.style.invisible = options.invisible;
    this.style.transparent = options.transparent;
  }

  this.hidden = options.hidden || false;
  this.fixed = options.fixed || false;
  this.align = options.align || 'left';
  this.valign = options.valign || 'top';
  this.wrap = options.wrap !== false;
  this.shrink = options.shrink;
  this.ch = options.ch || ' ';

  if (typeof options.padding === 'number' || !options.padding) {
    options.padding = {
      left: options.padding,
      top: options.padding,
      right: options.padding,
      bottom: options.padding
    };
  }

  this.padding = {
    left: options.padding.left || 0,
    top: options.padding.top || 0,
    right: options.padding.right || 0,
    bottom: options.padding.bottom || 0
  };

  this.border = options.border;
  if (this.border) {
    if (typeof this.border === 'string') {
      this.border = { type: this.border };
    }
    this.border.type = this.border.type || 'bg';
    if (this.border.type === 'ascii') this.border.type = 'line';
    this.border.ch = this.border.ch || ' ';
    this.style.border = this.style.border || this.border.style;
    if (!this.style.border) {
      this.style.border = {};
      this.style.border.fg = this.border.fg;
      this.style.border.bg = this.border.bg;
    }
    //this.border.style = this.style.border;
    if (this.border.left == null) this.border.left = true;
    if (this.border.top == null) this.border.top = true;
    if (this.border.right == null) this.border.right = true;
    if (this.border.bottom == null) this.border.bottom = true;
  }

  // if (options.mouse || options.clickable) {
  if (options.clickable) {
    this.screen._listenMouse(this);
  }

  if (options.input || options.keyable) {
    this.screen._listenKeys(this);
  }

  this.parseTags = options.parseTags || options.tags;

  this.setContent(options.content || '', true);

  if (options.label) {
    this.setLabel(options.label);
  }

  if (options.hoverText) {
    this.setHover(options.hoverText);
  }

  // TODO: Possibly move this to Node for onScreenEvent('mouse', ...).
  this.on('newListener', function fn(type) {
    // type = type.split(' ').slice(1).join(' ');
    if (type === 'mouse'
      || type === 'click'
      || type === 'mouseover'
      || type === 'mouseout'
      || type === 'mousedown'
      || type === 'mouseup'
      || type === 'mousewheel'
      || type === 'wheeldown'
      || type === 'wheelup'
      || type === 'mousemove') {
      self.screen._listenMouse(self);
    } else if (type === 'keypress' || type.indexOf('key ') === 0) {
      self.screen._listenKeys(self);
    }
  });

  this.on('resize', function() {
    self.parseContent();
  });

  this.on('attach', function() {
    self.parseContent();
  });

  this.on('detach', function() {
    delete self.lpos;
  });

  if (options.hoverBg != null) {
    options.hoverEffects = options.hoverEffects || {};
    options.hoverEffects.bg = options.hoverBg;
  }

  if (this.style.hover) {
    options.hoverEffects = this.style.hover;
  }

  if (this.style.focus) {
    options.focusEffects = this.style.focus;
  }

  if (options.effects) {
    if (options.effects.hover) options.hoverEffects = options.effects.hover;
    if (options.effects.focus) options.focusEffects = options.effects.focus;
  }

  [['hoverEffects', 'mouseover', 'mouseout', '_htemp'],
   ['focusEffects', 'focus', 'blur', '_ftemp']].forEach(function(props) {
    var pname = props[0], over = props[1], out = props[2], temp = props[3];
    self.screen.setEffects(self, self, over, out, self.options[pname], temp);
  });

  if (this.options.draggable) {
    this.draggable = true;
  }

  if (options.focused) {
    this.focus();
  }
}

//Element.prototype.__proto__ = Node.prototype;
inheritPrototype(Element,Node);

Element.prototype.type = 'element';

/* Deprictaed
Element.prototype.__defineGetter__('focused', function() {
  return this.screen.focused === this;
});
*/
Object.defineProperty(Element.prototype,'focused',{
  get: function () {return this.screen.focused === this;}
});

Element.prototype.sattr = function(style, fg, bg) {
  var bold = style.bold
    , underline = style.underline
    , blink = style.blink
    , inverse = style.inverse
    , invisible = style.invisible;

  // if (arguments.length === 1) {
  if (fg == null && bg == null) {
    fg = style.fg;
    bg = style.bg;
  }

  // This used to be a loop, but I decided
  // to unroll it for performance's sake.
  if (typeof bold === 'function') bold = bold(this);
  if (typeof underline === 'function') underline = underline(this);
  if (typeof blink === 'function') blink = blink(this);
  if (typeof inverse === 'function') inverse = inverse(this);
  if (typeof invisible === 'function') invisible = invisible(this);

  if (typeof fg === 'function') fg = fg(this);
  if (typeof bg === 'function') bg = bg(this);

  // return (this.uid << 24)
  //   | ((this.dockBorders ? 32 : 0) << 18)
  return ((invisible ? 16 : 0) << 18)
    | ((inverse ? 8 : 0) << 18)
    | ((blink ? 4 : 0) << 18)
    | ((underline ? 2 : 0) << 18)
    | ((bold ? 1 : 0) << 18)
    | (colors.convert(fg) << 9)
    | colors.convert(bg);
};

Element.prototype.onScreenEvent = function(type, handler) {
  var listeners = this._slisteners = this._slisteners || [];
  listeners.push({ type: type, handler: handler });
  this.screen.on(type, handler);
};

Element.prototype.onceScreenEvent = function(type, handler) {
  var listeners = this._slisteners = this._slisteners || [];
  var entry = { type: type, handler: handler };
  listeners.push(entry);
  this.screen.once(type, function() {
    var i = listeners.indexOf(entry);
    if (~i) listeners.splice(i, 1);
    return handler.apply(this, arguments);
  });
};

Element.prototype.removeScreenEvent = function(type, handler) {
  var listeners = this._slisteners = this._slisteners || [];
  for (var i = 0; i < listeners.length; i++) {
    var listener = listeners[i];
    if (listener.type === type && listener.handler === handler) {
      listeners.splice(i, 1);
      if (this._slisteners.length === 0) {
        delete this._slisteners;
      }
      break;
    }
  }
  this.screen.removeListener(type, handler);
};

Element.prototype.free = function() {
  var listeners = this._slisteners = this._slisteners || [];
  for (var i = 0; i < listeners.length; i++) {
    var listener = listeners[i];
    this.screen.removeListener(listener.type, listener.handler);
  }
  delete this._slisteners;
};

Element.prototype.hide = function() {
  if (this.hidden) return;
  this.clearPos();
  this.hidden = true;
  this.emit('hide');
  if (this.screen.focused === this) {
    this.screen.rewindFocus();
  }
};

Element.prototype.show = function() {
  if (!this.hidden) return;
  this.hidden = false;
  this.emit('show');
};

Element.prototype.toggle = function() {
  return this.hidden ? this.show() : this.hide();
};

Element.prototype.focus = function() {
  return this.screen.focused = this;
};
Element.prototype.isfocus = function() {
  return this.screen.focused == this;
};

Element.prototype.setStyle = function(styles) {
  for(var p in styles) {
    if (this.style[p]!=undefined) this.style[p]=styles[p];
  }
};


Element.prototype.setContent = function(content, noClear, noTags) {
  var changed=this.content!=content;
  if (!noClear) this.clearPos();
  this.content = content || '';
  this.parseContent(noTags);
  this.emit('set content');
  if (changed) this.emit('change',content);
};

Element.prototype.getContent = function() {
  if (!this._clines) return '';
  return this._clines.fake.join('\n');
};

Element.prototype.setText = function(content, noClear) {
  content = content || '';
  content = content.replace(/\x1b\[[\d;]*m/g, '');
  return this.setContent(content, noClear, true);
};

Element.prototype.getText = function() {
  return this.getContent().replace(/\x1b\[[\d;]*m/g, '');
};

Element.prototype.parseContent = function(noTags) {
  var self=this;
  if (this.detached) return false;

  var width = this.width - this.iwidth;
  if (this._clines == null
      || this._clines.width !== width
      || this._clines.content !== this.content) {
    var content = this.content;

    content = content
      .replace(/[\x00-\x08\x0b-\x0c\x0e-\x1a\x1c-\x1f\x7f]/g, '')
      .replace(/\x1b(?!\[[\d;]*m)/g, '')
      .replace(/\r\n|\r/g, '\n')
      .replace(/\t/g, this.screen.tabc);

    if (this.screen.fullUnicode) {
      // double-width chars will eat the next char after render. create a
      // blank character after it so it doesn't eat the real next char.
      content = content.replace(unicode.chars.all, '$1\x03');
      // iTerm2 cannot render combining characters properly.
      if (this.screen.program.isiTerm2) {
        content = content.replace(unicode.chars.combining, '');
      }
    } else {
      // no double-width: replace them with question-marks.
      content = content.replace(unicode.chars.all, '??');
      // delete combining characters since they're 0-width anyway.
      // NOTE: We could drop this, the non-surrogates would get changed to ? by
      // the unicode filter, and surrogates changed to ? by the surrogate
      // regex. however, the user might expect them to be 0-width.
      // NOTE: Might be better for performance to drop!
      content = content.replace(unicode.chars.combining, '');
      // no surrogate pairs: replace them with question-marks.
      content = content.replace(unicode.chars.surrogate, '?');
      // XXX Deduplicate code here:
      // content = helpers.dropUnicode(content);
    }

    if (!noTags) {
      content = this._parseTags(content);
    }

    this._clines = this._wrapContent(content, width);
    this._clines.width = width;
    this._clines.content = this.content;
    this._clines.attr = this._parseAttr(this._clines);
    this._clines.ci = [];
    this._clines.reduce(function(total, line) {
        self._clines.ci.push(total);
        return total + line.length + 1;
      }, 0);

    this._pcontent = this._clines.join('\n');
    this.emit('parsed content');

    return true;
  }

  // Need to calculate this every time because the default fg/bg may change.
  this._clines.attr = this._parseAttr(this._clines) || this._clines.attr;

  return false;
};

// Convert `{red-fg}foo{/red-fg}` to `\x1b[31mfoo\x1b[39m`.
Element.prototype._parseTags = function(text) {
  if (!this.parseTags) return text;
  if (!/\{\/?[\w\-,;!#]*\}/.test(text)) return text;

  var program = this.screen.program
    , out = ''
    , state
    , bg = []
    , fg = []
    , flag = []
    , cap
    , slash
    , param
    , attr
    , esc;

  for (;;) {
    if (!esc && (cap = /^\{escape\}/.exec(text))) {
      text = text.substring(cap[0].length);
      esc = true;
      continue;
    }

    if (esc && (cap = /^([\s\S]+?)\{\/escape\}/.exec(text))) {
      text = text.substring(cap[0].length);
      out += cap[1];
      esc = false;
      continue;
    }

    if (esc) {
      // throw new Error('Unterminated escape tag.');
      out += text;
      break;
    }

    if (cap = /^\{(\/?)([\w\-,;!#]*)\}/.exec(text)) {
      text = text.substring(cap[0].length);
      slash = cap[1] === '/';
      param = cap[2].replace(/-/g, ' ');

      if (param === 'open') {
        out += '{';
        continue;
      } else if (param === 'close') {
        out += '}';
        continue;
      }

      if (param.slice(-3) === ' bg') state = bg;
      else if (param.slice(-3) === ' fg') state = fg;
      else state = flag;

      if (slash) {
        if (!param) {
          out += program._attr('normal');
          bg.length = 0;
          fg.length = 0;
          flag.length = 0;
        } else {
          attr = program._attr(param, false);
          if (attr == null) {
            out += cap[0];
          } else {
            // if (param !== state[state.length - 1]) {
            //   throw new Error('Misnested tags.');
            // }
            state.pop();
            if (state.length) {
              out += program._attr(state[state.length - 1]);
            } else {
              out += attr;
            }
          }
        }
      } else {
        if (!param) {
          out += cap[0];
        } else {
          attr = program._attr(param);
          if (attr == null) {
            out += cap[0];
          } else {
            state.push(param);
            out += attr;
          }
        }
      }

      continue;
    }

    if (cap = /^[\s\S]+?(?=\{\/?[\w\-,;!#]*\})/.exec(text)) {
      text = text.substring(cap[0].length);
      out += cap[0];
      continue;
    }

    out += text;
    break;
  }

  return out;
};

Element.prototype._parseAttr = function(lines) {
  var dattr = this.sattr(this.style)
    , attr = dattr
    , attrs = []
    , line
    , i
    , j
    , c;

  if (lines[0].attr === attr) {
    return;
  }

  for (j = 0; j < lines.length; j++) {
    line = lines[j];
    attrs[j] = attr;
    for (i = 0; i < line.length; i++) {
      if (line[i] === '\x1b') {
        if (c = /^\x1b\[[\d;]*m/.exec(line.substring(i))) {
          attr = this.screen.attrCode(c[0], attr, dattr);
          i += c[0].length - 1;
        }
      }
    }
  }

  return attrs;
};

Element.prototype._align = function(line, width, align) {
  if (!align) return line;
  //if (!align && !~line.indexOf('{|}')) return line;

  var cline = line.replace(/\x1b\[[\d;]*m/g, '')
    , len = cline.length
    , s = width - len;

  if (this.shrink) {
    s = 0;
  }

  if (len === 0) return line;
  if (s < 0) return line;

  if (align === 'center') {
    s = Array(((s / 2) | 0) + 1).join(' ');
    return s + line + s;
  } else if (align === 'right') {
    s = Array(s + 1).join(' ');
    return s + line;
  } else if (this.parseTags && ~line.indexOf('{|}')) {
    var parts = line.split('{|}');
    var cparts = cline.split('{|}');
    s = Math.max(width - cparts[0].length - cparts[1].length, 0);
    s = Array(s + 1).join(' ');
    return parts[0] + s + parts[1];
  }

  return line;
};

Element.prototype._wrapContent = function(content, width) {
  var tags = this.parseTags
    , state = this.align
    , wrap = this.wrap
    , margin = 0
    , rtof = []   // out-line mapping! (number [])
    , ftor = []   // line-out mapping (number [][])
    , out = []    // formatted output array with descriptor attributes
    , no = 0
    , line
    , align
    , cap
    , total
    , i
    , part
    , j
    , lines
    , rest;

  lines = content.split('\n');

  if (!content) {
    out.push(content);
    out.rtof = [0];
    out.ftor = [[0]];
    out.fake = lines;
    out.real = out;
    out.mwidth = 0;
    return out;
  }

  if (this.scrollbar) margin++;
  if (this.type === 'textarea') margin++;
  if (width > margin) width -= margin;

main:
  for (; no < lines.length; no++) {
    line = lines[no];
    align = state;

    ftor.push([]);

    // Handle alignment tags.
    if (tags) {
      if (cap = /^\{(left|center|right)\}/.exec(line)) {
        line = line.substring(cap[0].length);
        align = state = cap[1] !== 'left'
          ? cap[1]
          : null;
      }
      if (cap = /\{\/(left|center|right)\}$/.exec(line)) {
        line = line.slice(0, -cap[0].length);
        //state = null;
        state = this.align;
      }
    }

    // If the string is apparently too long, wrap it.
    while (line.length > width) {
      // Measure the real width of the string.
      if (this.break=='all')
        i=width;
      else for (i = 0, total = 0; i < line.length; i++) {
        while (line[i] === '\x1b') {
          while (line[i] && line[i++] !== 'm');
        }
        if (!line[i]) break;
        if (++total === width) {
          // If we're not wrapping the text, we have to finish up the rest of
          // the control sequences before cutting off the line.
          i++;
          if (!wrap) {
            rest = line.substring(i).match(/\x1b\[[^m]*m/g);
            rest = rest ? rest.join('') : '';
            out.push(this._align(line.substring(0, i) + rest, width, align));
            ftor[no].push(out.length - 1);
            rtof.push(no);
            continue main;
          }
          if (!this.screen.fullUnicode) {
            // Try to find a space to break on.
            if (i !== line.length) {
              j = i;
              while (j > i - 10 && j > 0 && line[--j] !== ' ');
              if (line[j] === ' ') i = j + 1;
            }
          } else {
            // Try to find a character to break on.
            if (i !== line.length) {
              // <XXX>
              // Compensate for surrogate length
              // counts on wrapping (experimental):
              // NOTE: Could optimize this by putting
              // it in the parent for loop.
              if (unicode.isSurrogate(line, i)) i--;
              for (var s = 0, n = 0; n < i; n++) {
                if (unicode.isSurrogate(line, n)) s++, n++;
              }
              i += s;
              // </XXX>
              j = i;
              // Break _past_ space.
              // Break _past_ double-width chars.
              // Break _past_ surrogate pairs.
              // Break _past_ combining chars.
              while (j > i - 10 && j > 0) {
                j--;
                if (line[j] === ' '
                    || line[j] === '\x03'
                    || (unicode.isSurrogate(line, j - 1) && line[j + 1] !== '\x03')
                    || unicode.isCombining(line, j)) {
                  break;
                }
              }
              if (line[j] === ' '
                  || line[j] === '\x03'
                  || (unicode.isSurrogate(line, j - 1) && line[j + 1] !== '\x03')
                  || unicode.isCombining(line, j)) {
                i = j + 1;
              }
            }
          }
          break;
        }
      }

      part = line.substring(0, i);
      line = line.substring(i);

      out.push(this._align(part, width, align));
      ftor[no].push(out.length - 1);
      rtof.push(no);

      // Make sure we didn't wrap the line to the very end, otherwise
      // we get a pointless empty line after a newline.
      if (line === '') continue main;

      // If only an escape code got cut off, at it to `part`.
      if (/^(?:\x1b[\[\d;]*m)+$/.test(line)) {
        out[out.length - 1] += line;
        continue main;
      }
    }

    out.push(this._align(line, width, align));
    ftor[no].push(out.length - 1);
    rtof.push(no);
  }

  out.rtof = rtof;
  out.ftor = ftor;
  out.fake = lines;
  out.real = out;

  out.mwidth = out.reduce(function(current, line) {
    line = line.replace(/\x1b\[[\d;]*m/g, '');
    return line.length > current
      ? line.length
      : current;
  }, 0);

  return out;
};

/* Depricated
Element.prototype.__defineGetter__('visible', function() {
  var el = this;
  do {
    if (el.detached) return false;
    if (el.hidden) return false;
    // if (!el.lpos) return false;
    // if (el.position.width === 0 || el.position.height === 0) return false;
  } while (el = el.parent);
  return true;
});

Element.prototype.__defineGetter__('_detached', function() {
  var el = this;
  do {
    if (el.type === 'screen') return false;
    if (!el.parent) return true;
  } while (el = el.parent);
  return false;
});
*/
Object.defineProperty(Element.prototype,'visible',{
  get: function () {
    var el = this;
    do {
      if (el.detached) return false;
      if (el.hidden) return false;
      // if (!el.lpos) return false;
      // if (el.position.width === 0 || el.position.height === 0) return false;
    } while (el = el.parent);
    return true;
  }
});
Object.defineProperty(Element.prototype,'_detached',{
  get: function () {
    var el = this;
    do {
      if (el.type === 'screen') return false;
      if (!el.parent) return true;
    } while (el = el.parent);
    return false;
  }
});

Element.prototype.enableMouse = function() {
  this.screen._listenMouse(this);
};

Element.prototype.enableKeys = function() {
  this.screen._listenKeys(this);
};

Element.prototype.enableInput = function() {
  this.screen._listenMouse(this);
  this.screen._listenKeys(this);
};

/* Depricated:
Element.prototype.__defineGetter__('draggable', function() {
  return this._draggable === true;
});

Element.prototype.__defineSetter__('draggable', function(draggable) {
  return draggable ? this.enableDrag(draggable) : this.disableDrag();
});
*/
Object.defineProperty(Element.prototype,'draggable',{
  get: function () {
    return this._draggable === true;  
  },
  set: function (draggable) {
    return draggable ? this.enableDrag(draggable) : this.disableDrag();  
  }
});

Element.prototype.enableDrag = function(verify) {
  var self = this;

  if (this._draggable) return true;

  if (typeof verify !== 'function') {
    verify = function() { return true; };
  }

  this.enableMouse();

  this.on('mousedown', this._dragMD = function(data) {
    if (self.screen._dragging) return;
    if (!verify(data)) return;
    self.screen._dragging = self;
    self._drag = {
      x: data.x - self.aleft,
      y: data.y - self.atop
    };
    self.setFront();
  });

  this.onScreenEvent('mouse', this._dragM = function(data) {
    if (self.screen._dragging !== self) return;

    if (data.action !== 'mousedown' && data.action !== 'mousemove') {
      delete self.screen._dragging;
      delete self._drag;
      return;
    }

    // This can happen in edge cases where the user is
    // already dragging and element when it is detached.
    if (!self.parent) return;

    var ox = self._drag.x
      , oy = self._drag.y
      , px = self.parent.aleft
      , py = self.parent.atop
      , x = data.x - px - ox
      , y = data.y - py - oy;

    if (self.position.right != null) {
      if (self.position.left != null) {
        self.width = '100%-' + (self.parent.width - self.width);
      }
      self.position.right = null;
    }

    if (self.position.bottom != null) {
      if (self.position.top != null) {
        self.height = '100%-' + (self.parent.height - self.height);
      }
      self.position.bottom = null;
    }

    self.rleft = x;
    self.rtop = y;

    self.screen.render();
  });

  return this._draggable = true;
};

Element.prototype.disableDrag = function() {
  if (!this._draggable) return false;
  delete this.screen._dragging;
  delete this._drag;
  this.removeListener('mousedown', this._dragMD);
  this.removeScreenEvent('mouse', this._dragM);
  return this._draggable = false;
};

Element.prototype.key = function() {
  return this.screen.program.key.apply(this, arguments);
};

Element.prototype.onceKey = function() {
  return this.screen.program.onceKey.apply(this, arguments);
};

Element.prototype.unkey =
Element.prototype.removeKey = function() {
  return this.screen.program.unkey.apply(this, arguments);
};

Element.prototype.setIndex = function(index) {
  if (!this.parent) return;

  if (index < 0) {
    index = this.parent.children.length + index;
  }

  index = Math.max(index, 0);
  index = Math.min(index, this.parent.children.length - 1);

  var i = this.parent.children.indexOf(this);
  if (!~i) return;

  var item = this.parent.children.splice(i, 1)[0];
  this.parent.children.splice(index, 0, item);
};

Element.prototype.setFront = function() {
  return this.setIndex(-1);
};

Element.prototype.setBack = function() {
  return this.setIndex(0);
};

Element.prototype.clearPos = function(get, override) {
  if (this.detached) return;
  var lpos = this._getCoords(get);
  if (!lpos) return;
  this.screen.clearRegion(
    lpos.xi, lpos.xl,
    lpos.yi, lpos.yl,
    override);
};
Element.prototype.getLabel = function() {
  if (this._label) return this._label.getContent();
}

Element.prototype.setLabel = function(options) {
  var self = this;
  var Box = Require('term/widgets/box');

  if (typeof options === 'string') {
    options = { text: options };
  }

  if (this._label) {
    this._label.setContent(options.text);
    if (options.side !== 'right') {
      this._label.rleft = 2 + (this.border ? -1 : 0);
      this._label.position.right = undefined;
      if (!this.screen.autoPadding) {
        this._label.rleft = 2;
      }
    } else {
      this._label.rright = 2 + (this.border ? -1 : 0);
      this._label.position.left = undefined;
      if (!this.screen.autoPadding) {
        this._label.rright = 2;
      }
    }
    return;
  }

  this._label = new Box({
    screen: this.screen,
    parent: this,
    content: options.text,
    top: -this.itop,
    tags: this.parseTags,
    shrink: true,
    style: this.style.label
  });

  if (options.side !== 'right') {
    this._label.rleft = 2 - this.ileft;
  } else {
    this._label.rright = 2 - this.iright;
  }
  
  if (this.border && this.border.type=='none') this._label.rleft=0;
  
  this._label._isLabel = true;

  if (!this.screen.autoPadding) {
    if (options.side !== 'right') {
      this._label.rleft = 2;
    } else {
      this._label.rright = 2;
    }
    this._label.rtop = 0;
  }

  var reposition = function() {
    self._label.rtop = (self.childBase || 0) - self.itop;
    if (!self.screen.autoPadding) {
      self._label.rtop = (self.childBase || 0);
    }
    self.screen.render();
  };

  this.on('scroll', this._labelScroll = function() {
    reposition();
  });

  this.on('resize', this._labelResize = function() {
    nextTick(function() {
      reposition();
    });
  });
};

Element.prototype.removeLabel = function() {
  if (!this._label) return;
  this.removeListener('scroll', this._labelScroll);
  this.removeListener('resize', this._labelResize);
  this._label.detach();
  delete this._labelScroll;
  delete this._labelResize;
  delete this._label;
};

Element.prototype.setHover = function(options) {
  if (typeof options === 'string') {
    options = { text: options };
  }

  this._hoverOptions = options;
  this.enableMouse();
  this.screen._initHover();
};

Element.prototype.removeHover = function() {
  delete this._hoverOptions;
  if (!this.screen._hoverText || this.screen._hoverText.detached) return;
  this.screen._hoverText.detach();
  this.screen.render();
};

/**
 * Positioning
 */

// The below methods are a bit confusing: basically
// whenever Box.render is called `lpos` gets set on
// the element, an object containing the rendered
// coordinates. Since these don't update if the
// element is moved somehow, they're unreliable in
// that situation. However, if we can guarantee that
// lpos is good and up to date, it can be more
// accurate than the calculated positions below.
// In this case, if the element is being rendered,
// it's guaranteed that the parent will have been
// rendered first, in which case we can use the
// parant's lpos instead of recalculating it's
// position (since that might be wrong because
// it doesn't handle content shrinkage).

Element.prototype._getPos = function() {
  var pos = this.lpos;

  assert.ok(pos);

  if (pos.aleft != null) return pos;

  pos.aleft = pos.xi;
  pos.atop = pos.yi;
  pos.aright = this.screen.cols - pos.xl;
  pos.abottom = this.screen.rows - pos.yl;
  pos.width = pos.xl - pos.xi;
  pos.height = pos.yl - pos.yi;

  return pos;
};

/**
 * Position Getters
 */

Element.prototype._getWidth = function(get) {
  var parent = get ? this.parent._getPos() : this.parent
    , width = this.position.width
    , left
    , expr;

  if (typeof width === 'string') {
    if (width === 'half') width = '50%';
    expr = width.split(/(?=\+|-)/);
    width = expr[0];
    width = +width.slice(0, -1) / 100;
    width = parent.width * width | 0;
    width += +(expr[1] || 0);
    return width;
  }

  // This is for if the element is being streched or shrunken.
  // Although the width for shrunken elements is calculated
  // in the render function, it may be calculated based on
  // the content width, and the content width is initially
  // decided by the width the element, so it needs to be
  // calculated here.
  if (width == null) {
    left = this.position.left || 0;
    if (typeof left === 'string') {
      if (left === 'center') left = '50%';
      expr = left.split(/(?=\+|-)/);
      left = expr[0];
      left = +left.slice(0, -1) / 100;
      left = parent.width * left | 0;
      left += +(expr[1] || 0);
    }
    width = parent.width - (this.position.right || 0) - left;
    if (this.screen.autoPadding) {
      if ((this.position.left != null || this.position.right == null)
          && this.position.left !== 'center') {
        width -= this.parent.ileft;
      }
      width -= this.parent.iright;
    }
  }

  return width;
};

/* Depricated:
Element.prototype.__defineGetter__('width', function() {
  return this._getWidth(false);
});
*/
Object.defineProperty(Element.prototype,'width',{
  get: function () {return this._getWidth(false);},
  set: function (val) {
    if (this.position.width === val) return;
    if (/^\d+$/.test(val)) val = +val;
    this.emit('resize');
    this.clearPos();
    return this.position.width = val;
  }
});

Element.prototype._getHeight = function(get) {
  var parent = get ? this.parent._getPos() : this.parent
    , height = this.position.height
    , top
    , expr;

  if (typeof height === 'string') {
    if (height === 'half') height = '50%';
    expr = height.split(/(?=\+|-)/);
    height = expr[0];
    height = +height.slice(0, -1) / 100;
    height = parent.height * height | 0;
    height += +(expr[1] || 0);
    return height;
  }

  // This is for if the element is being streched or shrunken.
  // Although the width for shrunken elements is calculated
  // in the render function, it may be calculated based on
  // the content width, and the content width is initially
  // decided by the width the element, so it needs to be
  // calculated here.
  if (height == null) {
    top = this.position.top || 0;
    if (typeof top === 'string') {
      if (top === 'center') top = '50%';
      expr = top.split(/(?=\+|-)/);
      top = expr[0];
      top = +top.slice(0, -1) / 100;
      top = parent.height * top | 0;
      top += +(expr[1] || 0);
    }
    height = parent.height - (this.position.bottom || 0) - top;
    if (this.screen.autoPadding) {
      if ((this.position.top != null
          || this.position.bottom == null)
          && this.position.top !== 'center') {
        height -= this.parent.itop;
      }
      height -= this.parent.ibottom;
    }
  }

  return height;
};

/* Depricated 
Element.prototype.__defineGetter__('height', function() {
  return this._getHeight(false);
});
*/
Object.defineProperty(Element.prototype,'height',{
  get: function () {return this._getHeight(false);},
  set: function (val) {
    if (this.position.height === val) return;
    if (/^\d+$/.test(val)) val = +val;
    this.emit('resize');
    this.clearPos();
    return this.position.height = val;
  }
});

Element.prototype._getLeft = function(get) {
  var parent = get ? this.parent._getPos() : this.parent
    , left = this.position.left || 0
    , expr;

  if (typeof left === 'string') {
    if (left === 'center') left = '50%';
    expr = left.split(/(?=\+|-)/);
    left = expr[0];
    left = +left.slice(0, -1) / 100;
    left = parent.width * left | 0;
    left += +(expr[1] || 0);
    if (this.position.left === 'center') {
      left -= this._getWidth(get) / 2 | 0;
    }
  }

  if (this.position.left == null && this.position.right != null) {
    return this.screen.cols - this._getWidth(get) - this._getRight(get);
  }

  if (this.screen.autoPadding) {
    if ((this.position.left != null
        || this.position.right == null)
        && this.position.left !== 'center') {
      left += this.parent.ileft;
    }
  }

  return (parent.aleft || 0) + left;
};

/* Depricated:
Element.prototype.__defineGetter__('aleft', function() {
  return this._getLeft(false);
});
*/
Object.defineProperty(Element.prototype,'aleft',{
  get: function () {return this._getLeft(false);},
  set: function (val) {
    var expr;
    if (typeof val === 'string') {
      if (val === 'center') {
        val = this.screen.width / 2 | 0;
        val -= this.width / 2 | 0;
      } else {
        expr = val.split(/(?=\+|-)/);
        val = expr[0];
        val = +val.slice(0, -1) / 100;
        val = this.screen.width * val | 0;
        val += +(expr[1] || 0);
      }
    }
    val -= this.parent.aleft;
    if (this.position.left === val) return;
    this.emit('move');
    this.clearPos();
    return this.position.left = val;
  }
});

Element.prototype._getRight = function(get) {
  var parent = get ? this.parent._getPos() : this.parent
    , right= this.position.right || 0;

  // @blab
  if (typeof right === 'string') {
    // Hack; relative right align of elements; e.g., 50% of parent width
    // usually used with one left and one right aligned element in a row (both relative alignments)
    expr = right.split(/(?=\+|-)/);
    right = expr[0];
    right = +right.slice(0, -1) / 100;
    right = Math.ceil(parent.width * right) | 0;
    return right; 
  }

  if (this.position.right == null && this.position.left != null) {
    right = this.screen.cols - (this._getLeft(get) + this._getWidth(get));
    if (this.screen.autoPadding) {
      right += this.parent.iright;
    }
    return right;
  }

  right = (parent.aright || 0) + (this.position.right || 0);

  if (this.screen.autoPadding) {
    right += this.parent.iright;
  }

  return right;
};

/* Depricated
Element.prototype.__defineGetter__('aright', function() {
  return this._getRight(false);
});
*/
Object.defineProperty(Element.prototype,'aright',{
  get: function () {return this._getRight(false);},
  set: function (val) {
    val -= this.parent.aright;
    if (this.position.right === val) return;
    this.emit('move');
    this.clearPos();
    return this.position.right = val;  
  }
});

Element.prototype._getTop = function(get) {
  var parent = get ? this.parent._getPos() : this.parent
    , top = this.position.top || 0
    , expr;

  if (typeof top === 'string') {
    if (top === 'center') top = '50%';
    expr = top.split(/(?=\+|-)/);
    top = expr[0];
    top = +top.slice(0, -1) / 100;
    top = parent.height * top | 0;
    top += +(expr[1] || 0);
    if (this.position.top === 'center') {
      top -= this._getHeight(get) / 2 | 0;
    }
  }

  if (this.position.top == null && this.position.bottom != null) {
    return this.screen.rows - this._getHeight(get) - this._getBottom(get);
  }

  if (this.screen.autoPadding) {
    if ((this.position.top != null
        || this.position.bottom == null)
        && this.position.top !== 'center') {
      top += this.parent.itop;
    }
  }

  return (parent.atop || 0) + top;
};

/* Depricated
Element.prototype.__defineGetter__('atop', function() {
  return this._getTop(false);
});
*/
Object.defineProperty(Element.prototype,'atop',{
  get: function () {return this._getTop(false);},
  set: function (val) {
    var expr;
    if (typeof val === 'string') {
      if (val === 'center') {
        val = this.screen.height / 2 | 0;
        val -= this.height / 2 | 0;
      } else {
        expr = val.split(/(?=\+|-)/);
        val = expr[0];
        val = +val.slice(0, -1) / 100;
        val = this.screen.height * val | 0;
        val += +(expr[1] || 0);
      }
    }
    val -= this.parent.atop;
    if (this.position.top === val) return;
    this.emit('move');
    this.clearPos();
    return this.position.top = val; 
  }

});

Element.prototype._getBottom = function(get) {
  var parent = get ? this.parent._getPos() : this.parent
    , bottom;

  if (this.position.bottom == null && this.position.top != null) {
    bottom = this.screen.rows - (this._getTop(get) + this._getHeight(get));
    if (this.screen.autoPadding) {
      bottom += this.parent.ibottom;
    }
    return bottom;
  }

  bottom = (parent.abottom || 0) + (this.position.bottom || 0);

  if (this.screen.autoPadding) {
    bottom += this.parent.ibottom;
  }

  return bottom;
};

/* Depricated
Element.prototype.__defineGetter__('abottom', function() {
  return this._getBottom(false);
});

Element.prototype.__defineGetter__('rleft', function() {
  return this.aleft - this.parent.aleft;
});

Element.prototype.__defineGetter__('rright', function() {
  return this.aright - this.parent.aright;
});

Element.prototype.__defineGetter__('rtop', function() {
  return this.atop - this.parent.atop;
});

Element.prototype.__defineGetter__('rbottom', function() {
  return this.abottom - this.parent.abottom;
});
*/
Object.defineProperty(Element.prototype,'abottom',{
  get: function () {return this._getBottom(false);},
  set: function (val) {
    val -= this.parent.abottom;
    if (this.position.bottom === val) return;
    this.emit('move');
    this.clearPos();
    return this.position.bottom = val;  
  }
});
Object.defineProperty(Element.prototype,'rleft',{
  get: function () {return this.aleft - this.parent.aleft;},
  set: function (val) {
    if (this.position.left === val) return;
    if (/^\d+$/.test(val)) val = +val;
    this.emit('move');
    this.clearPos();
    return this.position.left = val;  
  }
});
Object.defineProperty(Element.prototype,'rright',{
  get: function () {return this.aright - this.parent.aright;},
  set: function (val) {
    if (this.position.right === val) return;
    this.emit('move');
    this.clearPos();
    return this.position.right = val;  
  }
});
Object.defineProperty(Element.prototype,'rtop',{
  get: function () {return this.atop - this.parent.atop;},
  set: function (val) {
    if (this.position.top === val) return;
    if (/^\d+$/.test(val)) val = +val;
    this.emit('move');
    this.clearPos();
    return this.position.top = val;  
  }
});
Object.defineProperty(Element.prototype,'rbottom',{
  get: function () {return this.abottom - this.parent.abottom;},
  set: function (val) {
    if (this.position.bottom === val) return;
    this.emit('move');
    this.clearPos();
    return this.position.bottom = val;  
  }
});

/**
 * Position Setters
 */

// NOTE:
// For aright, abottom, right, and bottom:
// If position.bottom is null, we could simply set top instead.
// But it wouldn't replicate bottom behavior appropriately if
// the parent was resized, etc.
/* Depricated
Element.prototype.__defineSetter__('width', function(val) {
  if (this.position.width === val) return;
  if (/^\d+$/.test(val)) val = +val;
  this.emit('resize');
  this.clearPos();
  return this.position.width = val;
});

Element.prototype.__defineSetter__('height', function(val) {
  if (this.position.height === val) return;
  if (/^\d+$/.test(val)) val = +val;
  this.emit('resize');
  this.clearPos();
  return this.position.height = val;
});

Element.prototype.__defineSetter__('aleft', function(val) {
  var expr;
  if (typeof val === 'string') {
    if (val === 'center') {
      val = this.screen.width / 2 | 0;
      val -= this.width / 2 | 0;
    } else {
      expr = val.split(/(?=\+|-)/);
      val = expr[0];
      val = +val.slice(0, -1) / 100;
      val = this.screen.width * val | 0;
      val += +(expr[1] || 0);
    }
  }
  val -= this.parent.aleft;
  if (this.position.left === val) return;
  this.emit('move');
  this.clearPos();
  return this.position.left = val;
});

Element.prototype.__defineSetter__('aright', function(val) {
  val -= this.parent.aright;
  if (this.position.right === val) return;
  this.emit('move');
  this.clearPos();
  return this.position.right = val;
});

Element.prototype.__defineSetter__('atop', function(val) {
  var expr;
  if (typeof val === 'string') {
    if (val === 'center') {
      val = this.screen.height / 2 | 0;
      val -= this.height / 2 | 0;
    } else {
      expr = val.split(/(?=\+|-)/);
      val = expr[0];
      val = +val.slice(0, -1) / 100;
      val = this.screen.height * val | 0;
      val += +(expr[1] || 0);
    }
  }
  val -= this.parent.atop;
  if (this.position.top === val) return;
  this.emit('move');
  this.clearPos();
  return this.position.top = val;
});

Element.prototype.__defineSetter__('abottom', function(val) {
  val -= this.parent.abottom;
  if (this.position.bottom === val) return;
  this.emit('move');
  this.clearPos();
  return this.position.bottom = val;
});

Element.prototype.__defineSetter__('rleft', function(val) {
  if (this.position.left === val) return;
  if (/^\d+$/.test(val)) val = +val;
  this.emit('move');
  this.clearPos();
  return this.position.left = val;
});

Element.prototype.__defineSetter__('rright', function(val) {
  if (this.position.right === val) return;
  this.emit('move');
  this.clearPos();
  return this.position.right = val;
});

Element.prototype.__defineSetter__('rtop', function(val) {
  if (this.position.top === val) return;
  if (/^\d+$/.test(val)) val = +val;
  this.emit('move');
  this.clearPos();
  return this.position.top = val;
});

Element.prototype.__defineSetter__('rbottom', function(val) {
  if (this.position.bottom === val) return;
  this.emit('move');
  this.clearPos();
  return this.position.bottom = val;
});
*/

/* Depricated
Element.prototype.__defineGetter__('ileft', function() {
  return (this.border ? 1 : 0) + this.padding.left;
  // return (this.border && this.border.left ? 1 : 0) + this.padding.left;
});

Element.prototype.__defineGetter__('itop', function() {
  return (this.border ? 1 : 0) + this.padding.top;
  // return (this.border && this.border.top ? 1 : 0) + this.padding.top;
});

Element.prototype.__defineGetter__('iright', function() {
  return (this.border ? 1 : 0) + this.padding.right;
  // return (this.border && this.border.right ? 1 : 0) + this.padding.right;
});

Element.prototype.__defineGetter__('ibottom', function() {
  return (this.border ? 1 : 0) + this.padding.bottom;
  // return (this.border && this.border.bottom ? 1 : 0) + this.padding.bottom;
});

Element.prototype.__defineGetter__('iwidth', function() {
  // return (this.border
  //   ? ((this.border.left ? 1 : 0) + (this.border.right ? 1 : 0)) : 0)
  //   + this.padding.left + this.padding.right;
  return (this.border ? 2 : 0) + this.padding.left + this.padding.right;
});

Element.prototype.__defineGetter__('iheight', function() {
  // return (this.border
  //   ? ((this.border.top ? 1 : 0) + (this.border.bottom ? 1 : 0)) : 0)
  //   + this.padding.top + this.padding.bottom;
  return (this.border ? 2 : 0) + this.padding.top + this.padding.bottom;
});

Element.prototype.__defineGetter__('tpadding', function() {
  return this.padding.left + this.padding.top
    + this.padding.right + this.padding.bottom;
});
*/
Object.defineProperty(Element.prototype,'ileft',{
  get: function () {
    return (this.border ? 1 : 0) + this.padding.left;
    // return (this.border && this.border.left ? 1 : 0) + this.padding.left;
  }
});
Object.defineProperty(Element.prototype,'itop',{
  get: function () {
    return (this.border ? 1 : 0) + this.padding.top;
    // return (this.border && this.border.top ? 1 : 0) + this.padding.top;
  }
});
Object.defineProperty(Element.prototype,'iright',{
  get: function () {
    return (this.border ? 1 : 0) + this.padding.right;
    // return (this.border && this.border.right ? 1 : 0) + this.padding.right;
  }
});
Object.defineProperty(Element.prototype,'ibottom',{
  get: function () {
    return (this.border ? 1 : 0) + this.padding.bottom;
    // return (this.border && this.border.bottom ? 1 : 0) + this.padding.bottom;
  }
});
Object.defineProperty(Element.prototype,'iwidth',{
  get: function () {
    // return (this.border
    //   ? ((this.border.left ? 1 : 0) + (this.border.right ? 1 : 0)) : 0)
    //   + this.padding.left + this.padding.right;
    return (this.border ? 2 : 0) + this.padding.left + this.padding.right;
  }
});
Object.defineProperty(Element.prototype,'iheight',{
  get: function () {
    // return (this.border
    //   ? ((this.border.top ? 1 : 0) + (this.border.bottom ? 1 : 0)) : 0)
    //   + this.padding.top + this.padding.bottom;
    return (this.border ? 2 : 0) + this.padding.top + this.padding.bottom;
  }
});
Object.defineProperty(Element.prototype,'tpadding',{
  get: function () {
    return this.padding.left + this.padding.top
      + this.padding.right + this.padding.bottom;
  }
});

/**
 * Relative coordinates as default properties
 */
/* Depricated
Element.prototype.__defineGetter__('left', function() {
  return this.rleft;
});

Element.prototype.__defineGetter__('right', function() {
  return this.rright;
});

Element.prototype.__defineGetter__('top', function() {
  return this.rtop;
});

Element.prototype.__defineGetter__('bottom', function() {
  return this.rbottom;
});
*/
Object.defineProperty(Element.prototype,'left',{
  get: function () {return this.rleft;},
  set: function (val) {
    return this.rleft = val;  
  }
});
Object.defineProperty(Element.prototype,'right',{
  get: function () {return this.rright;},
  set: function (val) {
    return this.rright = val; 
  }
});
Object.defineProperty(Element.prototype,'top',{
  get: function () {return this.rtop;},
  set: function (val) {
    return this.rtop = val;  
  }
});
Object.defineProperty(Element.prototype,'bottom',{
  get: function () {return this.rbottom;},
  set: function (val) {
    return this.rbottom = val;    
  }
});
  
/* Depricated
Element.prototype.__defineSetter__('left', function(val) {
  return this.rleft = val;
});

Element.prototype.__defineSetter__('right', function(val) {
  return this.rright = val;
});

Element.prototype.__defineSetter__('top', function(val) {
  return this.rtop = val;
});

Element.prototype.__defineSetter__('bottom', function(val) {
  return this.rbottom = val;
});
*/

/**
 * Rendering - here be dragons
 */

Element.prototype._getShrinkBox = function(xi, xl, yi, yl, get) {
  if (!this.children.length) {
    return { xi: xi, xl: xi + 1, yi: yi, yl: yi + 1 };
  }

  var i, el, ret, mxi = xi, mxl = xi + 1, myi = yi, myl = yi + 1;

  // This is a chicken and egg problem. We need to determine how the children
  // will render in order to determine how this element renders, but it in
  // order to figure out how the children will render, they need to know
  // exactly how their parent renders, so, we can give them what we have so
  // far.
  var _lpos;
  if (get) {
    _lpos = this.lpos;
    this.lpos = { xi: xi, xl: xl, yi: yi, yl: yl };
    //this.shrink = false;
  }

  for (i = 0; i < this.children.length; i++) {
    el = this.children[i];

    ret = el._getCoords(get);

    // Or just (seemed to work, but probably not good):
    // ret = el.lpos || this.lpos;

    if (!ret) continue;

    // Since the parent element is shrunk, and the child elements think it's
    // going to take up as much space as possible, an element anchored to the
    // right or bottom will inadvertantly make the parent's shrunken size as
    // large as possible. So, we can just use the height and/or width the of
    // element.
    // if (get) {
    if (el.position.left == null && el.position.right != null) {
      ret.xl = xi + (ret.xl - ret.xi);
      ret.xi = xi;
      if (this.screen.autoPadding) {
        // Maybe just do this no matter what.
        ret.xl += this.ileft;
        ret.xi += this.ileft;
      }
    }
    if (el.position.top == null && el.position.bottom != null) {
      ret.yl = yi + (ret.yl - ret.yi);
      ret.yi = yi;
      if (this.screen.autoPadding) {
        // Maybe just do this no matter what.
        ret.yl += this.itop;
        ret.yi += this.itop;
      }
    }

    if (ret.xi < mxi) mxi = ret.xi;
    if (ret.xl > mxl) mxl = ret.xl;
    if (ret.yi < myi) myi = ret.yi;
    if (ret.yl > myl) myl = ret.yl;
  }

  if (get) {
    this.lpos = _lpos;
    //this.shrink = true;
  }

  if (this.position.width == null
      && (this.position.left == null
      || this.position.right == null)) {
    if (this.position.left == null && this.position.right != null) {
      xi = xl - (mxl - mxi);
      if (!this.screen.autoPadding) {
        xi -= this.padding.left + this.padding.right;
      } else {
        xi -= this.ileft;
      }
    } else {
      xl = mxl;
      if (!this.screen.autoPadding) {
        xl += this.padding.left + this.padding.right;
        // XXX Temporary workaround until we decide to make autoPadding default.
        // See widget-listtable.js for an example of why this is necessary.
        // XXX Maybe just to this for all this being that this would affect
        // width shrunken normal shrunken lists as well.
        // if (this._isList) {
        if (this.type === 'list-table') {
          xl -= this.padding.left + this.padding.right;
          xl += this.iright;
        }
      } else {
        //xl += this.padding.right;
        xl += this.iright;
      }
    }
  }

  if (this.position.height == null
      && (this.position.top == null
      || this.position.bottom == null)
      && (!this.scrollable || this._isList)) {
    // NOTE: Lists get special treatment if they are shrunken - assume they
    // want all list items showing. This is one case we can calculate the
    // height based on items/boxes.
    if (this._isList) {
      myi = 0 - this.itop;
      myl = this.items.length + this.ibottom;
    }
    if (this.position.top == null && this.position.bottom != null) {
      yi = yl - (myl - myi);
      if (!this.screen.autoPadding) {
        yi -= this.padding.top + this.padding.bottom;
      } else {
        yi -= this.itop;
      }
    } else {
      yl = myl;
      if (!this.screen.autoPadding) {
        yl += this.padding.top + this.padding.bottom;
      } else {
        yl += this.ibottom;
      }
    }
  }

  return { xi: xi, xl: xl, yi: yi, yl: yl };
};

Element.prototype._getShrinkContent = function(xi, xl, yi, yl) {
  // @blab+
  if (!this._clines) return { xi: xi, xl: xl, yi: yi, yl: yl };
  var h = this._clines.length
    , w = this._clines.mwidth || 1;

  if (this.position.width == null
      && (this.position.left == null
      || this.position.right == null)) {
    if (this.position.left == null && this.position.right != null) {
      xi = xl - w - this.iwidth;
    } else {
      xl = xi + w + this.iwidth;
    }
  }

  if (this.position.height == null
      && (this.position.top == null
      || this.position.bottom == null)
      && (!this.scrollable || this._isList)) {
    if (this.position.top == null && this.position.bottom != null) {
      yi = yl - h - this.iheight;
    } else {
      yl = yi + h + this.iheight;
    }
  }

  return { xi: xi, xl: xl, yi: yi, yl: yl };
};

Element.prototype._getShrink = function(xi, xl, yi, yl, get) {
  var shrinkBox = this._getShrinkBox(xi, xl, yi, yl, get)
    , shrinkContent = this._getShrinkContent(xi, xl, yi, yl, get)
    , xll = xl
    , yll = yl;

  // Figure out which one is bigger and use it.
  if (shrinkBox.xl - shrinkBox.xi > shrinkContent.xl - shrinkContent.xi) {
    xi = shrinkBox.xi;
    xl = shrinkBox.xl;
  } else {
    xi = shrinkContent.xi;
    xl = shrinkContent.xl;
  }

  if (shrinkBox.yl - shrinkBox.yi > shrinkContent.yl - shrinkContent.yi) {
    yi = shrinkBox.yi;
    yl = shrinkBox.yl;
  } else {
    yi = shrinkContent.yi;
    yl = shrinkContent.yl;
  }

  // Recenter shrunken elements.
  if (xl < xll && this.position.left === 'center') {
    xll = (xll - xl) / 2 | 0;
    xi += xll;
    xl += xll;
  }

  if (yl < yll && this.position.top === 'center') {
    yll = (yll - yl) / 2 | 0;
    yi += yll;
    yl += yll;
  }

  return { xi: xi, xl: xl, yi: yi, yl: yl };
};

Element.prototype._getCoords = function(get, noscroll) {
  if (this.hidden) return;

  // if (this.parent._rendering) {
  //   get = true;
  // }

  var xi = this._getLeft(get)
    , xl = xi + this._getWidth(get)
    , yi = this._getTop(get)
    , yl = yi + this._getHeight(get)
    , base = this.childBase || 0
    , el = this
    , fixed = this.fixed
    , coords
    , v
    , noleft
    , noright
    , notop
    , nobot
    , ppos
    , b;

  // Attempt to shrink the element base on the
  // size of the content and child elements.
  if (this.shrink) {
    coords = this._getShrink(xi, xl, yi, yl, get);
    xi = coords.xi, xl = coords.xl;
    yi = coords.yi, yl = coords.yl;
  }

  // Find a scrollable ancestor if we have one.
  while (el = el.parent) {
    if (el.scrollable) {
      if (fixed) {
        fixed = false;
        continue;
      }
      break;
    }
  }

  // Check to make sure we're visible and
  // inside of the visible scroll area.
  // NOTE: Lists have a property where only
  // the list items are obfuscated.

  // Old way of doing things, this would not render right if a shrunken element
  // with lots of boxes in it was within a scrollable element.
  // See: $ node test/widget-shrink-fail.js
  // var thisparent = this.parent;

  var thisparent = el;
  if (el && !noscroll) {
    ppos = thisparent.lpos;

    // The shrink option can cause a stack overflow
    // by calling _getCoords on the child again.
    // if (!get && !thisparent.shrink) {
    //   ppos = thisparent._getCoords();
    // }

    if (!ppos) return;

    // TODO: Figure out how to fix base (and cbase to only
    // take into account the *parent's* padding.

    yi -= ppos.base;
    yl -= ppos.base;

    b = thisparent.border ? 1 : 0;

    // XXX
    // Fixes non-`fixed` labels to work with scrolling (they're ON the border):
    // if (this.position.left < 0
    //     || this.position.right < 0
    //     || this.position.top < 0
    //     || this.position.bottom < 0) {
    if (this._isLabel) {
      b = 0;
    }

    if (yi < ppos.yi + b) {
      if (yl - 1 < ppos.yi + b) {
        // Is above.
        return;
      } else {
        // Is partially covered above.
        notop = true;
        v = ppos.yi - yi;
        if (this.border) v--;
        if (thisparent.border) v++;
        base += v;
        yi += v;
      }
    } else if (yl > ppos.yl - b) {
      if (yi > ppos.yl - 1 - b) {
        // Is below.
        return;
      } else {
        // Is partially covered below.
        nobot = true;
        v = yl - ppos.yl;
        if (this.border) v--;
        if (thisparent.border) v++;
        yl -= v;
      }
    }

    // Shouldn't be necessary.
    // assert.ok(yi < yl);
    if (yi >= yl) return;

    // Could allow overlapping stuff in scrolling elements
    // if we cleared the pending buffer before every draw.
    if (xi < el.lpos.xi) {
      xi = el.lpos.xi;
      noleft = true;
      if (this.border) xi--;
      if (thisparent.border) xi++;
    }
    if (xl > el.lpos.xl) {
      xl = el.lpos.xl;
      noright = true;
      if (this.border) xl++;
      if (thisparent.border) xl--;
    }
    //if (xi > xl) return;
    if (xi >= xl) return;
  }

  if (this.noOverflow && this.parent.lpos) {
    if (xi < this.parent.lpos.xi + this.parent.ileft) {
      xi = this.parent.lpos.xi + this.parent.ileft;
    }
    if (xl > this.parent.lpos.xl - this.parent.iright) {
      xl = this.parent.lpos.xl - this.parent.iright;
    }
    if (yi < this.parent.lpos.yi + this.parent.itop) {
      yi = this.parent.lpos.yi + this.parent.itop;
    }
    if (yl > this.parent.lpos.yl - this.parent.ibottom) {
      yl = this.parent.lpos.yl - this.parent.ibottom;
    }
  }

  // if (this.parent.lpos) {
  //   this.parent.lpos._scrollBottom = Math.max(
  //     this.parent.lpos._scrollBottom, yl);
  // }

  return {
    xi: xi,
    xl: xl,
    yi: yi,
    yl: yl,
    base: base,
    noleft: noleft,
    noright: noright,
    notop: notop,
    nobot: nobot,
    renders: this.screen.renders
  };
};

Element.prototype.render = function() {
  this._emit('prerender');

  this.parseContent();

  var coords = this._getCoords(true);
  if (!coords) {
    delete this.lpos;
    return;
  }

  if (coords.xl - coords.xi <= 0) {
    coords.xl = Math.max(coords.xl, coords.xi);
    return;
  }

  if (coords.yl - coords.yi <= 0) {
    coords.yl = Math.max(coords.yl, coords.yi);
    return;
  }

  var lines = this.screen.lines
    , xi = coords.xi
    , xl = coords.xl
    , yi = coords.yi
    , yl = coords.yl
    , x
    , y
    , cell
    , attr
    , ch
    , content = this._pcontent
    , ci = this._clines.ci[coords.base]
    , battr
    , dattr
    , c
    , visible
    , i
    , bch = this.ch;

  // Clip content if it's off the edge of the screen
  // if (xi + this.ileft < 0 || yi + this.itop < 0) {
  //   var clines = this._clines.slice();
  //   if (xi + this.ileft < 0) {
  //     for (var i = 0; i < clines.length; i++) {
  //       var t = 0;
  //       var csi = '';
  //       var csis = '';
  //       for (var j = 0; j < clines[i].length; j++) {
  //         while (clines[i][j] === '\x1b') {
  //           csi = '\x1b';
  //           while (clines[i][j++] !== 'm') csi += clines[i][j];
  //           csis += csi;
  //         }
  //         if (++t === -(xi + this.ileft) + 1) break;
  //       }
  //       clines[i] = csis + clines[i].substring(j);
  //     }
  //   }
  //   if (yi + this.itop < 0) {
  //     clines = clines.slice(-(yi + this.itop));
  //   }
  //   content = clines.join('\n');
  // }

  if (coords.base >= this._clines.ci.length) {
    ci = this._pcontent.length;
  }

  this.lpos = coords;

  if (this.border && this.border.type === 'line') {
    this.screen._borderStops[coords.yi] = true;
    this.screen._borderStops[coords.yl - 1] = true;
    // if (!this.screen._borderStops[coords.yi]) {
    //   this.screen._borderStops[coords.yi] = { xi: coords.xi, xl: coords.xl };
    // } else {
    //   if (this.screen._borderStops[coords.yi].xi > coords.xi) {
    //     this.screen._borderStops[coords.yi].xi = coords.xi;
    //   }
    //   if (this.screen._borderStops[coords.yi].xl < coords.xl) {
    //     this.screen._borderStops[coords.yi].xl = coords.xl;
    //   }
    // }
    // this.screen._borderStops[coords.yl - 1] = this.screen._borderStops[coords.yi];
  }

  dattr = this.sattr(this.style);
  attr = dattr;

  // If we're in a scrollable text box, check to
  // see which attributes this line starts with.
  if (ci > 0) {
    attr = this._clines.attr[Math.min(coords.base, this._clines.length - 1)];
  }

  if (this.border) xi++, xl--, yi++, yl--;

  // If we have padding/valign, that means the
  // content-drawing loop will skip a few cells/lines.
  // To deal with this, we can just fill the whole thing
  // ahead of time. This could be optimized.
  if (this.tpadding || (this.valign && this.valign !== 'top')) {
    if (this.style.transparent) {
      for (y = Math.max(yi, 0); y < yl; y++) {
        if (!lines[y]) break;
        for (x = Math.max(xi, 0); x < xl; x++) {
          if (!lines[y][x]) break;
          lines[y][x][0] = colors.blend(attr, lines[y][x][0]);
          // lines[y][x][1] = bch;
          lines[y].dirty = true;
        }
      }
    } else {
      this.screen.fillRegion(dattr, bch, xi, xl, yi, yl);
    }
  }

  if (this.tpadding) {
    xi += this.padding.left, xl -= this.padding.right;
    yi += this.padding.top, yl -= this.padding.bottom;
  }

  // Determine where to place the text if it's vertically aligned.
  if (this.valign === 'middle' || this.valign === 'bottom') {
    visible = yl - yi;
    if (this._clines.length < visible) {
      if (this.valign === 'middle') {
        visible = visible / 2 | 0;
        visible -= this._clines.length / 2 | 0;
      } else if (this.valign === 'bottom') {
        visible -= this._clines.length;
      }
      ci -= visible * (xl - xi);
    }
  }

  // Draw the content and background.
  for (y = yi; y < yl; y++) {
    if (!lines[y]) {
      if (y >= this.screen.height || yl < this.ibottom) {
        break;
      } else {
        continue;
      }
    }
    for (x = xi; x < xl; x++) {
      cell = lines[y][x];
      if (!cell) {
        if (x >= this.screen.width || xl < this.iright) {
          break;
        } else {
          continue;
        }
      }

      ch = content[ci++] || bch;

      // if (!content[ci] && !coords._contentEnd) {
      //   coords._contentEnd = { x: x - xi, y: y - yi };
      // }

      // Handle escape codes.
      while (ch === '\x1b') {
        if (c = /^\x1b\[[\d;]*m/.exec(content.substring(ci - 1))) {
          ci += c[0].length - 1;
          attr = this.screen.attrCode(c[0], attr, dattr);
          // Ignore foreground changes for selected items.
          if (this.parent._isList && this.parent.interactive
              && this.parent.items[this.parent.selected] === this
              && this.parent.options.invertSelected !== false) {
            attr = (attr & ~(0x1ff << 9)) | (dattr & (0x1ff << 9));
          }
          ch = content[ci] || bch;
          ci++;
        } else {
          break;
        }
      }

      // Handle newlines.
      if (ch === '\t') ch = bch;
      if (ch === '\n') {
        // If we're on the first cell and we find a newline and the last cell
        // of the last line was not a newline, let's just treat this like the
        // newline was already "counted".
        if (x === xi && y !== yi && content[ci - 2] !== '\n') {
          x--;
          continue;
        }
        // We could use fillRegion here, name the
        // outer loop, and continue to it instead.
        ch = bch;
        for (; x < xl; x++) {
          cell = lines[y][x];
          if (!cell) break;
          if (this.style.transparent) {
            lines[y][x][0] = colors.blend(attr, lines[y][x][0]);
            if (content[ci]) lines[y][x][1] = ch;
            lines[y].dirty = true;
          } else {
            if (attr !== cell[0] || ch !== cell[1]) {
              lines[y][x][0] = attr;
              lines[y][x][1] = ch;
              lines[y].dirty = true;
            }
          }
        }
        continue;
      }

      if (this.screen.fullUnicode && content[ci - 1]) {
        var point = unicode.codePointAt(content, ci - 1);
        // Handle combining chars:
        // Make sure they get in the same cell and are counted as 0.
        if (unicode.combining[point]) {
          if (point > 0x00ffff) {
            ch = content[ci - 1] + content[ci];
            ci++;
          }
          if (x - 1 >= xi) {
            lines[y][x - 1][1] += ch;
          } else if (y - 1 >= yi) {
            lines[y - 1][xl - 1][1] += ch;
          }
          x--;
          continue;
        }
        // Handle surrogate pairs:
        // Make sure we put surrogate pair chars in one cell.
        if (point > 0x00ffff) {
          ch = content[ci - 1] + content[ci];
          ci++;
        }
      }

      if (this._noFill) continue;

      if (this.style.transparent) {
        lines[y][x][0] = colors.blend(attr, lines[y][x][0]);
        if (content[ci]) lines[y][x][1] = ch;
        lines[y].dirty = true;
      } else {
        if (attr !== cell[0] || ch !== cell[1]) {
          lines[y][x][0] = attr;
          lines[y][x][1] = ch;
          lines[y].dirty = true;
        }
      }
    }
  }

  // Draw the scrollbar.
  // Could possibly draw this after all child elements.
  if (this.scrollbar) {
    // XXX
    // i = this.getScrollHeight();
    i = Math.max(this._clines.length, this._scrollBottom());
  }
  if (coords.notop || coords.nobot) i = -Infinity;

  if (this.scrollbar && (yl - yi) < i) {
    x = xl - 1;
    if (this.scrollbar.ignoreBorder && this.border) x++;
    if (this.alwaysScroll) {
      y = this.childBase / (i - (yl - yi));
    } else {
      y = (this.childBase + this.childOffset) / (i - 1);
    }
    y = yi + ((yl - yi) * y | 0);
    if (y >= yl) y = yl - 1;
    cell = lines[y] && lines[y][x];
    if (cell) {
      if (this.track) {
        ch = this.track.ch || ' ';
        attr = this.sattr(this.style.track,
          this.style.track.fg || this.style.fg,
          this.style.track.bg || this.style.bg);    
        this.screen.fillRegion(attr, ch, x, x + 1, yi, yl);
      }
      ch = this.scrollbar.ch || ' ';
      attr = this.sattr(this.style.scrollbar,
        this.style.scrollbar.fg || this.style.fg,
        this.style.scrollbar.bg || this.style.bg);
      if (attr !== cell[0] || ch !== cell[1]) {
        lines[y][x][0] = attr;
        lines[y][x][1] = ch;
        lines[y].dirty = true;
      }
    }
  }

  if (this.border) xi--, xl++, yi--, yl++;

  if (this.tpadding) {
    xi -= this.padding.left, xl += this.padding.right;
    yi -= this.padding.top, yl += this.padding.bottom;
  }

  // Draw the border.
  if (this.border) {
    battr = this.sattr(this.style.border);
    y = yi;
    if (coords.notop) y = -1;
    for (x = xi; x < xl; x++) {
      if (!lines[y]) break;
      if (coords.noleft && x === xi) continue;
      if (coords.noright && x === xl - 1) continue;
      cell = lines[y][x];
      if (!cell) continue;
      if (this.border.type === 'line') {
        if (x === xi) {
          if (!this.border.double) ch = '\u250c'; else ch = '\u2554'; // '┌'
          if (!this.border.left) {
            if (this.border.top) {
              if (!this.border.double) ch = '\u2500'; else ch = '\u2550'; // '─'
            } else {
              continue;
            }
          } else {
            if (!this.border.top) {
              if (!this.border.double) ch = '\u2502'; else ch = '\u2551'; // '│'
            }
          }
        } else if (x === xl - 1) {
          if (!this.border.double) ch = '\u2510'; else ch = '\u2557'; // '┐'
          if (!this.border.right) {
            if (this.border.top) {
              if (!this.border.double) ch = '\u2500'; else ch = '\u2550'; // '─'
            } else {
              continue;
            }
          } else {
            if (!this.border.top) {
              if (!this.border.double) ch = '\u2502'; else ch = '\u2551'; // '│'
            }
          }
        } else {
          if (!this.border.double) ch = '\u2500'; else ch = '\u2550';// '─'
        }
      } else if (this.border.type === 'bg') {
        ch = this.border.ch;
      }
      if (!this.border.top && x !== xi && x !== xl - 1) {
        ch = ' ';
        if (dattr !== cell[0] || ch !== cell[1]) {
          lines[y][x][0] = dattr;
          lines[y][x][1] = ch;
          lines[y].dirty = true;
          continue;
        }
      }
      if (battr !== cell[0] || ch !== cell[1]) {
        lines[y][x][0] = battr;
        lines[y][x][1] = ch;
        lines[y].dirty = true;
      }
    }
    y = yi + 1;
    for (; y < yl - 1; y++) {
      if (!lines[y]) continue;
      cell = lines[y][xi];
      if (cell) {
        if (this.border.left) {
          if (this.border.type === 'line') {
            if (!this.border.double) ch = '\u2502'; else ch = '\u2551'; // '│'
          } else if (this.border.type === 'bg') {
            ch = this.border.ch;
          }
          if (!coords.noleft)
          if (battr !== cell[0] || ch !== cell[1]) {
            lines[y][xi][0] = battr;
            lines[y][xi][1] = ch;
            lines[y].dirty = true;
          }
        } else {
          ch = ' ';
          if (dattr !== cell[0] || ch !== cell[1]) {
            lines[y][xi][0] = dattr;
            lines[y][xi][1] = ch;
            lines[y].dirty = true;
          }
        }
      }
      cell = lines[y][xl - 1];
      if (cell) {
        if (this.border.right) {
          if (this.border.type === 'line') {
            if (!this.border.double) ch = '\u2502'; else ch = '\u2551'; // '│'
          } else if (this.border.type === 'bg') {
            ch = this.border.ch;
          }
          if (!coords.noright)
          if (battr !== cell[0] || ch !== cell[1]) {
            lines[y][xl - 1][0] = battr;
            lines[y][xl - 1][1] = ch;
            lines[y].dirty = true;
          }
        } else {
          ch = ' ';
          if (dattr !== cell[0] || ch !== cell[1]) {
            lines[y][xl - 1][0] = dattr;
            lines[y][xl - 1][1] = ch;
            lines[y].dirty = true;
          }
        }
      }
    }
    y = yl - 1;
    if (coords.nobot) y = -1;
    for (x = xi; x < xl; x++) {
      if (!lines[y]) break;
      if (coords.noleft && x === xi) continue;
      if (coords.noright && x === xl - 1) continue;
      cell = lines[y][x];
      if (!cell) continue;
      if (this.border.type === 'line') {
        if (x === xi) {
          if (!this.border.double) ch = '\u2514'; else ch = '\u255a'; // '└'
          if (!this.border.left) {
            if (this.border.bottom) {
              if (!this.border.double) ch = '\u2500'; else ch = '\u2550'; // '─'
            } else {
              continue;
            }
          } else {
            if (!this.border.bottom) {
              if (!this.border.double) ch = '\u2502'; else ch = '\u2551'; // '│'
            }
          }
        } else if (x === xl - 1) {
          if (!this.border.double) ch = '\u2518'; else ch = '\u255d'; // '┘'
          if (!this.border.right) {
            if (this.border.bottom) {
              if (!this.border.double) ch = '\u2500'; else ch = '\u2550'; // '─'
            } else {
              continue;
            }
          } else {
            if (!this.border.bottom) {
              if (!this.border.double) ch = '\u2502'; else ch = '\u2551'; // '│'
            }
          }
        } else {
          if (!this.border.double) ch = '\u2500'; else ch = '\u2550'; // '─'
        }
      } else if (this.border.type === 'bg') {
        ch = this.border.ch;
      }
      if (!this.border.bottom && x !== xi && x !== xl - 1) {
        ch = ' ';
        if (dattr !== cell[0] || ch !== cell[1]) {
          lines[y][x][0] = dattr;
          lines[y][x][1] = ch;
          lines[y].dirty = true;
        }
        continue;
      }
      if (battr !== cell[0] || ch !== cell[1]) {
        lines[y][x][0] = battr;
        lines[y][x][1] = ch;
        lines[y].dirty = true;
      }
    }
  }

  if (this.shadow) {
    // right
    y = Math.max(yi + 1, 0);
    for (; y < yl + 1; y++) {
      if (!lines[y]) break;
      x = xl;
      for (; x < xl + 2; x++) {
        if (!lines[y][x]) break;
        // lines[y][x][0] = colors.blend(this.dattr, lines[y][x][0]);
        lines[y][x][0] = colors.blend(lines[y][x][0]);
        lines[y].dirty = true;
      }
    }
    // bottom
    y = yl;
    for (; y < yl + 1; y++) {
      if (!lines[y]) break;
      for (x = Math.max(xi + 1, 0); x < xl; x++) {
        if (!lines[y][x]) break;
        // lines[y][x][0] = colors.blend(this.dattr, lines[y][x][0]);
        lines[y][x][0] = colors.blend(lines[y][x][0]);
        lines[y].dirty = true;
      }
    }
  }

  this.children.forEach(function(el) {
    if (el.screen._ci !== -1) {
      el.index = el.screen._ci++;
    }
    // if (el.screen._rendering) {
    //   el._rendering = true;
    // }
    el.render();
    // if (el.screen._rendering) {
    //   el._rendering = false;
    // }
  });

  this._emit('render', [coords]);

  return coords;
};

Element.prototype._render = Element.prototype.render;

/**
 * Content Methods
 */

Element.prototype.insertLine = function(i, line) {
  if (typeof line === 'string') line = line.split('\n');

  if (i !== i || i == null) {
    i = this._clines.ftor.length;
  }

  i = Math.max(i, 0);

  while (this._clines.fake.length < i) {
    this._clines.fake.push('');
    this._clines.ftor.push([this._clines.push('') - 1]);
    this._clines.rtof(this._clines.fake.length - 1);
  }

  // NOTE: Could possibly compare the first and last ftor line numbers to see
  // if they're the same, or if they fit in the visible region entirely.
  var start = this._clines.length
    , diff
    , real;

  if (i >= this._clines.ftor.length) {
    real = this._clines.ftor[this._clines.ftor.length - 1];
    real = real[real.length - 1] + 1;
  } else {
    real = this._clines.ftor[i][0];
  }

  for (var j = 0; j < line.length; j++) {
    this._clines.fake.splice(i + j, 0, line[j]);
  }

  this.setContent(this._clines.fake.join('\n'), true);

  diff = this._clines.length - start;

  if (diff > 0) {
    var pos = this._getCoords();
    if (!pos) return;

    var height = pos.yl - pos.yi - this.iheight
      , base = this.childBase || 0
      , visible = real >= base && real - base < height;

    if (pos && visible && this.screen.cleanSides(this)) {
      this.screen.insertLine(diff,
        pos.yi + this.itop + real - base,
        pos.yi,
        pos.yl - this.ibottom - 1);
    }
  }
};

Element.prototype.deleteLine = function(i, n) {
  n = n || 1;

  if (i !== i || i == null) {
    i = this._clines.ftor.length - 1;
  }

  i = Math.max(i, 0);
  i = Math.min(i, this._clines.ftor.length - 1);

  // NOTE: Could possibly compare the first and last ftor line numbers to see
  // if they're the same, or if they fit in the visible region entirely.
  var start = this._clines.length
    , diff
    , real = this._clines.ftor[i][0];

  while (n--) {
    this._clines.fake.splice(i, 1);
  }

  this.setContent(this._clines.fake.join('\n'), true);

  diff = start - this._clines.length;

  // XXX clearPos() without diff statement?
  var height = 0;

  if (diff > 0) {
    var pos = this._getCoords();
    if (!pos) return;

    height = pos.yl - pos.yi - this.iheight;

    var base = this.childBase || 0
      , visible = real >= base && real - base < height;

    if (pos && visible && this.screen.cleanSides(this)) {
      this.screen.deleteLine(diff,
        pos.yi + this.itop + real - base,
        pos.yi,
        pos.yl - this.ibottom - 1);
    }
  }

  if (this._clines.length < height) {
    this.clearPos();
  }
};

Element.prototype.insertTop = function(line) {
  var fake = this._clines.rtof[this.childBase || 0];
  return this.insertLine(fake, line);
};

Element.prototype.insertBottom = function(line) {
  var h = (this.childBase || 0) + this.height - this.iheight
    , i = Math.min(h, this._clines.length)
    , fake = this._clines.rtof[i - 1] + 1;

  return this.insertLine(fake, line);
};

Element.prototype.deleteTop = function(n) {
  var fake = this._clines.rtof[this.childBase || 0];
  return this.deleteLine(fake, n);
};

Element.prototype.deleteBottom = function(n) {
  var h = (this.childBase || 0) + this.height - 1 - this.iheight
    , i = Math.min(h, this._clines.length - 1)
    , fake = this._clines.rtof[i];

  n = n || 1;

  return this.deleteLine(fake - (n - 1), n);
};

Element.prototype.setLine = function(i, line) {
  i = Math.max(i, 0);
  while (this._clines.fake.length < i) {
    this._clines.fake.push('');
  }
  this._clines.fake[i] = line;
  return this.setContent(this._clines.fake.join('\n'), true);
};

Element.prototype.setBaseLine = function(i, line) {
  var fake = this._clines.rtof[this.childBase || 0];
  return this.setLine(fake + i, line);
};

Element.prototype.getLine = function(i) {
  i = Math.max(i, 0);
  i = Math.min(i, this._clines.fake.length - 1);
  return this._clines.fake[i];
};

Element.prototype.getBaseLine = function(i) {
  var fake = this._clines.rtof[this.childBase || 0];
  return this.getLine(fake + i);
};

Element.prototype.clearLine = function(i) {
  i = Math.min(i, this._clines.fake.length - 1);
  return this.setLine(i, '');
};

Element.prototype.clearBaseLine = function(i) {
  var fake = this._clines.rtof[this.childBase || 0];
  return this.clearLine(fake + i);
};

Element.prototype.unshiftLine = function(line) {
  return this.insertLine(0, line);
};

Element.prototype.shiftLine = function(n) {
  return this.deleteLine(0, n);
};

Element.prototype.pushLine = function(line) {
  if (!this.content) return this.setLine(0, line);
  return this.insertLine(this._clines.fake.length, line);
};

Element.prototype.popLine = function(n) {
  return this.deleteLine(this._clines.fake.length - 1, n);
};

Element.prototype.getLines = function() {
  return this._clines.fake.slice();
};

Element.prototype.getScreenLines = function() {
  return this._clines.slice();
};

Element.prototype.strWidth = function(text) {
  text = this.parseTags
    ? helpers.stripTags(text)
    : text;
  return this.screen.fullUnicode
    ? unicode.strWidth(text)
    : helpers.dropUnicode(text).length;
};

Element.prototype.screenshot = function(xi, xl, yi, yl) {
  xi = this.lpos.xi + this.ileft + (xi || 0);
  if (xl != null) {
    xl = this.lpos.xi + this.ileft + (xl || 0);
  } else {
    xl = this.lpos.xl - this.iright;
  }
  yi = this.lpos.yi + this.itop + (yi || 0);
  if (yl != null) {
    yl = this.lpos.yi + this.itop + (yl || 0);
  } else {
    yl = this.lpos.yl - this.ibottom;
  }
  return this.screen.screenshot(xi, xl, yi, yl);
};

/**
 * Expose
 */

module.exports = Element;
};
BundleModuleCode['term/widgets/arrows']=function (module,exports){
var Comp = Require('com/compat');

var Node = Require('term/widgets/node');
var Box = Require('term/widgets/box');
var Helpers = Require('term/helpers');
var Button = Require('term/widgets/button');


// Add up and down arrow buttons on the right outside of a widget
// options.arrows: {up:'[-]',down:'[+]',width:3,height:1,fg:'red',bg:'default'}}
module.exports = function (parent,options,up,down,nocontrol) {  
  var bbox;
  // Bbox computing for button positions; relatives can only be resolved to screen
  // coordinates?
  bbox=Helpers.bbox(parent.screen,options);
  parent._.up = new Button({
    screen: parent.screen,
    top: bbox.top+1,
    height: options.arrows.height||1,
    left: bbox.left+bbox.width,
    width: options.arrows.width||3,
    content: options.arrows.up||'[-]',
    align: 'center',
    style: {
      fg:options.arrows.fg||'red',
      bg: options.arrows.bg||'white',
      bold:true,
    },
    autoFocus: false,
    hidden:options.hidden,
    mouse: true
  });
  parent._.up.on('press',up);
  parent.screen.append(parent._.up);
  parent._.down = new Button({
    screen: this.screen,
    top: bbox.top+bbox.height-1-(options.arrows.height||1),
    height: options.arrows.height||1,
    left: bbox.left+bbox.width,
    width: options.arrows.width||3,
    content: options.arrows.down||'[+]',
    align: 'center',
    style: {
      fg:options.arrows.fg||'red',
      bg: options.arrows.bg||'white',
      bold:true,
    },
    autoFocus: false,
    hidden:options.hidden,
    mouse: true
  });
  parent._.down.on('press',down);
  parent.screen.append(parent._.down);
  if (!nocontrol) {
    parent._hide=parent.hide;
    parent.hide = function() {
      parent._hide();
      if (parent._.up) parent._.up.hide();
      if (parent._.down) parent._.down.hide();
      parent.screen.render();
    } 
    parent._show = parent.show;
    parent.show = function() {
      parent._show();
      if (parent._.up) parent._.up.show();
      if (parent._.down) parent._.down.show();
      parent.screen.render();
    } 
  }
}
};
BundleModuleCode['term/widgets/scrollabletext']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Christopher Jeffrey, Stefan Bosse
 **    $INITIAL:     (C) 2013-2015, Christopher Jeffrey and contributors
 **    $MODIFIED:    by sbosse
 **    $REVESIO:     1.2.1
 **
 **    $INFO:
 **
 **    scrollabletext.js - scrollable text element for blessed
 **
 **    $ENDOFINFO
 */

/**
 * Modules
 */
var Comp = Require('com/compat');

var Node = Require('term/widgets/node');
var ScrollableBox = Require('term/widgets/scrollablebox');

/**
 * ScrollableText
 */

function ScrollableText(options) {
  if (!instanceOf(this,Node)) {
    return new ScrollableText(options);
  }
  options = options || {};
  options.alwaysScroll = true;
  ScrollableBox.call(this, options);
}

//ScrollableText.prototype.__proto__ = ScrollableBox.prototype;
inheritPrototype(ScrollableText,ScrollableBox);

ScrollableText.prototype.type = 'scrollable-text';

/**
 * Expose
 */

module.exports = ScrollableText;
};
BundleModuleCode['term/widgets/scrollablebox']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Christopher Jeffrey and contributors, Stefan Bosse
 **    $INITIAL:     (C) 2013-2016, Christopher Jeffrey and contributors
 **    $MODIFIED:    sbosse (2017-2021).
 **    $VERSION:     1.2.3
 **
 **    $INFO:
 *
 * scrollablebox.js - scrollable box element for blessed
 *
 *    events: 'mouse', 'click' (low level), 'clicked' (high level)
 *
 **    $ENDOFINFO
 */

/**
 * Modules
 */
var Comp = Require('com/compat');

var Node = Require('term/widgets/node');
var Box = Require('term/widgets/box');

/**
 * ScrollableBox
 */

function ScrollableBox(options) {
  var self = this;

  if (!instanceOf(this,Node)) {
    return new ScrollableBox(options);
  }

  options = options || {};

  Box.call(this, options);

  if (options.scrollable === false) {
    return this;
  }

  this.scrollable = true;
  this.childOffset = 0;
  this.childBase = 0;
  this.baseLimit = options.baseLimit || Infinity;
  this.alwaysScroll = options.alwaysScroll;

  this.scrollbar = options.scrollbar;
  if (this.scrollbar) {
    this.scrollbar.ch = this.scrollbar.ch || ' ';
    this.style.scrollbar = this.style.scrollbar || this.scrollbar.style;
    if (!this.style.scrollbar) {
      this.style.scrollbar = {};
      this.style.scrollbar.fg = this.scrollbar.fg;
      this.style.scrollbar.bg = this.scrollbar.bg;
      this.style.scrollbar.bold = this.scrollbar.bold;
      this.style.scrollbar.underline = this.scrollbar.underline;
      this.style.scrollbar.inverse = this.scrollbar.inverse;
      this.style.scrollbar.invisible = this.scrollbar.invisible;
    }
    //this.scrollbar.style = this.style.scrollbar;
    if (this.track || this.scrollbar.track) {
      this.track = this.scrollbar.track || this.track;
      this.style.track = this.style.scrollbar.track || this.style.track;
      this.track.ch = this.track.ch || ' ';
      this.style.track = this.style.track || this.track.style;
      if (!this.style.track) {
        this.style.track = {};
        this.style.track.fg = this.track.fg;
        this.style.track.bg = this.track.bg;
        this.style.track.bold = this.track.bold;
        this.style.track.underline = this.track.underline;
        this.style.track.inverse = this.track.inverse;
        this.style.track.invisible = this.track.invisible;
      }
      this.track.style = this.style.track;
    }
    // Allow controlling of the scrollbar via the mouse:
    if (options.mouse) {
      this.on('mousedown', function(data) {
        if (self._scrollingBar) {
          // Do not allow dragging on the scrollbar:
          delete self.screen._dragging;
          delete self._drag;
          return;
        }
        var x = data.x - self.aleft;
        var y = data.y - self.atop;
        self.emit('clicked',{x:x-1,y:y-1,ev:'click'});
        if (x === self.width - self.iright - 1) {
          // Do not allow dragging on the scrollbar:
          delete self.screen._dragging;
          delete self._drag;
          var perc = (y - self.itop) / (self.height - self.iheight);
          self.setScrollPerc(perc * 100 | 0);
          self.screen.render();
          var smd, smu;
          self._scrollingBar = true;
          self.onScreenEvent('mousedown', smd = function(data) {
            var y = data.y - self.atop;
            var perc = y / self.height;
            self.setScrollPerc(perc * 100 | 0);
            self.screen.render();
          });
          // If mouseup occurs out of the window, no mouseup event fires, and
          // scrollbar will drag again on mousedown until another mouseup
          // occurs.
          self.onScreenEvent('mouseup', smu = function() {
            self._scrollingBar = false;
            self.removeScreenEvent('mousedown', smd);
            self.removeScreenEvent('mouseup', smu);
          });
        }
      });
    }
  }

  if (options.mouse) {
    this.on('wheeldown', function() {
      self.scroll(self.height / 2 | 0 || 1);
      self.screen.render();
    });
    this.on('wheelup', function() {
      self.scroll(-(self.height / 2 | 0) || -1);
      self.screen.render();
    });
  }

  if (options.keys && !options.ignoreKeys) {
    this.on('keypress', function(ch, key) {
      if (key.name === 'up' || (options.vi && key.name === 'k')) {
        self.scroll(-1);
        self.screen.render();
        return;
      }
      if (key.name === 'down' || (options.vi && key.name === 'j')) {
        self.scroll(1);
        self.screen.render();
        return;
      }
      if (key.name === 'pageup') {
        self.scroll(-(self.height / 2 | 0) || -1);
        self.screen.render();
        return;
      }
      if (key.name === 'pagedown') {
        self.scroll(self.height / 2 | 0 || 1);
        self.screen.render();
        return;
      }
      if (options.vi && key.name === 'u' && key.ctrl) {
        self.scroll(-(self.height / 2 | 0) || -1);
        self.screen.render();
        return;
      }
      if (options.vi && key.name === 'd' && key.ctrl) {
        self.scroll(self.height / 2 | 0 || 1);
        self.screen.render();
        return;
      }
      if (options.vi && key.name === 'b' && key.ctrl) {
        self.scroll(-self.height || -1);
        self.screen.render();
        return;
      }
      if (options.vi && key.name === 'f' && key.ctrl) {
        self.scroll(self.height || 1);
        self.screen.render();
        return;
      }
      if (options.vi && key.name === 'g' && !key.shift) {
        self.scrollTo(0);
        self.screen.render();
        return;
      }
      if (options.vi && key.name === 'g' && key.shift) {
        self.scrollTo(self.getScrollHeight());
        self.screen.render();
        return;
      }
    });
  }

  this.on('parsed content', function() {
    self._recalculateIndex();
  });

  self._recalculateIndex();
}

//ScrollableBox.prototype.__proto__ = Box.prototype;
inheritPrototype(ScrollableBox,Box);

ScrollableBox.prototype.type = 'scrollable-box';

/* depricated
// XXX Potentially use this in place of scrollable checks elsewhere.
ScrollableBox.prototype.__defineGetter__('reallyScrollable', function() {
  if (this.shrink) return this.scrollable;
  return this.getScrollHeight() > this.height;
});
*/
Object.defineProperty(ScrollableBox.prototype,'reallyScrollable',{
  get: function () {  
    if (this.shrink) return this.scrollable;
    return this.getScrollHeight() > this.height;
  },
  set: function (val) {
  }
});


ScrollableBox.prototype._scrollBottom = function() {
  if (!this.scrollable) return 0;

  // We could just calculate the children, but we can
  // optimize for lists by just returning the items.length.
  if (this._isList) {
    return this.items ? this.items.length : 0;
  }

  if (this.lpos && this.lpos._scrollBottom) {
    return this.lpos._scrollBottom;
  }

  var bottom = this.children.reduce(function(current, el) {
    // el.height alone does not calculate the shrunken height, we need to use
    // getCoords. A shrunken box inside a scrollable element will not grow any
    // larger than the scrollable element's context regardless of how much
    // content is in the shrunken box, unless we do this (call getCoords
    // without the scrollable calculation):
    // See: $ node test/widget-shrink-fail-2.js
    if (!el.detached) {
      var lpos = el._getCoords(false, true);
      if (lpos) {
        return Math.max(current, el.rtop + (lpos.yl - lpos.yi));
      }
    }
    return Math.max(current, el.rtop + el.height);
  }, 0);

  // XXX Use this? Makes .getScrollHeight() useless!
  // if (bottom < this._clines.length) bottom = this._clines.length;

  if (this.lpos) this.lpos._scrollBottom = bottom;

  return bottom;
};

ScrollableBox.prototype.setScroll =
ScrollableBox.prototype.scrollTo = function(offset, always) {
  // XXX
  // At first, this appeared to account for the first new calculation of childBase:
  this.scroll(0);
  return this.scroll(offset - (this.childBase + this.childOffset), always);

};

ScrollableBox.prototype.getScroll = function() {
  return this.childBase + this.childOffset;
};

ScrollableBox.prototype.scroll = function(offset, always) {
  if (!this.scrollable) return;

  if (this.detached) return;

  // Handle scrolling.
  var visible = this.height - this.iheight
    , base = this.childBase
    , d
    , p
    , t
    , b
    , max
    , emax;

  if (this.alwaysScroll || always) {
    // Semi-workaround
    this.childOffset = offset > 0
      ? visible - 1 + offset
      : offset;
  } else {
    this.childOffset += offset;
  }

  if (this.childOffset > visible - 1) {
    d = this.childOffset - (visible - 1);
    this.childOffset -= d;
    this.childBase += d;
  } else if (this.childOffset < 0) {
    d = this.childOffset;
    this.childOffset += -d;
    this.childBase += d;
  }

  if (this.childBase < 0) {
    this.childBase = 0;
  } else if (this.childBase > this.baseLimit) {
    this.childBase = this.baseLimit;
  }

  // Find max "bottom" value for
  // content and descendant elements.
  // Scroll the content if necessary.
  if (this.childBase === base) {
    return this.emit('scroll');
  }

  // When scrolling text, we want to be able to handle SGR codes as well as line
  // feeds. This allows us to take preformatted text output from other programs
  // and put it in a scrollable text box.
  this.parseContent();

  // XXX
  // max = this.getScrollHeight() - (this.height - this.iheight);

  max = this._clines.length - (this.height - this.iheight);
  if (max < 0) max = 0;
  emax = this._scrollBottom() - (this.height - this.iheight);
  if (emax < 0) emax = 0;

  this.childBase = Math.min(this.childBase, Math.max(emax, max));

  if (this.childBase < 0) {
    this.childBase = 0;
  } else if (this.childBase > this.baseLimit) {
    this.childBase = this.baseLimit;
  }

  // Optimize scrolling with CSR + IL/DL.
  p = this.lpos;
  // Only really need _getCoords() if we want
  // to allow nestable scrolling elements...
  // or if we **really** want shrinkable
  // scrolling elements.
  // p = this._getCoords();
  if (p && this.childBase !== base && this.screen.cleanSides(this)) {
    t = p.yi + this.itop;
    b = p.yl - this.ibottom - 1;
    d = this.childBase - base;

    if (d > 0 && d < visible) {
      // scrolled down
      this.screen.deleteLine(d, t, t, b);
    } else if (d < 0 && -d < visible) {
      // scrolled up
      d = -d;
      this.screen.insertLine(d, t, t, b);
    }
  }

  return this.emit('scroll');
};

ScrollableBox.prototype.scrollBottom = function () {
  // Workaround: inserting lines when scrollbar was manually set
  // breaks scroll window (can't usee _scrollBottom...)
  this.scrollTo(10000000);
};

ScrollableBox.prototype._recalculateIndex = function() {
  var max, emax;

  if (this.detached || !this.scrollable) {
    return 0;
  }

  // XXX
  // max = this.getScrollHeight() - (this.height - this.iheight);

  max = this._clines.length - (this.height - this.iheight);
  if (max < 0) max = 0;
  emax = this._scrollBottom() - (this.height - this.iheight);
  if (emax < 0) emax = 0;

  this.childBase = Math.min(this.childBase, Math.max(emax, max));

  if (this.childBase < 0) {
    this.childBase = 0;
  } else if (this.childBase > this.baseLimit) {
    this.childBase = this.baseLimit;
  }
};

ScrollableBox.prototype.resetScroll = function() {
  if (!this.scrollable) return;
  this.childOffset = 0;
  this.childBase = 0;
  return this.emit('scroll');
};

ScrollableBox.prototype.getScrollHeight = function() {
  return Math.max(this._clines.length, this._scrollBottom());
};

ScrollableBox.prototype.getScrollPerc = function(s) {
  var pos = this.lpos || this._getCoords();
  if (!pos) return s ? -1 : 0;

  var height = (pos.yl - pos.yi) - this.iheight
    , i = this.getScrollHeight()
    , p;

  if (height < i) {
    if (this.alwaysScroll) {
      p = this.childBase / (i - height);
    } else {
      p = (this.childBase + this.childOffset) / (i - 1);
    }
    return p * 100;
  }

  return s ? -1 : 0;
};

ScrollableBox.prototype.setScrollPerc = function(i) {
  // XXX
  // var m = this.getScrollHeight();
  var m = Math.max(this._clines.length, this._scrollBottom());
  return this.scrollTo((i / 100) * m | 0);
};

/**
 * Expose
 */

module.exports = ScrollableBox;
};
BundleModuleCode['term/widgets/chat']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Christopher Jeffrey, Stefan Bosse
 **    $INITIAL:     (C) 2013-2018, Christopher Jeffrey and contributors
 **    $MODIFIED:    by sbosse (2017-2021)
 **    $REVESIO:     1.5.7
 **
 **    $INFO:
 **
 **    chat.js - interactive chat terminal shell based on textarea element for blessed
 **
 **    events: 'eval' (high level passing command line after enter key was hit)
 ** 
 **    public methods:
 **
 **     print(string)
 **     ask(ask,options,callback)
 **
 **     typeof options = { choices? : string [], mutual?: boolean,
 **                        range? : [number,number], 
 **    $ENDOFINFO
 */

/**
 * Modules
 */
var Comp = Require('com/compat');

var unicode = Require('term/unicode');

var nextTick = global.setImmediate || process.nextTick.bind(process);

var Node = Require('term/widgets/node');
var Input = Require('term/widgets/input');

function setCharAt(str,index,chr) {
    if(index > str.length-1) return str;
    return str.substring(0,index) + chr + str.substring(index+1);
}
/**
 * Chat
 */

function Chat(options) {
  var self = this;

  if (!instanceOf(this,Node)) {
    return new Chat(options);
  }

  options = options || {};

  options.scrollable = options.scrollable !== false;

  Input.call(this, options);

  this.screen._listenKeys(this);

  this.value = options.value || '';
  // cursor position
  this.cpos = {x:-1,y:-1};
  this.cursorControl=true;
  this.multiline=options.multiline;
  this.tags=true;   // we need formatting tag support
  this.input='text';  

  this.history=[];
  this.historyTop=0;
  
  this.__updateCursor = this._updateCursor.bind(this);
  this.on('resize', this.__updateCursor);
  this.on('move', this.__updateCursor);

  if (options.inputOnFocus) {
    this.on('focus', this.readInput.bind(this, null));
  }

  if (!options.inputOnFocus && options.keys) {
    this.on('keypress', function(ch, key) {
      if (self._reading) return;
      if (key.name === 'enter' || (options.vi && key.name === 'i')) {
        return self.readInput();
      }
      if (key.name === 'e') {
        return self.readEditor();
      }
    });
  }

  if (options.mouse) {
    this.on('click', function(data) {
      if (self._reading) return;
      if (data.button !== 'right') return;
      self.readEditor();
    });
  }
  
  var offsetY = 0;
  if (this._clines) offsetY=this._clines.length-(this.childBase||0);
  if (options.prompt) {
    this.value=options.prompt;
    this.prompt=options.prompt;
    this.inputRange={x0:options.prompt.length,y0:offsetY,y1:offsetY,last:0,line:0};
  } else {
    this.inputRange={x0:0,y0:offsetY,y1:offsetY,last:0,line:0};
    this.prompt='';
  }
}

//Chat.prototype.__proto__ = Input.prototype;
inheritPrototype(Chat,Input);

Chat.prototype.type = 'terminal';


Chat.prototype._updateCursor = function(get) {
  var offsetY=this.childBase||0;
  if (this.screen.focused !== this) {
    // at least update cursor to its bound
    this.cpos.y=Math.min(this._clines.length - 1 - offsetY,this.cpos.y);
    return;
  }
  var lpos = get ? this.lpos : this._getCoords();
  if (!lpos) return;

  var last = this._clines[this._clines.length - 1]
    , program = this.screen.program
    , line
    , offsetY = this.childBase||0
    , cx
    , cy;

  // Stop a situation where the textarea begins scrolling
  // and the last cline appears to always be empty from the
  // _typeScroll `+ '\n'` thing.
  // Maybe not necessary anymore?
  if (last === '' && this.value[this.value.length - 1] !== '\n') {
    last = this._clines[this._clines.length - 2] || '';
  }

  line = Math.min(
    this._clines.length - 1 - (this.childBase || 0),
    (lpos.yl - lpos.yi) - this.iheight - 1);

  // When calling clearValue() on a full textarea with a border, the first
  // argument in the above Math.min call ends up being -2. Make sure we stay
  // positive.
  line = Math.max(0, line);
  
  if (this.cpos.x==-1 || !this.cursorControl) this.cpos.x = this.strWidth(last);
  if (this.cpos.y==-1 || !this.cursorControl) this.cpos.y = line;
  this.cpos.y = Math.min(this.cpos.y,line);
  this.cpos.x = Math.min(this.cpos.x,this.strWidth(this._clines[offsetY+this.cpos.y]));
    
  cx = lpos.xi + this.ileft + this.cpos.x;
  cy = lpos.yi + this.itop + this.cpos.y;

  // XXX Not sure, but this may still sometimes
  // cause problems when leaving editor.
  if (cy === program.y && cx === program.x) {
    return;
  }

  if (cy === program.y) {
    if (cx > program.x) {
      program.cuf(cx - program.x);
    } else if (cx < program.x) {
      program.cub(program.x - cx);
    }
  } else if (cx === program.x) {
    if (cy > program.y) {
      program.cud(cy - program.y);
    } else if (cy < program.y) {
      program.cuu(program.y - cy);
    }
  } else {
    program.cup(cy, cx);
  }
};

Chat.prototype.input =
Chat.prototype.setInput =
Chat.prototype.readInput = function(callback) {
  var self = this
    , focused = this.screen.focused === this;

  if (this._reading) return;
  this._reading = true;

  this._callback = callback;

  if (!focused) {
    this.screen.saveFocus();
    this.focus();
  }

  this.screen.grabKeys = true;

  this._updateCursor();
  this.screen.program.showCursor();
  //this.screen.program.sgr('normal');

  this._done = function fn(err, value) {
    if (!self._reading) return;

    if (fn.done) return;
    fn.done = true;

    self._reading = false;

    delete self._callback;
    delete self._done;

    self.removeListener('keypress', self.__listener);
    delete self.__listener;

    self.removeListener('blur', self.__done);
    delete self.__done;

    self.screen.program.hideCursor();
    self.screen.grabKeys = false;

    if (!focused) {
      self.screen.restoreFocus();
    }

    if (self.options.inputOnFocus) {
      self.screen.rewindFocus();
    }

    // Ugly
    if (err === 'stop') return;

    if (err) {
      self.emit('error', err);
    } else if (value != null) {
      self.emit('submit', value);
    } else {
      self.emit('cancel', value);
    }
    self.emit('action', value);

    if (!callback) return;

    return err
      ? callback(err)
      : callback(null, value);
  };

  // Put this in a nextTick so the current
  // key event doesn't trigger any keys input.
  nextTick(function() {
    if (self.__listener) {
      // double fired?
      return;
    }
    self.__listener = self._listener.bind(self);
    self.on('keypress', self.__listener);
  });

  this.__done = this._done.bind(this, null, null);
  this.on('blur', this.__done);
};

// Ask request
Chat.prototype.ask = function (message,options,callback) {
  var offsetY = this.childBase||0,
      prompt = this.prompt,
      self=this;
  options=options||{};
  if (this.request) {
    // pending request; queue new request
    return;
  }
  // restore point
  var restorePos = offsetY+this.cpos.y;
  
  function ask(command,restore) {
    offsetY = self.childBase||0;
    var start = self.getLinearPos(self.value,offsetY+self.inputRange.y0,0),
        end   = self.getLinearPos(self.value,offsetY+self.inputRange.y1,
                                  self._clines[offsetY+self.inputRange.y1].length);
    var line; 
    if (restore) start=self.getLinearPos(self.value,restorePos,0);
    self.value=self.value.slice(0,start)+self.prompt+command+self.value.slice(end);
    self.screen.render();
    self.scrollBottom();
    self.cpos.x=self._clines[self._clines.length-1].length;
    self.cpos.y += 10; // bottom
    self._updateCursor(true);
    self.inputRange.y1=self.cpos.y;
    offsetY = self.childBase||0;
    // find start y
    var y0=self.cpos.y; 
// Log(self.cpos,offsetY,y0);
    self.inputRange.x0=self.prompt.length;
    self.inputRange.x1=null;

    while (self._clines[offsetY+y0].indexOf(self.prompt)!=0) y0--;
    self.inputRange.y0=y0; 
    self.inputRange.last=self.inputRange.y1-self.inputRange.y0;
  }

  this.print('{bold}'+message+'{/bold}');
  
  // choices
  if (options.choices) {
    // form can be multiline
    var choices = options.choices.slice();
    // Make buttons (with click handler)
    this.prompt='? ';
    var delim   = options.layout=='vertical'?'\n':' ',
        indent  = options.layout=='vertical'?'  ':'';
        
    if (!options.mutable) {
      line=choices.map(function (s,i) { return (i>0?indent:'')+'[ ] '+s}).join(delim);
      line += (delim+indent+'[Ok] [Cancel]');
      choices.push(restore1); choices.push(restore1);
    } else {
      line=choices.map(function (s,i) { return (i>0?indent:'')+'['+s+']'}).join(delim);
      line += (delim+indent+'[Cancel]');
      choices.push(restore1);
    }

    ask(line);
    this.input='mouse';
    this.screen.program.hideCursor(true);

    // get selector positions, install clicked handler
    var y=this.inputRange.y0,x=this.inputRange.x0;

    line=this._clines[offsetY+y];
    var actions = choices.map(function (choice,index) {
      // find [ ]/[...] in _clines
      while (line[x] && line[x] != '[') {
        x++;
        if (!line[x] && y<self.inputRange.y1) {
          x=0;
          y++;
          line=self._clines[offsetY+y];
        }
      }
      if (line[x] && line[x+1]==' ') {
        x++;
        return { choice:choice, index:index, fake: self.getLinearPos(self.value, offsetY+y,x), pos:{x:x,y:y}};
      } else {
        x++;
        var start = { x:x,y:y };
        // find end
        while (line[x] && line[x] != ']') {
          x++;
          if (!line[x] && y<self.inputRange.y1) {
            x=0;
            y++;
            line=self._clines[offsetY+y];
          }
        }
        var end = { x:x-1,y:y }
        return typeof choice=='string'?{ choice:choice, index:index, pos:[start,end]}:
                                       { action:choice, index:index, pos:[start,end]}; 
      }
    });
// Log(actions)
    function clicked1(pos) {
      var offsetY=self.childBase||0;
      function within (o) {
        if (typeof o.x != 'undefined') {
          return pos.x==o.x && pos.y==o.y
        } else if (o.length==2) {
          if (o[0].y==o[1].y && o[0].y==pos.y)
            return pos.x>=o[0].x&&pos.x<=o[1].x;
          else if (pos.y>=o[0].y && pos.y<=o[1].y) // multi-line
            return pos.x>=o[0].x&&pos.x<=o[1].x
        }
      }
      actions.forEach(function (action) {
        if (!action) return;
        if (within(action.pos)) {
          // fired
          if (action.choice) {
            if (self.selected.indexOf(action.choice)!=-1) {
              // deselect
              self.selected=self.selected.filter(function (choice) { return choice!=action.choice });
              self.value=setCharAt(self.value,action.fake,' ');
              self.screen.render();
            } else {
              // select
              if (!options.mutable) {
                self.value=setCharAt(self.value,action.fake,'X');
                self.screen.render();
                changed1();
              }
              self.selected.push(action.choice);
              if (options.mutable) restore1();
            }
          }
          if (action.action) {
            action.action();
          }
        }
      })
    };
    function changed1() {
      if (options.timeout) {
        if (self.timer) clearTimeout(self.timer);
        self.timer=setTimeout(restore1,options.timeout);
      }
    }
    function restore1 (timeout) {
      var result = callback?callback(self.selected):null;    
      if (self.selected != undefined && self.selected.length) 
        self.print(self.selected.join(', '),self.style.user);
      if (result) self.print(result);
      // restore text line
      self.prompt=prompt;
      ask('',!result && self.selected.length==0);
      self.input='text';
      self.removeListener('clicked',clicked1);
      if (!timeout && self.timer) clearTimeout(self.timer);
      self.timer=null;
      self.request=null;
    }
    this.on('clicked',clicked1);
    this.selected=[];
    if (options.timeout) {
      this.timer=setTimeout(restore1,options.timeout);
    }
  } 
  
  // range //
  if (options.range) {
    // assumption: entire form fits in one ine
    var sign=options.range[0]<0,
        step=options.step||1,
        digits = Math.floor(Math.log10(options.range[1]-options.range[0]+1))+1;
    if (sign) digits++;
    var value=options.default||options.value||options.range[0];
    line = '[-] '+Comp.printf.sprintf("%"+digits+"d",value)+' [+] [Ok] [Cancel]';
    this.prompt='? ';
    ask(line);
    var x0=this._clines[offsetY+this.cpos.y].indexOf('[-] ')+4;
    var x1=this._clines[offsetY+this.cpos.y].indexOf(' [+]')-1;
    this.cpos.x=x1;
    this.inputRange.x0=x0;
    this.inputRange.x1=x1;
    this.inputRange.right=true;
    this.inputRange.action=function () {
        var _value = Number(self._clines[offsetY+self.cpos.y].slice(x0,x1+1));
        if (_value>= options.range[0] && _value<= options.range[1]) {
          self.selected=value;
          restore2();
        } else {
          // invalid; try again
          value=options.range[0];
          line = '[-] '+Comp.printf.sprintf("%"+digits+"d",value)+' [+] [Ok] [Cancel]';
          ask(line);
          self.inputRange.x0=x0;
          self.inputRange.x1=x1;
          self.cpos.x=x1;
          self._updateCursor();
        }    
    }
    this._updateCursor();
    actions=[
      {pos:{x:this._clines[offsetY+this.cpos.y].indexOf('[-]')+1,y:this.cpos.y},action:function () {
        // decrease value
        value=Math.max(options.range[0],value-step);
        line = '[-] '+Comp.printf.sprintf("%"+digits+"d",value)+' [+] [Ok] [Cancel]';
        ask(line);
        self.inputRange.x0=x0;
        self.inputRange.x1=x1;
        self.cpos.x=x1;
        self._updateCursor();
      }},
      {pos:{x:this._clines[offsetY+this.cpos.y].indexOf('[+]')+1,y:this.cpos.y},action:function () {
        // increase value
        value=Math.min(options.range[1],value+step);
        line = '[-] '+Comp.printf.sprintf("%"+digits+"d",value)+' [+] [Ok] [Cancel]';
        ask(line);
        self.inputRange.x0=x0;
        self.inputRange.x1=x1;
        self.cpos.x=x1;
        self._updateCursor();
      }},
      {pos:[{x:this._clines[offsetY+this.cpos.y].indexOf('[Ok]')+1,y:this.cpos.y},
            {x:this._clines[offsetY+this.cpos.y].indexOf('[Ok]')+2,y:this.cpos.y}],action:function () {
        // Ok
        var _value = Number(self._clines[offsetY+self.cpos.y].slice(x0,x1+1));
        if (_value>= options.range[0] && _value<= options.range[1]) {
          self.selected=value;
          restore2();
        } else {
          // invalid; try again
          value=options.range[0];
          line = '[-] '+Comp.printf.sprintf("%"+digits+"d",value)+' [+] [Ok] [Cancel]';
          ask(line);
          self.inputRange.x0=x0;
          self.inputRange.x1=x1;
          self.cpos.x=x1;
          self._updateCursor();
        }
      }},
      {pos:[{x:this._clines[offsetY+this.cpos.y].indexOf('[Cancel]')+1,y:this.cpos.y},
            {x:this._clines[offsetY+this.cpos.y].indexOf('[Cancel]')+6,y:this.cpos.y}],action:function () {
        // Cancel
        restore2()
      }}
    ]
    function clicked2(pos) {
      var offsetY=self.childBase||0;
      function within (o) {
        if (typeof o.x != 'undefined') {
          return pos.x==o.x && pos.y==o.y
        } else if (o.length==2) {
          if (o[0].y==o[1].y && o[0].y==pos.y)
            return pos.x>=o[0].x&&pos.x<=o[1].x;
          else if (pos.y>=o[0].y && pos.y<=o[1].y) // multi-line
            return pos.x>=o[0].x&&pos.x<=o[1].x
        }
      }
      actions.forEach(function (action) {
        if (!action) return;
        if (within(action.pos)) action.action ();
      })
    }
    function changed2() {
      var _value = Number(self._clines[offsetY+self.cpos.y].slice(x0,x1+1));
      if (isNaN(_value) || _value < options.range[0] || _value > options.range[1]) {
      } else value=_value;
      if (options.timeout) {
        if (self.timer) clearTimeout(self.timer);
        self.timer=setTimeout(restore2,options.timeout);
      }
    }
    function restore2 (timeout) {
      var result = callback?callback(self.selected):null;
      if (self.selected != undefined) self.print(self.selected,self.style.user);
      if (result) self.print(result);
      // restore text line
      self.prompt=prompt;
      ask('',!result && self.selected == undefined);
      self.removeListener('clicked',clicked2);
      self.removeListener('modified',changed2);
      if (!timeout && self.timer) clearTimeout(self.timer);
      self.timer=null;
      self.inputRange.action=null;
      self.inputRange.right=null;
      self.request=null;
    }
    // this.on('clicked',clicked);
    this.selected=null;
    this.on('clicked',clicked2);
    this.on('modified',changed2);
    if (options.timeout) {
      this.timer=setTimeout(restore2,options.timeout);
    }
  }
  if (options.text) {
    this.selected=null;
    function changed3() {
      var offsetY=self.childBase||0,
          vpos = self.getLinearPos(self.value,offsetY+self.inputRange.y0,self.inputRange.x0);
      self.selected= value = self.value.slice(vpos,1000000);
      if (options.timeout) {
        if (self.timer) clearTimeout(self.timer);
        self.timer=setTimeout(restore3,options.timeout);
      }
    }
    function restore3 (timeout) {
      var result = callback?callback(self.selected):null;    
      if (self.selected != undefined) self.print(self.selected,self.style.user);
      if (result) self.print(result);
      // restore text line
      self.prompt=prompt;
      // Log(self.inputRange,self.value)
      ask('',!result && self.selected == undefined);
      self.removeListener('modified',changed3);
      if (!timeout && self.timer) clearTimeout(self.timer);
      self.timer=null;
      self.inputRange.action=null;
      self.request=null;
    }
    this.inputRange.action=function () {
      restore3()
    }
    this.on('modified',changed3);
    if (options.timeout) {
      this.timer=setTimeout(restore3,options.timeout);
    }  
  }
  this.request={message:message,options:options,callback:callback};
}


// Public API: Bot message
Chat.prototype.message = function (line,style) {
  return this.print(line,style||this.style.bot);
}

// Print ONE line (call mutiple times for multi-line text). Auto wrapping is supprted, though.
Chat.prototype.print = function (line,style) {
// Log(this.inputRange,this._clines.length); 
  var offsetY = this.childBase||0,
      cn1 = this._clines.length, y0=this.inputRange.y0,
      start = this.getLinearPos(this.value,offsetY+this.inputRange.y0,0),
      end   = this.getLinearPos(this.value,offsetY+this.inputRange.y1,
                                this._clines[offsetY+this.inputRange.y1].length);
//Log(this.cpos,this.inputRange,this.value)

  if (style) {
    if (style.color) line = '{'+style.color+'-fg}'+line+'{/'+style.color+'-fg}';
    if (style.align=='right') line = '{right}'+line+'{/right}';
  }
  
  var command = this.value.slice(start,end);
  this.value=this.value.slice(0,start)+line+'\n'+command+this.value.slice(end);
  this.screen.render();
  // Update inputRange
  var cn2= this._clines.length;
  this.scrollBottom();
  this.cpos.y += 10;
  this.cpos.x=this.inputRange.x0=this.prompt.length;
  this._updateCursor(true);
  this.inputRange.y0=Math.min(this._clines.length-1,this.cpos.y-this.inputRange.last);
  this.inputRange.y1=Math.min(this._clines.length-1,this.cpos.y);
// Log(this.cpos,this.inputRange,this.value)
}

Chat.prototype._listener = function(ch, key) { 
  // Cursor position must be synced with scrollablebox and vice versa (if scrollable)! A real mess.
  var done = this._done
    , self = this
    , value = this.value
    , clinesLength=this._clines.length
    , offsetY = this.childBase||0 // scrollable line offset if any
    , newline = false
    , lastchar = false
    , backspace = false
    , controlkey = false
    , lastline = (this.cpos.y+offsetY+1) == clinesLength;

// Log(key)  
  if (key.name === 'return') return;
  if (key.name === 'delete') {
    if (this.inputRange.x1 != undefined) {
      // ranged edit
      vpos=this.getLinearPos(this.value,offsetY+this.cpos.y, this.cpos.x);
        // shift right x0..x1 text, delete current char at this position
        var vpos0 = vpos-(this.cpos.x-this.inputRange.x0);
        this.value = this.value.substr(0,vpos0)+' '+
                     this.value.substr(vpos0,vpos-vpos0)+
                     this.value.substr(vpos+1,1000000);
        this.screen.render();
    }
    return;
  }
  if (key.name === 'enter') {
// Log(this._clines)
    if (this.inputRange.action) return this.inputRange.action();
    // clear input line; execute command; create new input line
    var start = this.getLinearPos(this.value,offsetY+this.inputRange.y0,0),
        end   = this.getLinearPos(this.value,offsetY+this.inputRange.y1,
                                  this._clines[offsetY+this.inputRange.y1].length);
// Log(this.inputRange,start,end,this.value,this._clines[0])
    var command = this.value.slice(start+this.prompt.length,end);
    if (command && command != this.history[this.historyTop-1])  {  
      this.history.push(command);
      this.historyTop=this.history.length;
    }
    this.value=this.value.slice(0,start)+this.prompt+this.value.slice(end);
    this.screen.render();
    offsetY = this.childBase||0;
    self.cpos.y += 10;  // bottom
    this._updateCursor(true);
    this.cpos.x=self._clines[offsetY+self.cpos.y].length;
    this.inputRange.y0=this.inputRange.y1=this.cpos.y; this.inputRange.last=0;
    this.print(command,this.style.user);
    this.emit('eval',command);
    return;
  }
  if (this.input!='text') return;


  function history(delta) {
    if (self.historyTop+delta<0 ) return self.scrollBottom();
    self.historyTop += delta;
    var start = self.getLinearPos(self.value,offsetY+self.inputRange.y0,0),
        end   = self.getLinearPos(self.value,offsetY+self.inputRange.y1,
                                  self._clines[offsetY+self.inputRange.y1].length);
    var command = self.history[self.historyTop]||'';
    self.historyTop = Math.min(Math.max(0,self.historyTop),self.history.length);
    self.value=self.value.slice(0,start)+self.prompt+command+self.value.slice(end);
    self.screen.render();
    self.scrollBottom();
    self.cpos.x=self._clines[self._clines.length-1].length;
    self.cpos.y += 10; // bottom
    self._updateCursor(true);
    self.inputRange.y1=self.cpos.y;
    offsetY = self.childBase||0;
    // find start y
    var y0=self.cpos.y; while (self._clines[offsetY+y0].indexOf(self.prompt)!=0) y0--;
    self.inputRange.y0=y0; 
    self.inputRange.last=self.inputRange.y1-self.inputRange.y0;
  }

  // Handle cursor positiong by keys.
  if (this.cursorControl) switch (key.name) {
    case 'left':
      controlkey=true;
      if (this.cpos.y==this.inputRange.y0 && this.cpos.x>this.inputRange.x0) this.cpos.x--;
      else if (this.cpos.y!=this.inputRange.y0 && this.cpos.x>0) this.cpos.x--;
      else if (this.cpos.y!=this.inputRange.y0 && this.cpos.x==0) {
        this.cpos.y--;
        this.cpos.x=this._clines[offsetY+this.cpos.y].length;
      }
      this._updateCursor(true);
      break;
    case 'right':
      controlkey=true;
      if (this.inputRange.x1 != undefined && this.cpos.x>=this.inputRange.x1) return;
      if (this.cpos.y>=this.inputRange.y0 && this.cpos.y<=this.inputRange.y1 && 
          this.cpos.x<this._clines[offsetY+this.cpos.y].length-1) {
        this.cpos.x++;
      } else if (this.cpos.y>=this.inputRange.y0 && this.cpos.y<this.inputRange.y1 && 
          this.cpos.x==this._clines[offsetY+this.cpos.y].length-1) {
        this.cpos.x=0;
        this.cpos.y++;
      } else if (this.cpos.y>=this.inputRange.y0 && this.cpos.y==this.inputRange.y1 && 
          this.cpos.x<this._clines[offsetY+this.cpos.y].length) {
        this.cpos.x++;
      }
      this._updateCursor(true);
      break;
    case 'up':
      controlkey=true;
      history(-1);
      return;
      break;
    case 'down':
      controlkey=true;
      history(1);
      return;
      break;
  }
  

  if (this.options.keys && key.ctrl && key.name === 'e') {
    return this.readEditor();
  }
  // Challenge: sync with line wrapping and adjust cursor and scrolling (done in element._wrapContent)
  
  // TODO: Optimize typing by writing directly
  // to the screen and screen buffer here.
  if (key.name === 'escape') {
    done(null, null);
  } else if (key.name === 'backspace') {
    backspace=true;
    if (this.cpos.y==this.inputRange.y0 && this.cpos.x<=this.inputRange.x0) return;
    if (this.inputRange.x1 != undefined) {
      // ranged edit
      vpos=this.getLinearPos(this.value,offsetY+this.cpos.y, this.cpos.x);
        // shift right x0..x1 text, delete current char at this position
      var vpos0 = vpos-(this.cpos.x-this.inputRange.x0);
      this.value = this.value.substr(0,vpos0)+' '+
                     this.value.substr(vpos0,vpos-vpos0)+
                     this.value.substr(vpos+1,1000000);
      this.screen.render();
      return;
    }
    if (this.value.length) {
      if (this.screen.fullUnicode) {
        if (unicode.isSurrogate(this.value, this.value.length - 2)) {
        // || unicode.isCombining(this.value, this.value.length - 1)) {
          this.value = this.value.slice(0, -2);
        } else {
          this.value = this.value.slice(0, -1);
        }
      } else {
        if (!this.cursorControl || 
             this.cpos.x==-1 ||
             (this.cpos.x==this._clines[offsetY+this.cpos.y].length &&
              this.cpos.y==this._clines.length-1-offsetY)) {
          // Delete last char of last line
          this.value = this.value.slice(0, -1);
        } else {
          // Delete char at current cursor position
          vpos=this.getLinearPos(this.value,offsetY+this.cpos.y, this.cpos.x);
          // vpos+= this.cpos.x;
          this.value = this.value.substr(0,vpos-1)+
                       this.value.substr(vpos,1000000);
        }
      }
      if (this.cpos.x>0) this.cpos.x--;
      else {this.cpos.x=-1; if (offsetY==0 && this.cpos.y>0 && lastline) this.cpos.y--; };
    }
  } else if (!controlkey && ch && this.inputRange.x1==undefined) {
    if (!/^[\x00-\x08\x0b-\x0c\x0e-\x1f\x7f]$/.test(ch)) {
      if (!this.cursorControl || 
           this.cpos.x==-1 ||
           (this.cpos.x==this._clines[offsetY+this.cpos.y].length &&
            this.cpos.y==this._clines.length-1-offsetY)) {
        // Append new char at end of (last) line
        lastchar=true;
        this.value += ch;
      } else {
        // Insert new char into line at current cursor position
        vpos=this.getLinearPos(this.value,offsetY+this.cpos.y, this.cpos.x);
        // vpos+= this.cpos.x;
        this.value = this.value.substr(0,vpos)+ch+
                     this.value.substr(vpos,1000000);
      }
      if (newline) {
        this.cpos.x=0;    // first left position is zero!
        this.cpos.y++;
      } else
        this.cpos.x++;
    }
  } else if (!controlkey && ch && this.inputRange.x1!=undefined) {
    // Range Edit (e.g., numerical form field)
    //////////////
    // shift or overwrite mode in limited char range (e.g., range selector)
    if (!/^[\x00-\x08\x0b-\x0c\x0e-\x1f\x7f]$/.test(ch)) {
      vpos=this.getLinearPos(this.value,offsetY+this.cpos.y, this.cpos.x);
      if (this.cpos.x!=this.inputRange.x1) {
        // Replace new char into line at current cursor position
        // vpos+= this.cpos.x;
        this.value = setCharAt(this.value,vpos,ch);
      } else {
        // shift left x0..x1 text, insert new char at right position
        var vpos0 = vpos-(this.inputRange.x1-this.inputRange.x0);
        this.value = this.value.substr(0,vpos0)+
                     this.value.substr(vpos0+1,vpos-vpos0)+ch+
                     this.value.substr(vpos+1,1000000);
        
      }
      if (this.cpos.x < this.inputRange.x1) this.cpos.x++;
    }
  }

  var rmline=this.cpos.x==-1;
  // TODO: clean up this mess; use rtof and ftor attributes of _clines
  // to determine where we are (react correctly on line wrap extension and reduction)
  if (this.value !== value) {
    var cn0=clinesLength,
        endofline=this.cpos.x==this._clines[offsetY+this.cpos.y].length+1,
        cn1=this._clines.length;
    var linelength1=this._clines[offsetY+this.cpos.y] && this._clines[offsetY+this.cpos.y].length;
    this.screen.render();
    var linelength2=this._clines[offsetY+this.cpos.y] && this._clines[offsetY+this.cpos.y].length;
    var cn2=this._clines.length;
// Log(this.cpos,linelength1,linelength2,cn0,cn2,this.inputRange,lastline,lastchar,endofline);
// Log('L',this.cpos,lastline,lastchar,endofline,linelength1,linelength2);
    if (cn2>cn0 && endofline) {
      this.scrollBottom();
      // wrap expansion
      this.cpos.y++; 
      this.inputRange.last++;
      if (this._clines[offsetY+this.cpos.y] && lastchar) this.cpos.x=this._clines[offsetY+this.cpos.y].length;
      else this.cpos.x=linelength1-linelength2-1;
      this._updateCursor(true);
      this.inputRange.y0=this.cpos.y-this.inputRange.last;
      this.inputRange.y1=this.cpos.y;
    } else if (cn2<cn0 && !rmline) { 
      // wrap reduction
      if (this.cpos.y>0 && !lastline && lastchar) this.cpos.y--;
      this.inputRange.last--;
      if (this._clines[offsetY+this.cpos.y]) this.cpos.x=this._clines[offsetY+this.cpos.y].length;
      this._updateCursor(true);
      this.inputRange.y0=this.cpos.y-this.inputRange.last;
      this.inputRange.y1=this.cpos.y;
      offsetY = this.childBase||0;
      this.cpos.x=this._clines[offsetY+this.cpos.y].length;
      this._updateCursor(true);
    } else if (linelength2<linelength1 && !backspace) {
      // wrap shift
      this.cpos.y++; 
      this.cpos.x=linelength1-linelength2;
      this._updateCursor(true);
    }
    if (offsetY>0 && backspace) {
      // @fix line deleted; refresh again due to miscalculation of height in scrollablebox!
      this.scroll(0);
      this.screen.render();
      if (rmline) {
        if (this._clines[offsetY+this.cpos.y]) this.cpos.x=this._clines[offsetY+this.cpos.y].length;
        else if (this._clines[offsetY+this.cpos.y-1]) this.cpos.x=this._clines[offsetY+this.cpos.y-1].length;
        this._updateCursor(true);
      }
    }
    this.emit('modified');
  }
  
};

// Return start position of nth (c)line in linear value string 
Chat.prototype.getLinearPos = function(v,clineIndex,cposx) {
  var lines=v.split('\n'),
      line=this._clines[clineIndex], // assuming search in plain text line (no ctrl/spaces aligns)
      flinenum=this._clines.rtof[clineIndex],
      vpos=flinenum>0?
           lines.slice(0,flinenum)
                .map(function (line) { return line.length+1 })
                .reduce(function (a,b) { return a+b }):0;
// console.log(clineIndex,lines.length,flinenum,lines[flinenum],line);
  // TODO: search from a start position in line estimated by _clines.ftor array
  function search(part,line) {
    // assuming search offset in plain text line (no ctrl/spaces aligns)
    var i=line.indexOf(part);
    if (i==-1) return 0;
    else return i;
  }
  if (lines[flinenum]) {
    return vpos+search(line,lines[flinenum])+cposx;
  } else
    return vpos+cposx;
}

Chat.prototype._typeScroll = function() {
  // XXX Workaround
  var height = this.height - this.iheight;
  // Scroll down?
// if (typeof Log != 'undefined') Log(this.childBase,this.childOffset,this.cpos.y,height);
  //if (this._clines.length - this.childBase > height) {
  if (this.cpos.y == height) {
    this.scroll(this._clines.length);
  }
};

Chat.prototype.getValue = function() {
  return this.value;
};

Chat.prototype.setValue = function(value) {
  if (value == null) {
    value = this.value;
  }
  if (this._value !== value) {
    this.value = value;
    this._value = value;
    this.setContent(this.value);
    this._typeScroll();
    this._updateCursor();
  }
};

Chat.prototype.clearInput =
Chat.prototype.clearValue = function() {
  return this.setValue('');
};

Chat.prototype.submit = function() {
  if (!this.__listener) return;
  return this.__listener('\x1b', { name: 'escape' });
};

Chat.prototype.cancel = function() {
  if (!this.__listener) return;
  return this.__listener('\x1b', { name: 'escape' });
};

Chat.prototype.render = function() {
  this.setValue();
  return this._render();
};

Chat.prototype.editor =
Chat.prototype.setEditor =
Chat.prototype.readEditor = function(callback) {
  var self = this;

  if (this._reading) {
    var _cb = this._callback
      , cb = callback;

    this._done('stop');

    callback = function(err, value) {
      if (_cb) _cb(err, value);
      if (cb) cb(err, value);
    };
  }

  if (!callback) {
    callback = function() {};
  }

  return this.screen.readEditor({ value: this.value }, function(err, value) {
    if (err) {
      if (err.message === 'Unsuccessful.') {
        self.screen.render();
        return self.readInput(callback);
      }
      self.screen.render();
      self.readInput(callback);
      return callback(err);
    }
    self.setValue(value);
    self.screen.render();
    return self.readInput(callback);
  });
};

/**
 * Expose
 */

module.exports = Chat;
};
BundleModuleCode['term/widgets/text']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Christopher Jeffrey and contributors, Stefan Bosse
 **    $INITIAL:     (C) 2013-2015, Christopher Jeffrey and contributors
 **    $MODIFIED:    by sbosse (2016-2017)
 **    $REVESIO:     1.2.1
 **
 **    $INFO:
 **
 **    text.js - text element for blessed
 **
 ** Usage:
 
   var obj = blessed.text({
    width: options.width||(options.content.length),
    left: (options.center?int(screen.width/2-options.content.length/2):options.left),
    right : options.right,
    top: options.top||0,
    height: 3,
    focus:false,
    align: 'center',
    content: options.content||'?',
    style: {
      bold:true
    }  
  });
  screen.append(obj);

  obj.setContent('New text');
  
 **    $ENDOFINFO
 */
var Comp = Require('com/compat');

/**
 * Modules
 */

var Node = Require('term/widgets/node');
var Element = Require('term/widgets/element');

/**
 * Text
 */

function Text(options) {
  if (!instanceOf(this, Node)) {
    return new Text(options);
  }
  options = options || {};
  options.shrink = true;
  Element.call(this, options);
}

//Text.prototype.__proto__ = Element.prototype;
inheritPrototype(Text,Element);

Text.prototype.type = 'text';

/**
 * Expose
 */

module.exports = Text;
};
BundleModuleCode['term/widgets/line']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Christopher Jeffrey, Stefan Bosse
 **    $INITIAL:     (C) 2013-2015, Christopher Jeffrey and contributors
 **    $MODIFIED:    by sbosse
 **    $REVESIO:     1.2.1
 **
 **    $INFO:
 **
 **    line.js - line element for blessed
 **
 **    $ENDOFINFO
 */

/**
 * Modules
 */
var Comp = Require('com/compat');

var Node = Require('term/widgets/node');
var Box = Require('term/widgets/box');

/**
 * Line
 */

function Line(options) {
  if (!instanceOf(this,Node)) {
    return new Line(options);
  }

  options = options || {};

  var orientation = options.orientation || 'vertical';
  delete options.orientation;

  if (orientation === 'vertical') {
    options.width = 1;
  } else {
    options.height = 1;
  }

  Box.call(this, options);

  this.ch = !options.type || options.type === 'line'
    ? orientation === 'horizontal' ? '─' : '│'
    : options.ch || ' ';

  this.border = {
    type: 'bg',
    __proto__: this
  };

  this.style.border = this.style;
}

//Line.prototype.__proto__ = Box.prototype;
inheritPrototype(Line,Box);

Line.prototype.type = 'line';

/**
 * Expose
 */

module.exports = Line;
};
BundleModuleCode['term/widgets/bigtext']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Christopher Jeffrey, Stefan Bosse
 **    $INITIAL:     (C) 2013-2015, Christopher Jeffrey and contributors
 **    $MODIFIED:    by sbosse
 **    $REVESIO:     1.2.1
 **
 **    $INFO:
 **
 **    bigtext.js - bigtext element for blessed
 **
 **    $ENDOFINFO
 */

/**
 * Modules
 */
var Comp = Require('com/compat');

var fs = require('fs');

var Node = Require('term/widgets/node');
var Box = Require('term/widgets/box');

/**
 * BigText
 */

function BigText(options) {
  if (!instanceOf(this instanceof Node)) {
    return new BigText(options);
  }
  options = options || {};
  options.font = options.font
    || __dirname + '/../../usr/fonts/ter-u14n.json';
  options.fontBold = options.font
    || __dirname + '/../../usr/fonts/ter-u14b.json';
  this.fch = options.fch;
  this.ratio = {};
  this.font = this.loadFont(options.font);
  this.fontBold = this.loadFont(options.font);
  Box.call(this, options);
  if (this.style.bold) {
    this.font = this.fontBold;
  }
}

//BigText.prototype.__proto__ = Box.prototype;
inheritPrototype(BigText,Box);

BigText.prototype.type = 'bigtext';

BigText.prototype.loadFont = function(filename) {
  var self = this
    , data
    , font;

  data = JSON.parse(fs.readFileSync(filename, 'utf8'));

  this.ratio.width = data.width;
  this.ratio.height = data.height;

  function convertLetter(ch, lines) {
    var line, i;

    while (lines.length > self.ratio.height) {
      lines.shift();
      lines.pop();
    }

    lines = lines.map(function(line) {
      var chs = line.split('');
      chs = chs.map(function(ch) {
        return ch === ' ' ? 0 : 1;
      });
      while (chs.length < self.ratio.width) {
        chs.push(0);
      }
      return chs;
    });

    while (lines.length < self.ratio.height) {
      line = [];
      for (i = 0; i < self.ratio.width; i++) {
        line.push(0);
      }
      lines.push(line);
    }

    return lines;
  }

  font = Object.keys(data.glyphs).reduce(function(out, ch) {
    var lines = data.glyphs[ch].map;
    out[ch] = convertLetter(ch, lines);
    return out;
  }, {});

  delete font[' '];

  return font;
};

BigText.prototype.setContent = function(content) {
  this.content = '';
  this.text = content || '';
};

BigText.prototype.render = function() {
  if (this.position.width == null || this._shrinkWidth) {
    // if (this.width - this.iwidth < this.ratio.width * this.text.length + 1) {
      this.position.width = this.ratio.width * this.text.length + 1;
      this._shrinkWidth = true;
    // }
  }
  if (this.position.height == null || this._shrinkHeight) {
    // if (this.height - this.iheight < this.ratio.height + 0) {
      this.position.height = this.ratio.height + 0;
      this._shrinkHeight = true;
    // }
  }

  var coords = this._render();
  if (!coords) return;

  var lines = this.screen.lines
    , left = coords.xi + this.ileft
    , top = coords.yi + this.itop
    , right = coords.xl - this.iright
    , bottom = coords.yl - this.ibottom;

  var dattr = this.sattr(this.style)
    , bg = dattr & 0x1ff
    , fg = (dattr >> 9) & 0x1ff
    , flags = (dattr >> 18) & 0x1ff
    , attr = (flags << 18) | (bg << 9) | fg;

  for (var x = left, i = 0; x < right; x += this.ratio.width, i++) {
    var ch = this.text[i];
    if (!ch) break;
    var map = this.font[ch];
    if (!map) continue;
    for (var y = top; y < Math.min(bottom, top + this.ratio.height); y++) {
      if (!lines[y]) continue;
      var mline = map[y - top];
      if (!mline) continue;
      for (var mx = 0; mx < this.ratio.width; mx++) {
        var mcell = mline[mx];
        if (mcell == null) break;
        if (this.fch && this.fch !== ' ') {
          lines[y][x + mx][0] = dattr;
          lines[y][x + mx][1] = mcell === 1 ? this.fch : this.ch;
        } else {
          lines[y][x + mx][0] = mcell === 1 ? attr : dattr;
          lines[y][x + mx][1] = mcell === 1 ? ' ' : this.ch;
        }
      }
      lines[y].dirty = true;
    }
  }

  return coords;
};

/**
 * Expose
 */

module.exports = BigText;
};
BundleModuleCode['term/widgets/list']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR.
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2013-2015, Christopher Jeffrey and contributors
 **    $MODIFIED:    by sbosse (2017-2018)
 **    $REVESIO:     1.2.5
 **
 **    $INFO:
 **
 **    list.js - list element for blessed
 **
 **     Added: 
 **       - 'arrows', arrow buttons
 **
 **     Options: {selectlast,selectoffset,label, border, style, arrows?}
 **
 **       selectlast:boolean    (try to) select always last selected item after modification
 **       selectoffset:number   additional (positive, downto) scroll shift on selection 
 **                             (otherwise selected line jumps to bottom of window)
 **
 **     Events In: click, keypress, element wheeldown, element wheelup, resize, adopt, remove
 **     Events Out: action(item,selected), select(item,selected), selected(item)
 **     Item content text: item.content
 **
 **
 **    Usage:
 **     Create list: list=new List({..});
 **     Update list: list.setItems([content1:string,content2:string,..]);
 **     Get selected item content (label): list.getSelected().getContent();
 **
 **    $ENDOFINFO
 */

/**
 * Modules
 */
var Comp = Require('com/compat');
var Io = Require('com/io');
var helpers = Require('term/helpers');

var Node = Require('term/widgets/node');
var Box = Require('term/widgets/box');
var Button = Require('term/widgets/button');
var Arrows = Require('term/widgets/arrows');

/**
 * List
 */

function List(options) {
  var self = this;

  if (!instanceOf(this,Node)) {
    return new List(options);
  }


  options = options || {};

  options.ignoreKeys = true;
  // Possibly put this here: this.items = [];
  options.scrollable = true;
  Box.call(this, options);
  this.value = '';
  this.items = [];
  this.ritems = [];
  this.selected = 0;
  this._isList = true;

  if (!this.style.selected) {
    this.style.selected = {};
    this.style.selected.bg = options.selectedBg;
    this.style.selected.fg = options.selectedFg;
    this.style.selected.bold = options.selectedBold;
    this.style.selected.underline = options.selectedUnderline;
    this.style.selected.blink = options.selectedBlink;
    this.style.selected.inverse = options.selectedInverse;
    this.style.selected.invisible = options.selectedInvisible;
  }

  if (!this.style.item) {
    this.style.item = {};
    this.style.item.bg = options.itemBg;
    this.style.item.fg = options.itemFg;
    this.style.item.bold = options.itemBold;
    this.style.item.underline = options.itemUnderline;
    this.style.item.blink = options.itemBlink;
    this.style.item.inverse = options.itemInverse;
    this.style.item.invisible = options.itemInvisible;
  }

  // Legacy: for apps written before the addition of item attributes.
  ['bg', 'fg', 'bold', 'underline',
   'blink', 'inverse', 'invisible'].forEach(function(name) {
    if (self.style[name] != null && self.style.item[name] == null) {
      self.style.item[name] = self.style[name];
    }
  });

  if (this.options.itemHoverBg) {
    this.options.itemHoverEffects = { bg: this.options.itemHoverBg };
  }

  if (this.options.itemHoverEffects) {
    this.style.item.hover = this.options.itemHoverEffects;
  }

  if (this.options.itemFocusEffects) {
    this.style.item.focus = this.options.itemFocusEffects;
  }

  this.interactive = options.interactive !== false;

  this.mouse = options.mouse || false;

  if (options.items) {
    this.ritems = options.items;
    options.items.forEach(this.add.bind(this));
  }

  this.select(0);

  if (options.mouse) {
    this.screen._listenMouse(this);
    this.on('element wheeldown', function() {
      self.select(self.selected + 2);
      self.screen.render();
    });
    this.on('element wheelup', function() {
      self.select(self.selected - 2);
      self.screen.render();
    });
  }

  if (options.keys) {
    this.on('keypress', function(ch, key) {
      if (key.name === 'up' || (options.vi && key.name === 'k')) {
        self.up();
        self.screen.render();
        self.emit('selected', self.items[self.selected]);
        return;
      }
      if (key.name === 'down' || (options.vi && key.name === 'j')) {
        self.down();
        self.screen.render();
        self.emit('selected', self.items[self.selected]);
        return;
      }
      if (key.name === 'enter'
          || (options.vi && key.name === 'l' && !key.shift)) {
        self.enterSelected();
        return;
      }
      if (key.name === 'escape' || (options.vi && key.name === 'q')) {
        self.cancelSelected();
        return;
      }
      if (options.vi && key.name === 'u' && key.ctrl) {
        self.move(-((self.height - self.iheight) / 2) | 0);
        self.screen.render();
        return;
      }
      if (options.vi && key.name === 'd' && key.ctrl) {
        self.move((self.height - self.iheight) / 2 | 0);
        self.screen.render();
        return;
      }
      if (options.vi && key.name === 'b' && key.ctrl) {
        self.move(-(self.height - self.iheight));
        self.screen.render();
        return;
      }
      if (options.vi && key.name === 'f' && key.ctrl) {
        self.move(self.height - self.iheight);
        self.screen.render();
        return;
      }
      if (options.vi && key.name === 'h' && key.shift) {
        self.move(self.childBase - self.selected);
        self.screen.render();
        return;
      }
      if (options.vi && key.name === 'm' && key.shift) {
        // TODO: Maybe use Math.min(this.items.length,
        // ... for calculating visible items elsewhere.
        var visible = Math.min(
          self.height - self.iheight,
          self.items.length) / 2 | 0;
        self.move(self.childBase + visible - self.selected);
        self.screen.render();
        return;
      }
      if (options.vi && key.name === 'l' && key.shift) {
        // XXX This goes one too far on lists with an odd number of items.
        self.down(self.childBase
          + Math.min(self.height - self.iheight, self.items.length)
          - self.selected);
        self.screen.render();
        return;
      }
      if (options.vi && key.name === 'g' && !key.shift) {
        self.select(0);
        self.screen.render();
        return;
      }
      if (options.vi && key.name === 'g' && key.shift) {
        self.select(self.items.length - 1);
        self.screen.render();
        return;
      }

      if (options.vi && (key.ch === '/' || key.ch === '?')) {
        if (typeof self.options.search !== 'function') {
          return;
        }
        return self.options.search(function(err, value) {
          if (typeof err === 'string' || typeof err === 'function'
              || typeof err === 'number' || (err && err.test)) {
            value = err;
            err = null;
          }
          if (err || !value) return self.screen.render();
          self.select(self.fuzzyFind(value, key.ch === '?'));
          self.screen.render();
        });
      }
    });
  }

  this.on('resize', function() {
    var visible = self.height - self.iheight;
    // if (self.selected < visible - 1) {
    if (visible >= self.selected + 1) {
      self.childBase = 0;
      self.childOffset = self.selected;
    } else {
      // Is this supposed to be: self.childBase = visible - self.selected + 1; ?
      self.childBase = self.selected - visible + 1;
      self.childOffset = visible - 1;
    }
  });

  this.on('adopt', function(el) {
    if (!~self.items.indexOf(el)) {
      el.fixed = true;
    }
  });

  // Ensure children are removed from the
  // item list if they are items.
  this.on('remove', function(el) {
    self.removeItem(el);
  });

  if (options.arrows) 
    Arrows(
      self,
      options,
      function () { self.select(self.selected - 2); self.screen.render()},
      function () { self.select(self.selected + 2); self.screen.render()}
    );
}

//List.prototype.__proto__ = Box.prototype;
inheritPrototype(List,Box);

List.prototype.type = 'list';

List.prototype.createItem = function(content) {
  var self = this;

  // Note: Could potentially use Button here.
  var options = {
    screen: this.screen,
    content: content,
    align: this.align || 'left',
    top: 0,
    left: 0,
    right: (this.scrollbar ? 1 : 0),
    tags: this.parseTags,
    height: 1,
    hoverEffects: this.mouse ? this.style.item.hover : null,
    focusEffects: this.mouse ? this.style.item.focus : null,
    autoFocus: false
  };

  if (!this.screen.autoPadding) {
    options.top = 1;
    options.left = this.ileft;
    options.right = this.iright + (this.scrollbar ? 1 : 0);
  }

  // if (this.shrink) {
  // XXX NOTE: Maybe just do this on all shrinkage once autoPadding is default?
  if (this.shrink && this.options.normalShrink) {
    delete options.right;
    options.width = 'shrink';
  }

  ['bg', 'fg', 'bold', 'underline',
   'blink', 'inverse', 'invisible'].forEach(function(name) {
    options[name] = function() {
      var attr = self.items[self.selected] === item && self.interactive
        ? self.style.selected[name]
        : self.style.item[name];
      if (typeof attr === 'function') attr = attr(item);
      return attr;
    };
  });

  if (this.style.transparent) {
    options.transparent = true;
  }

  var item = new Box(options);

  if (this.mouse) {
    item.on('click', function() {
      self.focus();
      if (self.items[self.selected] === item) {
        self.emit('action', item, self.selected);
        self.emit('select', item, self.selected);
        return;
      }
      self.select(item);
      self.emit('selected', self.items[self.selected]);
      self.screen.render();
    });
  }

  this.emit('create item');

  return item;
};

List.prototype.add =
List.prototype.addItem =
List.prototype.appendItem = function(content) {
  content = typeof content === 'string' ? content : content.getContent();

  var item = this.createItem(content);
  item.position.top = this.items.length;
  if (!this.screen.autoPadding) {
    item.position.top = this.itop + this.items.length;
  }

  this.ritems.push(content);
  this.items.push(item);
  this.append(item);

  if (this.items.length === 1) {
    this.select(0);
  }

  this.emit('add item');

  return item;
};

List.prototype.removeItem = function(child) {
  var i = this.getItemIndex(child);
  if (~i && this.items[i]) {
    child = this.items.splice(i, 1)[0];
    this.ritems.splice(i, 1);
    this.remove(child);
    for (var j = i; j < this.items.length; j++) {
      this.items[j].position.top--;
    }
    if (i === this.selected) {
      this.select(i - 1);
    }
  }
  this.emit('remove item');
  return child;
};

List.prototype.insertItem = function(child, content) {
  content = typeof content === 'string' ? content : content.getContent();
  var i = this.getItemIndex(child);
  if (!~i) return;
  if (i >= this.items.length) return this.appendItem(content);
  var item = this.createItem(content);
  for (var j = i; j < this.items.length; j++) {
    this.items[j].position.top++;
  }
  item.position.top = i + (!this.screen.autoPadding ? 1 : 0);
  this.ritems.splice(i, 0, content);
  this.items.splice(i, 0, item);
  this.append(item);
  if (i === this.selected) {
    this.select(i + 1);
  }
  this.emit('insert item');
};

List.prototype.getItem = function(child) {
  return this.items[this.getItemIndex(child)];
};

List.prototype.setItem = function(child, content) {
  content = typeof content === 'string' ? content : content.getContent();
  var i = this.getItemIndex(child);
  if (!~i) return;
  this.items[i].setContent(content);
  this.ritems[i] = content;
};

List.prototype.clearItems = function() {
  return this.setItems([]);
};

List.prototype.setItems = function(items) {
  var original = this.items.slice()
    , selected = this.selected
    , sel = this.ritems[this.selected]
    , i = 0;

  items = items.slice();

  this.select(0);

  for (; i < items.length; i++) {
    if (this.items[i]) {
      this.items[i].setContent(items[i]);
    } else {
      this.add(items[i]);
    }
  }

  for (; i < original.length; i++) {
    this.remove(original[i]);
  }

  this.ritems = items;

  // Try to find our old item if it still exists.
  // But how to deal with ambiquous string items? indexOf can point to wrong item!?
  sel = items.indexOf(sel);
  if (this.options.selectlast) this.select(selected);
  /*
  if (~sel) {
    this.select(sel);
  } else */ if (items.length === original.length) {
    this.select(selected);
  } else {
    this.select(Math.min(selected, items.length - 1));
  }

  this.emit('set items');
};

List.prototype.pushItem = function(content) {
  this.appendItem(content);
  return this.items.length;
};

List.prototype.popItem = function() {
  return this.removeItem(this.items.length - 1);
};

List.prototype.unshiftItem = function(content) {
  this.insertItem(0, content);
  return this.items.length;
};

List.prototype.shiftItem = function() {
  return this.removeItem(0);
};

List.prototype.spliceItem = function(child, n) {
  var self = this;
  var i = this.getItemIndex(child);
  if (!~i) return;
  var items = Array.prototype.slice.call(arguments, 2);
  var removed = [];
  while (n--) {
    removed.push(this.removeItem(i));
  }
  items.forEach(function(item) {
    self.insertItem(i++, item);
  });
  return removed;
};

List.prototype.find =
List.prototype.fuzzyFind = function(search, back) {
  var start = this.selected + (back ? -1 : 1)
    , i;

  if (typeof search === 'number') search += '';

  if (search && search[0] === '/' && search[search.length - 1] === '/') {
    try {
      search = new RegExp(search.slice(1, -1));
    } catch (e) {
      ;
    }
  }

  var test = typeof search === 'string'
    ? function(item) { return !!~item.indexOf(search); }
    : (search.test ? search.test.bind(search) : search);

  if (typeof test !== 'function') {
    if (this.screen.options.debug) {
      throw new Error('fuzzyFind(): `test` is not a function.');
    }
    return this.selected;
  }

  if (!back) {
    for (i = start; i < this.ritems.length; i++) {
      if (test(helpers.cleanTags(this.ritems[i]))) return i;
    }
    for (i = 0; i < start; i++) {
      if (test(helpers.cleanTags(this.ritems[i]))) return i;
    }
  } else {
    for (i = start; i >= 0; i--) {
      if (test(helpers.cleanTags(this.ritems[i]))) return i;
    }
    for (i = this.ritems.length - 1; i > start; i--) {
      if (test(helpers.cleanTags(this.ritems[i]))) return i;
    }
  }

  return this.selected;
};

List.prototype.getItemIndex = function(child) {
  if (typeof child === 'number') {
    return child;
  } else if (typeof child === 'string') {
    var i = this.ritems.indexOf(child);
    if (~i) return i;
    for (i = 0; i < this.ritems.length; i++) {
      if (helpers.cleanTags(this.ritems[i]) === child) {
        return i;
      }
    }
    return -1;
  } else {
    return this.items.indexOf(child);
  }
};

List.prototype.getSelected = function() {
  return this.items[this.selected]; 
}

List.prototype.select = function(index) {
  var lastindex=this.selected;
  if (!this.interactive) {
    return;
  }
  if (!this.items.length) {
    this.selected = 0;
    this.value = '';
    this.scrollTo(0);
    return;
  }
  if (typeof index === 'object') {
    index = this.items.indexOf(index);
  }
  
  if (index < 0) {
    index = 0;
  } else if (index >= this.items.length) {
    index = this.items.length - 1;
  }

  if (this.selected === index && this._listInitialized) return;
  this._listInitialized = true;

  this.selected = index;
  this.value = helpers.cleanTags(this.ritems[this.selected]);
  if (!this.parent) return;
  if (index>=lastindex)
    this.scrollTo(this.selected+(this.options.selectoffset||0));
  else
    this.scrollTo(this.selected);
    

  // XXX Move `action` and `select` events here.
  this.emit('select item', this.items[this.selected], this.selected);
};

List.prototype.move = function(offset) {
  this.select(this.selected + offset);
};

List.prototype.up = function(offset) {
  this.move(-(offset || 1));
};

List.prototype.down = function(offset) {
  this.move(offset || 1);
};

List.prototype.pick = function(label, callback) {
  if (!callback) {
    callback = label;
    label = null;
  }

  if (!this.interactive) {
    return callback();
  }

  var self = this;
  var focused = this.screen.focused;
  if (focused && focused._done) focused._done('stop');
  this.screen.saveFocus();

  // XXX Keep above:
  // var parent = this.parent;
  // this.detach();
  // parent.append(this);

  this.focus();
  this.show();
  this.select(0);
  if (label) this.setLabel(label);
  this.screen.render();
  this.once('action', function(el, selected) {
    if (label) self.removeLabel();
    self.screen.restoreFocus();
    self.hide();
    self.screen.render();
    if (!el) return callback();
    return callback(null, helpers.cleanTags(self.ritems[selected]));
  });
};

List.prototype.enterSelected = function(i) {
  if (i != null) this.select(i);
  this.emit('action', this.items[this.selected], this.selected);
  this.emit('select', this.items[this.selected], this.selected);
};

List.prototype.cancelSelected = function(i) {
  if (i != null) this.select(i);
  this.emit('action');
  this.emit('cancel');
};

/**
 * Expose
 */

module.exports = List;
};
BundleModuleCode['term/widgets/form']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Christopher Jeffrey, Stefan Bosse
 **    $INITIAL:     (C) 2013-2015, Christopher Jeffrey and contributors
 **    $MODIFIED:    by sbosse
 **    $REVESIO:     1.2.1
 **
 **    $INFO:
 **
 **    form.js - form element for blessed
 **
 **    $ENDOFINFO
 */

/**
 * Modules
 */
var Comp = Require('com/compat');

var Node = Require('term/widgets/node');
var Box = Require('term/widgets/box');

/**
 * Form
 */

function Form(options) {
  var self = this;

  if (!instanceOf(this,Node)) {
    return new Form(options);
  }

  options = options || {};

  options.ignoreKeys = true;
  Box.call(this, options);

  if (options.keys) {
    this.screen._listenKeys(this);
    this.on('element keypress', function(el, ch, key) {
      if ((key.name === 'tab' && !key.shift)
          || (el.type === 'textbox' && options.autoNext && key.name === 'enter')
          || key.name === 'down'
          || (options.vi && key.name === 'j')) {
        if (el.type === 'textbox' || el.type === 'textarea') {
          if (key.name === 'j') return;
          if (key.name === 'tab') {
            // Workaround, since we can't stop the tab from being added.
            el.emit('keypress', null, { name: 'backspace' });
          }
          el.emit('keypress', '\x1b', { name: 'escape' });
        }
        self.focusNext();
        return;
      }

      if ((key.name === 'tab' && key.shift)
          || key.name === 'up'
          || (options.vi && key.name === 'k')) {
        if (el.type === 'textbox' || el.type === 'textarea') {
          if (key.name === 'k') return;
          el.emit('keypress', '\x1b', { name: 'escape' });
        }
        self.focusPrevious();
        return;
      }

      if (key.name === 'escape') {
        self.focus();
        return;
      }
    });
  }
}

//Form.prototype.__proto__ = Box.prototype;
inheritPrototype(Form,Box),

Form.prototype.type = 'form';

Form.prototype._refresh = function() {
  // XXX Possibly remove this if statement and refresh on every focus.
  // Also potentially only include *visible* focusable elements.
  // This would remove the need to check for _selected.visible in previous()
  // and next().
  if (!this._children) {
    var out = [];

    this.children.forEach(function fn(el) {
      if (el.keyable) out.push(el);
      el.children.forEach(fn);
    });

    this._children = out;
  }
};

Form.prototype._visible = function() {
  return !!this._children.filter(function(el) {
    return el.visible;
  }).length;
};

Form.prototype.next = function() {
  this._refresh();

  if (!this._visible()) return;

  if (!this._selected) {
    this._selected = this._children[0];
    if (!this._selected.visible) return this.next();
    if (this.screen.focused !== this._selected) return this._selected;
  }

  var i = this._children.indexOf(this._selected);
  if (!~i || !this._children[i + 1]) {
    this._selected = this._children[0];
    if (!this._selected.visible) return this.next();
    return this._selected;
  }

  this._selected = this._children[i + 1];
  if (!this._selected.visible) return this.next();
  return this._selected;
};

Form.prototype.previous = function() {
  this._refresh();

  if (!this._visible()) return;

  if (!this._selected) {
    this._selected = this._children[this._children.length - 1];
    if (!this._selected.visible) return this.previous();
    if (this.screen.focused !== this._selected) return this._selected;
  }

  var i = this._children.indexOf(this._selected);
  if (!~i || !this._children[i - 1]) {
    this._selected = this._children[this._children.length - 1];
    if (!this._selected.visible) return this.previous();
    return this._selected;
  }

  this._selected = this._children[i - 1];
  if (!this._selected.visible) return this.previous();
  return this._selected;
};

Form.prototype.focusNext = function() {
  var next = this.next();
  if (next) next.focus();
};

Form.prototype.focusPrevious = function() {
  var previous = this.previous();
  if (previous) previous.focus();
};

Form.prototype.resetSelected = function() {
  this._selected = null;
};

Form.prototype.focusFirst = function() {
  this.resetSelected();
  this.focusNext();
};

Form.prototype.focusLast = function() {
  this.resetSelected();
  this.focusPrevious();
};

Form.prototype.submit = function() {
  var out = {};

  this.children.forEach(function fn(el) {
    if (el.value != null) {
      var name = el.name || el.type;
      if (Array.isArray(out[name])) {
        out[name].push(el.value);
      } else if (out[name]) {
        out[name] = [out[name], el.value];
      } else {
        out[name] = el.value;
      }
    }
    el.children.forEach(fn);
  });

  this.emit('submit', out);

  return this.submission = out;
};

Form.prototype.cancel = function() {
  this.emit('cancel');
};

Form.prototype.reset = function() {
  this.children.forEach(function fn(el) {
    switch (el.type) {
      case 'screen':
        break;
      case 'box':
        break;
      case 'text':
        break;
      case 'line':
        break;
      case 'scrollable-box':
        break;
      case 'list':
        el.select(0);
        return;
      case 'form':
        break;
      case 'input':
        break;
      case 'textbox':
        el.clearInput();
        return;
      case 'textarea':
        el.clearInput();
        return;
      case 'button':
        delete el.value;
        break;
      case 'progress-bar':
        el.setProgress(0);
        break;
      case 'file-manager':
        el.refresh(el.options.cwd);
        return;
      case 'checkbox':
        el.uncheck();
        return;
      case 'radio-set':
        break;
      case 'radio-button':
        el.uncheck();
        return;
      case 'prompt':
        break;
      case 'question':
        break;
      case 'message':
        break;
      case 'info':
        break;
      case 'loading':
        break;
      case 'list-bar':
        //el.select(0);
        break;
      case 'dir-manager':
        el.refresh(el.options.cwd);
        return;
      case 'terminal':
        el.write('');
        return;
      case 'image':
        //el.clearImage();
        return;
    }
    el.children.forEach(fn);
  });

  this.emit('reset');
};

/**
 * Expose
 */

module.exports = Form;
};
BundleModuleCode['term/widgets/textarea']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Christopher Jeffrey, Stefan Bosse
 **    $INITIAL:     (C) 2013-2018, Christopher Jeffrey and contributors
 **    $MODIFIED:    by sbosse (2017-2021)
 **    $REVESIO:     1.5.2
 **
 **    $INFO:
 **
 **    textarea.js - textarea element for blessed
 **
 **    new: cursor control
 **
 **    special options: {cursorControl}
 ** 
 **    $ENDOFINFO
 */

/**
 * Modules
 */
var Comp = Require('com/compat');

var unicode = Require('term/unicode');

var nextTick = global.setImmediate || process.nextTick.bind(process);

var Node = Require('term/widgets/node');
var Input = Require('term/widgets/input');

/**
 * Textarea
 */

function Textarea(options) {
  var self = this;

  if (!instanceOf(this,Node)) {
    return new Textarea(options);
  }

  options = options || {};

  options.scrollable = options.scrollable !== false;

  Input.call(this, options);

  this.screen._listenKeys(this);

  this.value = options.value || '';
  // cursor position
  this.cpos = {x:-1,y:-1};
  this.cursorControl=true;
  this.multiline=options.multiline;
  
  this.__updateCursor = this._updateCursor.bind(this);
  this.on('resize', this.__updateCursor);
  this.on('move', this.__updateCursor);

  if (options.inputOnFocus) {
    this.on('focus', this.readInput.bind(this, null));
  }

  if (!options.inputOnFocus && options.keys) {
    this.on('keypress', function(ch, key) {
      if (self._reading) return;
      if (key.name === 'enter' || (options.vi && key.name === 'i')) {
        return self.readInput();
      }
      if (key.name === 'e') {
        return self.readEditor();
      }
    });
  }

  if (options.mouse) {
    this.on('click', function(data) {
      if (self._reading) return;
      if (data.button !== 'right') return;
      self.readEditor();
    });
  }
}

//Textarea.prototype.__proto__ = Input.prototype;
inheritPrototype(Textarea,Input);

Textarea.prototype.type = 'textarea';

Textarea.prototype._updateCursor = function(get) {
  if (this.screen.focused !== this) {
    return;
  }
  var lpos = get ? this.lpos : this._getCoords();
  if (!lpos) return;

  var last = this._clines[this._clines.length - 1]
    , program = this.screen.program
    , line
    , offsetY = this.childBase||0
    , cx
    , cy;

  // Stop a situation where the textarea begins scrolling
  // and the last cline appears to always be empty from the
  // _typeScroll `+ '\n'` thing.
  // Maybe not necessary anymore?
  if (last === '' && this.value[this.value.length - 1] !== '\n') {
    last = this._clines[this._clines.length - 2] || '';
  }

  line = Math.min(
    this._clines.length - 1 - (this.childBase || 0),
    (lpos.yl - lpos.yi) - this.iheight - 1);

  // When calling clearValue() on a full textarea with a border, the first
  // argument in the above Math.min call ends up being -2. Make sure we stay
  // positive.
  line = Math.max(0, line);
  if (this.cpos.x==-1 || !this.cursorControl) this.cpos.x = this.strWidth(last);
  if (this.cpos.y==-1 || !this.cursorControl) this.cpos.y = line;
  this.cpos.y = Math.min(this.cpos.y,line);
  this.cpos.x = Math.min(this.cpos.x,this.strWidth(this._clines[offsetY+this.cpos.y]));
    
  cx = lpos.xi + this.ileft + this.cpos.x;
  cy = lpos.yi + this.itop + this.cpos.y;

  // XXX Not sure, but this may still sometimes
  // cause problems when leaving editor.
  if (cy === program.y && cx === program.x) {
    return;
  }

  if (cy === program.y) {
    if (cx > program.x) {
      program.cuf(cx - program.x);
    } else if (cx < program.x) {
      program.cub(program.x - cx);
    }
  } else if (cx === program.x) {
    if (cy > program.y) {
      program.cud(cy - program.y);
    } else if (cy < program.y) {
      program.cuu(program.y - cy);
    }
  } else {
    program.cup(cy, cx);
  }
};

Textarea.prototype.input =
Textarea.prototype.setInput =
Textarea.prototype.readInput = function(callback) {
  var self = this
    , focused = this.screen.focused === this;

  if (this._reading) return;
  this._reading = true;

  this._callback = callback;

  if (!focused) {
    this.screen.saveFocus();
    this.focus();
  }

  this.screen.grabKeys = true;

  this._updateCursor();
  this.screen.program.showCursor();
  //this.screen.program.sgr('normal');
  this._done = function fn(err, value) {
    if (!self._reading) return;

    if (fn.done) return;
    fn.done = true;


    delete self._callback;
    delete self._done;

    self.removeListener('keypress', self.__listener);
    delete self.__listener;

    self.removeListener('blur', self.__done);
    delete self.__done;

    self.screen.program.hideCursor();
    self.screen.grabKeys = false;

    if (!focused) {
      self.screen.restoreFocus();
    }

    if (self.options.inputOnFocus) {
      self.screen.rewindFocus();
    }

    self._reading = false;

    // Ugly
    if (err === 'stop') return;

    if (err) {
      self.emit('error', err);
    } else if (value != null) {
      self.emit('submit', value);
    } else {
      self.emit('cancel', value);
    }
    self.emit('action', value);

    if (!callback) return;

    return err
      ? callback(err)
      : callback(null, value);
  };

  // Put this in a nextTick so the current
  // key event doesn't trigger any keys input.
  
  nextTick(function() {
    if (self.__listener) {
      // double fired?
      return;
    }
    self.__listener = self._listener.bind(self);
    self.on('keypress', self.__listener);
  });

  this.__done = this._done.bind(this, null, null);
  this.on('blur', this.__done);
};

Textarea.prototype._listener = function(ch, key) {
  // Cursor position must be synced with scrollablebox and vice versa (if scrollable)! A real mess.
  var done = this._done
    , self = this
    , value = this.value
    , clinesLength=this._clines.length
    , offsetY = this.childBase||0 // scrollable line offset if any
    , newline = false
    , backspace = false
    , lastline = (this.cpos.y+offsetY+1) == clinesLength;

  if (key.name == 'linefeed' ||
      (!this.multiline && key.name=='enter')) return this.emit('enter',this.value);
  
  if (key.name === 'return') return;
  if (key.name === 'enter') {
    ch = '\n';
    // this.cpos.x=1;
    // this.cpos.y++;
    newline=true;
  }

  // Handle cursor positiong by keys.
  if (this.cursorControl) switch (key.name) {
    case 'left':
      if (this.cpos.x>0) this.cpos.x--;
      else {
        if (this.cpos.y>0) {
          this.cpos.y--;
          this.cpos.x=this._clines[offsetY+this.cpos.y].length;
        } else if (offsetY>0) {
          if (this.scrollable) this.scroll(-1);
          self.screen.render();
          this.cpos.x=this._clines[offsetY+this.cpos.y-1].length;          
        }
      }
      this._updateCursor(true);
      break;
    case 'right':
      var next=++this.cpos.x;
      this._updateCursor(true);
      if (this.cpos.x!=next && (offsetY+this.cpos.y+1)<this._clines.length) {
        next=++this.cpos.y;
        this.cpos.x=0;
        this._updateCursor(true);
        if (this.scrollable && this.cpos.y!=next) this.scroll(1);
      }
      break;
    case 'up':
      if (this.cpos.y>0) {
        this.cpos.y--;
        //this.cpos.x=this.strWidth(this._clines[this.cpos.y]);
      }
      this._updateCursor(true);
      break;
    case 'down':
      this.cpos.y++;
      this._updateCursor(true);
      break;
  }
  

  if (this.options.keys && key.ctrl && key.name === 'e') {
    return this.readEditor();
  }
  // Challenge: sync with line wrapping and adjust cursor and scrolling (done in element._wrapContent)
  
  // TODO: Optimize typing by writing directly
  // to the screen and screen buffer here.
  if (key.name === 'escape') {
    done(null, null);
  } else if (key.name === 'backspace') {
    backspace=true;
    if (this.value.length) {
      if (this.screen.fullUnicode) {
        if (unicode.isSurrogate(this.value, this.value.length - 2)) {
        // || unicode.isCombining(this.value, this.value.length - 1)) {
          this.value = this.value.slice(0, -2);
        } else {
          this.value = this.value.slice(0, -1);
        }
      } else {
        if (!this.cursorControl || 
             this.cpos.x==-1 ||
             (this.cpos.x==this._clines[offsetY+this.cpos.y].length &&
              this.cpos.y==this._clines.length-1-offsetY)) {
          // Delete last char of last line
          this.value = this.value.slice(0, -1);
        } else {
          // Delete char at current cursor position
          vpos=this.getLinearPos(this.value,offsetY+this.cpos.y, this.cpos.x);
          // vpos+= this.cpos.x;
          this.value = this.value.substr(0,vpos-1)+
                       this.value.substr(vpos,1000000);
        }
      }
      if (this.cpos.x>0) this.cpos.x--;
      else {this.cpos.x=-1; if (offsetY==0 && this.cpos.y>0 && lastline) this.cpos.y--; };
    }
  } else if (ch) {
    if (!/^[\x00-\x08\x0b-\x0c\x0e-\x1f\x7f]$/.test(ch)) {
      if (!this.cursorControl || 
           this.cpos.x==-1 ||
           (this.cpos.x==this._clines[offsetY+this.cpos.y].length &&
            this.cpos.y==this._clines.length-1-offsetY))
        // Append new char at end of (last) line
        this.value += ch;
      else {
        // Insert new char into line at current cursor position
        vpos=this.getLinearPos(this.value,offsetY+this.cpos.y, this.cpos.x);
        // vpos+= this.cpos.x;
        this.value = this.value.substr(0,vpos)+ch+
                     this.value.substr(vpos,1000000);
      }
      if (newline) {
        this.cpos.x=0;    // first left position is zero!
        this.cpos.y++;
      } else  
        this.cpos.x++;
    }
  }

  var rmline=this.cpos.x==-1;
  // if (this.childOffset!=undefined) this.childOffset=this.cpos.y;
  
  // TODO: clean up this mess; use rtof and ftor attributes of _clines
  // to determine where we are (react correctly on line wrap extension and reduction)
  
  if (this.value !== value) {
    var cn0=clinesLength,
        cn1=this._clines.length,
        y0=this.cpos.y,
        linelength=this._clines[offsetY+this.cpos.y] && this._clines[offsetY+this.cpos.y].length,
        endofline=this.cpos.x==linelength+1;
// Log(this.cpos,this.childBase);    
    this.screen.render();
    var cn2=this._clines.length;
// Log(this.cpos,lastline,endofline,rmline,newline,backspace,cn0,cn2,this.childBase);
    if (!newline && endofline && cn2>cn0) {
      // wrap expansion
      if (this.scrollable && lastline) this.scrollBottom();
      this.cpos.y++; 
      this._updateCursor(true);
      if (this._clines[offsetY+this.cpos.y]) this.cpos.x=this._clines[offsetY+this.cpos.y].length;
      this._updateCursor(true);
    } else if (cn2<cn0 && !rmline) { 
      // wrap reduction
      if (this.cpos.y>0 && !lastline  && endofline) this.cpos.y--;
      this._updateCursor(true);
      offsetY=this.childBase||0;
      if (this._clines[offsetY+this.cpos.y]) this.cpos.x=this._clines[offsetY+this.cpos.y].length;
      this._updateCursor(true);
    } else if (cn2<cn0 && !rmline && this.cpos.x==0) { 
      // wrap reduction
      if (this._clines[offsetY+this.cpos.y]) this.cpos.x=this._clines[offsetY+this.cpos.y].length;
      this._updateCursor(true);
    };
    if (offsetY>0 && backspace) {
      // @fix line deleted; refresh again due to miscalculation of height in scrollablebox!
      if (this.scrollable) this.scroll(0);
      this.screen.render();
      if (rmline && cn0!=cn2) {
        if (this._clines[offsetY+this.cpos.y]) this.cpos.x=this._clines[offsetY+this.cpos.y].length;
        else if (this._clines[offsetY+this.cpos.y-1]) this.cpos.x=this._clines[offsetY+this.cpos.y-1].length;
        this._updateCursor(true);
      }
    }
  }
  
};

// Return start position of nth (c)line in linear value string 
Textarea.prototype.getLinearPos = function(v,clineIndex,cposx) {
  // clineIndex is the index in the _clines array, cposx the cursor position in this line!
  var vpos=0,len=v.length,cline,clinePos=0,clineNum=0;
  cline=this._clines[clineNum];
  // To support auto line wrapping the clines have to be parsed, too!
  while (vpos < len && clineIndex) {
    if (v.charAt(vpos)=='\n') {
        clinePos=-1;
        clineIndex--;
        clineNum++;
        cline=this._clines[clineNum];
    } else {
      if (v.charAt(vpos) != cline.charAt(clinePos)) {
        // 
        clinePos=0;
        clineIndex--;
        clineNum++;
        cline=this._clines[clineNum];
        continue;
      }
    }
    vpos++; clinePos++;
  }
  if (clineIndex==0) return vpos+cposx;
  else 0
}

Textarea.prototype._typeScroll = function() {
  // XXX Workaround
  var height = this.height - this.iheight;
  // Scroll down?
// if (typeof Log != 'undefined') Log(this.childBase,this.childOffset,this.cpos.y,height);
  //if (this._clines.length - this.childBase > height) {
  if (this.cpos.y == height) {
    if (this.scrollable) this.scroll(this._clines.length);
  }
};

Textarea.prototype.getValue = function() {
  return this.value;
};

Textarea.prototype.setValue = function(value) {
  if (value == null) {
    value = this.value;
  }
  if (this._value !== value) {
    this.value = value;
    this._value = value;
    this.setContent(this.value);
    this._typeScroll();
    this._updateCursor();
  }
};

Textarea.prototype.clearInput =
Textarea.prototype.clearValue = function() {
  return this.setValue('');
};

Textarea.prototype.submit = function() {
  if (!this.__listener) return;
  return this.__listener('\x1b', { name: 'escape' });
};

Textarea.prototype.cancel = function() {
  if (!this.__listener) return;
  return this.__listener('\x1b', { name: 'escape' });
};

Textarea.prototype.render = function() {
  this.setValue();
  return this._render();
};

Textarea.prototype.editor =
Textarea.prototype.setEditor =
Textarea.prototype.readEditor = function(callback) {
  var self = this;

  if (this._reading) {
    var _cb = this._callback
      , cb = callback;

    this._done('stop');

    callback = function(err, value) {
      if (_cb) _cb(err, value);
      if (cb) cb(err, value);
    };
  }

  if (!callback) {
    callback = function() {};
  }

  return this.screen.readEditor({ value: this.value }, function(err, value) {
    if (err) {
      if (err.message === 'Unsuccessful.') {
        self.screen.render();
        return self.readInput(callback);
      }
      self.screen.render();
      self.readInput(callback);
      return callback(err);
    }
    self.setValue(value);
    self.screen.render();
    return self.readInput(callback);
  });
};

/**
 * Expose
 */

module.exports = Textarea;
};
BundleModuleCode['term/widgets/textbox']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Christopher Jeffrey, Stefan Bosse
 **    $INITIAL:     (C) 2013-2018, Christopher Jeffrey and contributors
 **    $MODIFIED:    by sbosse
 **    $REVESIO:     1.5.2
 **
 **    $INFO:
 **
 **    textbox.js - textbox element for blessed
 **
 **     Special options: {wrap,multiline,screct,censor}
 **
 **  Usage:
  
     1. Editable
     
     var obj = blessed.textbox({
      label: options.label||'My Input',
      value: options.value||'default value',
      fg: 'blue',
      bg: 'default',
      barBg: 'default',
      barFg: 'blue',
      width: options.width||(self.screen.width-(options.left*2||2)),
      height: 3,
      left: options.left,
      right : options.right,
      top: options.top||0,
      keys: true,
      vi: true,
      mouse: true,
      inputOnFocus: true,
      focus:true,
      //draggable:true,
      border: {
        type: 'line'
      },
      style: {
        fg:'blue',
        focus : {
          border: {
            fg: 'red'
          }      
        }
      }
    });
    
    2. Non editable

    var obj = blessed.textbox({
      top: options.top||1,
      left: options.left||1,
      width: options.width,
      height: options.height||3,
      label: options.label,
      focus:false,
      //draggable:true,
      border: {
        type: 'line'
      },
      style: {
        fg:'blue'
      }
    });
    
    screen.append(obj);
    
    obj.setValue('Some Text')
 **
 **
 ** New options: wrap:boolean
 **
 **    $ENDOFINFO
 */
var options = {
  version:'1.5.2'
}
/**
 * Modules
 */
var Comp = Require('com/compat');

var Node = Require('term/widgets/node');
var Textarea = Require('term/widgets/textarea');

/**
 * Textbox
 */

function Textbox(options) {
  if (!instanceOf(this,Node)) {
    return new Textbox(options);
  }

  options = options || {};

  options.scrollable = options.scrollable||false;

  Textarea.call(this, options);

  // Special options
  this.secret = options.secret;
  this.censor = options.censor;
  this.wrap   = options.wrap;
  this.multiline = options.multiline;
}

//Textbox.prototype.__proto__ = Textarea.prototype;
inheritPrototype(Textbox,Textarea);

Textbox.prototype.type = 'textbox';

Textbox.prototype.__olistener = Textbox.prototype._listener;
Textbox.prototype._listener = function(ch, key) {
  if (!this.multiline && key.name === 'enter') {
    this.emit('enter',this.value);
    this._done(null, this.value);
    return;
  }
  return this.__olistener(ch, key);
};

Textbox.prototype.setValue = function(value) {
  var visible, val, i, line, sep;
  if (value == null) {
    value = this.value;
  }
  if (this._value !== value) {
    if (!this.multiline) {
      value = value.replace(/\n/g, '');
    }
    this.value = value;
    this._value = value;
    if (this.secret) {
      this.setContent('');
    } else if (this.censor) {
      this.setContent(Array(this.value.length + 1).join('*'));
    } else if (this.wrap && this.value.length > (this.width - this.iwidth - 1)) {
      line='';i=0, sep='';
      visible=this.width - this.iwidth - 1;
      val=this.value;
      while (val.length>0) {
        line = line + val.substr(0,visible);
        val = val.substr(visible,val.length-visible);  
        sep='\n';
      }
      this.setContent(line);    
    } else if (this.multiline) {
      val = this.value.replace(/\t/g, this.screen.tabc);
      this.setContent(val);
    } else {
      visible = -(this.width - this.iwidth - 1);
      val = this.value.replace(/\t/g, this.screen.tabc);
      this.setContent(val.slice(visible));
    }
    this._updateCursor();
  }
};
// setValue + update screen IM
Textbox.prototype.update = function(value) {
  this.setValue(value);
  this.screen.render();
}

Textbox.prototype.submit = function() {
  if (!this.__listener) return;
  return this.__listener('\r', { name: 'enter' });
};

/**
 * Expose
 */

module.exports = Textbox;
};
BundleModuleCode['term/widgets/progressbar']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Christopher Jeffrey, Stefan Bosse
 **    $INITIAL:     (C) 2013-2015, Christopher Jeffrey and contributors
 **    $MODIFIED:    by sbosse
 **    $REVESIO:     1.2.1
 **
 **    $INFO:
 **
 **    progressbar.js - progress bar element for blessed
 **
 **    $ENDOFINFO
 */

/**
 * Modules
 */
var Comp = Require('com/compat');

var Node = Require('term/widgets/node');
var Input = Require('term/widgets/input');

/**
 * ProgressBar
 */

function ProgressBar(options) {
  var self = this;

  if (!instanceOf(this,Node)) {
    return new ProgressBar(options);
  }

  options = options || {};

  Input.call(this, options);

  this.filled = options.filled || 0;
  if (typeof this.filled === 'string') {
    this.filled = +this.filled.slice(0, -1);
  }
  this.value = this.filled;

  this.pch = options.pch || ' ';

  // XXX Workaround that predates the usage of `el.ch`.
  if (options.ch) {
    this.pch = options.ch;
    this.ch = ' ';
  }
  if (options.bch) {
    this.ch = options.bch;
  }

  if (!this.style.bar) {
    this.style.bar = {};
    this.style.bar.fg = options.barFg;
    this.style.bar.bg = options.barBg;
  }

  this.orientation = options.orientation || 'horizontal';

  if (options.keys) {
    this.on('keypress', function(ch, key) {
      var back, forward;
      if (self.orientation === 'horizontal') {
        back = ['left', 'h'];
        forward = ['right', 'l'];
      } else if (self.orientation === 'vertical') {
        back = ['down', 'j'];
        forward = ['up', 'k'];
      }
      if (key.name === back[0] || (options.vi && key.name === back[1])) {
        self.progress(-5);
        self.screen.render();
        return;
      }
      if (key.name === forward[0] || (options.vi && key.name === forward[1])) {
        self.progress(5);
        self.screen.render();
        return;
      }
    });
  }

  if (options.mouse) {
    this.on('click', function(data) {
      var x, y, m, p;
      if (!self.lpos) return;
      if (self.orientation === 'horizontal') {
        x = data.x - self.lpos.xi;
        m = (self.lpos.xl - self.lpos.xi) - self.iwidth;
        p = x / m * 100 | 0;
      } else if (self.orientation === 'vertical') {
        y = data.y - self.lpos.yi;
        m = (self.lpos.yl - self.lpos.yi) - self.iheight;
        p = y / m * 100 | 0;
      }
      self.setProgress(p);
    });
  }
}

//ProgressBar.prototype.__proto__ = Input.prototype;
inheritPrototype(ProgressBar,Input);

ProgressBar.prototype.type = 'progress-bar';

ProgressBar.prototype.render = function() {
  var ret = this._render();
  if (!ret) return;

  var xi = ret.xi
    , xl = ret.xl
    , yi = ret.yi
    , yl = ret.yl
    , dattr;

  if (this.border) xi++, yi++, xl--, yl--;

  if (this.orientation === 'horizontal') {
    xl = xi + ((xl - xi) * (this.filled / 100)) | 0;
  } else if (this.orientation === 'vertical') {
    yi = yi + ((yl - yi) - (((yl - yi) * (this.filled / 100)) | 0));
  }

  dattr = this.sattr(this.style.bar);

  this.screen.fillRegion(dattr, this.pch, xi, xl, yi, yl);

  if (this.content) {
    var line = this.screen.lines[yi];
    for (var i = 0; i < this.content.length; i++) {
      line[xi + i][1] = this.content[i];
    }
    line.dirty = true;
  }

  return ret;
};

ProgressBar.prototype.progress = function(filled) {
  this.filled += filled;
  if (this.filled < 0) this.filled = 0;
  else if (this.filled > 100) this.filled = 100;
  if (this.filled === 100) {
    this.emit('complete');
  }
  this.value = this.filled;
};

ProgressBar.prototype.setProgress = function(filled) {
  this.filled = 0;
  this.progress(filled);
};

ProgressBar.prototype.reset = function() {
  this.emit('reset');
  this.filled = 0;
  this.value = this.filled;
};

/**
 * Expose
 */

module.exports = ProgressBar;
};
BundleModuleCode['term/widgets/filemanager']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Christopher Jeffrey and contributors, Stefan Bosse
 **    $INITIAL:     (C) 2013-2018, Christopher Jeffrey and contributors
 **    $VERSION:     1.4.2
 **
 **    $INFO:
 *
 * filemanager.js - file manager element for blessed
 *  
 * Events: 'ioerror','cd','file'
 *
 * New options: okayButton, cancelButton, autohide, select (select emits file event),
 *              noshow, arrows: {up:'[-]',down:'[+]',width:3,height:1,fg:'red',bg:'default'}},
 *              box:{bg,border}, input:{fg,bg,border}
 *
 **    $ENDOFINFO
 */

var options = {
  version:'1.4.2'
}
/**
 * Modules
 */
var Comp = Require('com/compat');

var path = Require('path')
  , fs = Require('fs');


var Node = Require('term/widgets/node');
var List = Require('term/widgets/list');
var Button = Require('term/widgets/button');
var Helpers = Require('term/helpers');
var Box = Require('term/widgets/box');
var TextBox = Require('term/widgets/textbox');
var Arrows = Require('term/widgets/arrows');
var Screen = Require('term/widgets/screen');

/**
 * FileManager
 */

function FileManager(options) {
  var self = this,
      bbox,
      off1=0,
      off2=0,
      arrows=options.arrows;

  if (!instanceOf(this,Node)) {
    return new FileManager(options);
  }

  options = options || {};
  options.parseTags = true;
  options.mouse = true;
  options.arrows=_;   // optional arrows are handled here, not in list

  if (options.parent == Screen.global) {
    // Screen overlay; adjust top/height settings
    bbox=Helpers.bbox(Screen.global,options);
    if (options.box) bbox.top += 1,bbox.height -= 2,bbox.width -= 4,bbox.left += 2;
    if (options.input) bbox.height -= 3;
    if (options.cancelButton||options.okayButton) bbox.height -= 1;
    if (arrows) bbox.width -= 4,bbox.left += 2;
    options.top=bbox.top;
    options.left=bbox.left;
    options.height=bbox.height;
    options.width=bbox.width;
  }
      
  // options.label = ' {blue-fg}%path{/blue-fg} ';
  List.call(this, options);

  options.arrows=arrows;
  
  this.cwd = options.cwd || process.cwd();
  this.file = this.cwd;
  this.value = this.cwd;
  this.noshow = options.noshow;
  
  if (options.parent == this.screen) {
    // Collect clickable elements of this widget
    this._clickable=this.screen.clickable;
    this.screen.clickable=[];
    
    // compute for button positions
    bbox=Helpers.bbox(this.screen,options);
    if (options.cancelButton||options.okayButton) off1=2;
    if (options.input) off2=3;
    if (options.box) 
      this._.box = new Box({
        top:bbox.top-2,
        width:bbox.width+8,
        left:bbox.left-4,
        height:bbox.height+3+off1+off2,
        hidden:options.hidden,
        border:options.box.border,
        style:{
          label:options.label,
          fg:options.box.fg,
          bg:options.box.bg||'white'
        }
      });
    if (this._.box) this.screen.append(this._.box);

    if (options.input) {
      this._.input =  new TextBox({
        screen: this.screen,
        top: bbox.top+bbox.height+(options.box?1:0)-1,
        height: options.input.border&&options.input.border.type=='line'?3:1,
        width: bbox.width,
        left: bbox.left,
        keys: options.input.mutable?true:undefined,
        vi: options.input.mutable?true:undefined,
        mouse: options.input.mutable?true:undefined,
        inputOnFocus: options.input.mutable?true:undefined,
        value: options.input.value||'<input>',
        hidden:options.hidden,
        border: options.input.border,
        style: {
          fg:options.input.fg||'black',
          bg:options.input.bg||'white',
          bold:true
        }
      });
      this.screen.append(this._.input);
    }
    
    if (options.okayButton) {
      this._.okay = new Button({
        screen: this.screen,
        top: bbox.top+bbox.height+(options.box?1:0)+off2,
        height: 1,
        left: bbox.left+1,
        width: 10,
        content: options.okayButton,
        align: 'center',
        style: {
          fg:'white',
          bg: 'blue',
          bold:true,
        },
        autoFocus: false,
        hidden:options.hidden,
        mouse: true
      });
      this._.okay.on('press',function () { 
        var item=self.items[self.selected],
            value = self._.input?
                    self._.input.getValue():
                    item.content.replace(/\{[^{}]+\}/g, '').replace(/@$/, ''),
            file=path.resolve(self.cwd, value);
        self.emit('file', file);
        self.hide(); 
      });
      this.screen.append(this._.okay);
    }
    if (options.cancelButton) {
      this._.cancel = new Button({
        screen: this.screen,
        top: bbox.top+bbox.height+(options.box?1:0)+off2,
        height: 1,
        left: bbox.left+bbox.width-10-1,
        width: 10,
        content: options.cancelButton,
        align: 'center',
        style: {
          fg:'white',
          bg: 'red',
          bold:true,
        },
        autoFocus: false,
        hidden:options.hidden,
        mouse: true
      });
      this._.cancel.on('press',function () { self.hide(); });
      this.screen.append(this._.cancel);
    }
    if (options.arrows) 
      Arrows(
        self,
        options,
        function () {self.emit('element wheelup')},
        function () {self.emit('element wheeldown')},
        true
      );
    this._hide=this.hide;
    this.hide = function() {
      self._hide();
      if (self._.box) self._.box.hide();
      if (self._.input) self._.input.hide();
      if (self._.okay) self._.okay.hide();
      if (self._.cancel) self._.cancel.hide();
      if (self._.up) self._.up.hide();
      if (self._.down) self._.down.hide();
      self.screen.render();
      // restore all clickable elements
      self.screen.clickable=self._clickable;
    } 
    this._show = this.show;
    this.show = function() {
      // save all screen clickable elements; enable only this clickables
      self._clickable=self.screen.clickable;
      self.screen.clickable=self.clickable;
      self._show();
      if (self._.box) self._.box.show();
      if (self._.input) self._.input.show();
      if (self._.okay) self._.okay.show();
      if (self._.cancel) self._.cancel.show();
      if (self._.up) self._.up.show();
      if (self._.down) self._.down.show();
      self.screen.render();
    } 
    
    // Save clickable elements of this widget; restore screen
    this.clickable=this.screen.clickable;
    this.screen.clickable=this._clickable;
  }
  if (options.label && ~options.label.indexOf('%path')) {
    this._label.setContent(options.label.replace('%path', this.cwd));
  }
  if (this._.input) 
    this.on('selected', function(item) {
      var value = item.content.replace(/\{[^{}]+\}/g, '').replace(/@$/, '');
      if (value.indexOf('/') != -1) value='';
      self._.input.setValue(value);
      self._.input.update();
    });
  
 
  this.on('select', function(item) {
    var value = item.content.replace(/\{[^{}]+\}/g, '').replace(/@$/, '')
      , file = path.resolve(self.cwd, value);
    return fs.stat(file, function(err, stat) {
      var _cwd=self.cwd;
      if (err) {
        return self.emit('ioerror', err, file);
      }
      self.file = file;
      self.value = file;
      if (stat.isDirectory()) {
        self.cwd = file;
        self.refresh(undefined,function (err) {
          if (err) self.cwd=_cwd;
          else if (options.label && ~options.label.indexOf('%path')) {
            self._label.setContent(options.label.replace('%path', self.cwd));
            self.emit('cd', file, self.cwd);
            self.screen.render();
          }
        });
      } else {
        if (self.options.select) self.emit('file', file);
        if (self.options.select && self.options.autohide) self.hide();
      }
    });
  });


}

//FileManager.prototype.__proto__ = List.prototype;
inheritPrototype(FileManager,List);

FileManager.prototype.type = 'file-manager'; 

FileManager.prototype.refresh = function(cwd, callback) {
  var self = this;

  if (cwd) this.cwd = cwd;
  else cwd = this.cwd;

  return fs.readdir(cwd, function(err, list) {
    if (err && err.code === 'ENOENT') {
      self.cwd = cwd !== process.env.HOME
        ? process.env.HOME
        : '/';
      return self.refresh(undefined,callback);
    }

    if (err) {
      if (callback) return callback(err);
      return self.emit('ioerror', err, cwd);
    }
    
    var dirs = []
      , files = [];

    list.unshift('..');

    list.forEach(function(name) {
      var f = path.resolve(cwd, name)
        , stat;

      try {
        stat = fs.lstatSync(f);
      } catch (e) {
        ;
      }

      if ((stat && stat.isDirectory()) || name === '..') {
        dirs.push({
          name: name,
          text: '{light-blue-fg}' + name + '{/light-blue-fg}/',
          dir: true
        });
      } else if (stat && stat.isSymbolicLink()) {
        files.push({
          name: name,
          text: '{light-cyan-fg}' + name + '{/light-cyan-fg}@',
          dir: false
        });
      } else {
        files.push({
          name: name,
          text: name,
          dir: false
        });
      }
    });

    dirs = Helpers.asort(dirs);
    files = Helpers.asort(files);

    list = dirs.concat(files).map(function(data) {
      return data.text;
    });

    self.setItems(list);
    self.select(0);
    self.screen.render();

    self.emit('refresh');

    if (callback) callback();
  });

};

FileManager.prototype.pick = function(cwd, callback) {
  if (!callback) {
    callback = cwd;
    cwd = null;
  }
  var self = this
    , focused = this.screen.focused === this
    , hidden = this.hidden
    , onfile
    , oncancel;

  function resume() {
    self.removeListener('file', onfile);
    self.removeListener('cancel', oncancel);
    if (hidden) {
      self.hide();
    }
    if (!focused) {
      self.screen.restoreFocus();
    }
    self.screen.render();
  }

  this.on('file', onfile = function(file) {
    resume();
    return callback(null, file);
  });

  this.on('cancel', oncancel = function() {
    resume();
    return callback();
  });

  this.refresh(cwd, function(err) {
    if (err) return callback(err);

    if (hidden) {
      self.show();
    }

    if (!focused) {
      self.screen.saveFocus();
      self.focus();
    }

    self.screen.render();
  });
};

FileManager.prototype.reset = function(cwd, callback) {
  if (!callback) {
    callback = cwd;
    cwd = null;
  }
  this.cwd = cwd || this.options.cwd;
  this.refresh(callback);
};

/**
 * Expose
 */

module.exports = FileManager;
};
BundleModuleCode['term/widgets/checkbox']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Christopher Jeffrey, Stefan Bosse
 **    $INITIAL:     (C) 2013-2015, Christopher Jeffrey and contributors
 **    $MODIFIED:    by sbosse (2017-2021)
 **    $REVESIO:     1.4.1
 **
 **    $INFO:
 **
 **    checkbox.js - checkbox element for blessed
 **
 **  Usage:
 **
 **   var obj = blessed.checkbox({
 **     checked: options.value||false,  
 **     left: options.left,
 **     right : options.right,
 **     top: options.top||0,
 **     mouse: true,
 **     inputOnFocus: true,
 **     height: 1,
 **     text:options.text||'empty'
 **   });
 **   screen.append(obj);
 **   obj.on('check',function () {});
 **   
 ** Events: 
 **   'check' 'uncheck' 'select'
 **   
 **     
 **    $ENDOFINFO
 */
  
/**
 * Modules
 */
var Comp = Require('com/compat');

var Node = Require('term/widgets/node');
var Input = Require('term/widgets/input');

/**
 * Checkbox
 */

function Checkbox(options) {
  var self = this;

  if (!instanceOf(this,Node)) {
    return new Checkbox(options);
  }

  options = options || {};

  Input.call(this, options);

  this.text = options.content || options.text || '';
  this.checked = this.value = options.checked || false;

  this.on('keypress', function(ch, key) {
    if (key.name === 'enter' || key.name === 'space') {
      self.toggle();
      self.screen.render();
    }
  });

  if (options.mouse) {
    this.on('click', function() {
      self.toggle();
      self.screen.render();
    });
  }

  this.on('focus', function() {
    var lpos = self.lpos;
    if (!lpos) return;
    self.screen.program.lsaveCursor('checkbox');
    self.screen.program.cup(lpos.yi, lpos.xi + 1);
    self.screen.program.showCursor();
  });

  this.on('blur', function() {
    self.screen.program.lrestoreCursor('checkbox', true);
  });
}

//Checkbox.prototype.__proto__ = Input.prototype;
inheritPrototype(Checkbox,Input);

Checkbox.prototype.type = 'checkbox';

Checkbox.prototype.render = function() {
  this.clearPos(true);
  this.setContent('[' + (this.checked ? 'x' : ' ') + '] ' + this.text, true);
  return this._render();
};

Checkbox.prototype.check = function() {
  if (this.checked) return;
  this.checked = this.value = true;
  this.emit('check',this);
  this.emit('select',true);
};

Checkbox.prototype.uncheck = function() {
  if (!this.checked) return;
  this.checked = this.value = false;
  this.emit('uncheck',this);
  this.emit('select',false);
};

Checkbox.prototype.toggle = function() {
  return this.checked
    ? this.uncheck()
    : this.check();
};

/**
 * Expose
 */

module.exports = Checkbox;
};
BundleModuleCode['term/widgets/radioset']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Christopher Jeffrey, Stefan Bosse
 **    $INITIAL:     (C) 2013-2015, Christopher Jeffrey and contributors
 **    $MODIFIED:    by sbosse
 **    $REVESIO:     1.2.1
 **
 **    $INFO:
 **
 **    radioset.js - radio set element for blessed
 **
 **    $ENDOFINFO
 */

/**
 * Modules
 */
var Comp = Require('com/compat');

var Node = Require('term/widgets/node');
var Box = Require('term/widgets/box');

/**
 * RadioSet
 */

function RadioSet(options) {
  if (!instanceOf(this,Node)) {
    return new RadioSet(options);
  }
  options = options || {};
  // Possibly inherit parent's style.
  // options.style = this.parent.style;
  Box.call(this, options);
}

//RadioSet.prototype.__proto__ = Box.prototype;
inheritPrototype(RadioSet,Box);

RadioSet.prototype.type = 'radio-set';

/**
 * Expose
 */

module.exports = RadioSet;
};
BundleModuleCode['term/widgets/radiobutton']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Christopher Jeffrey, Stefan Bosse
 **    $INITIAL:     (C) 2013-2015, Christopher Jeffrey and contributors
 **    $MODIFIED:    by sbosse (2017-2021)
 **    $REVESIO:     1.3.1
 **
 **    $INFO:
 **
 **    radiobutton.js - radio button element for blessed
 **
 **     Added:
 **       - Simplified group management (using options.group identifier instead radioset parent)
 **
 **  Usage:
 **
 **  var obj = blessed.radiobutton({
 **      checked: options.value||false,  
 **      left: options.left,
 **      right : options.right,
 **      top: options.top||0,
 **      group:options.group,
 **      mouse: true,
 **      inputOnFocus: true,
 **      height: 1,
 **      text:options.text||'empty'
 **    });
 **    screen.append(obj);
 **    obj.on('select',function);
 ** 
 ** Events: 
 **   'check' 'uncheck' 'select'
 **    $ENDOFINFO
 */

/**
 * Modules
 */
var Comp = Require('com/compat');

var Node = Require('term/widgets/node');
var Checkbox = Require('term/widgets/checkbox');

/**
 * RadioButton
 */

function RadioButton(options) {
  var self = this;

  if (!instanceOf(this,Node)) {
    return new RadioButton(options);
  }

  options = options || {};
  this.group=options.group;
  Checkbox.call(this, options);

  this.on('check', function() {
    var el = self,
        group=self.group;
    while (el = el.parent) {
      if (el.type === 'radio-set'
          || el.type === 'form') break;
    }
    el = el || self.parent;
    var index=0;
    el.forDescendants(function(el) {
      if (el.type !== 'radio-button' || el === self || el.group!=group) {
        return;
      }
      index++;
      el.uncheck();
    });
  });
}

//RadioButton.prototype.__proto__ = Checkbox.prototype;
inheritPrototype(RadioButton,Checkbox);

RadioButton.prototype.type = 'radio-button';

RadioButton.prototype.render = function() {
  this.clearPos(true);
  this.setContent('(' + (this.checked ? '*' : ' ') + ') ' + this.text, true);
  return this._render();
};

RadioButton.prototype.toggle = RadioButton.prototype.check;

/**
 * Expose
 */

module.exports = RadioButton;
};
BundleModuleCode['term/widgets/prompt']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Christopher Jeffrey and contributors, Stefan Bosse
 **    $INITIAL:     (C) 2013-2015, Christopher Jeffrey and contributors
 **    $MODIFIED:    by sbosse (2016-2017)
 **    $REVESIO:     1.2.1
 **
 **    $INFO:
 **
 **    prompt.js - prompt element for blessed
 **
 **    $ENDOFINFO
 */

/**
 * Modules
 */
var Comp = Require('com/compat');

var Node = Require('term/widgets/node');
var Box = Require('term/widgets/box');
var Button = Require('term/widgets/button');
var Textbox = Require('term/widgets/textbox');

/**
 * Prompt
 */

function Prompt(options) {
  if (!instanceOf(this, Node)) {
    return new Prompt(options);
  }

  options = options || {};

  options.hidden = true;

  Box.call(this, options);

  this._.input = new Textbox({
    parent: this,
    top: 3,
    height: 1,
    left: 2,
    right: 2,
    bg: 'black'
  });

  this._.okay = new Button({
    parent: this,
    top: 5,
    height: 1,
    left: 2,
    width: 6,
    content: 'Okay',
    align: 'center',
    bg: 'black',
    hoverBg: 'blue',
    autoFocus: false,
    mouse: true
  });

  this._.cancel = new Button({
    parent: this,
    top: 5,
    height: 1,
    shrink: true,
    left: 10,
    width: 8,
    content: 'Cancel',
    align: 'center',
    bg: 'black',
    hoverBg: 'blue',
    autoFocus: false,
    mouse: true
  });
}

//Prompt.prototype.__proto__ = Box.prototype;
inheritPrototype(Prompt,Box);

Prompt.prototype.type = 'prompt';

Prompt.prototype.input =
Prompt.prototype.setInput =
Prompt.prototype.readInput = function(text, value, callback) {
  var self = this;
  var okay, cancel;

  if (!callback) {
    callback = value;
    value = '';
  }

  // Keep above:
  // var parent = this.parent;
  // this.detach();
  // parent.append(this);

  this.show();
  this.setContent(' ' + text);

  this._.input.value = value;

  this.screen.saveFocus();

  this._.okay.on('press', okay = function() {
    self._.input.submit();
  });

  this._.cancel.on('press', cancel = function() {
    self._.input.cancel();
  });

  this._.input.readInput(function(err, data) {
    self.hide();
    self.screen.restoreFocus();
    self._.okay.removeListener('press', okay);
    self._.cancel.removeListener('press', cancel);
    return callback(err, data);
  });

  this.screen.render();
};

/**
 * Expose
 */

module.exports = Prompt;
};
BundleModuleCode['term/widgets/question']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Christopher Jeffrey, Stefan Bosse
 **    $INITIAL:     (C) 2013-2015, Christopher Jeffrey and contributors
 **    $MODIFIED:    by sbosse (C) 2006-2017
 **    $REVESIO:     1.3.5
 **
 **    $INFO:
 **
 **     question.js - question element for blessed (overlay)
 **
 **  Usage:

    var width=options.width,height=options.height;
    if (Comp.obj.isString(options.width)) {
      // relative value in %!
      width=Comp.pervasives.int_of_string(options.width);
      width=int(self.screen.width*width/100);
    }
    if (Comp.obj.isString(options.height)) {
      // relative value in %!
      height=Comp.pervasives.int_of_string(options.height);
      height=int(self.screen.height*height/100);
    }
    var obj = blessed.Question({
      width: width,
      left: (options.center?int(self.screen.width/2-width/2):options.left),
      right : options.right,
      top: options.top||(options.center?int(self.screen.height/2-height/2):0),
      height: height,
      okButton     : options.okButton||'Okay',
      cancelButton : options.cancelButton||'Cancel',
      style: {
        bg:'red',
        fg:'white',
        bold:true
      }  
    });
    screen.append(obj);

    ...
    var dia = dialog({width:'50%',height:6,center:true,
              okButton     : 'Okay',
              cancelButton : 'Cancel'
      });
    dia.ask('You need to start the network service first!',function (answer) {});

 
 **    $ENDOFINFO
 */

/**
 * Modules
 */
var Comp = Require('com/compat');

var Node = Require('term/widgets/node');
var Box = Require('term/widgets/box');
var Button = Require('term/widgets/button');

/**
 * Question
 */

function Question(options) {
  if (!instanceOf(this,Node)) {
    return new Question(options);
  }

  options = options || {};
  options.hidden = true;
  if (!options.height || options.height<5) options.height=5;

  Box.call(this, options);

  // Collect clickable elements of this widget
  this._clickable=this.screen.clickable;
  this.screen.clickable=[];

  this._.okay = new Button({
    screen: this.screen,
    parent: this,
    top: options.height?(options.height-2):3,
    height: 1,
    left: 2,
    width: 10,
    content: options.okButton||'Okay',
    align: 'center',
    bg: 'black',
    hoverBg: 'blue',
    autoFocus: false,
    mouse: true
  });

  if (options.cancelButton) 
    this._.cancel = new Button({
      screen: this.screen,
      parent: this,
      top: options.height?(options.height-2):3,
      height: 1,
      left: options.width?(options.width-12):10,
      width: 10,
      content: options.cancelButton,
      align: 'center',
      bg: 'black',
      hoverBg: 'blue',
      autoFocus: false,
      mouse: true
    });
  // Save clickable elements of this widget; restore screen
  this.clickable=this.screen.clickable;
  this.screen.clickable=this._clickable;
}

//Question.prototype.__proto__ = Box.prototype;
inheritPrototype(Question,Box);

Question.prototype.type = 'question';

Question.prototype.ask = function(text, callback) {
  var self = this,
      press, okay, cancel,
      off,room;
  if (!callback) callback=function () {};
  
  // Keep above:
  // var parent = this.parent;
  // this.detach();
  // parent.append(this);
  
  // save all clickable elements; enable only this clickables
  this._clickable=this.screen.clickable;
  this.screen.clickable=this.clickable;
  this.show();
  if (text.length > this.options.width-4) {
    var tokens=text.split(' '),
        curlen=0,temp='';
    for(var t in tokens) {
      var token=tokens[t];
      if (curlen+token.length+1 < this.options.width-4) {
        temp = temp+token+' ';
        curlen = curlen + token.length + 1;
      } else {
        if (token.length < this.options.width-4) {
          temp = temp + '\n' + token+ ' ';
          curlen = token.length + 1;
        } else {
          off=0,room=this.options.width-4-curlen;
          temp = temp+token.substr(0,room);
          off=room;
          room=this.options.width-4;
          while (off < token.length) {
            frag = token.substr(off,room);
            temp = temp + '\n' + frag;
            off += room;
            curlen = frag;
          }
          temp = temp + ' ';
          curlen++;
        }
      } 
    }
    text=temp;
  }
  this.setContent('\n  ' + text.replace(/\n/g,'\n  '));

  this.onScreenEvent('keypress', press = function(ch, key) {
    if (key.name === 'mouse') return;
    if (key.name !== 'enter'
        && key.name !== 'escape'
        && key.name !== 'q'
        && key.name !== 'y'
        && key.name !== 'n') {
      return;
    }
    done(null, key.name === 'enter' || key.name === 'y');
  });

  this._.okay.on('press', okay = function() {
    done(null, true);
  });

  if (this._.cancel) this._.cancel.on('press', cancel = function() {
    done(null, false);
  });

  this.screen.saveFocus();
  this.focus();

  function done(err, data) {
    self.hide();
    // restore all clickable elements
    self.screen.clickable=self._clickable;
    self.screen.restoreFocus();
    self.removeScreenEvent('keypress', press);
    self._.okay.removeListener('press', okay);
    if (self._.cancel) self._.cancel.removeListener('press', cancel);
    return callback(err, data);
  }

  this.screen.render();
};

/**
 * Expose
 */

module.exports = Question;
};
BundleModuleCode['term/widgets/message']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Christopher Jeffrey and contributors, Stefan Bosse
 **    $INITIAL:     (C) 2013-2015, Christopher Jeffrey and contributors
 **    $MODIFIED:    by sbosse (2016-2017)
 **    $REVESIO:     1.2.1
 **
 **    $INFO:
 **
 **    message.js - message element for blessed
 **
 **    $ENDOFINFO
 */

var Comp = Require('com/compat');

/**
 * Modules
 */

var Node = Require('term/widgets/node');
var Box = Require('term/widgets/box');

/**
 * Message / Error
 */

function Message(options) {
  if (!instanceOf(this,Node)) {
    return new Message(options);
  }

  options = options || {};
  options.tags = true;

  Box.call(this, options);
}

//Message.prototype.__proto__ = Box.prototype;
inheritPrototype(Message,Box);

Message.prototype.type = 'message';

Message.prototype.log =
Message.prototype.display = function(text, time, callback) {
  var self = this;

  if (typeof time === 'function') {
    callback = time;
    time = null;
  }

  if (time == null) time = 3;

  // Keep above:
  // var parent = this.parent;
  // this.detach();
  // parent.append(this);

  if (this.scrollable) {
    this.screen.saveFocus();
    this.focus();
    this.scrollTo(0);
  }

  this.show();
  this.setContent(text);
  this.screen.render();

  if (time === Infinity || time === -1 || time === 0) {
    var end = function() {
      if (end.done) return;
      end.done = true;
      if (self.scrollable) {
        try {
          self.screen.restoreFocus();
        } catch (e) {
          ;
        }
      }
      self.hide();
      self.screen.render();
      if (callback) callback();
    };

    setTimeout(function() {
      self.onScreenEvent('keypress', function fn(ch, key) {
        if (key.name === 'mouse') return;
        if (self.scrollable) {
          if ((key.name === 'up' || (self.options.vi && key.name === 'k'))
            || (key.name === 'down' || (self.options.vi && key.name === 'j'))
            || (self.options.vi && key.name === 'u' && key.ctrl)
            || (self.options.vi && key.name === 'd' && key.ctrl)
            || (self.options.vi && key.name === 'b' && key.ctrl)
            || (self.options.vi && key.name === 'f' && key.ctrl)
            || (self.options.vi && key.name === 'g' && !key.shift)
            || (self.options.vi && key.name === 'g' && key.shift)) {
            return;
          }
        }
        if (self.options.ignoreKeys && ~self.options.ignoreKeys.indexOf(key.name)) {
          return;
        }
        self.removeScreenEvent('keypress', fn);
        end();
      });
      // XXX May be affected by new element.options.mouse option.
      if (!self.options.mouse) return;
      self.onScreenEvent('mouse', function fn(data) {
        if (data.action === 'mousemove') return;
        self.removeScreenEvent('mouse', fn);
        end();
      });
    }, 10);

    return;
  }

  setTimeout(function() {
    self.hide();
    self.screen.render();
    if (callback) callback();
  }, time * 1000);
};

Message.prototype.error = function(text, time, callback) {
  return this.display('{red-fg}Error: ' + text + '{/red-fg}', time, callback);
};

/**
 * Expose
 */

module.exports = Message;
};
BundleModuleCode['term/widgets/keyboard']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     sbosse (C) 2006-2018
 **    $REVESIO:     1.2.3
 **
 **    $INFO:
 **
 **     keyboard.js - software keyboard (overlay)
 **
 **  Options:
 **  typeof options = {
 **     top,left,width,height,
 **     button?={width,height} is button size,
 **     margin?={x,y} is button margin,
 **     compact?:boolean,
 **     delButton?:string,
 **     nlButton?:string,
 **     okayButton?:string,
 **     cancelButton?:string,
 **  }
 **
 **    $ENDOFINFO
 */
var options = {
  version:'1.2.3'
}
/**
 * Modules
 */
var Comp = Require('com/compat');

var Node = Require('term/widgets/node');
var Box = Require('term/widgets/box');
var Button = Require('term/widgets/button');
var TextBox = Require('term/widgets/textbox');
var Helpers = Require('term/helpers');
/**
 * Keyboard
 */

function Keyboard(options) {
  var self=this,
      x,y,key,i=0,bbox;

  if (!instanceOf(this,Node)) {
    return new Keyboard(options);
  }
  
  options = options || {};
  options.hidden = true;
  if (!options.height || options.height<10) options.height=10;
  
  Box.call(this, options);
  
  // Collect clickable elements of this widget
  this._clickable=this.screen.clickable;
  this.screen.clickable=[];
  
  if (!options.button) options.button={width:3,height:2};
  if (!options.margin) options.margin={x:2,y:1};

  this.shift=false;
  this.group=0;
  this._.buttons=[];

  var Keys = [
    [
    'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o',
    'p','q','r','s','t','u','v','w','x','y','z'
    ],
    [
    '0','1','2','3','4','5','6','7','8','9','.','+','-','*',':',';'
    ],
    [
    '"','!','=','_','<','>','(',')','{','}','[',']','?','#','~',' '
    ]
  ];
  
  
  var keys = Comp.array.flatten(Keys);
  var complen=Keys[0].length+Keys[1].length;
  
  // compute for button positions
  bbox=Helpers.bbox(this.screen,options);

  if (options.okayButton) {
    this._.okay = new Button({
      screen: this.screen,
      parent: this,
      top: bbox.height-3,
      height: 1,
      left: 1,
      width: Math.max(6,options.okayButton.length+2),
      content: options.okayButton,
      align: 'center',
      autoFocus: false,
      mouse: true,
      style: {
        bold:true,
        bg:'green',
        fg:'white'
      }
    });
    this._.okay.on('press',function () { self.hide(); if (self._.callback) self._.callback(self._.input.getValue())});
  }
  if (options.cancelButton) {
    this._.cancel = new Button({
      screen: this.screen,
      parent: this,
      top: bbox.height-3,
      height: 1,
      right: 1,
      width: options.cancelButton.length+2,
      content: options.cancelButton,
      align: 'center',
      autoFocus: false,
      mouse: true,
      style: {
        bold:true,
        bg:'red',
        fg:'white'
      }
    });
    this._.cancel.on('press',function () { self.hide(); });
  }
  this._.shift = new Button({
      screen: this.screen,
      parent: this,
      top: bbox.height-3,
      height: 1,
      right: int(bbox.width/2)+(options.compact?1:int(options.margin.x)),
      width: (options.shiftButton && options.shiftButton.length+2)||6,
      content: options.shiftButton||'Shft',
      align: 'center',
      autoFocus: false,
      mouse: true
    });
  this._.shift.on('press',function () { 
      self.shift=~self.shift;
      for(var i=0;i<26;i++) {
        self._.buttons[i].setContent(self.shift?Keys[0][i].toUpperCase():Keys[0][i]);
      }
      if (options.compact && self.shift) for(i in Keys[1]) self._.buttons[26+Number(i)].setContent(Keys[2][i]);
      if (options.compact && !self.shift) for(i in Keys[1]) self._.buttons[26+Number(i)].setContent(Keys[1][i]);
      if (self.shift && options.nlButton) self._.delete.setContent(options.nlButton);
      else if (!self.shift && options.nlButton) self._.delete.setContent(options.delButton||'DEL');
      self.screen.render();
  });
  this._.delete = new Button({
      screen: this.screen,
      parent: this,
      top: bbox.height-3,
      height: 1,
      left: int(bbox.width/2)+(options.compact?0:int(options.margin.x)),
      width: (options.delButton && options.delButton.length+2)||6,
      content: options.delButton||'DEL',
      align: 'center',
      autoFocus: false,
      mouse: true
  });
  this._.delete.on('press',function () {
    var line=self._.input.getValue();
    if (!self.shift || !options.nlButton) {
      // Delete last character
      self._.input.setValue(line.substring(0,line.length-1));
    } else if (self.shift && options.nlButton) {
      // Insert newline
      self._.input.setValue(line+'\n');
    }
    //self.screen.render();
    self._.input.update();
  });
  
  this._.input =  new TextBox({
      parent: this,
      value: options.value||'content',
      width: bbox.width-4,
      height: 1,
      left: 1,
      top: 0,
      style: {
        fg:(options.style.input&&options.style.input.fg)||'black',
        bg:(options.style.input&&options.style.input.bg)||'white',
        bold:true
      }
  });
  y=1+options.margin.y;
  
  i=0;
  while ((options.compact?i<complen:true) && keys[i] && y < (bbox.height-options.button.height-options.margin.y)-1) {
    x=options.margin.x;
    while ((options.compact?i<complen:true) && keys[i] && x < (bbox.width-options.button.width-options.margin.x)) {
      function make(i) {
        key = new Button ({
          screen: self.screen,
          parent: self,
          top: y,
          height: options.button.height,
          left: x,
          width: options.button.width,
          content: keys[i],
          align: 'center',
          autoFocus: false,
          mouse: true
        });
        self._.buttons.push(key);
        key.on('press',function () {self.emit('key',i)});
      }
      make(i);
      i++,x += (options.button.width+options.margin.x);
    }
    y += (options.button.height+options.margin.y);
  }
  // Save clickable elements of this widget; restore screen
  this.clickable=this.screen.clickable;
  this.screen.clickable=this._clickable;

  this._hide=this.hide;
  this.hide = function() {
    self._hide();
    self.screen.render();
    // restore all clickable elements
    self.screen.clickable=self._clickable;
  } 
  this._show = this.show;
  this.show = function() {
    // save all screen clickable elements; enable only this clickables
    self._clickable=self.screen.clickable;
    self.screen.clickable=self.clickable;
    self._show();
    self.screen.render();
  }
  this.on('key',function (index) {
    var line=self._.input.getValue(),ch;
    if (options.compact) {
      if (index<26) {
        ch=self.shift?Keys[0][index].toUpperCase():Keys[0][index];
      } else {
        ch=self.shift?Keys[2][index-26]:Keys[1][index-26];
      }
    } else {
      if (!self.shift || index>26) ch = keys[index];
      else ch = keys[index].toUpperCase();
    }
    line += ch;
    self._.input.setValue(line);
    //self.screen.render();
    self._.input.update();
  });
}

//Question.prototype.__proto__ = Box.prototype;
inheritPrototype(Keyboard,Box);

Keyboard.prototype.setCallback = function (cb) {
  this._.callback=cb
}

Keyboard.prototype.setValue = function (line) {
  this._.input.setValue(line);
  this._.input.update();
}

Keyboard.prototype.type = 'keyboard';
/**
 * Expose
 */

module.exports = Keyboard;
};
BundleModuleCode['term/widgets/loading']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Christopher Jeffrey, Stefan Bosse
 **    $INITIAL:     (C) 2013-2015, Christopher Jeffrey and contributors
 **    $MODIFIED:    by sbosse
 **    $REVESIO:     1.2.1
 **
 **    $INFO:
 **
 **    loading.js - loading element for blessed
 **
 **    $ENDOFINFO
 */

/**
 * Modules
 */
var Comp = Require('com/compat');

var Node = Require('term/widgets/node');
var Box = Require('term/widgets/box');
var Text = Require('term/widgets/text');

/**
 * Loading
 */

function Loading(options) {
  if (!instanceOf(this,Node)) {
    return new Loading(options);
  }

  options = options || {};

  Box.call(this, options);

  this._.icon = new Text({
    parent: this,
    align: 'center',
    top: 2,
    left: 1,
    right: 1,
    height: 1,
    content: '|'
  });
}

//Loading.prototype.__proto__ = Box.prototype;
inheritPrototype(Loading,Box);

Loading.prototype.type = 'loading';

Loading.prototype.load = function(text) {
  var self = this;

  // XXX Keep above:
  // var parent = this.parent;
  // this.detach();
  // parent.append(this);

  this.show();
  this.setContent(text);

  if (this._.timer) {
    this.stop();
  }

  this.screen.lockKeys = true;

  this._.timer = setInterval(function() {
    if (self._.icon.content === '|') {
      self._.icon.setContent('/');
    } else if (self._.icon.content === '/') {
      self._.icon.setContent('-');
    } else if (self._.icon.content === '-') {
      self._.icon.setContent('\\');
    } else if (self._.icon.content === '\\') {
      self._.icon.setContent('|');
    }
    self.screen.render();
  }, 200);
};

Loading.prototype.stop = function() {
  this.screen.lockKeys = false;
  this.hide();
  if (this._.timer) {
    clearInterval(this._.timer);
    delete this._.timer;
  }
  this.screen.render();
};

/**
 * Expose
 */

module.exports = Loading;
};
BundleModuleCode['term/widgets/listbar']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Christopher Jeffrey and contributors, Stefan Bosse
 **    $INITIAL:     (C) 2013-2016, Christopher Jeffrey and contributors
 **    $CREATED:     sbosse on 28-3-15.
 **    $VERSION:     1.2.2
 **
 **    $INFO:
 *
 * listbar.js - listbar element for blessed
 *
 **    $ENDOFINFO
 */
/**
 * Modules
 */
var Comp = Require('com/compat');

var helpers = Require('term/helpers');

var Node = Require('term/widgets/node');
var Box = Require('term/widgets/box');

/**
 * Listbar / HorizontalList
 */

function Listbar(options) {
  var self = this;

  if (!instanceOf(this,Node)) {
    return new Listbar(options);
  }

  options = options || {};

  this.items = [];
  this.ritems = [];
  this.commands = [];

  this.leftBase = 0;
  this.leftOffset = 0;

  this.mouse = options.mouse || false;

  Box.call(this, options);

  if (!this.style.selected) {
    this.style.selected = {};
  }

  if (!this.style.item) {
    this.style.item = {};
  }

  if (options.commands || options.items) {
    this.setItems(options.commands || options.items);
  }

  if (options.keys) {
    this.on('keypress', function(ch, key) {
      if (key.name === 'left'
          || (options.vi && key.name === 'h')
          || (key.shift && key.name === 'tab')) {
        self.moveLeft();
        self.screen.render();
        // Stop propagation if we're in a form.
        if (key.name === 'tab') return false;
        return;
      }
      if (key.name === 'right'
          || (options.vi && key.name === 'l')
          || key.name === 'tab') {
        self.moveRight();
        self.screen.render();
        // Stop propagation if we're in a form.
        if (key.name === 'tab') return false;
        return;
      }
      if (key.name === 'enter'
          || (options.vi && key.name === 'k' && !key.shift)) {
        self.emit('action', self.items[self.selected], self.selected);
        self.emit('select', self.items[self.selected], self.selected);
        var item = self.items[self.selected];
        if (item._.cmd.callback) {
          item._.cmd.callback();
        }
        self.screen.render();
        return;
      }
      if (key.name === 'escape' || (options.vi && key.name === 'q')) {
        self.emit('action');
        self.emit('cancel');
        return;
      }
    });
  }

  if (options.autoCommandKeys) {
    this.onScreenEvent('keypress', function(ch) {
      if (/^[0-9]$/.test(ch)) {
        var i = +ch - 1;
        if (!~i) i = 9;
        return self.selectTab(i);
      }
    });
  }

  this.on('focus', function() {
    self.select(self.selected);
  });
}

//Listbar.prototype.__proto__ = Box.prototype;
inheritPrototype(Listbar,Box);

Listbar.prototype.type = 'listbar';

Object.defineProperty(Listbar.prototype,'selected',{
  get: function () {return this.leftBase + this.leftOffset;}
});
/* Depricated
Listbar.prototype.__defineGetter__('selected', function() {
  return this.leftBase + this.leftOffset;
});
*/

Listbar.prototype.setItems = function(commands) {
  var self = this;

  if (!Array.isArray(commands)) {
    commands = Object.keys(commands).reduce(function(obj, key, i) {
      var cmd = commands[key]
        , cb;

      if (typeof cmd === 'function') {
        cb = cmd;
        cmd = { callback: cb };
      }

      if (cmd.text == null) cmd.text = key;
      if (cmd.prefix == null) cmd.prefix = ++i + '';

      if (cmd.text == null && cmd.callback) {
        cmd.text = cmd.callback.name;
      }

      obj.push(cmd);

      return obj;
    }, []);
  }

  this.items.forEach(function(el) {
    el.detach();
  });

  this.items = [];
  this.ritems = [];
  this.commands = [];

  commands.forEach(function(cmd) {
    self.add(cmd);
  });

  this.emit('set items');
};

Listbar.prototype.add =
Listbar.prototype.addItem =
Listbar.prototype.appendItem = function(item, callback) {
  var self = this
    , prev = this.items[this.items.length - 1]
    , drawn
    , cmd
    , title
    , len;

  if (!this.parent) {
    drawn = 0;
  } else {
    drawn = prev ? prev.aleft + prev.width : 0;
    if (!this.screen.autoPadding) {
      drawn += this.ileft;
    }
  }

  if (typeof item === 'object') {
    cmd = item;
    if (cmd.prefix == null) cmd.prefix = (this.items.length + 1) + '';
  }

  if (typeof item === 'string') {
    cmd = {
      prefix: (this.items.length + 1) + '',
      text: item,
      callback: callback
    };
  }

  if (typeof item === 'function') {
    cmd = {
      prefix: (this.items.length + 1) + '',
      text: item.name,
      callback: item
    };
  }

  if (cmd.keys && cmd.keys[0]) {
    cmd.prefix = cmd.keys[0];
  }

  var t = helpers.generateTags(this.style.prefix || { fg: 'lightblack' });

  title = (cmd.prefix != null ? t.open + cmd.prefix + t.close + ':' : '') + cmd.text;

  len = ((cmd.prefix != null ? cmd.prefix + ':' : '') + cmd.text).length;

  var options = {
    screen: this.screen,
    top: 0,
    left: drawn + 1,
    height: 1,
    content: title,
    width: len + 2,
    align: 'center',
    autoFocus: false,
    tags: true,
    mouse: true,
    style: helpers.merge({}, this.style.item),
    noOverflow: true
  };

  if (!this.screen.autoPadding) {
    options.top += this.itop;
    options.left += this.ileft;
  }

  ['bg', 'fg', 'bold', 'underline',
   'blink', 'inverse', 'invisible'].forEach(function(name) {
    options.style[name] = function() {
      var attr = self.items[self.selected] === el
        ? self.style.selected[name]
        : self.style.item[name];
      if (typeof attr === 'function') attr = attr(el);
      return attr;
    };
  });

  var el = new Box(options);

  this._[cmd.text] = el;
  cmd.element = el;
  el._.cmd = cmd;

  this.ritems.push(cmd.text);
  this.items.push(el);
  this.commands.push(cmd);
  this.append(el);

  if (cmd.callback) {
    if (cmd.keys) {
      this.screen.key(cmd.keys, function() {
        self.emit('action', el, self.selected);
        self.emit('select', el, self.selected);
        if (el._.cmd.callback) {
          el._.cmd.callback();
        }
        self.select(el);
        self.screen.render();
      });
    }
  }

  if (this.items.length === 1) {
    this.select(0);
  }

  // XXX May be affected by new element.options.mouse option.
  if (this.mouse) {
    el.on('click', function() {
      self.emit('action', el, self.selected);
      self.emit('select', el, self.selected);
      if (el._.cmd.callback) {
        el._.cmd.callback();
      }
      self.select(el);
      self.screen.render();
    });
  }

  this.emit('add item');
};

Listbar.prototype.render = function() {
  var self = this
    , drawn = 0;

  if (!this.screen.autoPadding) {
    drawn += this.ileft;
  }

  this.items.forEach(function(el, i) {
    if (i < self.leftBase) {
      el.hide();
    } else {
      el.rleft = drawn + 1;
      drawn += el.width + 2;
      el.show();
    }
  });

  return this._render();
};

Listbar.prototype.select = function(offset) {
  if (typeof offset !== 'number') {
    offset = this.items.indexOf(offset);
  }

  if (offset < 0) {
    offset = 0;
  } else if (offset >= this.items.length) {
    offset = this.items.length - 1;
  }

  if (!this.parent) {
    this.emit('select item', this.items[offset], offset);
    return;
  }

  var lpos = this._getCoords();
  if (!lpos) return;

  var self = this
    , width = (lpos.xl - lpos.xi) - this.iwidth
    , drawn = 0
    , visible = 0
    , el;

  el = this.items[offset];
  if (!el) return;

  this.items.forEach(function(el, i) {
    if (i < self.leftBase) return;

    var lpos = el._getCoords();
    if (!lpos) return;

    if (lpos.xl - lpos.xi <= 0) return;

    drawn += (lpos.xl - lpos.xi) + 2;

    if (drawn <= width) visible++;
  });

  var diff = offset - (this.leftBase + this.leftOffset);
  if (offset > this.leftBase + this.leftOffset) {
    if (offset > this.leftBase + visible - 1) {
      this.leftOffset = 0;
      this.leftBase = offset;
    } else {
      this.leftOffset += diff;
    }
  } else if (offset < this.leftBase + this.leftOffset) {
    diff = -diff;
    if (offset < this.leftBase) {
      this.leftOffset = 0;
      this.leftBase = offset;
    } else {
      this.leftOffset -= diff;
    }
  }

  // XXX Move `action` and `select` events here.
  this.emit('select item', el, offset);
};

Listbar.prototype.removeItem = function(child) {
  var i = typeof child !== 'number'
    ? this.items.indexOf(child)
    : child;

  if (~i && this.items[i]) {
    child = this.items.splice(i, 1)[0];
    this.ritems.splice(i, 1);
    this.commands.splice(i, 1);
    this.remove(child);
    if (i === this.selected) {
      this.select(i - 1);
    }
  }

  this.emit('remove item');
};

Listbar.prototype.move = function(offset) {
  this.select(this.selected + offset);
};

Listbar.prototype.moveLeft = function(offset) {
  this.move(-(offset || 1));
};

Listbar.prototype.moveRight = function(offset) {
  this.move(offset || 1);
};

Listbar.prototype.selectTab = function(index) {
  var item = this.items[index];
  if (item) {
    if (item._.cmd.callback) {
      item._.cmd.callback();
    }
    this.select(index);
    this.screen.render();
  }
  this.emit('select tab', item, index);
};

/**
 * Expose
 */

module.exports = Listbar;
};
BundleModuleCode['term/widgets/table']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Christopher Jeffrey, Stefan Bosse
 **    $INITIAL:     (C) 2013-2015, Christopher Jeffrey and contributors
 **    $MODIFIED:    by sbosse (2017-2021)
 **    $REVESIO:     1.2.2
 **
 **    $INFO:
 **
 **    table.js - table element for blessed
 **
 **     typoef options = {
 **        data: string  [][],
 **     }
 **
 **
 **    $ENDOFINFO
 */

/**
 * Modules
 */
var Comp = Require('com/compat');

var Node = Require('term/widgets/node');
var Box = Require('term/widgets/box');

/**
 * Table
 */

function Table(options) {
  var self = this;

  if (!instanceOf(this,Node)) {
    return new Table(options);
  }

  options = options || {};
  options.shrink = true;
  options.style = options.style || {};
  options.style.border = options.style.border || {};
  options.style.header = options.style.header || {};
  options.style.cell = options.style.cell || {};
  options.align = options.align || 'center';

  // Regular tables do not get custom height (this would
  // require extra padding). Maybe add in the future.
  delete options.height;

  Box.call(this, options);

  this.pad = options.pad != null
    ? options.pad
    : 2;

  this.setData(options.rows || options.data);

  this.on('attach', function() {
    self.setContent('');
    self.setData(self.rows);
  });

  this.on('resize', function() {
    self.setContent('');
    self.setData(self.rows);
    self.screen.render();
  });
}

//Table.prototype.__proto__ = Box.prototype;
inheritPrototype(Table,Box);

Table.prototype.type = 'table';

Table.prototype._calculateMaxes = function() {
  var self = this;
  var maxes = [];

  if (this.detached) return;

  this.rows = this.rows || [];

  this.rows.forEach(function(row) {
    row.forEach(function(cell, i) {
      var clen = self.strWidth(cell);
      if (!maxes[i] || maxes[i] < clen) {
        maxes[i] = clen;
      }
    });
  });

  var total = maxes.reduce(function(total, max) {
    return total + max;
  }, 0);
  total += maxes.length + 1;

  // XXX There might be an issue with resizing where on the first resize event
  // width appears to be less than total if it's a percentage or left/right
  // combination.
  if (this.width < total) {
    delete this.position.width;
  }

  if (this.position.width != null) {
    var missing = this.width - total;
    var w = missing / maxes.length | 0;
    var wr = missing % maxes.length;
    maxes = maxes.map(function(max, i) {
      if (i === maxes.length - 1) {
        return max + w + wr;
      }
      return max + w;
    });
  } else {
    maxes = maxes.map(function(max) {
      return max + self.pad;
    });
  }

  return this._maxes = maxes;
};

Table.prototype.setRows =
Table.prototype.setData = function(rows) {
  var self = this
    , text = ''
    , align = this.align;

  this.rows = rows || [];

  this._calculateMaxes();

  if (!this._maxes) return;

  this.rows.forEach(function(row, i) {
    var isFooter = i === self.rows.length - 1;
    row.forEach(function(cell, i) {
      var width = self._maxes[i];
      var clen = self.strWidth(cell);

      if (i !== 0) {
        text += ' ';
      }

      while (clen < width) {
        if (align === 'center') {
          cell = ' ' + cell + ' ';
          clen += 2;
        } else if (align === 'left') {
          cell = cell + ' ';
          clen += 1;
        } else if (align === 'right') {
          cell = ' ' + cell;
          clen += 1;
        }
      }

      if (clen > width) {
        if (align === 'center') {
          cell = cell.substring(1);
          clen--;
        } else if (align === 'left') {
          cell = cell.slice(0, -1);
          clen--;
        } else if (align === 'right') {
          cell = cell.substring(1);
          clen--;
        }
      }

      text += cell;
    });
    if (!isFooter) {
      text += '\n\n';
    }
  });

  delete this.align;
  this.setContent(text);
  this.align = align;

};

Table.prototype.render = function() {
  var self = this;

  var coords = this._render();
  if (!coords) return;

  this._calculateMaxes();

  if (!this._maxes) return coords;

  var lines = this.screen.lines
    , xi = coords.xi
    , yi = coords.yi
    , rx
    , ry
    , i;

  var dattr = this.sattr(this.style)
    , hattr = this.sattr(this.style.header)
    , cattr = this.sattr(this.style.cell)
    , battr = this.sattr(this.style.border);

  var width = coords.xl - coords.xi - this.iright
    , height = coords.yl - coords.yi - this.ibottom;

  // Apply attributes to header cells and cells.
  for (var y = this.itop; y < height; y++) {
    if (!lines[yi + y]) break;
    for (var x = this.ileft; x < width; x++) {
      if (!lines[yi + y][xi + x]) break;
      // Check to see if it's not the default attr. Allows for tags:
      if (lines[yi + y][xi + x][0] !== dattr) continue;
      if (y === this.itop) {
        lines[yi + y][xi + x][0] = hattr;
      } else {
        lines[yi + y][xi + x][0] = cattr;
      }
      lines[yi + y].dirty = true;
    }
  }

  if (!this.border || this.options.noCellBorders) return coords;

  // Draw border with correct angles.
  ry = 0;
  for (i = 0; i < self.rows.length + 1; i++) {
    if (!lines[yi + ry]) break;
    rx = 0;
    self._maxes.forEach(function(max, i) {
      rx += max;
      if (i === 0) {
        if (!lines[yi + ry][xi + 0]) return;
        // left side
        if (ry === 0) {
          // top
          lines[yi + ry][xi + 0][0] = battr;
          // lines[yi + ry][xi + 0][1] = '\u250c'; // '┌'
        } else if (ry / 2 === self.rows.length) {
          // bottom
          lines[yi + ry][xi + 0][0] = battr;
          // lines[yi + ry][xi + 0][1] = '\u2514'; // '└'
        } else {
          // middle
          lines[yi + ry][xi + 0][0] = battr;
          lines[yi + ry][xi + 0][1] = '\u251c'; // '├'
          // XXX If we alter iwidth and ileft for no borders - nothing should be written here
          if (!self.border.left) {
            lines[yi + ry][xi + 0][1] = '\u2500'; // '─'
          }
        }
        lines[yi + ry].dirty = true;
      } else if (i === self._maxes.length - 1) {
        if (!lines[yi + ry][xi + rx + 1]) return;
        // right side
        if (ry === 0) {
          // top
          rx++;
          lines[yi + ry][xi + rx][0] = battr;
          // lines[yi + ry][xi + rx][1] = '\u2510'; // '┐'
        } else if (ry / 2 === self.rows.length) {
          // bottom
          rx++;
          lines[yi + ry][xi + rx][0] = battr;
          // lines[yi + ry][xi + rx][1] = '\u2518'; // '┘'
        } else {
          // middle
          rx++;
          lines[yi + ry][xi + rx][0] = battr;
          lines[yi + ry][xi + rx][1] = '\u2524'; // '┤'
          // XXX If we alter iwidth and iright for no borders - nothing should be written here
          if (!self.border.right) {
            lines[yi + ry][xi + rx][1] = '\u2500'; // '─'
          }
        }
        lines[yi + ry].dirty = true;
        return;
      }
      if (!lines[yi + ry][xi + rx + 1]) return;
      // center
      if (ry === 0) {
        // top
        rx++;
        lines[yi + ry][xi + rx][0] = battr;
        lines[yi + ry][xi + rx][1] = '\u252c'; // '┬'
        // XXX If we alter iheight and itop for no borders - nothing should be written here
        if (!self.border.top) {
          lines[yi + ry][xi + rx][1] = '\u2502'; // '│'
        }
      } else if (ry / 2 === self.rows.length) {
        // bottom
        rx++;
        lines[yi + ry][xi + rx][0] = battr;
        lines[yi + ry][xi + rx][1] = '\u2534'; // '┴'
        // XXX If we alter iheight and ibottom for no borders - nothing should be written here
        if (!self.border.bottom) {
          lines[yi + ry][xi + rx][1] = '\u2502'; // '│'
        }
      } else {
        // middle
        if (self.options.fillCellBorders) {
          var lbg = (ry <= 2 ? hattr : cattr) & 0x1ff;
          rx++;
          lines[yi + ry][xi + rx][0] = (battr & ~0x1ff) | lbg;
        } else {
          rx++;
          lines[yi + ry][xi + rx][0] = battr;
        }
        lines[yi + ry][xi + rx][1] = '\u253c'; // '┼'
        // rx++;
      }
      lines[yi + ry].dirty = true;
    });
    ry += 2;
  }

  // Draw internal borders.
  for (ry = 1; ry < self.rows.length * 2; ry++) {
    if (!lines[yi + ry]) break;
    rx = 0;
    self._maxes.slice(0, -1).forEach(function(max) {
      rx += max;
      if (!lines[yi + ry][xi + rx + 1]) return;
      if (ry % 2 !== 0) {
        if (self.options.fillCellBorders) {
          var lbg = (ry <= 2 ? hattr : cattr) & 0x1ff;
          rx++;
          lines[yi + ry][xi + rx][0] = (battr & ~0x1ff) | lbg;
        } else {
          rx++;
          lines[yi + ry][xi + rx][0] = battr;
        }
        lines[yi + ry][xi + rx][1] = '\u2502'; // '│'
        lines[yi + ry].dirty = true;
      } else {
        rx++;
      }
    });
    rx = 1;
    self._maxes.forEach(function(max) {
      while (max--) {
        if (ry % 2 === 0) {
          if (!lines[yi + ry]) break;
          if (!lines[yi + ry][xi + rx + 1]) break;
          if (self.options.fillCellBorders) {
            var lbg = (ry <= 2 ? hattr : cattr) & 0x1ff;
            lines[yi + ry][xi + rx][0] = (battr & ~0x1ff) | lbg;
          } else {
            lines[yi + ry][xi + rx][0] = battr;
          }
          lines[yi + ry][xi + rx][1] = '\u2500'; // '─'
          lines[yi + ry].dirty = true;
        }
        rx++;
      }
      rx++;
    });
  }

  return coords;
};

/**
 * Expose
 */

module.exports = Table;
};
BundleModuleCode['term/widgets/listtable']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Christopher Jeffrey and contributors, Stefan Bosse
 **    $INITIAL:     (C) 2013-2015, Christopher Jeffrey and contributors
 **    $CREATED:     sbosse on 28-3-15.
 **    $VERSION:     1.2.1
 **
 **    $INFO:
 *
 * listtable.js - list table element for blessed
 *
 **    $ENDOFINFO
 */
/**
 * Modules
 */
var Comp = Require('com/compat');

var Node = Require('term/widgets/node');
var Box = Require('term/widgets/box');
var List = Require('term/widgets/list');
var Table = Require('term/widgets/table');

/**
 * ListTable
 */

function ListTable(options) {
  var self = this;

  if (!instanceOf(this,Node)) {
    return new ListTable(options);
  }

  options = options || {};
  options.shrink = true;
  options.normalShrink = true;
  options.style = options.style || {};
  options.style.border = options.style.border || {};
  options.style.header = options.style.header || {};
  options.style.cell = options.style.cell || {};
  this.__align = options.align || 'center';
  delete options.align;

  options.style.selected = options.style.cell.selected;
  options.style.item = options.style.cell;

  List.call(this, options);

  this._header = new Box({
    parent: this,
    left: this.screen.autoPadding ? 0 : this.ileft,
    top: 0,
    width: 'shrink',
    height: 1,
    style: options.style.header,
    tags: options.parseTags || options.tags
  });

  this.on('scroll', function() {
    self._header.setFront();
    self._header.rtop = self.childBase;
    if (!self.screen.autoPadding) {
      self._header.rtop = self.childBase + (self.border ? 1 : 0);
    }
  });

  this.pad = options.pad != null
    ? options.pad
    : 2;

  this.setData(options.rows || options.data);

  this.on('attach', function() {
    self.setData(self.rows);
  });

  this.on('resize', function() {
    var selected = self.selected;
    self.setData(self.rows);
    self.select(selected);
    self.screen.render();
  });
}

//ListTable.prototype.__proto__ = List.prototype;
inheritPrototype(ListTable,List);

ListTable.prototype.type = 'list-table';

ListTable.prototype._calculateMaxes = Table.prototype._calculateMaxes;

ListTable.prototype.setRows =
ListTable.prototype.setData = function(rows) {
  var self = this
    , align = this.__align;

  if (this.visible && this.lpos) {
    this.clearPos();
  }

  this.clearItems();

  this.rows = rows || [];

  this._calculateMaxes();

  if (!this._maxes) return;

  this.addItem('');

  this.rows.forEach(function(row, i) {
    var isHeader = i === 0;
    var text = '';
    row.forEach(function(cell, i) {
      var width = self._maxes[i];
      var clen = self.strWidth(cell);

      if (i !== 0) {
        text += ' ';
      }

      while (clen < width) {
        if (align === 'center') {
          cell = ' ' + cell + ' ';
          clen += 2;
        } else if (align === 'left') {
          cell = cell + ' ';
          clen += 1;
        } else if (align === 'right') {
          cell = ' ' + cell;
          clen += 1;
        }
      }

      if (clen > width) {
        if (align === 'center') {
          cell = cell.substring(1);
          clen--;
        } else if (align === 'left') {
          cell = cell.slice(0, -1);
          clen--;
        } else if (align === 'right') {
          cell = cell.substring(1);
          clen--;
        }
      }

      text += cell;
    });
    if (isHeader) {
      self._header.setContent(text);
    } else {
      self.addItem(text);
    }
  });

  this._header.setFront();

  this.select(0);
};

ListTable.prototype._select = ListTable.prototype.select;
ListTable.prototype.select = function(i) {
  if (i === 0) {
    i = 1;
  }
  if (i <= this.childBase) {
    this.setScroll(this.childBase - 1);
  }
  return this._select(i);
};

ListTable.prototype.render = function() {
  var self = this;

  var coords = this._render();
  if (!coords) return;

  this._calculateMaxes();

  if (!this._maxes) return coords;

  var lines = this.screen.lines
    , xi = coords.xi
    , yi = coords.yi
    , rx
    , ry
    , i;

  var battr = this.sattr(this.style.border);

  var height = coords.yl - coords.yi - this.ibottom;

  if (!this.border || this.options.noCellBorders) return coords;

  // Draw border with correct angles.
  ry = 0;
  for (i = 0; i < height + 1; i++) {
    if (!lines[yi + ry]) break;
    rx = 0;
    self._maxes.slice(0, -1).forEach(function(max) {
      rx += max;
      if (!lines[yi + ry][xi + rx + 1]) return;
      // center
      if (ry === 0) {
        // top
        rx++;
        lines[yi + ry][xi + rx][0] = battr;
        lines[yi + ry][xi + rx][1] = '\u252c'; // '┬'
        // XXX If we alter iheight and itop for no borders - nothing should be written here
        if (!self.border.top) {
          lines[yi + ry][xi + rx][1] = '\u2502'; // '│'
        }
        lines[yi + ry].dirty = true;
      } else if (ry === height) {
        // bottom
        rx++;
        lines[yi + ry][xi + rx][0] = battr;
        lines[yi + ry][xi + rx][1] = '\u2534'; // '┴'
        // XXX If we alter iheight and ibottom for no borders - nothing should be written here
        if (!self.border.bottom) {
          lines[yi + ry][xi + rx][1] = '\u2502'; // '│'
        }
        lines[yi + ry].dirty = true;
      } else {
        // middle
        rx++;
      }
    });
    ry += 1;
  }

  // Draw internal borders.
  for (ry = 1; ry < height; ry++) {
    if (!lines[yi + ry]) break;
    rx = 0;
    self._maxes.slice(0, -1).forEach(function(max) {
      rx += max;
      if (!lines[yi + ry][xi + rx + 1]) return;
      if (self.options.fillCellBorders !== false) {
        var lbg = lines[yi + ry][xi + rx][0] & 0x1ff;
        rx++;
        lines[yi + ry][xi + rx][0] = (battr & ~0x1ff) | lbg;
      } else {
        rx++;
        lines[yi + ry][xi + rx][0] = battr;
      }
      lines[yi + ry][xi + rx][1] = '\u2502'; // '│'
      lines[yi + ry].dirty = true;
    });
  }

  return coords;
};

/**
 * Expose
 */

module.exports = ListTable;
};
BundleModuleCode['term/widgets/terminal']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Christopher Jeffrey, Stefan Bosse
 **    $INITIAL:     (C) 2013-2018, Christopher Jeffrey and contributors
 **    $MODIFIED:    by sbosse (2017-2021)
 **    $REVESIO:     1.5.2
 **
 **    $INFO:
 **
 **    terminal.js - interactive terminal shell based on textarea element for blessed
 **
 **    events: 'eval' (high level passing command line after enter key was hit)
 ** 
 **    $ENDOFINFO
 */

/**
 * Modules
 */
var Comp = Require('com/compat');

var unicode = Require('term/unicode');

var nextTick = global.setImmediate || process.nextTick.bind(process);

var Node = Require('term/widgets/node');
var Input = Require('term/widgets/input');

/**
 * Terminal
 */

function Terminal(options) {
  var self = this;

  if (!instanceOf(this,Node)) {
    return new Terminal(options);
  }

  options = options || {};

  options.scrollable = options.scrollable !== false;

  Input.call(this, options);

  this.screen._listenKeys(this);

  this.value = options.value || '';
  // cursor position
  this.cpos = {x:-1,y:-1};
  this.cursorControl=true;
  this.multiline=options.multiline;
  this.history=[];
  this.historyTop=0;
  this.break='all';
  
  this.__updateCursor = this._updateCursor.bind(this);
  this.on('resize', this.__updateCursor);
  this.on('move', this.__updateCursor);

  if (options.inputOnFocus) {
    this.on('focus', this.readInput.bind(this, null));
  }

  if (!options.inputOnFocus && options.keys) {
    this.on('keypress', function(ch, key) {
      if (self._reading) return;
      if (key.name === 'enter' || (options.vi && key.name === 'i')) {
        return self.readInput();
      }
      if (key.name === 'e') {
        return self.readEditor();
      }
    });
  }

  if (options.mouse) {
    this.on('click', function(data) {
      if (self._reading) return;
      if (data.button !== 'right') return;
      self.readEditor();
    });
  }
  var offsetY = 0;
  if (this._clines) offsetY=this._clines.length-(this.childBase||0);
  if (options.prompt) {
    this.value=options.prompt;
    this.prompt=options.prompt;
    this.inputRange={x0:options.prompt.length,y0:offsetY,y1:offsetY,last:0,line:0};
  } else {
    this.inputRange={x0:0,y0:offsetY,y1:offsetY,last:0,line:0};
    this.prompt='';
  }
}

//Terminal.prototype.__proto__ = Input.prototype;
inheritPrototype(Terminal,Input);

Terminal.prototype.type = 'terminal';

Terminal.prototype._updateCursor = function(get) {
  if (this.screen.focused !== this) {
    return;
  }
  var lpos = get ? this.lpos : this._getCoords();
  if (!lpos) return;

  var last = this._clines[this._clines.length - 1]
    , program = this.screen.program
    , line
    , offsetY = this.childBase||0
    , cx
    , cy;

  // Stop a situation where the textarea begins scrolling
  // and the last cline appears to always be empty from the
  // _typeScroll `+ '\n'` thing.
  // Maybe not necessary anymore?
  if (last === '' && this.value[this.value.length - 1] !== '\n') {
    last = this._clines[this._clines.length - 2] || '';
  }

  line = Math.min(
    this._clines.length - 1 - (this.childBase || 0),
    (lpos.yl - lpos.yi) - this.iheight - 1);

  // When calling clearValue() on a full textarea with a border, the first
  // argument in the above Math.min call ends up being -2. Make sure we stay
  // positive.
  line = Math.max(0, line);
  
  if (this.cpos.x==-1 || !this.cursorControl) this.cpos.x = this.strWidth(last);
  if (this.cpos.y==-1 || !this.cursorControl) this.cpos.y = line;
  this.cpos.y = Math.min(this.cpos.y,line);
  this.cpos.x = Math.min(this.cpos.x,this.strWidth(this._clines[offsetY+this.cpos.y]));
    
  cx = lpos.xi + this.ileft + this.cpos.x;
  cy = lpos.yi + this.itop + this.cpos.y;

  // XXX Not sure, but this may still sometimes
  // cause problems when leaving editor.
  if (cy === program.y && cx === program.x) {
    return;
  }

  if (cy === program.y) {
    if (cx > program.x) {
      program.cuf(cx - program.x);
    } else if (cx < program.x) {
      program.cub(program.x - cx);
    }
  } else if (cx === program.x) {
    if (cy > program.y) {
      program.cud(cy - program.y);
    } else if (cy < program.y) {
      program.cuu(program.y - cy);
    }
  } else {
    program.cup(cy, cx);
  }
};

Terminal.prototype.input =
Terminal.prototype.setInput =
Terminal.prototype.readInput = function(callback) {
  var self = this
    , focused = this.screen.focused === this;

  if (this._reading) return;
  this._reading = true;

  this._callback = callback;

  if (!focused) {
    this.screen.saveFocus();
    this.focus();
  }

  this.screen.grabKeys = true;

  this._updateCursor();
  this.screen.program.showCursor();
  //this.screen.program.sgr('normal');

  this._done = function fn(err, value) {
    if (!self._reading) return;

    if (fn.done) return;
    fn.done = true;

    self._reading = false;

    delete self._callback;
    delete self._done;

    self.removeListener('keypress', self.__listener);
    delete self.__listener;

    self.removeListener('blur', self.__done);
    delete self.__done;

    self.screen.program.hideCursor();
    self.screen.grabKeys = false;

    if (!focused) {
      self.screen.restoreFocus();
    }

    if (self.options.inputOnFocus) {
      self.screen.rewindFocus();
    }

    // Ugly
    if (err === 'stop') return;

    if (err) {
      self.emit('error', err);
    } else if (value != null) {
      self.emit('submit', value);
    } else {
      self.emit('cancel', value);
    }
    self.emit('action', value);

    if (!callback) return;

    return err
      ? callback(err)
      : callback(null, value);
  };

  // Put this in a nextTick so the current
  // key event doesn't trigger any keys input.
  nextTick(function() {
    if (self.__listener) {
      // double fired?
      return;
    }
    self.__listener = self._listener.bind(self);
    self.on('keypress', self.__listener);
  });

  this.__done = this._done.bind(this, null, null);
  this.on('blur', this.__done);
};

// Print ONE line (call mutiple times for multi-line text). Auto wrapping is supprted, though.
Terminal.prototype.print = function (line) {
// Log(this.inputRange,this._clines.length); 
  var offsetY = this.childBase||0,
      cn1 = this._clines.length, y0=this.inputRange.y0,
      start = this.getLinearPos(this.value,offsetY+this.inputRange.y0,0),
      end   = this.getLinearPos(this.value,offsetY+this.inputRange.y1,
                                this._clines[offsetY+this.inputRange.y1].length);
  var command = this.value.slice(start,end);
  this.value=this.value.slice(0,start)+line+'\n'+command+this.value.slice(end);
  this.screen.render();
  // Update inputRange
  var cn2= this._clines.length;
  this.scrollBottom();
  this.cpos.y += 10;
  this.cpos.x=this.inputRange.x0=this.prompt.length;
  this._updateCursor(true);
  this.inputRange.y0=Math.min(this._clines.length-1,this.cpos.y-this.inputRange.last);
  this.inputRange.y1=Math.min(this._clines.length-1,this.cpos.y);
}

Terminal.prototype._listener = function(ch, key) { 
  // Cursor position must be synced with scrollablebox and vice versa (if scrollable)! A real mess.
  var done = this._done
    , self = this
    , value = this.value
    , clinesLength=this._clines.length
    , offsetY = this.childBase||0 // scrollable line offset if any
    , newline = false
    , lastchar = false
    , backspace = false
    , controlkey = false
    , lastline = (this.cpos.y+offsetY+1) == clinesLength;

  
  if (key.name === 'return') return;
  if (key.name === 'enter') {
// Log(this._clines)
    // clear input line; execute command; create new input line
    var start = this.getLinearPos(this.value,offsetY+this.inputRange.y0,0),
        end   = this.getLinearPos(this.value,offsetY+this.inputRange.y1,
                                  this._clines[offsetY+this.inputRange.y1].length);
    var command = this.value.slice(start+this.prompt.length,end);
    this.value=this.value.slice(0,start)+this.prompt+this.value.slice(end);
    if (command && command != this.history[this.historyTop-1])  {  
      this.history.push(command);
      this.historyTop=this.history.length;
    }
    this.screen.render();
    offsetY = this.childBase||0;
    self.cpos.y += 10;  // bottom
    this._updateCursor(true);
    this.cpos.x=self._clines[offsetY+self.cpos.y].length;
    this.inputRange.y0=this.inputRange.y1=this.cpos.y; this.inputRange.last=0;
    this.emit('eval',command);
    return;
  }
  function history(delta) {
    if (self.historyTop+delta<0 ) return self.scrollBottom();
    self.historyTop += delta;
    var start = self.getLinearPos(self.value,offsetY+self.inputRange.y0,0),
        end   = self.getLinearPos(self.value,offsetY+self.inputRange.y1,
                                  self._clines[offsetY+self.inputRange.y1].length);
    var command = self.history[self.historyTop]||'';
    self.historyTop = Math.min(Math.max(0,self.historyTop),self.history.length);
    self.value=self.value.slice(0,start)+self.prompt+command+self.value.slice(end);
    self.screen.render();
    self.scrollBottom();
    self.cpos.x=self._clines[self._clines.length-1].length;
    self.cpos.y += 10; // bottom
    self._updateCursor(true);
    self.inputRange.y1=self.cpos.y;
    offsetY = self.childBase||0;
    // find start y
    var y0=self.cpos.y; while (self._clines[offsetY+y0].indexOf(self.prompt)!=0) y0--;
    self.inputRange.y0=y0; 
    self.inputRange.last=self.inputRange.y1-self.inputRange.y0;
  }
  // Handle cursor positiong by keys.
  if (this.cursorControl) switch (key.name) {
    case 'left':
      controlkey=true;
      if (this.cpos.y==this.inputRange.y0 && this.cpos.x>this.inputRange.x0) this.cpos.x--;
      else if (this.cpos.y!=this.inputRange.y0 && this.cpos.x>0) this.cpos.x--;
      else if (this.cpos.y!=this.inputRange.y0 && this.cpos.x==0) {
        this.cpos.y--;
        this.cpos.x=this._clines[offsetY+this.cpos.y].length;
      }
      this._updateCursor(true);
      break;
    case 'right':
      controlkey=true;
      if (this.cpos.y>=this.inputRange.y0 && this.cpos.y<=this.inputRange.y1 && 
          this.cpos.x<this._clines[offsetY+this.cpos.y].length-1) {
        this.cpos.x++;
      } else if (this.cpos.y>=this.inputRange.y0 && this.cpos.y<this.inputRange.y1 && 
          this.cpos.x==this._clines[offsetY+this.cpos.y].length-1) {
        this.cpos.x=0;
        this.cpos.y++;
      } else if (this.cpos.y>=this.inputRange.y0 && this.cpos.y==this.inputRange.y1 && 
          this.cpos.x<this._clines[offsetY+this.cpos.y].length) {
        this.cpos.x++;
      }
      this._updateCursor(true);
      break;
    case 'up':
      controlkey=true;
      history(-1);
      return;
      break;
    case 'down':
      controlkey=true;
      history(1);
      return;
      break;
  }
  

  if (this.options.keys && key.ctrl && key.name === 'e') {
    return this.readEditor();
  }
  // Challenge: sync with line wrapping and adjust cursor and scrolling (done in element._wrapContent)
  
  // TODO: Optimize typing by writing directly
  // to the screen and screen buffer here.
  if (key.name === 'escape') {
    done(null, null);
  } else if (key.name === 'backspace') {
    backspace=true;
    if (this.cpos.y==this.inputRange.y0 && this.cpos.x<=this.inputRange.x0) return;
    if (this.value.length) {
      if (this.screen.fullUnicode) {
        if (unicode.isSurrogate(this.value, this.value.length - 2)) {
        // || unicode.isCombining(this.value, this.value.length - 1)) {
          this.value = this.value.slice(0, -2);
        } else {
          this.value = this.value.slice(0, -1);
        }
      } else {
        if (!this.cursorControl || 
             this.cpos.x==-1 ||
             (this.cpos.x==this._clines[offsetY+this.cpos.y].length &&
              this.cpos.y==this._clines.length-1-offsetY)) {
          // Delete last char of last line
          this.value = this.value.slice(0, -1);
        } else {
          // Delete char at current cursor position
          vpos=this.getLinearPos(this.value,offsetY+this.cpos.y, this.cpos.x);
          // vpos+= this.cpos.x;
          this.value = this.value.substr(0,vpos-1)+
                       this.value.substr(vpos,1000000);
        }
      }
      if (this.cpos.x>0) this.cpos.x--;
      else {this.cpos.x=-1; if (offsetY==0 && this.cpos.y>0 && lastline) this.cpos.y--; };
    }
  } else if (!controlkey && ch) {
    if (!/^[\x00-\x08\x0b-\x0c\x0e-\x1f\x7f]$/.test(ch)) {
      if (!this.cursorControl || 
           this.cpos.x==-1 ||
           (this.cpos.x==this._clines[offsetY+this.cpos.y].length &&
            this.cpos.y==this._clines.length-1-offsetY)) {
        // Append new char at end of (last) line
        lastchar=true;
        this.value += ch;
      } else {
        // Insert new char into line at current cursor position
        vpos=this.getLinearPos(this.value,offsetY+this.cpos.y, this.cpos.x);
        // vpos+= this.cpos.x;
        this.value = this.value.substr(0,vpos)+ch+
                     this.value.substr(vpos,1000000);
      }
      if (newline) {
        this.cpos.x=0;    // first left position is zero!
        this.cpos.y++;
      } else  
        this.cpos.x++;
    }
  }

  var rmline=this.cpos.x==-1;
  this.inputRange.line = this._clines.rtof[offsetY+this.cpos.y];
// Log(this.cpos,  this.inputRange);
  // TODO: clean up this mess; use rtof and ftor attributes of _clines
  // to determine where we are (react correctly on line wrap extension and reduction)
  if (this.value !== value) {
    var cn0=clinesLength,
        endofline=this.cpos.x==this._clines[offsetY+this.cpos.y].length+1,
        cn1=this._clines.length;
    var linelength1=this._clines[offsetY+this.cpos.y] && this._clines[offsetY+this.cpos.y].length;
    this.screen.render();
    var linelength2=this._clines[offsetY+this.cpos.y] && this._clines[offsetY+this.cpos.y].length;
    var cn2=this._clines.length;
// Log(this.cpos,this.inputRange,linelength1,linelength2,cn0,cn2,this.inputRange,lastline,lastchar,endofline);
// Log('L',this.cpos,lastline,lastchar,endofline,linelength1,linelength2);
    if (cn2>cn0 && endofline) {
      this.scrollBottom();
      // wrap expansion
      this.cpos.y++; 
      this.inputRange.last++;
      if (this._clines[offsetY+this.cpos.y] && lastchar) this.cpos.x=this._clines[offsetY+this.cpos.y].length;
      else this.cpos.x=linelength1-linelength2-1;
      this._updateCursor(true);
      this.inputRange.y0=this.cpos.y-this.inputRange.last;
      this.inputRange.y1=this.cpos.y;
    } else if (cn2<cn0 && !rmline) { 
      // wrap reduction
      if (this.cpos.y>0 && !lastline && lastchar) this.cpos.y--;
      this.inputRange.last--;
      if (this._clines[offsetY+this.cpos.y]) this.cpos.x=this._clines[offsetY+this.cpos.y].length;
      this._updateCursor(true);
      this.inputRange.y0=this.cpos.y-this.inputRange.last;
      this.inputRange.y1=this.cpos.y;
      offsetY = this.childBase||0;
      this.cpos.x=this._clines[offsetY+this.cpos.y].length;
      this._updateCursor(true);
    } else if (linelength2<linelength1 && !backspace) {
      // wrap shift
      this.cpos.y++; 
      this.cpos.x=linelength1-linelength2;
      this._updateCursor(true);
    }
    if (offsetY>0 && backspace) {
      // @fix line deleted; refresh again due to miscalculation of height in scrollablebox!
      this.scroll(0);
      this.screen.render();
      if (rmline) {
        if (this._clines[offsetY+this.cpos.y]) this.cpos.x=this._clines[offsetY+this.cpos.y].length;
        else if (this._clines[offsetY+this.cpos.y-1]) this.cpos.x=this._clines[offsetY+this.cpos.y-1].length;
        this._updateCursor(true);
      }
    }
  }
  
};

// Return start position of nth (c)line in linear value string 
Terminal.prototype.getLinearPos = function(v,clineIndex,cposx) {
  var lines=v.split('\n'),
      line=this._clines[clineIndex], // assuming search in plain text line (no ctrl/spaces aligns)
      flinenum=this._clines.rtof[clineIndex],
      vpos=flinenum>0?
           lines.slice(0,flinenum)
                .map(function (line) { return line.length+1 })
                .reduce(function (a,b) { return a+b }):0;
// console.log(clineIndex,lines.length,flinenum,lines[flinenum],line);
  // TODO: search from a start position in line estimated by _clines.ftor array
  function search(part,line) {
    // assuming search offset in plain text line (no ctrl/spaces aligns)
    var i=line.indexOf(part);
    if (i==-1) return 0;
    else return i;
  }
  if (lines[flinenum]) {
    return vpos+search(line,lines[flinenum])+cposx;
  } else
    return vpos+cposx;


  // clineIndex is the index in the _clines array, cposx the cursor position in this line!
  var vpos=0,len=v.length,cline,clinePos=0,clineNum=0;
  cline=this._clines[clineNum];
  // To support auto line wrapping the clines have to be parsed, too!
  while (vpos < len && clineIndex) {
    if (v.charAt(vpos)=='\n') {
        clinePos=-1;
        clineIndex--;
        clineNum++;
        cline=this._clines[clineNum];
    } else {
      if (v.charAt(vpos) != cline.charAt(clinePos)) {
        // 
        clinePos=0;
        clineIndex--;
        clineNum++;
        cline=this._clines[clineNum];
        continue;
      }
    }
    vpos++; clinePos++;
  }
  if (clineIndex==0) return vpos+cposx;
  else 0
}

Terminal.prototype._typeScroll = function() {
  // XXX Workaround
  var height = this.height - this.iheight;
  // Scroll down?
// if (typeof Log != 'undefined') Log(this.childBase,this.childOffset,this.cpos.y,height);
  //if (this._clines.length - this.childBase > height) {
  if (this.cpos.y == height) {
    this.scroll(this._clines.length);
  }
};

Terminal.prototype.getValue = function() {
  return this.value;
};

Terminal.prototype.setValue = function(value) {
  if (value == null) {
    value = this.value;
  }
  if (this._value !== value) {
    this.value = value;
    this._value = value;
    this.setContent(this.value);
    this._typeScroll();
    this._updateCursor();
  }
};

Terminal.prototype.clearInput =
Terminal.prototype.clearValue = function() {
  return this.setValue('');
};

Terminal.prototype.submit = function() {
  if (!this.__listener) return;
  return this.__listener('\x1b', { name: 'escape' });
};

Terminal.prototype.cancel = function() {
  if (!this.__listener) return;
  return this.__listener('\x1b', { name: 'escape' });
};

Terminal.prototype.render = function() {
  this.setValue();
  return this._render();
};

Terminal.prototype.editor =
Terminal.prototype.setEditor =
Terminal.prototype.readEditor = function(callback) {
  var self = this;

  if (this._reading) {
    var _cb = this._callback
      , cb = callback;

    this._done('stop');

    callback = function(err, value) {
      if (_cb) _cb(err, value);
      if (cb) cb(err, value);
    };
  }

  if (!callback) {
    callback = function() {};
  }

  return this.screen.readEditor({ value: this.value }, function(err, value) {
    if (err) {
      if (err.message === 'Unsuccessful.') {
        self.screen.render();
        return self.readInput(callback);
      }
      self.screen.render();
      self.readInput(callback);
      return callback(err);
    }
    self.setValue(value);
    self.screen.render();
    return self.readInput(callback);
  });
};

/**
 * Expose
 */

module.exports = Terminal;
};
BundleModuleCode['term/widgets/image']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Christopher Jeffrey and contributors, Stefan Bosse
 **    $INITIAL:     (C) 2013-2015, Christopher Jeffrey and contributors
 **    $MODIFIED:    by sbosse (2016-2017)
 **    $REVESIO:     1.2.1
 **
 **    $INFO:
 **
 **    image.js - image element for blessed
 **
 **    $ENDOFINFO
 */

/**
 * Modules
 */
var Comp = Require('com/compat');

/**
 * Modules
 */

var Node = Require('term/widgets/node');
var Box = Require('term/widgets/box');

/**
 * Image
 */

function Image(options) {
  if (!instanceOf(this,Node)) {
    return new Image(options);
  }

  options = options || {};
  options.type = options.itype || options.type || 'ansi';

  Box.call(this, options);

  if (options.type === 'ansi' && this.type !== 'ansiimage') {
    var ANSIImage = require('./ansiimage');
    Object.getOwnPropertyNames(ANSIImage.prototype).forEach(function(key) {
      if (key === 'type') return;
      Object.defineProperty(this, key,
        Object.getOwnPropertyDescriptor(ANSIImage.prototype, key));
    }, this);
    ANSIImage.call(this, options);
    return this;
  }

  if (options.type === 'overlay' && this.type !== 'overlayimage') {
    var OverlayImage = require('./overlayimage');
    Object.getOwnPropertyNames(OverlayImage.prototype).forEach(function(key) {
      if (key === 'type') return;
      Object.defineProperty(this, key,
        Object.getOwnPropertyDescriptor(OverlayImage.prototype, key));
    }, this);
    OverlayImage.call(this, options);
    return this;
  }

  throw new Error('`type` must either be `ansi` or `overlay`.');
}

//Image.prototype.__proto__ = Box.prototype;
inheritPrototype(Image,Box);

Image.prototype.type = 'image';

/**
 * Expose
 */

module.exports = Image;
};
BundleModuleCode['term/widgets/ansiimage']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Christopher Jeffrey and contributors, Stefan Bosse
 **    $INITIAL:     (C) 2013-2015, Christopher Jeffrey and contributors
 **    $MODIFIED:    sbosse (2017).
 **    $VERSION:     1.2.2
 **
 **    $INFO:
 *
 * ansiimage.js - render PNGS/GIFS as ANSI
 *
 **    $ENDOFINFO
 */

/**
 * Modules
 */
var Comp = Require('com/compat');

var cp = Require('child_process');

var colors = Require('term/colors');

var Node = Require('term/widgets/node');
var Box = Require('term/widgets/box');

var tng = Require('term/tng');

/**
 * ANSIImage
 */

function ANSIImage(options) {
  var self = this;

  if (!instanceOf(this,Node)) {
    return new ANSIImage(options);
  }

  options = options || {};
  options.shrink = true;

  Box.call(this, options);

  this.scale = this.options.scale || 1.0;
  this.options.animate = this.options.animate !== false;
  this._noFill = true;

  if (this.options.file) {
    this.setImage(this.options.file);
  }

  this.screen.on('prerender', function() {
    var lpos = self.lpos;
    if (!lpos) return;
    // prevent image from blending with itself if there are alpha channels
    self.screen.clearRegion(lpos.xi, lpos.xl, lpos.yi, lpos.yl);
  });

  this.on('destroy', function() {
    self.stop();
  });
}

//ANSIImage.prototype.__proto__ = Box.prototype;
inheritPrototype(ANSIImage,Box);

ANSIImage.prototype.type = 'ansiimage';

ANSIImage.curl = function(url) {
  try {
    return cp.execFileSync('curl',
      ['-s', '-A', '', url],
      { stdio: ['ignore', 'pipe', 'ignore'] });
  } catch (e) {
    ;
  }
  try {
    return cp.execFileSync('wget',
      ['-U', '', '-O', '-', url],
      { stdio: ['ignore', 'pipe', 'ignore'] });
  } catch (e) {
    ;
  }
  throw new Error('curl or wget failed.');
};

ANSIImage.prototype.setImage = function(file) {
  this.file = typeof file === 'string' ? file : null;

  if (/^https?:/.test(file)) {
    file = ANSIImage.curl(file);
  }

  var width = this.position.width;
  var height = this.position.height;

  if (width != null) {
    width = this.width;
  }

  if (height != null) {
    height = this.height;
  }

  try {
    this.setContent('');

    this.img = tng(file, {
      colors: colors,
      width: width,
      height: height,
      scale: this.scale,
      ascii: this.options.ascii,
      speed: this.options.speed,
      filename: this.file
    });

    if (width == null || height == null) {
      this.width = this.img.cellmap[0].length;
      this.height = this.img.cellmap.length;
    }

    if (this.img.frames && this.options.animate) {
      this.play();
    } else {
      this.cellmap = this.img.cellmap;
    }
  } catch (e) {
    this.setContent('Image Error: ' + e.message);
    this.img = null;
    this.cellmap = null;
  }
};

ANSIImage.prototype.play = function() {
  var self = this;
  if (!this.img) return;
  return this.img.play(function(bmp, cellmap) {
    self.cellmap = cellmap;
    self.screen.render();
  });
};

ANSIImage.prototype.pause = function() {
  if (!this.img) return;
  return this.img.pause();
};

ANSIImage.prototype.stop = function() {
  if (!this.img) return;
  return this.img.stop();
};

ANSIImage.prototype.clearImage = function() {
  this.stop();
  this.setContent('');
  this.img = null;
  this.cellmap = null;
};

ANSIImage.prototype.render = function() {
  var coords = this._render();
  if (!coords) return;

  if (this.img && this.cellmap) {
    this.img.renderElement(this.cellmap, this);
  }

  return coords;
};

/**
 * Expose
 */

module.exports = ANSIImage;
};
BundleModuleCode['term/tng']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Christopher Jeffrey and contributors, Stefan Bosse 
 **    $INITIAL:     (C) 2013-2015, Christopher Jeffrey and contributors (MIT License)
 **    $REVESIO:     1.1.6
 **
 **    $INFO:
 *
 * tng.js - png reader
 *
 *     $ENDINFO
 */


var fs = Require('fs')
  , util = Require('util')
  , path = Require('path')
  , zlib = Require('zlib')
  , assert = Require('assert')
  , cp = Require('child_process')
  , exec = cp?cp.execFileSync:_;

/**
 * PNG
 */

function PNG(file, options) {
  var buf
    , chunks
    , idat
    , pixels;

  if (!(this instanceof PNG)) {
    return new PNG(file, options);
  }

  if (!file) throw new Error('no file');

  this.options = options || {};
  this.colors = options.colors || Require('term/colors');
  this.optimization = this.options.optimization || 'mem';
  this.speed = this.options.speed || 1;

  if (Buffer.isBuffer(file)) {
    this.file = this.options.filename || null;
    buf = file;
  } else {
    this.options.filename = file;
    this.file = path.resolve(process.cwd(), file);
    buf = fs.readFileSync(this.file);
  }

  this.format = buf.readUInt32BE(0) === 0x89504e47 ? 'png'
    : buf.slice(0, 3).toString('ascii') === 'GIF' ? 'gif'
    : buf.readUInt16BE(0) === 0xffd8 ? 'jpg'
    : path.extname(this.file).slice(1).toLowerCase() || 'png';

  if (this.format !== 'png') {
    try {
      return this.toPNG(buf);
    } catch (e) {
      throw e;
    }
  }

  chunks = this.parseRaw(buf);
  idat = this.parseChunks(chunks);
  pixels = this.parseLines(idat);

  this.bmp = this.createBitmap(pixels);
  this.cellmap = this.createCellmap(this.bmp);
  this.frames = this.compileFrames(this.frames);
}

PNG.prototype.parseRaw = function(buf) {
  var chunks = []
    , index = 0
    , i = 0
    , buf
    , len
    , type
    , name
    , data
    , crc
    , check
    , critical
    , public_
    , conforming
    , copysafe
    , pos;

  this._debug(this.file);

  if (buf.readUInt32BE(0) !== 0x89504e47
      || buf.readUInt32BE(4) !== 0x0d0a1a0a) {
    throw new Error('bad header');
  }

  i += 8;

  while (i < buf.length) {
    try {
      len = buf.readUInt32BE(i);
      i += 4;
      pos = i;
      type = buf.slice(i, i + 4);
      name = type.toString('ascii');
      i += 4;
      data = buf.slice(i, i + len);
      i += len;
      check = this.crc32(buf.slice(pos, i));
      crc = buf.readInt32BE(i);
      i += 4;
      critical = !!(~type[0] & 32);
      public_ = !!(~type[1] & 32);
      conforming = !!(~type[2] & 32);
      copysafe = !!(~type[3] & 32);
      if (crc !== check) {
        throw new Error(name + ': bad crc');
      }
    } catch (e) {
      if (this.options.debug) throw e;
      break;
    }
    chunks.push({
      index: index++,
      id: name.toLowerCase(),
      len: len,
      pos: pos,
      end: i,
      type: type,
      name: name,
      data: data,
      crc: crc,
      check: check,
      raw: buf.slice(pos, i),
      flags: {
        critical: critical,
        public_: public_,
        conforming: conforming,
        copysafe: copysafe
      }
    });
  }

  return chunks;
};

PNG.prototype.parseChunks = function(chunks) {
  var i
    , chunk
    , name
    , data
    , p
    , idat
    , info;

  for (i = 0; i < chunks.length; i++) {
    chunk = chunks[i];
    name = chunk.id;
    data = chunk.data;
    info = {};
    switch (name) {
      case 'ihdr': {
        this.width = info.width = data.readUInt32BE(0);
        this.height = info.height = data.readUInt32BE(4);
        this.bitDepth = info.bitDepth = data.readUInt8(8);
        this.colorType = info.colorType = data.readUInt8(9);
        this.compression = info.compression = data.readUInt8(10);
        this.filter = info.filter = data.readUInt8(11);
        this.interlace = info.interlace = data.readUInt8(12);
        switch (this.bitDepth) {
          case 1: case 2: case 4: case 8: case 16: case 24: case 32: break;
          default: throw new Error('bad bit depth: ' + this.bitDepth);
        }
        switch (this.colorType) {
          case 0: case 2: case 3: case 4: case 6: break;
          default: throw new Error('bad color: ' + this.colorType);
        }
        switch (this.compression) {
          case 0: break;
          default: throw new Error('bad compression: ' + this.compression);
        }
        switch (this.filter) {
          case 0: case 1: case 2: case 3: case 4: break;
          default: throw new Error('bad filter: ' + this.filter);
        }
        switch (this.interlace) {
          case 0: case 1: break;
          default: throw new Error('bad interlace: ' + this.interlace);
        }
        break;
      }
      case 'plte': {
        this.palette = info.palette = [];
        for (p = 0; p < data.length; p += 3) {
          this.palette.push({
            r: data[p + 0],
            g: data[p + 1],
            b: data[p + 2],
            a: 255
          });
        }
        break;
      }
      case 'idat': {
        this.size = this.size || 0;
        this.size += data.length;
        this.idat = this.idat || [];
        this.idat.push(data);
        info.size = data.length;
        break;
      }
      case 'iend': {
        this.end = true;
        break;
      }
      case 'trns': {
        this.alpha = info.alpha = Array.prototype.slice.call(data);
        if (this.palette) {
          for (p = 0; p < data.length; p++) {
            if (!this.palette[p]) break;
            this.palette[p].a = data[p];
          }
        }
        break;
      }
      // https://wiki.mozilla.org/APNG_Specification
      case 'actl': {
        this.actl = info = {};
        this.frames = [];
        this.actl.numFrames = data.readUInt32BE(0);
        this.actl.numPlays = data.readUInt32BE(4);
        break;
      }
      case 'fctl': {
        // IDAT is the first frame depending on the order:
        // IDAT is a frame: acTL->fcTL->IDAT->[fcTL]->fdAT
        // IDAT is not a frame: acTL->IDAT->[fcTL]->fdAT
        if (!this.idat) {
          this.idat = [];
          this.frames.push({
            idat: true,
            fctl: info,
            fdat: this.idat
          });
        } else {
          this.frames.push({
            fctl: info,
            fdat: []
          });
        }
        info.sequenceNumber = data.readUInt32BE(0);
        info.width = data.readUInt32BE(4);
        info.height = data.readUInt32BE(8);
        info.xOffset = data.readUInt32BE(12);
        info.yOffset = data.readUInt32BE(16);
        info.delayNum = data.readUInt16BE(20);
        info.delayDen = data.readUInt16BE(22);
        info.disposeOp = data.readUInt8(24);
        info.blendOp = data.readUInt8(25);
        break;
      }
      case 'fdat': {
        info.sequenceNumber = data.readUInt32BE(0);
        info.data = data.slice(4);
        this.frames[this.frames.length - 1].fdat.push(info.data);
        break;
      }
    }
    chunk.info = info;
  }

  this._debug(chunks);

  if (this.frames) {
    this.frames = this.frames.map(function(frame, i) {
      frame.fdat = this.decompress(frame.fdat);
      if (!frame.fdat.length) throw new Error('no data');
      return frame;
    }, this);
  }

  idat = this.decompress(this.idat);
  if (!idat.length) throw new Error('no data');

  return idat;
};

PNG.prototype.parseLines = function(data) {
  var pixels = []
    , x
    , p
    , prior
    , line
    , filter
    , samples
    , pendingSamples
    , ch
    , shiftStart
    , i
    , toShift
    , sample;

  this.sampleDepth =
    this.colorType === 0 ? 1
    : this.colorType === 2 ? 3
    : this.colorType === 3 ? 1
    : this.colorType === 4 ? 2
    : this.colorType === 6 ? 4
    : 1;
  this.bitsPerPixel = this.bitDepth * this.sampleDepth;
  this.bytesPerPixel = Math.ceil(this.bitsPerPixel / 8);
  this.wastedBits = ((this.width * this.bitsPerPixel) / 8) - ((this.width * this.bitsPerPixel / 8) | 0);
  this.byteWidth = Math.ceil(this.width * (this.bitsPerPixel / 8));

  this.shiftStart = ((this.bitDepth + (8 / this.bitDepth - this.bitDepth)) - 1) | 0;
  this.shiftMult = this.bitDepth >= 8 ? 0 : this.bitDepth;
  this.mask = this.bitDepth === 32 ? 0xffffffff : (1 << this.bitDepth) - 1;

  if (this.interlace === 1) {
    samples = this.sampleInterlacedLines(data);
    for (i = 0; i < samples.length; i += this.sampleDepth) {
      pixels.push(samples.slice(i, i + this.sampleDepth));
    }
    return pixels;
  }

  for (p = 0; p < data.length; p += this.byteWidth) {
    prior = line || [];
    filter = data[p++];
    line = data.slice(p, p + this.byteWidth);
    line = this.unfilterLine(filter, line, prior);
    samples = this.sampleLine(line);
    for (i = 0; i < samples.length; i += this.sampleDepth) {
      pixels.push(samples.slice(i, i + this.sampleDepth));
    }
  }

  return pixels;
};

PNG.prototype.unfilterLine = function(filter, line, prior) {
  for (var x = 0; x < line.length; x++) {
    if (filter === 0) {
      break;
    } else if (filter === 1) {
      line[x] = this.filters.sub(x, line, prior, this.bytesPerPixel);
    } else if (filter === 2) {
      line[x] = this.filters.up(x, line, prior, this.bytesPerPixel);
    } else if (filter === 3) {
      line[x] = this.filters.average(x, line, prior, this.bytesPerPixel);
    } else if (filter === 4) {
      line[x] = this.filters.paeth(x, line, prior, this.bytesPerPixel);
    }
  }
  return line;
};

PNG.prototype.sampleLine = function(line, width) {
  var samples = []
    , x = 0
    , pendingSamples
    , ch
    , i
    , sample
    , shiftStart
    , toShift;

  while (x < line.length) {
    pendingSamples = this.sampleDepth;
    while (pendingSamples--) {
      ch = line[x];
      if (this.bitDepth === 16) {
        ch = (ch << 8) | line[++x];
      } else if (this.bitDepth === 24) {
        ch = (ch << 16) | (line[++x] << 8) | line[++x];
      } else if (this.bitDepth === 32) {
        ch = (ch << 24) | (line[++x] << 16) | (line[++x] << 8) | line[++x];
      } else if (this.bitDepth > 32) {
        throw new Error('bitDepth ' + this.bitDepth + ' unsupported.');
      }
      shiftStart = this.shiftStart;
      toShift = shiftStart - (x === line.length - 1 ? this.wastedBits : 0);
      for (i = 0; i <= toShift; i++) {
        sample = (ch >> (this.shiftMult * shiftStart)) & this.mask;
        if (this.colorType !== 3) {
          if (this.bitDepth < 8) { // <= 8 would work too, doesn't matter
            // sample = sample * (0xff / this.mask) | 0; // would work too
            sample *= 0xff / this.mask;
            sample |= 0;
          } else if (this.bitDepth > 8) {
            sample = (sample / this.mask) * 255 | 0;
          }
        }
        samples.push(sample);
        shiftStart--;
      }
      x++;
    }
  }

  // Needed for deinterlacing?
  if (width != null) {
    samples = samples.slice(0, width * this.sampleDepth);
  }

  return samples;
};

// http://www.w3.org/TR/PNG-Filters.html
PNG.prototype.filters = {
  sub: function Sub(x, line, prior, bpp) {
    if (x < bpp) return line[x];
    return (line[x] + line[x - bpp]) % 256;
  },
  up: function Up(x, line, prior, bpp) {
    return (line[x] + (prior[x] || 0)) % 256;
  },
  average: function Average(x, line, prior, bpp) {
    if (x < bpp) return Math.floor((prior[x] || 0) / 2);
    // if (x < bpp) return (prior[x] || 0) >> 1;
    return (line[x]
      + Math.floor((line[x - bpp] + prior[x]) / 2)
      // + ((line[x - bpp] + prior[x]) >> 1)
    ) % 256;
  },
  paeth: function Paeth(x, line, prior, bpp) {
    if (x < bpp) return prior[x] || 0;
    return (line[x] + this._predictor(
      line[x - bpp], prior[x] || 0, prior[x - bpp] || 0
    )) % 256;
  },
  _predictor: function PaethPredictor(a, b, c) {
    // a = left, b = above, c = upper left
    var p = a + b - c
      , pa = Math.abs(p - a)
      , pb = Math.abs(p - b)
      , pc = Math.abs(p - c);
    if (pa <= pb && pa <= pc) return a;
    if (pb <= pc) return b;
    return c;
  }
};

/**
 * Adam7 deinterlacing ported to javascript from PyPNG:
 * pypng - Pure Python library for PNG image encoding/decoding
 * Copyright (c) 2009-2015, David Jones (MIT License).
 * https://github.com/drj11/pypng
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation files
 * (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge,
 * publish, distribute, sublicense, and/or sell copies of the Software,
 * and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

PNG.prototype.sampleInterlacedLines = function(raw) {
  var psize
    , vpr
    , samples
    , source_offset
    , i
    , pass
    , xstart
    , ystart
    , xstep
    , ystep
    , recon
    , ppr
    , row_size
    , y
    , filter_type
    , scanline
    , flat
    , offset
    , k
    , end_offset
    , skip
    , j
    , k
    , f;

  var adam7 = [
    [0, 0, 8, 8],
    [4, 0, 8, 8],
    [0, 4, 4, 8],
    [2, 0, 4, 4],
    [0, 2, 2, 4],
    [1, 0, 2, 2],
    [0, 1, 1, 2]
  ];

  // Fractional bytes per pixel
  psize = (this.bitDepth / 8) * this.sampleDepth;

  // Values per row (of the target image)
  vpr = this.width * this.sampleDepth;

  // Make a result array, and make it big enough. Interleaving
  // writes to the output array randomly (well, not quite), so the
  // entire output array must be in memory.
  samples = new Buffer(vpr * this.height);
  samples.fill(0);

  source_offset = 0;

  for (i = 0; i < adam7.length; i++) {
    pass = adam7[i];
    xstart = pass[0];
    ystart = pass[1];
    xstep = pass[2];
    ystep = pass[3];
    if (xstart >= this.width) continue;
    // The previous (reconstructed) scanline. Empty array at the
    // beginning of a pass to indicate that there is no previous
    // line.
    recon = [];
    // Pixels per row (reduced pass image)
    ppr = Math.ceil((this.width - xstart) / xstep);
    // Row size in bytes for this pass.
    row_size = Math.ceil(psize * ppr);
    for (y = ystart; y < this.height; y += ystep) {
      filter_type = raw[source_offset];
      source_offset += 1;
      scanline = raw.slice(source_offset, source_offset + row_size);
      source_offset += row_size;
      recon = this.unfilterLine(filter_type, scanline, recon);
      // Convert so that there is one element per pixel value
      flat = this.sampleLine(recon, ppr);
      if (xstep === 1) {
        assert.equal(xstart, 0);
        offset = y * vpr;
        for (k = offset, f = 0; k < offset + vpr; k++, f++) {
          samples[k] = flat[f];
        }
      } else {
        offset = y * vpr + xstart * this.sampleDepth;
        end_offset = (y + 1) * vpr;
        skip = this.sampleDepth * xstep;
        for (j = 0; j < this.sampleDepth; j++) {
          for (k = offset + j, f = j; k < end_offset; k += skip, f += this.sampleDepth) {
            samples[k] = flat[f];
          }
        }
      }
    }
  }

  return samples;
};

PNG.prototype.createBitmap = function(pixels) {
  var bmp = []
    , i;

  if (this.colorType === 0) {
    pixels = pixels.map(function(sample) {
      return { r: sample[0], g: sample[0], b: sample[0], a: 255 };
    });
  } else if (this.colorType === 2) {
    pixels = pixels.map(function(sample) {
      return { r: sample[0], g: sample[1], b: sample[2], a: 255 };
    });
  } else if (this.colorType === 3) {
    pixels = pixels.map(function(sample) {
      if (!this.palette[sample[0]]) throw new Error('bad palette index');
      return this.palette[sample[0]];
    }, this);
  } else if (this.colorType === 4) {
    pixels = pixels.map(function(sample) {
      return { r: sample[0], g: sample[0], b: sample[0], a: sample[1] };
    });
  } else if (this.colorType === 6) {
    pixels = pixels.map(function(sample) {
      return { r: sample[0], g: sample[1], b: sample[2], a: sample[3] };
    });
  }

  for (i = 0; i < pixels.length; i += this.width) {
    bmp.push(pixels.slice(i, i + this.width));
  }

  return bmp;
};

PNG.prototype.createCellmap = function(bmp, options) {
  var bmp = bmp || this.bmp
    , options = options || this.options
    , cellmap = []
    , scale = options.scale || 0.20
    , height = bmp.length
    , width = bmp[0].length
    , cmwidth = options.width
    , cmheight = options.height
    , line
    , x
    , y
    , xx
    , yy
    , scale
    , xs
    , ys;

  if (cmwidth) {
    scale = cmwidth / width;
  } else if (cmheight) {
    scale = cmheight / height;
  }

  if (!cmheight) {
    cmheight = Math.round(height * scale);
  }

  if (!cmwidth) {
    cmwidth = Math.round(width * scale);
  }

  ys = height / cmheight;
  xs = width / cmwidth;

  for (y = 0; y < bmp.length; y += ys) {
    line = [];
    yy = Math.round(y);
    if (!bmp[yy]) break;
    for (x = 0; x < bmp[yy].length; x += xs) {
      xx = Math.round(x);
      if (!bmp[yy][xx]) break;
      line.push(bmp[yy][xx]);
    }
    cellmap.push(line);
  }

  return cellmap;
};

PNG.prototype.renderANSI = function(bmp) {
  var self = this
    , out = '';

  bmp.forEach(function(line, y) {
    line.forEach(function(pixel, x) {
      var outch = self.getOutch(x, y, line, pixel);
      out += self.pixelToSGR(pixel, outch);
    });
    out += '\n';
  });

  return out;
};

PNG.prototype.renderContent = function(bmp, el) {
  var self = this
    , out = '';

  bmp.forEach(function(line, y) {
    line.forEach(function(pixel, x) {
      var outch = self.getOutch(x, y, line, pixel);
      out += self.pixelToTags(pixel, outch);
    });
    out += '\n';
  });

  el.setContent(out);

  return out;
};

PNG.prototype.renderScreen = function(bmp, screen, xi, xl, yi, yl) {
  var self = this
    , lines = screen.lines
    , cellLines
    , y
    , yy
    , x
    , xx
    , alpha
    , attr
    , ch;

  cellLines = bmp.reduce(function(cellLines, line, y) {
    var cellLine = [];
    line.forEach(function(pixel, x) {
      var outch = self.getOutch(x, y, line, pixel)
        , cell = self.pixelToCell(pixel, outch);
      cellLine.push(cell);
    });
    cellLines.push(cellLine);
    return cellLines;
  }, []);

  for (y = yi; y < yl; y++) {
    yy = y - yi;
    for (x = xi; x < xl; x++) {
      xx = x - xi;
      if (lines[y] && lines[y][x] && cellLines[yy] && cellLines[yy][xx]) {
        alpha = cellLines[yy][xx].pop();
        // completely transparent
        if (alpha === 0.0) {
          continue;
        }
        // translucency / blending
        if (alpha < 1.0) {
          attr = cellLines[yy][xx][0];
          ch = cellLines[yy][xx][1];
          lines[y][x][0] = this.colors.blend(lines[y][x][0], attr, alpha);
          if (ch !== ' ') lines[y][x][1] = ch;
          lines[y].dirty = true;
          continue;
        }
        // completely opaque
        lines[y][x] = cellLines[yy][xx];
        lines[y].dirty = true;
      }
    }
  }
};

PNG.prototype.renderElement = function(bmp, el) {
  var xi = el.aleft + el.ileft
    , xl = el.aleft + el.width - el.iright
    , yi = el.atop + el.itop
    , yl = el.atop + el.height - el.ibottom;

  return this.renderScreen(bmp, el.screen, xi, xl, yi, yl);
};

PNG.prototype.pixelToSGR = function(pixel, ch) {
  var bga = 1.0
    , fga = 0.5
    , a = pixel.a / 255
    , bg
    , fg;

  bg = this.colors.match(
    pixel.r * a * bga | 0,
    pixel.g * a * bga | 0,
    pixel.b * a * bga | 0);

  if (ch && this.options.ascii) {
    fg = this.colors.match(
      pixel.r * a * fga | 0,
      pixel.g * a * fga | 0,
      pixel.b * a * fga | 0);
    if (a === 0) {
      return '\x1b[38;5;' + fg + 'm' + ch + '\x1b[m';
    }
    return '\x1b[38;5;' + fg + 'm\x1b[48;5;' + bg + 'm' + ch + '\x1b[m';
  }

  if (a === 0) return ' ';

  return '\x1b[48;5;' + bg + 'm \x1b[m';
};

PNG.prototype.pixelToTags = function(pixel, ch) {
  var bga = 1.0
    , fga = 0.5
    , a = pixel.a / 255
    , bg
    , fg;

  bg = this.colors.RGBtoHex(
    pixel.r * a * bga | 0,
    pixel.g * a * bga | 0,
    pixel.b * a * bga | 0);

  if (ch && this.options.ascii) {
    fg = this.colors.RGBtoHex(
      pixel.r * a * fga | 0,
      pixel.g * a * fga | 0,
      pixel.b * a * fga | 0);
    if (a === 0) {
      return '{' + fg + '-fg}' + ch + '{/}';
    }
    return '{' + fg + '-fg}{' + bg + '-bg}' + ch + '{/}';
  }

  if (a === 0) return ' ';

  return '{' + bg + '-bg} {/' + bg + '-bg}';
};

PNG.prototype.pixelToCell = function(pixel, ch) {
  var bga = 1.0
    , fga = 0.5
    , a = pixel.a / 255
    , bg
    , fg;

  bg = this.colors.match(
    pixel.r * bga | 0,
    pixel.g * bga | 0,
    pixel.b * bga | 0);

  if (ch && this.options.ascii) {
    fg = this.colors.match(
      pixel.r * fga | 0,
      pixel.g * fga | 0,
      pixel.b * fga | 0);
  } else {
    fg = 0x1ff;
    ch = null;
  }

  // if (a === 0) bg = 0x1ff;

  return [(0 << 18) | (fg << 9) | (bg << 0), ch || ' ', a];
};

// Taken from libcaca:
PNG.prototype.getOutch = (function() {
  var dchars = '????8@8@#8@8##8#MKXWwz$&%x><\\/xo;+=|^-:i\'.`,  `.        ';

  var luminance = function(pixel) {
    var a = pixel.a / 255
      , r = pixel.r * a
      , g = pixel.g * a
      , b = pixel.b * a
      , l = 0.2126 * r + 0.7152 * g + 0.0722 * b;

    return l / 255;
  };

  return function(x, y, line, pixel) {
    var lumi = luminance(pixel)
      , outch = dchars[lumi * (dchars.length - 1) | 0];

    return outch;
  };
})();

PNG.prototype.compileFrames = function(frames) {
  return this.optimization === 'mem'
    ? this.compileFrames_lomem(frames)
    : this.compileFrames_locpu(frames);
};

PNG.prototype.compileFrames_lomem = function(frames) {
  if (!this.actl) return;
  return frames.map(function(frame, i) {
    this.width = frame.fctl.width;
    this.height = frame.fctl.height;

    var pixels = frame._pixels || this.parseLines(frame.fdat)
      , bmp = frame._bmp || this.createBitmap(pixels)
      , fc = frame.fctl;

    return {
      actl: this.actl,
      fctl: frame.fctl,
      delay: (fc.delayNum / (fc.delayDen || 100)) * 1000 | 0,
      bmp: bmp
    };
  }, this);
};

PNG.prototype.compileFrames_locpu = function(frames) {
  if (!this.actl) return;

  this._curBmp = null;
  this._lastBmp = null;

  return frames.map(function(frame, i) {
    this.width = frame.fctl.width;
    this.height = frame.fctl.height;

    var pixels = frame._pixels || this.parseLines(frame.fdat)
      , bmp = frame._bmp || this.createBitmap(pixels)
      , renderBmp = this.renderFrame(bmp, frame, i)
      , cellmap = this.createCellmap(renderBmp)
      , fc = frame.fctl;

    return {
      actl: this.actl,
      fctl: frame.fctl,
      delay: (fc.delayNum / (fc.delayDen || 100)) * 1000 | 0,
      bmp: renderBmp,
      cellmap: cellmap
    };
  }, this);
};

PNG.prototype.renderFrame = function(bmp, frame, i) {
  var first = this.frames[0]
    , last = this.frames[i - 1]
    , fc = frame.fctl
    , xo = fc.xOffset
    , yo = fc.yOffset
    , lxo
    , lyo
    , x
    , y
    , line
    , p;

  if (!this._curBmp) {
    this._curBmp = [];
    for (y = 0; y < first.fctl.height; y++) {
      line = [];
      for (x = 0; x < first.fctl.width; x++) {
        p = bmp[y][x];
        line.push({ r: p.r, g: p.g, b: p.b, a: p.a });
      }
      this._curBmp.push(line);
    }
  }

  if (last && last.fctl.disposeOp !== 0) {
    lxo = last.fctl.xOffset;
    lyo = last.fctl.yOffset;
    for (y = 0; y < last.fctl.height; y++) {
      for (x = 0; x < last.fctl.width; x++) {
        if (last.fctl.disposeOp === 0) {
          // none / keep
        } else if (last.fctl.disposeOp === 1) {
          // background / clear
          this._curBmp[lyo + y][lxo + x] = { r: 0, g: 0, b: 0, a: 0 };
        } else if (last.fctl.disposeOp === 2) {
          // previous / restore
          p = this._lastBmp[y][x];
          this._curBmp[lyo + y][lxo + x] = { r: p.r, g: p.g, b: p.b, a: p.a };
        }
      }
    }
  }

  if (frame.fctl.disposeOp === 2) {
    this._lastBmp = [];
    for (y = 0; y < frame.fctl.height; y++) {
      line = [];
      for (x = 0; x < frame.fctl.width; x++) {
        p = this._curBmp[yo + y][xo + x];
        line.push({ r: p.r, g: p.g, b: p.b, a: p.a });
      }
      this._lastBmp.push(line);
    }
  } else {
    this._lastBmp = null;
  }

  for (y = 0; y < frame.fctl.height; y++) {
    for (x = 0; x < frame.fctl.width; x++) {
      p = bmp[y][x];
      if (fc.blendOp === 0) {
        // source
        this._curBmp[yo + y][xo + x] = { r: p.r, g: p.g, b: p.b, a: p.a };
      } else if (fc.blendOp === 1) {
        // over
        if (p.a !== 0) {
          this._curBmp[yo + y][xo + x] = { r: p.r, g: p.g, b: p.b, a: p.a };
        }
      }
    }
  }

  return this._curBmp;
};

PNG.prototype._animate = function(callback) {
  if (!this.frames) {
    return callback(this.bmp, this.cellmap);
  }

  var self = this
    , numPlays = this.actl.numPlays || Infinity
    , running = 0
    , i = -1;

  this._curBmp = null;
  this._lastBmp = null;

  var next_lomem = function() {
    if (!running) return;

    var frame = self.frames[++i];
    if (!frame) {
      if (!--numPlays) return callback();
      i = -1;
      // XXX may be able to optimize by only setting the self._curBmp once???
      self._curBmp = null;
      self._lastBmp = null;
      return setImmediate(next);
    }

    var bmp = frame.bmp
      , renderBmp = self.renderFrame(bmp, frame, i)
      , cellmap = self.createCellmap(renderBmp);

    callback(renderBmp, cellmap);
    return setTimeout(next, frame.delay / self.speed | 0);
  };

  var next_locpu = function() {
    if (!running) return;
    var frame = self.frames[++i];
    if (!frame) {
      if (!--numPlays) return callback();
      i = -1;
      return setImmediate(next);
    }
    callback(frame.bmp, frame.cellmap);
    return setTimeout(next, frame.delay / self.speed | 0);
  };

  var next = this.optimization === 'mem'
    ? next_lomem
    : next_locpu;

  this._control = function(state) {
    if (state === -1) {
      i = -1;
      self._curBmp = null;
      self._lastBmp = null;
      running = 0;
      callback(self.frames[0].bmp,
        self.frames[0].cellmap || self.createCellmap(self.frames[0].bmp));
      return;
    }
    if (state === running) return;
    running = state;
    return next();
  };

  this._control(1);
};

PNG.prototype.play = function(callback) {
  if (!this._control || callback) {
    this.stop();
    return this._animate(callback);
  }
  this._control(1);
};

PNG.prototype.pause = function() {
  if (!this._control) return;
  this._control(0);
};

PNG.prototype.stop = function() {
  if (!this._control) return;
  this._control(-1);
};

PNG.prototype.toPNG = function(input) {
  var options = this.options
    , file = this.file
    , format = this.format
    , buf
    , img
    , gif
    , i
    , control
    , disposeOp;

  if (format !== 'gif') {
    buf = exec('convert', [format + ':-', 'png:-'],
      { stdio: ['pipe', 'pipe', 'ignore'], input: input });
    img = PNG(buf, options);
    img.file = file;
    return img;
  }

  gif = GIF(input, options);

  this.width = gif.width;
  this.height = gif.height;
  this.frames = [];

  for (i = 0; i < gif.images.length; i++) {
    img = gif.images[i];
    // Convert from gif disposal to png disposal. See:
    // http://www.w3.org/Graphics/GIF/spec-gif89a.txt
    control = img.control || gif;
    disposeOp = Math.max(0, (control.disposeMethod || 0) - 1);
    if (disposeOp > 2) disposeOp = 0;
    this.frames.push({
      fctl: {
        sequenceNumber: i,
        width: img.width,
        height: img.height,
        xOffset: img.left,
        yOffset: img.top,
        delayNum: control.delay,
        delayDen: 100,
        disposeOp: disposeOp,
        blendOp: 1
      },
      fdat: [],
      _pixels: [],
      _bmp: img.bmp
    });
  }

  this.bmp = this.frames[0]._bmp;
  this.cellmap = this.createCellmap(this.bmp);

  if (this.frames.length > 1) {
    this.actl = { numFrames: gif.images.length, numPlays: gif.numPlays || 0 };
    this.frames = this.compileFrames(this.frames);
  } else {
    this.frames = undefined;
  }

  return this;
};

// Convert a gif to an apng using imagemagick. Unfortunately imagemagick
// doesn't support apngs, so we coalesce the gif frames into one image and then
// slice them into frames.
PNG.prototype.gifMagick = function(input) {
  var options = this.options
    , file = this.file
    , format = this.format
    , buf
    , fmt
    , img
    , frames
    , frame
    , width
    , height
    , iwidth
    , twidth
    , i
    , lines
    , line
    , x
    , y;

  buf = exec('convert',
    [format + ':-', '-coalesce', '+append', 'png:-'],
    { stdio: ['pipe', 'pipe', 'ignore'], input: input });

  fmt = '{"W":%W,"H":%H,"w":%w,"h":%h,"d":%T,"x":"%X","y":"%Y"},'
  frames = exec('identify', ['-format', fmt, format + ':-'],
    { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'], input: input });
  frames = JSON.parse('[' + frames.trim().slice(0, -1) + ']');

  img = PNG(buf, options);
  img.file = file;
  Object.keys(img).forEach(function(key) {
    this[key] = img[key];
  }, this);

  width = frames[0].W;
  height = frames[0].H;
  iwidth = 0;
  twidth = 0;

  this.width = width;
  this.height = height;

  this.frames = [];

  for (i = 0; i < frames.length; i++) {
    frame = frames[i];
    frame.x = +frame.x;
    frame.y = +frame.y;

    iwidth = twidth;
    twidth += width;

    lines = [];
    for (y = frame.y; y < height; y++) {
      line = [];
      for (x = iwidth + frame.x; x < twidth; x++) {
        line.push(img.bmp[y][x]);
      }
      lines.push(line);
    }

    this.frames.push({
      fctl: {
        sequenceNumber: i,
        width: frame.w,
        height: frame.h,
        xOffset: frame.x,
        yOffset: frame.y,
        delayNum: frame.d,
        delayDen: 100,
        disposeOp: 0,
        blendOp: 0
      },
      fdat: [],
      _pixels: [],
      _bmp: lines
    });
  }

  this.bmp = this.frames[0]._bmp;
  this.cellmap = this.createCellmap(this.bmp);

  if (this.frames.length > 1) {
    this.actl = { numFrames: frames.length, numPlays: 0 };
    this.frames = this.compileFrames(this.frames);
  } else {
    this.frames = undefined;
  }

  return this;
};

PNG.prototype.decompress = function(buffers) {
  return zlib.inflateSync(new Buffer(buffers.reduce(function(out, data) {
    return out.concat(Array.prototype.slice.call(data));
  }, [])));
};

/**
 * node-crc
 * https://github.com/alexgorbatchev/node-crc
 * https://github.com/alexgorbatchev/node-crc/blob/master/LICENSE
 *
 * The MIT License (MIT)
 *
 * Copyright 2014 Alex Gorbatchev
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

PNG.prototype.crc32 = (function() {
  var crcTable = [
    0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
    0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
    0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
    0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
    0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
    0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
    0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
    0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
    0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
    0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
    0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
    0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
    0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
    0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
    0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
    0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
    0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
    0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
    0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
    0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
    0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
    0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
    0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
    0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
    0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
    0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
    0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
    0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
    0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
    0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
    0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
    0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
    0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
    0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
    0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
    0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
    0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
    0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
    0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
    0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
    0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
    0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
    0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
  ];

  return function crc32(buf) {
    //var crc = previous === 0 ? 0 : ~~previous ^ -1;
    var crc = -1;
    for (var i = 0, len = buf.length; i < len; i++) {
      crc = crcTable[(crc ^ buf[i]) & 0xff] ^ (crc >>> 8);
    }
    return crc ^ -1;
  };
})();

PNG.prototype._debug = function() {
  if (!this.options.log) return;
  return this.options.log.apply(null, arguments);
};

/**
 * GIF
 */

function GIF(file, options) {
  var self = this;

  if (!(this instanceof GIF)) {
    return new GIF(file, options);
  }

  var info = {}
    , p = 0
    , buf
    , i
    , total
    , sig
    , desc
    , img
    , ext
    , label
    , size;

  if (!file) throw new Error('no file');

  options = options || {};

  this.options = options;

  // XXX If the gif is not optimized enough
  // it may OOM the process with too many frames.
  // TODO: Implement in PNG reader.
  this.pixelLimit = this.options.pixelLimit || 7622550;
  this.totalPixels = 0;

  if (Buffer.isBuffer(file)) {
    buf = file;
    file = null;
  } else {
    file = path.resolve(process.cwd(), file);
    buf = fs.readFileSync(file);
  }

  sig = buf.slice(0, 6).toString('ascii');
  if (sig !== 'GIF87a' && sig !== 'GIF89a') {
    throw new Error('bad header: ' + sig);
  }

  this.width = buf.readUInt16LE(6);
  this.height = buf.readUInt16LE(8);

  this.flags = buf.readUInt8(10);
  this.gct = !!(this.flags & 0x80);
  this.gctsize = (this.flags & 0x07) + 1;

  this.bgIndex = buf.readUInt8(11);
  this.aspect = buf.readUInt8(12);
  p += 13;

  if (this.gct) {
    this.colors = [];
    total = 1 << this.gctsize;
    for (i = 0; i < total; i++, p += 3) {
      this.colors.push([buf[p], buf[p + 1], buf[p + 2], 255]);
    }
  }

  this.images = [];
  this.extensions = [];

  try {
    while (p < buf.length) {
      desc = buf.readUInt8(p);
      p += 1;
      if (desc === 0x2c) {
        img = {};

        img.left = buf.readUInt16LE(p);
        p += 2;
        img.top = buf.readUInt16LE(p);
        p += 2;

        img.width = buf.readUInt16LE(p);
        p += 2;
        img.height = buf.readUInt16LE(p);
        p += 2;

        img.flags = buf.readUInt8(p);
        p += 1;

        img.lct = !!(img.flags & 0x80);
        img.ilace = !!(img.flags & 0x40);
        img.lctsize = (img.flags & 0x07) + 1;

        if (img.lct) {
          img.lcolors = [];
          total = 1 << img.lctsize;
          for (i = 0; i < total; i++, p += 3) {
            img.lcolors.push([buf[p], buf[p + 1], buf[p + 2], 255]);
          }
        }

        img.codeSize = buf.readUInt8(p);
        p += 1;

        img.size = buf.readUInt8(p);
        p += 1;

        img.lzw = [buf.slice(p, p + img.size)];
        p += img.size;

        while (buf[p] !== 0x00) {
          // Some gifs screw up their size.
          // XXX Same for all subblocks?
          if (buf[p] === 0x3b && p === buf.length - 1) {
            p--;
            break;
          }
          size = buf.readUInt8(p);
          p += 1;
          img.lzw.push(buf.slice(p, p + size));
          p += size;
        }

        assert.equal(buf.readUInt8(p), 0x00);
        p += 1;

        if (ext && ext.label === 0xf9) {
          img.control = ext;
        }

        this.totalPixels += img.width * img.height;

        this.images.push(img);

        if (this.totalPixels >= this.pixelLimit) {
          break;
        }
      } else if (desc === 0x21) {
        // Extensions:
        // http://www.w3.org/Graphics/GIF/spec-gif89a.txt
        ext = {};
        label = buf.readUInt8(p);
        p += 1;
        ext.label = label;
        if (label === 0xf9) {
          size = buf.readUInt8(p);
          assert.equal(size, 0x04);
          p += 1;
          ext.fields = buf.readUInt8(p);
          ext.disposeMethod = (ext.fields >> 2) & 0x07;
          ext.useTransparent = !!(ext.fields & 0x01);
          p += 1;
          ext.delay = buf.readUInt16LE(p);
          p += 2;
          ext.transparentColor = buf.readUInt8(p);
          p += 1;
          while (buf[p] !== 0x00) {
            size = buf.readUInt8(p);
            p += 1;
            p += size;
          }
          assert.equal(buf.readUInt8(p), 0x00);
          p += 1;
          this.delay = ext.delay;
          this.transparentColor = ext.transparentColor;
          this.disposeMethod = ext.disposeMethod;
          this.useTransparent = ext.useTransparent;
        } else if (label === 0xff) {
          // https://wiki.whatwg.org/wiki/GIF#Specifications
          size = buf.readUInt8(p);
          p += 1;
          ext.id = buf.slice(p, p + 8).toString('ascii');
          p += 8;
          ext.auth = buf.slice(p, p + 3).toString('ascii');
          p += 3;
          ext.data = [];
          while (buf[p] !== 0x00) {
            size = buf.readUInt8(p);
            p += 1;
            ext.data.push(buf.slice(p, p + size));
            p += size;
          }
          ext.data = new Buffer(ext.data.reduce(function(out, data) {
            return out.concat(Array.prototype.slice.call(data));
          }, []));
          // AnimExts looping extension (identical to netscape)
          if (ext.id === 'ANIMEXTS' && ext.auth === '1.0') {
            ext.id = 'NETSCAPE';
            ext.auth = '2.0';
            ext.animexts = true;
          }
          // Netscape extensions
          if (ext.id === 'NETSCAPE' && ext.auth === '2.0') {
            if (ext.data.readUInt8(0) === 0x01) {
              // Netscape looping extension
              // http://graphcomp.com/info/specs/ani_gif.html
              ext.numPlays = ext.data.readUInt16LE(1);
              this.numPlays = ext.numPlays;
            } else if (ext.data.readUInt8(0) === 0x02) {
              // Netscape buffering extension
              this.minBuffer = ext.data;
            }
          }
          // Adobe XMP extension
          if (ext.id === 'XMP Data' && ext.auth === 'XMP') {
            ext.xmp = ext.data.toString('utf8');
            this.xmp = ext.xmp;
          }
          // ICC extension
          if (ext.id === 'ICCRGBG1' && ext.auth === '012') {
            // NOTE: Says size is 4 bytes, not 1? Maybe just buffer size?
            this.icc = ext.data;
          }
          // fractint extension
          if (ext.id === 'fractint' && /^00[1-7]$/.test(ext.auth)) {
            // NOTE: Says size is 4 bytes, not 1? Maybe just buffer size?
            // Size: '!\377\013' == [0x00, 0x15, 0xff, 0x0b]
            this.fractint = ext.data;
          }
          assert.equal(buf.readUInt8(p), 0x00);
          p += 1;
        } else {
          ext.data = [];
          while (buf[p] !== 0x00) {
            size = buf.readUInt8(p);
            p += 1;
            ext.data.push(buf.slice(p, p + size));
            p += size;
          }
          assert.equal(buf.readUInt8(p), 0x00);
          p += 1;
        }
        this.extensions.push(ext);
      } else if (desc === 0x3b) {
        break;
      } else if (p === buf.length - 1) {
        // } else if (desc === 0x00 && p === buf.length - 1) {
        break;
      } else {
        throw new Error('unknown block');
      }
    }
  } catch (e) {
    if (options.debug) {
      throw e;
    }
  }

  this.images = this.images.map(function(img, imageIndex) {
    var control = img.control || this;

    img.lzw = new Buffer(img.lzw.reduce(function(out, data) {
      return out.concat(Array.prototype.slice.call(data));
    }, []));

    try {
      img.data = this.decompress(img.lzw, img.codeSize);
    } catch (e) {
      if (options.debug) throw e;
      return;
    }

    var interlacing = [
      [ 0, 8 ],
      [ 4, 8 ],
      [ 2, 4 ],
      [ 1, 2 ],
      [ 0, 0 ]
    ];

    var table = img.lcolors || this.colors
      , row = 0
      , col = 0
      , ilp = 0
      , p = 0
      , b
      , idx
      , i
      , y
      , x
      , line
      , pixel;

    img.samples = [];
    // Rewritten version of:
    // https://github.com/lbv/ka-cs-programs/blob/master/lib/gif-reader.js
    for (;;) {
      b = img.data[p++];
      if (b == null) break;
      idx = (row * img.width + col) * 4;
      if (!table[b]) {
        if (options.debug) throw new Error('bad samples');
        table[b] = [0, 0, 0, 0];
      }
      img.samples[idx] = table[b][0];
      img.samples[idx + 1] = table[b][1];
      img.samples[idx + 2] = table[b][2];
      img.samples[idx + 3] = table[b][3];
      if (control.useTransparent && b === control.transparentColor) {
        img.samples[idx + 3] = 0;
      }
      if (++col >= img.width) {
        col = 0;
        if (img.ilace) {
          row += interlacing[ilp][1];
          if (row >= img.height) {
            row = interlacing[++ilp][0];
          }
        } else {
          row++;
        }
      }
    }

    img.pixels = [];
    for (i = 0; i < img.samples.length; i += 4) {
      img.pixels.push(img.samples.slice(i, i + 4));
    }

    img.bmp = [];
    for (y = 0, p = 0; y < img.height; y++) {
      line = [];
      for (x = 0; x < img.width; x++) {
        pixel = img.pixels[p++];
        if (!pixel) {
          if (options.debug) throw new Error('no pixel');
          line.push({ r: 0, g: 0, b: 0, a: 0 });
          continue;
        }
        line.push({ r: pixel[0], g: pixel[1], b: pixel[2], a: pixel[3] });
      }
      img.bmp.push(line);
    }

    return img;
  }, this).filter(Boolean);

  if (!this.images.length) {
    throw new Error('no image data or bad decompress');
  }
}

// Rewritten version of:
// https://github.com/lbv/ka-cs-programs/blob/master/lib/gif-reader.js
GIF.prototype.decompress = function(input, codeSize) {
  var bitDepth = codeSize + 1
    , CC = 1 << codeSize
    , EOI = CC + 1
    , stack = []
    , table = []
    , ntable = 0
    , oldCode = null
    , buffer = 0
    , nbuffer = 0
    , p = 0
    , buf = []
    , bits
    , read
    , ans
    , n
    , code
    , i
    , K
    , b
    , maxElem;

  for (;;) {
    if (stack.length === 0) {
      bits = bitDepth;
      read = 0;
      ans = 0;
      while (read < bits) {
        if (nbuffer === 0) {
          if (p >= input.length) return buf;
          buffer = input[p++];
          nbuffer = 8;
        }
        n = Math.min(bits - read, nbuffer);
        ans |= (buffer & ((1 << n) - 1)) << read;
        read += n;
        nbuffer -= n;
        buffer >>= n;
      }
      code = ans;

      if (code === EOI) {
        break;
      }

      if (code === CC) {
        table = [];
        for (i = 0; i < CC; ++i) {
          table[i] = [i, -1, i];
        }
        bitDepth = codeSize + 1;
        maxElem = 1 << bitDepth;
        ntable = CC + 2;
        oldCode = null;
        continue;
      }

      if (oldCode === null) {
        oldCode = code;
        buf.push(table[code][0]);
        continue;
      }

      if (code < ntable) {
        for (i = code; i >= 0; i = table[i][1]) {
          stack.push(table[i][0]);
        }
        table[ntable++] = [
          table[code][2],
          oldCode,
          table[oldCode][2]
        ];
      } else {
        K = table[oldCode][2];
        table[ntable++] = [K, oldCode, K];
        for (i = code; i >= 0; i = table[i][1]) {
          stack.push(table[i][0]);
        }
      }

      oldCode = code;
      if (ntable === maxElem) {
        maxElem = 1 << (++bitDepth);
        if (bitDepth > 12) bitDepth = 12;
      }
    }
    b = stack.pop();
    if (b == null) break;
    buf.push(b);
  }

  return buf;
};

/**
 * Expose
 */

exports = PNG;
exports.png = PNG;
exports.gif = GIF;

module.exports = exports;
};
BundleModuleCode['term/widgets/overlayimage']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Christopher Jeffrey and contributors, Stefan Bosse
 **    $INITIAL:     (C) 2013-2015, Christopher Jeffrey and contributors
 **    $MODIFIED:    sbosse (2017).
 **    $VERSION:     1.2.2
 **
 **    $INFO:
 *
 * overlayimage.js - w3m image element for blessed
 *
 **    $ENDOFINFO
 */

/**
 * Modules
 */
var Comp = Require('com/compat');

var fs = Require('fs')
  , cp = Require('child_process');

var helpers = Require('term/helpers');

var Node = Require('term/widgets/node');
var Box = Require('term/widgets/box');

/**
 * OverlayImage
 * Good example of w3mimgdisplay commands:
 * https://github.com/hut/ranger/blob/master/ranger/ext/img_display.py
 */

function OverlayImage(options) {
  var self = this;

  if (!instanceOf(this,Node)) {
    return new OverlayImage(options);
  }

  options = options || {};

  Box.call(this, options);

  if (options.w3m) {
    OverlayImage.w3mdisplay = options.w3m;
  }

  if (OverlayImage.hasW3MDisplay == null) {
    if (fs.existsSync(OverlayImage.w3mdisplay)) {
      OverlayImage.hasW3MDisplay = true;
    } else if (options.search !== false) {
      var file = helpers.findFile('/usr', 'w3mimgdisplay')
              || helpers.findFile('/lib', 'w3mimgdisplay')
              || helpers.findFile('/bin', 'w3mimgdisplay');
      if (file) {
        OverlayImage.hasW3MDisplay = true;
        OverlayImage.w3mdisplay = file;
      } else {
        OverlayImage.hasW3MDisplay = false;
      }
    }
  }

  this.on('hide', function() {
    self._lastFile = self.file;
    self.clearImage();
  });

  this.on('show', function() {
    if (!self._lastFile) return;
    self.setImage(self._lastFile);
  });

  this.on('detach', function() {
    self._lastFile = self.file;
    self.clearImage();
  });

  this.on('attach', function() {
    if (!self._lastFile) return;
    self.setImage(self._lastFile);
  });

  this.onScreenEvent('resize', function() {
    self._needsRatio = true;
  });

  // Get images to overlap properly. Maybe not worth it:
  // this.onScreenEvent('render', function() {
  //   self.screen.program.flush();
  //   if (!self._noImage) return;
  //   function display(el, next) {
  //     if (el.type === 'w3mimage' && el.file) {
  //       el.setImage(el.file, next);
  //     } else {
  //       next();
  //     }
  //   }
  //   function done(el) {
  //     el.children.forEach(recurse);
  //   }
  //   function recurse(el) {
  //     display(el, function() {
  //       var pending = el.children.length;
  //       el.children.forEach(function(el) {
  //         display(el, function() {
  //           if (!--pending) done(el);
  //         });
  //       });
  //     });
  //   }
  //   recurse(self.screen);
  // });

  this.onScreenEvent('render', function() {
    self.screen.program.flush();
    if (!self._noImage) {
      self.setImage(self.file);
    }
  });

  if (this.options.file || this.options.img) {
    this.setImage(this.options.file || this.options.img);
  }
}

//OverlayImage.prototype.__proto__ = Box.prototype;
inheritPrototype(OverlayImage,Box);

OverlayImage.prototype.type = 'overlayimage';

OverlayImage.w3mdisplay = '/usr/lib/w3m/w3mimgdisplay';

OverlayImage.prototype.spawn = function(file, args, opt, callback) {
  var spawn = require('child_process').spawn
    , ps;

  opt = opt || {};
  ps = spawn(file, args, opt);

  ps.on('error', function(err) {
    if (!callback) return;
    return callback(err);
  });

  ps.on('exit', function(code) {
    if (!callback) return;
    if (code !== 0) return callback(new Error('Exit Code: ' + code));
    return callback(null, code === 0);
  });

  return ps;
};

OverlayImage.prototype.setImage = function(img, callback) {
  var self = this;

  if (this._settingImage) {
    this._queue = this._queue || [];
    this._queue.push([img, callback]);
    return;
  }
  this._settingImage = true;

  var reset = function() {
    self._settingImage = false;
    self._queue = self._queue || [];
    var item = self._queue.shift();
    if (item) {
      self.setImage(item[0], item[1]);
    }
  };

  if (OverlayImage.hasW3MDisplay === false) {
    reset();
    if (!callback) return;
    return callback(new Error('W3M Image Display not available.'));
  }

  if (!img) {
    reset();
    if (!callback) return;
    return callback(new Error('No image.'));
  }

  this.file = img;

  return this.getPixelRatio(function(err, ratio) {
    if (err) {
      reset();
      if (!callback) return;
      return callback(err);
    }

    return self.renderImage(img, ratio, function(err, success) {
      if (err) {
        reset();
        if (!callback) return;
        return callback(err);
      }

      if (self.shrink || self.options.autofit) {
        delete self.shrink;
        delete self.options.shrink;
        self.options.autofit = true;
        return self.imageSize(function(err, size) {
          if (err) {
            reset();
            if (!callback) return;
            return callback(err);
          }

          if (self._lastSize
              && ratio.tw === self._lastSize.tw
              && ratio.th === self._lastSize.th
              && size.width === self._lastSize.width
              && size.height === self._lastSize.height
              && self.aleft === self._lastSize.aleft
              && self.atop === self._lastSize.atop) {
            reset();
            if (!callback) return;
            return callback(null, success);
          }

          self._lastSize = {
            tw: ratio.tw,
            th: ratio.th,
            width: size.width,
            height: size.height,
            aleft: self.aleft,
            atop: self.atop
          };

          self.position.width = size.width / ratio.tw | 0;
          self.position.height = size.height / ratio.th | 0;

          self._noImage = true;
          self.screen.render();
          self._noImage = false;

          reset();
          return self.renderImage(img, ratio, callback);
        });
      }

      reset();
      if (!callback) return;
      return callback(null, success);
    });
  });
};

OverlayImage.prototype.renderImage = function(img, ratio, callback) {
  var self = this;

  if (cp.execSync) {
    callback = callback || function(err, result) { return result; };
    try {
      return callback(null, this.renderImageSync(img, ratio));
    } catch (e) {
      return callback(e);
    }
  }

  if (OverlayImage.hasW3MDisplay === false) {
    if (!callback) return;
    return callback(new Error('W3M Image Display not available.'));
  }

  if (!ratio) {
    if (!callback) return;
    return callback(new Error('No ratio.'));
  }

  // clearImage unsets these:
  var _file = self.file;
  var _lastSize = self._lastSize;
  return self.clearImage(function(err) {
    if (err) return callback(err);

    self.file = _file;
    self._lastSize = _lastSize;

    var opt = {
      stdio: 'pipe',
      env: process.env,
      cwd: process.env.HOME
    };

    var ps = self.spawn(OverlayImage.w3mdisplay, [], opt, function(err, success) {
      if (!callback) return;
      return err
        ? callback(err)
        : callback(null, success);
    });

    var width = self.width * ratio.tw | 0
      , height = self.height * ratio.th | 0
      , aleft = self.aleft * ratio.tw | 0
      , atop = self.atop * ratio.th | 0;

    var input = '0;1;'
      + aleft + ';'
      + atop + ';'
      + width + ';'
      + height + ';;;;;'
      + img
      + '\n4;\n3;\n';

    self._props = {
      aleft: aleft,
      atop: atop,
      width: width,
      height: height
    };

    ps.stdin.write(input);
    ps.stdin.end();
  });
};

OverlayImage.prototype.clearImage = function(callback) {
  if (cp.execSync) {
    callback = callback || function(err, result) { return result; };
    try {
      return callback(null, this.clearImageSync());
    } catch (e) {
      return callback(e);
    }
  }

  if (OverlayImage.hasW3MDisplay === false) {
    if (!callback) return;
    return callback(new Error('W3M Image Display not available.'));
  }

  if (!this._props) {
    if (!callback) return;
    return callback(null);
  }

  var opt = {
    stdio: 'pipe',
    env: process.env,
    cwd: process.env.HOME
  };

  var ps = this.spawn(OverlayImage.w3mdisplay, [], opt, function(err, success) {
    if (!callback) return;
    return err
      ? callback(err)
      : callback(null, success);
  });

  var width = this._props.width + 2
    , height = this._props.height + 2
    , aleft = this._props.aleft
    , atop = this._props.atop;

  if (this._drag) {
    aleft -= 10;
    atop -= 10;
    width += 10;
    height += 10;
  }

  var input = '6;'
   + aleft + ';'
   + atop + ';'
   + width + ';'
   + height
   + '\n4;\n3;\n';

  delete this.file;
  delete this._props;
  delete this._lastSize;

  ps.stdin.write(input);
  ps.stdin.end();
};

OverlayImage.prototype.imageSize = function(callback) {
  var img = this.file;

  if (cp.execSync) {
    callback = callback || function(err, result) { return result; };
    try {
      return callback(null, this.imageSizeSync());
    } catch (e) {
      return callback(e);
    }
  }

  if (OverlayImage.hasW3MDisplay === false) {
    if (!callback) return;
    return callback(new Error('W3M Image Display not available.'));
  }

  if (!img) {
    if (!callback) return;
    return callback(new Error('No image.'));
  }

  var opt = {
    stdio: 'pipe',
    env: process.env,
    cwd: process.env.HOME
  };

  var ps = this.spawn(OverlayImage.w3mdisplay, [], opt);

  var buf = '';

  ps.stdout.setEncoding('utf8');

  ps.stdout.on('data', function(data) {
    buf += data;
  });

  ps.on('error', function(err) {
    if (!callback) return;
    return callback(err);
  });

  ps.on('exit', function() {
    if (!callback) return;
    var size = buf.trim().split(/\s+/);
    return callback(null, {
      raw: buf.trim(),
      width: +size[0],
      height: +size[1]
    });
  });

  var input = '5;' + img + '\n';

  ps.stdin.write(input);
  ps.stdin.end();
};

OverlayImage.prototype.termSize = function(callback) {
  var self = this;

  if (cp.execSync) {
    callback = callback || function(err, result) { return result; };
    try {
      return callback(null, this.termSizeSync());
    } catch (e) {
      return callback(e);
    }
  }

  if (OverlayImage.hasW3MDisplay === false) {
    if (!callback) return;
    return callback(new Error('W3M Image Display not available.'));
  }

  var opt = {
    stdio: 'pipe',
    env: process.env,
    cwd: process.env.HOME
  };

  var ps = this.spawn(OverlayImage.w3mdisplay, ['-test'], opt);

  var buf = '';

  ps.stdout.setEncoding('utf8');

  ps.stdout.on('data', function(data) {
    buf += data;
  });

  ps.on('error', function(err) {
    if (!callback) return;
    return callback(err);
  });

  ps.on('exit', function() {
    if (!callback) return;

    if (!buf.trim()) {
      // Bug: w3mimgdisplay will sometimes
      // output nothing. Try again:
      return self.termSize(callback);
    }

    var size = buf.trim().split(/\s+/);

    return callback(null, {
      raw: buf.trim(),
      width: +size[0],
      height: +size[1]
    });
  });

  ps.stdin.end();
};

OverlayImage.prototype.getPixelRatio = function(callback) {
  var self = this;

  if (cp.execSync) {
    callback = callback || function(err, result) { return result; };
    try {
      return callback(null, this.getPixelRatioSync());
    } catch (e) {
      return callback(e);
    }
  }

  // XXX We could cache this, but sometimes it's better
  // to recalculate to be pixel perfect.
  if (this._ratio && !this._needsRatio) {
    return callback(null, this._ratio);
  }

  return this.termSize(function(err, dimensions) {
    if (err) return callback(err);

    self._ratio = {
      tw: dimensions.width / self.screen.width,
      th: dimensions.height / self.screen.height
    };

    self._needsRatio = false;

    return callback(null, self._ratio);
  });
};

OverlayImage.prototype.renderImageSync = function(img, ratio) {
  if (OverlayImage.hasW3MDisplay === false) {
    throw new Error('W3M Image Display not available.');
  }

  if (!ratio) {
    throw new Error('No ratio.');
  }

  // clearImage unsets these:
  var _file = this.file;
  var _lastSize = this._lastSize;

  this.clearImageSync();

  this.file = _file;
  this._lastSize = _lastSize;

  var width = this.width * ratio.tw | 0
    , height = this.height * ratio.th | 0
    , aleft = this.aleft * ratio.tw | 0
    , atop = this.atop * ratio.th | 0;

  var input = '0;1;'
    + aleft + ';'
    + atop + ';'
    + width + ';'
    + height + ';;;;;'
    + img
    + '\n4;\n3;\n';

  this._props = {
    aleft: aleft,
    atop: atop,
    width: width,
    height: height
  };

  try {
    cp.execFileSync(OverlayImage.w3mdisplay, [], {
      env: process.env,
      encoding: 'utf8',
      input: input,
      timeout: 1000
    });
  } catch (e) {
    ;
  }

  return true;
};

OverlayImage.prototype.clearImageSync = function() {
  if (OverlayImage.hasW3MDisplay === false) {
    throw new Error('W3M Image Display not available.');
  }

  if (!this._props) {
    return false;
  }

  var width = this._props.width + 2
    , height = this._props.height + 2
    , aleft = this._props.aleft
    , atop = this._props.atop;

  if (this._drag) {
    aleft -= 10;
    atop -= 10;
    width += 10;
    height += 10;
  }

  var input = '6;'
   + aleft + ';'
   + atop + ';'
   + width + ';'
   + height
   + '\n4;\n3;\n';

  delete this.file;
  delete this._props;
  delete this._lastSize;

  try {
    cp.execFileSync(OverlayImage.w3mdisplay, [], {
      env: process.env,
      encoding: 'utf8',
      input: input,
      timeout: 1000
    });
  } catch (e) {
    ;
  }

  return true;
};

OverlayImage.prototype.imageSizeSync = function() {
  var img = this.file;

  if (OverlayImage.hasW3MDisplay === false) {
    throw new Error('W3M Image Display not available.');
  }

  if (!img) {
    throw new Error('No image.');
  }

  var buf = '';
  var input = '5;' + img + '\n';

  try {
    buf = cp.execFileSync(OverlayImage.w3mdisplay, [], {
      env: process.env,
      encoding: 'utf8',
      input: input,
      timeout: 1000
    });
  } catch (e) {
    ;
  }

  var size = buf.trim().split(/\s+/);

  return {
    raw: buf.trim(),
    width: +size[0],
    height: +size[1]
  };
};

OverlayImage.prototype.termSizeSync = function(_, recurse) {
  if (OverlayImage.hasW3MDisplay === false) {
    throw new Error('W3M Image Display not available.');
  }

  var buf = '';

  try {
    buf = cp.execFileSync(OverlayImage.w3mdisplay, ['-test'], {
      env: process.env,
      encoding: 'utf8',
      timeout: 1000
    });
  } catch (e) {
    ;
  }

  if (!buf.trim()) {
    // Bug: w3mimgdisplay will sometimes
    // output nothing. Try again:
    recurse = recurse || 0;
    if (++recurse === 5) {
      throw new Error('Term size not determined.');
    }
    return this.termSizeSync(_, recurse);
  }

  var size = buf.trim().split(/\s+/);

  return {
    raw: buf.trim(),
    width: +size[0],
    height: +size[1]
  };
};

OverlayImage.prototype.getPixelRatioSync = function() {
  // XXX We could cache this, but sometimes it's better
  // to recalculate to be pixel perfect.
  if (this._ratio && !this._needsRatio) {
    return this._ratio;
  }
  this._needsRatio = false;

  var dimensions = this.termSizeSync();

  this._ratio = {
    tw: dimensions.width / this.screen.width,
    th: dimensions.height / this.screen.height
  };

  return this._ratio;
};

OverlayImage.prototype.displayImage = function(callback) {
  return this.screen.displayImage(this.file, callback);
};

/**
 * Expose
 */

module.exports = OverlayImage;
};
BundleModuleCode['term/widgets/video']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Christopher Jeffrey and contributors, Stefan Bosse
 **    $INITIAL:     (C) 2013-2015, Christopher Jeffrey and contributors
 **    $MODIFIED:    sbosse (2017).
 **    $VERSION:     1.2.2
 **
 **    $INFO:
 *
 * video.js - video element for blessed
 *
 **    $ENDOFINFO
 */

/**
 * Modules
 */
var Comp = Require('com/compat');

var cp = Require('child_process');

var Node = Require('term/widgets/node');
var Box = Require('term/widgets/box');
var Terminal = Require('term/widgets/terminal');

/**
 * Video
 */

function Video(options) {
  var self = this
    , shell
    , args;

  if (!instanceOf(this,Node)) {
    return new Video(options);
  }

  options = options || {};

  Box.call(this, options);

  if (this.exists('mplayer')) {
    shell = 'mplayer';
    args = ['-vo', 'caca', '-quiet', options.file];
  } else if (this.exists('mpv')) {
    shell = 'mpv';
    args = ['--vo', 'caca', '--really-quiet', options.file];
  } else {
    this.parseTags = true;
    this.setContent('{red-fg}{bold}Error:{/bold}'
      + ' mplayer or mpv not installed.{/red-fg}');
    return this;
  }

  var opts = {
    parent: this,
    left: 0,
    top: 0,
    width: this.width - this.iwidth,
    height: this.height - this.iheight,
    shell: shell,
    args: args.slice()
  };

  this.now = Date.now() / 1000 | 0;
  this.start = opts.start || 0;
  if (this.start) {
    if (shell === 'mplayer') {
      opts.args.unshift('-ss', this.start + '');
    } else if (shell === 'mpv') {
      opts.args.unshift('--start', this.start + '');
    }
  }

  var DISPLAY = process.env.DISPLAY;
  delete process.env.DISPLAY;
  this.tty = new Terminal(opts);
  process.env.DISPLAY = DISPLAY;

  this.on('click', function() {
    self.tty.pty.write('p');
  });

  // mplayer/mpv cannot resize itself in the terminal, so we have
  // to restart it at the correct start time.
  this.on('resize', function() {
    self.tty.destroy();

    var opts = {
      parent: self,
      left: 0,
      top: 0,
      width: self.width - self.iwidth,
      height: self.height - self.iheight,
      shell: shell,
      args: args.slice()
    };

    var watched = (Date.now() / 1000 | 0) - self.now;
    self.now = Date.now() / 1000 | 0;
    self.start += watched;
    if (shell === 'mplayer') {
      opts.args.unshift('-ss', self.start + '');
    } else if (shell === 'mpv') {
      opts.args.unshift('--start', self.start + '');
    }

    var DISPLAY = process.env.DISPLAY;
    delete process.env.DISPLAY;
    self.tty = new Terminal(opts);
    process.env.DISPLAY = DISPLAY;
    self.screen.render();
  });
}

//Video.prototype.__proto__ = Box.prototype;
inheritPrototype(Video,Box);

Video.prototype.type = 'video';

Video.prototype.exists = function(program) {
  try {
    return !!+cp.execSync('type '
      + program + ' > /dev/null 2> /dev/null'
      + ' && echo 1', { encoding: 'utf8' }).trim();
  } catch (e) {
    return false;
  }
};

/**
 * Expose
 */

module.exports = Video;
};
BundleModuleCode['term/widgets/layout']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Christopher Jeffrey, Stefan Bosse
 **    $INITIAL:     (C) 2013-2015, Christopher Jeffrey and contributors
 **    $MODIFIED:    by sbosse
 **    $REVESIO:     1.2.1
 **
 **    $INFO:
 **
 **    layout.js - layout element for blessed
 **
 **    $ENDOFINFO
 */

/**
 * Modules
 */
var Comp = Require('com/compat');

var Node = Require('term/widgets/node');
var Element = Require('term/widgets/element');

/**
 * Layout
 */

function Layout(options) {
  if (!instanceOf(this,Node)) {
    return new Layout(options);
  }

  options = options || {};

  if ((options.width == null
      && (options.left == null && options.right == null))
      || (options.height == null
      && (options.top == null && options.bottom == null))) {
    throw new Error('`Layout` must have a width and height!');
  }

  options.layout = options.layout || 'inline';

  Element.call(this, options);

  if (options.renderer) {
    this.renderer = options.renderer;
  }
}

//Layout.prototype.__proto__ = Element.prototype;
inheritPrototype(Layout,Element);

Layout.prototype.type = 'layout';

Layout.prototype.isRendered = function(el) {
  if (!el.lpos) return false;
  return (el.lpos.xl - el.lpos.xi) > 0
      && (el.lpos.yl - el.lpos.yi) > 0;
};

Layout.prototype.getLast = function(i) {
  while (this.children[--i]) {
    var el = this.children[i];
    if (this.isRendered(el)) return el;
  }
};

Layout.prototype.getLastCoords = function(i) {
  var last = this.getLast(i);
  if (last) return last.lpos;
};

Layout.prototype._renderCoords = function() {
  var coords = this._getCoords(true);
  var children = this.children;
  this.children = [];
  this._render();
  this.children = children;
  return coords;
};

Layout.prototype.renderer = function(coords) {
  var self = this;

  // The coordinates of the layout element
  var width = coords.xl - coords.xi
    , height = coords.yl - coords.yi
    , xi = coords.xi
    , yi = coords.yi;

  // The current row offset in cells (which row are we on?)
  var rowOffset = 0;

  // The index of the first child in the row
  var rowIndex = 0;
  var lastRowIndex = 0;

  // Figure out the highest width child
  if (this.options.layout === 'grid') {
    var highWidth = this.children.reduce(function(out, el) {
      out = Math.max(out, el.width);
      return out;
    }, 0);
  }

  return function iterator(el, i) {
    // Make our children shrinkable. If they don't have a height, for
    // example, calculate it for them.
    el.shrink = true;

    // Find the previous rendered child's coordinates
    var last = self.getLast(i);

    // If there is no previously rendered element, we are on the first child.
    if (!last) {
      el.position.left = 0;
      el.position.top = 0;
    } else {
      // Otherwise, figure out where to place this child. We'll start by
      // setting it's `left`/`x` coordinate to right after the previous
      // rendered element. This child will end up directly to the right of it.
      el.position.left = last.lpos.xl - xi;

      // Make sure the position matches the highest width element
      if (self.options.layout === 'grid') {
        // Compensate with width:
        // el.position.width = el.width + (highWidth - el.width);
        // Compensate with position:
        el.position.left += highWidth - (last.lpos.xl - last.lpos.xi);
      }

      // If our child does not overlap the right side of the Layout, set it's
      // `top`/`y` to the current `rowOffset` (the coordinate for the current
      // row).
      if (el.position.left + el.width <= width) {
        el.position.top = rowOffset;
      } else {
        // Otherwise we need to start a new row and calculate a new
        // `rowOffset` and `rowIndex` (the index of the child on the current
        // row).
        rowOffset += self.children.slice(rowIndex, i).reduce(function(out, el) {
          if (!self.isRendered(el)) return out;
          out = Math.max(out, el.lpos.yl - el.lpos.yi);
          return out;
        }, 0);
        lastRowIndex = rowIndex;
        rowIndex = i;
        el.position.left = 0;
        el.position.top = rowOffset;
      }
    }

    // Make sure the elements on lower rows graviatate up as much as possible
    if (self.options.layout === 'inline') {
      var above = null;
      var abovea = Infinity;
      for (var j = lastRowIndex; j < rowIndex; j++) {
        var l = self.children[j];
        if (!self.isRendered(l)) continue;
        var abs = Math.abs(el.position.left - (l.lpos.xi - xi));
        // if (abs < abovea && (l.lpos.xl - l.lpos.xi) <= el.width) {
        if (abs < abovea) {
          above = l;
          abovea = abs;
        }
      }
      if (above) {
        el.position.top = above.lpos.yl - yi;
      }
    }

    // If our child overflows the Layout, do not render it!
    // Disable this feature for now.
    if (el.position.top + el.height > height) {
      // Returning false tells blessed to ignore this child.
      // return false;
    }
  };
};

Layout.prototype.render = function() {
  this._emit('prerender');

  var coords = this._renderCoords();
  if (!coords) {
    delete this.lpos;
    return;
  }

  if (coords.xl - coords.xi <= 0) {
    coords.xl = Math.max(coords.xl, coords.xi);
    return;
  }

  if (coords.yl - coords.yi <= 0) {
    coords.yl = Math.max(coords.yl, coords.yi);
    return;
  }

  this.lpos = coords;

  if (this.border) coords.xi++, coords.xl--, coords.yi++, coords.yl--;
  if (this.tpadding) {
    coords.xi += this.padding.left, coords.xl -= this.padding.right;
    coords.yi += this.padding.top, coords.yl -= this.padding.bottom;
  }

  var iterator = this.renderer(coords);

  if (this.border) coords.xi--, coords.xl++, coords.yi--, coords.yl++;
  if (this.tpadding) {
    coords.xi -= this.padding.left, coords.xl += this.padding.right;
    coords.yi -= this.padding.top, coords.yl += this.padding.bottom;
  }

  this.children.forEach(function(el, i) {
    if (el.screen._ci !== -1) {
      el.index = el.screen._ci++;
    }
    var rendered = iterator(el, i);
    if (rendered === false) {
      delete el.lpos;
      return;
    }
    // if (el.screen._rendering) {
    //   el._rendering = true;
    // }
    el.render();
    // if (el.screen._rendering) {
    //   el._rendering = false;
    // }
  });

  this._emit('render', [coords]);

  return coords;
};

/**
 * Expose
 */

module.exports = Layout;
};
BundleModuleCode['term/widgets/tree']=function (module,exports){
/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR.
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2013-2015, Christopher Jeffrey and contributors
 **    $MODIFIED:    by sbosse (2017-2018)
 **    $REVESIO:     1.3.1
 **
 **    $INFO:
 **
 **    Tree Widget
 **
 **     Added:
 **       - 'preselect' event emission on node selection. The preselection
 **        event allows node children modificiation before screen rendering.
 **       - 'arrows', optional arrow buttons
 **
 **     Events Out: preselect(node), select(node), selected(node)
 **
 **     Node label text: node.name
 **     Node parent element: node.parent 
 **     Get path:
 **       var path=node.name;
 **       node=node.parent;
 **       while(node) 
 **         path=node.name+(node.name!='/'?'/':'')+path,
 **         node=node.parent;
 **
 **
 **    $ENDOFINFO
 */

var Comp = Require('com/compat');

var Node = Require('term/widgets/node');
var Box = Require('term/widgets/box');
var List = Require('term/widgets/list');
var Arrows = Require('term/widgets/arrows');

function Tree(options) {  
  var self=this,
      height=0;
  function strequal(str1,str2) {
        var i;
        var eq=true;
        if (str1.length != str2.length) return false;
        for(i=0;i<str1.length;i++) { if (str1.charAt(i)!=str2.charAt(i)) eq=false;}
        return eq;
  };

  if (!instanceOf(this,Node)) {
   return new Tree(options);
  }

  options = options || {};
  if (!options.style) options.style = {
      border: {
        fg: 'black'
      },
      focus: {
        border: {
          fg: 'red'
        }
      }
  };
  if (!options.border) options.border = {
      type:'line'
  };

  options.style.border._fg=options.style.border.fg; // save border style, can change
  options.bold = true;
  var self = this;
  this.options = options;
  this.data = {};
  this.nodeLines = [];
  this.lineNbr = 0;
  this.init=true;
  Box.call(this, options);

  var boxfocus=this.focus;
  
  if (options.height) {
    if (typeof options.height == 'number') height=options.height;
    else if (typeof options.height == 'string') {
        var perc=0;
        perc=parseInt(options.height);
        height=this.screen.height*perc/100;
    }
  }
  
  options.extended = options.extended || false;
  options.keys = options.keys || ['space','enter'];

  options.template = options.template || {};
  options.template.extend = options.template.extend || ' [+]';
  options.template.retract = options.template.retract || ' [-]';
  options.template.lines = options.template.lines || true;

  this.listOptions = {
          height: 0,
          top: 1,
          width: 0,
          left: 1,    
          selectedFg: 'white',
          selectedBg: 'blue',
          fg: "green",
          keys: true ,
          mouse:true ,
          selectoffset:height>8?4:2,
          };
          
  if (!options.scrollbar) this.listOptions.scrollbar = 
  {
      ch: ' ',
      track: {
        bg: 'yellow'
      },
      style: {
        fg: 'cyan',
        inverse: true
      }
  }; 
  else if (options.scrollbar != null) 
    this.listOptions.scrollbar = options.scrollbar;

  /*
  ** Tree content
  */
  this.list = new List(this.listOptions);

  
  this.list.key(options.keys,function(){
    var ind = this.getItemIndex(this.selected);
    var line = self.nodeLines[ind];
    self.emit('preselect',line);
    self.nodeLines[ind].extended = !self.nodeLines[ind].extended;
    self.setData(self.data);
    self.screen.render();
    self.emit('select',line);
  });
  this.list.on('element click',function(w,ev){
    var ind = this.getItemIndex(this.selected);
    //console.log(ind)
    var line = self.nodeLines[ind];
    var pos = ev.x-w.aleft;
    var item = this.ritems[ind];
    if (item) {
      self.emit('preselect',line);
      var len1 = self.options.template.extend.length;
      var len2 = self.options.template.retract.length;
      var roi1 = item.indexOf(self.options.template.extend);
      var roi2 = item.indexOf(self.options.template.retract);
      if ((pos > roi1 && pos < roi1+len1) ||
          (pos > roi2 && pos < roi2+len1)) {
        self.nodeLines[ind].extended = !self.nodeLines[ind].extended;
        self.setData(self.data);
        self.screen.render();
        self.emit('select',line);    
      }
    }
  });

  if (options.arrows) 
    Arrows(
      self,
      options,
      function () { self.list.select(self.list.selected - 2); self.screen.render()},
      function () { self.list.select(self.list.selected + 2); self.screen.render()}
    );

  this.on('mousedown', function(data) {
    self.focus();
    Box.prototype.render.call(self);
    self.screen.render();
  });

  // Propagate selection events of list items ...
  this.list.on('selected', function() {
    var ind = this.getItemIndex(this.selected);
    var line = self.nodeLines[ind];
    self.emit('selected',line);
  });
  this.append(this.list);
  
}

Tree.prototype.walk = function (node,treeDepth) {

  var lines = [];

  if (!node.parent)
    node.parent = null;

  if (treeDepth == '' && node.name) {
    this.lineNbr = 0;
    this.nodeLines[this.lineNbr++] = node;
    lines.push(node.name);
    treeDepth = ' ';
  }

  node.depth = treeDepth.length-1;

  if (node.children && node.extended) {

    var i = 0;
    
    if (typeof node.children == 'function')
      node.childrenContent = node.children(node);
    
    if(!node.childrenContent)
      node.childrenContent = node.children;

    for (var child in node.childrenContent) {
      
      if(!node.childrenContent[child].name)
        node.childrenContent[child].name = child;

      var childIndex = child;
      child = node.childrenContent[child];
      child.parent = node;
      child.position = i++;
      
      if(typeof child.extended == 'undefined')
        child.extended = this.options.extended;
      
      if (typeof child.children == 'function')
        child.childrenContent = child.children(child);
      else
        child.childrenContent = child.children;
      
      var isLastChild = child.position == Object.keys(child.parent.childrenContent).length - 1;
      var tree;
      var suffix = '';
      if (isLastChild) {
        tree = '└';
      } else {
        tree = '├';
      }
      if (!child.childrenContent || Object.keys(child.childrenContent).length == 0){
        tree += '─';
      } else if(child.extended) {
        tree += '┬';
        suffix = this.options.template.retract;
      } else {
        tree += '─';
        suffix = this.options.template.extend;
      }

      if (!this.options.template.lines){
        tree = '|-';
      }

      lines.push(treeDepth + tree + child.name + suffix);

      this.nodeLines[this.lineNbr++] = child;

      var parentTree;
      if (isLastChild || !this.options.template.lines){
        parentTree = treeDepth+" ";
      } else {
        parentTree = treeDepth+"│";
      }
      lines = lines.concat(this.walk(child, parentTree));
    }
  }
  return lines;
}

Tree.prototype.focus = function(){
  this.list.focus();
}


Tree.prototype.render = function() {
//console.log(this.style.border._fg)
  if((this.screen.focused == this.list || this.screen.focused == this)) {
    // List is focussed, propagate style changes to the Box element.
    if (this.style.focus.border.fg) this.style.border.fg=this.style.focus.border.fg;  
  } else if ((this.screen.focused != this.list && this.screen.focused != this)) {
    // List is not focussed, restore style changes of the Box element.
    if (this.style.border._fg) this.style.border.fg=this.style.border._fg;
  }

  this.list.width = this.width-3;
  this.list.height = this.height-3;
  Box.prototype.render.call(this);
}

Tree.prototype.setData = function(data) {
  var formatted = [];
  formatted = this.walk(data,'');
  this.data = data;
  if (this.init) {
    this.screen.render();
    this.init=false;
  };
  this.list.setItems(formatted);
  this.screen.render();
}

//Tree.prototype.__proto__ = Box.prototype;
inheritPrototype(Tree,Box);

Tree.prototype.type = 'tree';

module.exports = Tree
};
FilesEmbedded['term/def/xterm']=function (format){return Base64.decodeBuf('GgEcACYADwCdAWwFeHRlcm18WDExIHRlcm1pbmFsIGVtdWxhdG9yAAABAAABAAAAAQAAAAABAQAAAAAAAAABAAABAAABAAAAAAAAAAABUAAIABgA//////////////////////////8IAEAAAAAEAAYACAAZAB4AJgAqAC4A//85AEoATABQAFcA//9ZAGYA//9qAG4AeAB8AP////+AAIQAiQCOAP////+XAJwA//+hAKYAqwCwALkAvQDEAP//zQDSANgA3gD////////wAP///////wIB//8GAf///////wgB//8NAf//////////EQEVARsBHwEjAScBLQEzATkBPwFFAUkB//9OAf//UgFXAVwBYAFnAf//bgFyAXoB/////////////////////////////4IBiwH/////lAGdAaYBrwG4AcEBygHTAdwB5QH////////uAfIB9wH///wB/wH/////EQIUAh8CIgIkAicCeQL//3wC////////////////fgL//////////4IC//+3Av////+7AsEC/////////////////////////////8cCywL//////////////////////////////////////////////////////////////////88C/////9YC///////////dAuQC6wL/////8gL///kC////////AAP/////////////BwMNAxMDGgMhAygDLwM3Az8DRwNPA1cDXwNnA28DdgN9A4QDiwOTA5sDowOrA7MDuwPDA8sD0gPZA+AD5wPvA/cD/wMHBA8EFwQfBCcELgQ1BDwEQwRLBFMEWwRjBGsEcwR7BIMEigSRBJgE/////////////////////////////////////////////////////////////50EqAStBLUEuQT//////////8IECAX///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////9OBf///////1IFXAX/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////ZgVpBRtbWgAHAA0AG1slaSVwMSVkOyVwMiVkcgAbWzNnABtbSBtbMkoAG1tLABtbSgAbWyVpJXAxJWRHABtbJWklcDElZDslcDIlZEgACgAbW0gAG1s/MjVsAAgAG1s/MTJsG1s/MjVoABtbQwAbW0EAG1s/MTI7MjVoABtbUAAbW00AGygwABtbNW0AG1sxbQAbWz8xMDQ5aAAbWzRoABtbOG0AG1s3bQAbWzdtABtbNG0AG1slcDElZFgAGyhCABsoQhtbbQAbWz8xMDQ5bAAbWzRsABtbMjdtABtbMjRtABtbPzVoJDwxMDAvPhtbPzVsABtbIXAbWz8zOzRsG1s0bBs+ABtbTAAIABtbM34AG09CABtPUAAbWzIxfgAbT1EAG09SABtPUwAbWzE1fgAbWzE3fgAbWzE4fgAbWzE5fgAbWzIwfgAbT0gAG1syfgAbT0QAG1s2fgAbWzV+ABtPQwAbWzE7MkIAG1sxOzJBABtPQQAbWz8xbBs+ABtbPzFoGz0AG1s/MTAzNGwAG1s/MTAzNGgAG1slcDElZFAAG1slcDElZE0AG1slcDElZEIAG1slcDElZEAAG1slcDElZFMAG1slcDElZEwAG1slcDElZEQAG1slcDElZEMAG1slcDElZFQAG1slcDElZEEAG1tpABtbNGkAG1s1aQAbYwAbWyFwG1s/Mzs0bBtbNGwbPgAbOAAbWyVpJXAxJWRkABs3AAoAG00AJT8lcDkldBsoMCVlGyhCJTsbWzAlPyVwNiV0OzElOyU/JXAyJXQ7NCU7JT8lcDElcDMlfCV0OzclOyU/JXA0JXQ7NSU7JT8lcDcldDs4JTttABtIAAkAG09FAGBgYWFmZmdnaWlqamtrbGxtbW5ub29wcHFxcnJzc3R0dXV2dnd3eHh5eXp6e3t8fH19fn4AG1taABtbPzdoABtbPzdsABtPRgAbT00AG1szOzJ+ABtbMTsyRgAbWzE7MkgAG1syOzJ+ABtbMTsyRAAbWzY7Mn4AG1s1OzJ+ABtbMTsyQwAbWzIzfgAbWzI0fgAbWzE7MlAAG1sxOzJRABtbMTsyUgAbWzE7MlMAG1sxNTsyfgAbWzE3OzJ+ABtbMTg7Mn4AG1sxOTsyfgAbWzIwOzJ+ABtbMjE7Mn4AG1syMzsyfgAbWzI0OzJ+ABtbMTs1UAAbWzE7NVEAG1sxOzVSABtbMTs1UwAbWzE1OzV+ABtbMTc7NX4AG1sxODs1fgAbWzE5OzV+ABtbMjA7NX4AG1syMTs1fgAbWzIzOzV+ABtbMjQ7NX4AG1sxOzZQABtbMTs2UQAbWzE7NlIAG1sxOzZTABtbMTU7Nn4AG1sxNzs2fgAbWzE4OzZ+ABtbMTk7Nn4AG1syMDs2fgAbWzIxOzZ+ABtbMjM7Nn4AG1syNDs2fgAbWzE7M1AAG1sxOzNRABtbMTszUgAbWzE7M1MAG1sxNTszfgAbWzE3OzN+ABtbMTg7M34AG1sxOTszfgAbWzIwOzN+ABtbMjE7M34AG1syMzszfgAbWzI0OzN+ABtbMTs0UAAbWzE7NFEAG1sxOzRSABtbMUsAG1slaSVkOyVkUgAbWzZuABtbPzE7MmMAG1tjABtbMzk7NDltABtbMyU/JXAxJXsxfSU9JXQ0JWUlcDElezN9JT0ldDYlZSVwMSV7NH0lPSV0MSVlJXAxJXs2fSU9JXQzJWUlcDElZCU7bQAbWzQlPyVwMSV7MX0lPSV0NCVlJXAxJXszfSU9JXQ2JWUlcDElezR9JT0ldDElZSVwMSV7Nn0lPSV0MyVlJXAxJWQlO20AG1tNABtbMyVwMSVkbQAbWzQlcDElZG0AG2wAG20AAgAAAD4AfgDvAgEBAAAHABMAGQArADEAOwBCAEkAUABXAF4AZQBsAHMAegCBAIgAjwCWAJ0ApACrALIAuQDAAMcAzgDVANwA4wDqAPEA+AD/AAYBDQEUARsBIgEpATABNwE+AUUBTAFTAVoBYQFoAW8BdgF9AYQBiwGSAZkBoAH//////////wAAAwAGAAkADAAPABIAFQAYAB0AIgAnACwAMQA1ADoAPwBEAEkATgBUAFoAYABmAGwAcgB4AH4AhACKAI8AlACZAJ4AowCpAK8AtQC7AMEAxwDNANMA2QDfAOUA6wDxAPcA/QADAQkBDwEVARsBHwEkASkBLgEzATgBPAFAAUQBG10xMTIHABtdMTI7JXAxJXMHABtbMztKABtdNTI7JXAxJXM7JXAyJXMHABtbMiBxABtbJXAxJWQgcQAbWzM7M34AG1szOzR+ABtbMzs1fgAbWzM7Nn4AG1szOzd+ABtbMTsyQgAbWzE7M0IAG1sxOzRCABtbMTs1QgAbWzE7NkIAG1sxOzdCABtbMTszRgAbWzE7NEYAG1sxOzVGABtbMTs2RgAbWzE7N0YAG1sxOzNIABtbMTs0SAAbWzE7NUgAG1sxOzZIABtbMTs3SAAbWzI7M34AG1syOzR+ABtbMjs1fgAbWzI7Nn4AG1syOzd+ABtbMTszRAAbWzE7NEQAG1sxOzVEABtbMTs2RAAbWzE7N0QAG1s2OzN+ABtbNjs0fgAbWzY7NX4AG1s2OzZ+ABtbNjs3fgAbWzU7M34AG1s1OzR+ABtbNTs1fgAbWzU7Nn4AG1s1Ozd+ABtbMTszQwAbWzE7NEMAG1sxOzVDABtbMTs2QwAbWzE7N0MAG1sxOzJBABtbMTszQQAbWzE7NEEAG1sxOzVBABtbMTs2QQAbWzE7N0EAQVgAWFQAQ3IAQ3MARTMATXMAU2UAU3MAa0RDMwBrREM0AGtEQzUAa0RDNgBrREM3AGtETgBrRE4zAGtETjQAa0RONQBrRE42AGtETjcAa0VORDMAa0VORDQAa0VORDUAa0VORDYAa0VORDcAa0hPTTMAa0hPTTQAa0hPTTUAa0hPTTYAa0hPTTcAa0lDMwBrSUM0AGtJQzUAa0lDNgBrSUM3AGtMRlQzAGtMRlQ0AGtMRlQ1AGtMRlQ2AGtMRlQ3AGtOWFQzAGtOWFQ0AGtOWFQ1AGtOWFQ2AGtOWFQ3AGtQUlYzAGtQUlY0AGtQUlY1AGtQUlY2AGtQUlY3AGtSSVQzAGtSSVQ0AGtSSVQ1AGtSSVQ2AGtSSVQ3AGtVUABrVVAzAGtVUDQAa1VQNQBrVVA2AGtVUDcAa2EyAGtiMQBrYjMAa2MyAA==')};
FilesEmbedded['term/def/windows-ansi']=function (format){return Base64.decodeBuf('GgEoACYAEAB9AUQCYW5zaXxhbnNpL3BjLXRlcm0gY29tcGF0aWJsZSB3aXRoIGNvbG9yAAABAAAAAAAAAAAAAAABAQAAAAAAAAABAAAAAAAAAAAAAAAAAAABUAAIABgA//////////////////////////8IAEAAAwAAAAQABgD//wgADQAUABgAHAD//ycAOAA8AP//QAD/////RAD//0gA//9MAFAA/////1QAWgBfAP//////////ZAD//2kAbgBzAHgAgQCHAP///////48AkwD/////////////////////lwD//5sA/////////////50A/////////////////////////////////////6EApQD//6kA////////rQD///////+xAP///////////////////////////////////////7UA//+6AMMAzADVAN4A5wDwAPkAAgELAf//////////FAEZAR4B/////////////zIB//89Af//PwGVAf//mAH/////////////////////////////nAH//9sB////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////3wH/////////////////////////////////////////////////////////////5AHvAfQBBwILAv//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////FAIeAv///////ygCLAIwAjQC/////////////////////////////zgCPgIbW1oABwANABtbMmcAG1tIG1tKABtbSwAbW0oAG1slaSVwMSVkRwAbWyVpJXAxJWQ7JXAyJWRIABtbQgAbW0gAG1tEABtbQwAbW0EAG1tQABtbTQAbWzExbQAbWzVtABtbMW0AG1s4bQAbWzdtABtbN20AG1s0bQAbWyVwMSVkWAAbWzEwbQAbWzA7MTBtABtbbQAbW20AG1tMAAgAG1tCABtbSAAbW0wAG1tEABtbQwAbW0EADRtbUwAbWyVwMSVkUAAbWyVwMSVkTQAbWyVwMSVkQgAbWyVwMSVkQAAbWyVwMSVkUwAbWyVwMSVkTAAbWyVwMSVkRAAbWyVwMSVkQwAbWyVwMSVkVAAbWyVwMSVkQQAbWzRpABtbNWkAJXAxJWMbWyVwMiV7MX0lLSVkYgAbWyVpJXAxJWRkAAoAG1swOzEwJT8lcDEldDs3JTslPyVwMiV0OzQlOyU/JXAzJXQ7NyU7JT8lcDQldDs1JTslPyVwNiV0OzElOyU/JXA3JXQ7OCU7JT8lcDkldDsxMSU7bQAbSAAbW0kAKxAsES0YLhkw22AEYbGm+C3xaLBq2Wu/bNptwG7Fb35wxHHEcsRzX3TDdbR2wXfCeLN583rye+N82H2cfv4AG1taABtbMUsAG1slaSVkOyVkUgAbWzZuABtbPyVbOzAxMjM0NTY3ODldYwAbW2MAG1szOTs0OW0AG1szJXAxJWRtABtbNCVwMSVkbQAbKEIAGylCABsqQgAbK0IAG1sxMW0AG1sxMG0AAQAAAAAAAQADAAEAAABBWAA=')};

var Base64=Require('os/base64');
module.exports=Require('/home/sbosse/proj/jam/js/top/jamsh.js');