module.exports = function(GedcomX){

  /**
   * Comment on a discussion
   *
   * @class Comment
   * @extends ExtensibleData
   * @param {Object} [json]
   */
  var Comment = function(json){

    // Protect against forgetting the new keyword when calling the constructor
    if(!(this instanceof Comment)){
      return new Comment(json);
    }

    // If the given object is already an instance then just return it. DON'T copy it.
    if(Comment.isInstance(json)){
      return json;
    }

    this.init(json);
  };

  Comment.prototype = Object.create(GedcomX.ExtensibleData.prototype);

  Comment._gedxClass = Comment.prototype._gedxClass = 'GedcomX.Comment';

  Comment.jsonProps = [
    'text',
    'created',
    'contributor'
  ];

  /**
   * Check whether the given object is an instance of this class.
   *
   * @memberof Comment
   * @static
   * @param {Object} obj
   * @returns {Boolean}
   */
  Comment.isInstance = function(obj){
    return GedcomX.utils.isInstance(obj, this._gedxClass);
  };

  /**
   * Initialize from JSON
   *
   * @memberof Comment
   * @param {Object}
   * @return Comment this
   */
  Comment.prototype.init = function(json){

    GedcomX.ExtensibleData.prototype.init.call(this, json);

    if(json){
      this.setText(json.text);
      this.setCreated(json.created);
      this.setContributor(json.contributor);
    }
    return this;
  };

  /**
   * Get the text
   *
   * @memberof Comment
   * @returns {String} text
   */
  Comment.prototype.getText = function(){
    return this.text;
  };

  /**
   * Set the text
   *
   * @memberof Comment
   * @param {String} text
   * @returns {Comment} this
   */
  Comment.prototype.setText = function(text){
    this.text = text;
    return this;
  };

  /**
   * Get the created
   *
   * @memberof Comment
   * @returns {Date} created
   */
  Comment.prototype.getCreated = function(){
    return this.created;
  };

  /**
   * Set the created
   *
   * @memberof Comment
   * @param {Date|Integer} created
   * @returns {Comment} this
   */
  Comment.prototype.setCreated = function(created){
    if(created){
      this.created = new Date(created);
    }
    return this;
  };

  /**
   * Get the contributor
   *
   * @memberof Comment
   * @returns {ResourceReference} contributor
   */
  Comment.prototype.getContributor = function(){
    return this.contributor;
  };

  /**
   * Set the contributor
   *
   * @memberof Comment
   * @param {ResourceReference} contributor
   * @returns {Comment} this
   */
  Comment.prototype.setContributor = function(contributor){
    if(contributor){
      this.contributor = GedcomX.ResourceReference(contributor);
    }
    return this;
  };

  /**
   * Export the object as JSON
   *
   * @memberof Comment
   * @return {Object} JSON object
   */
  Comment.prototype.toJSON = function(){
    return this._toJSON(GedcomX.ExtensibleData, Comment.jsonProps);
  };

  GedcomX.Comment = Comment;

};


// (object, object) => object
function instance(state, methods) {
    let api = {};
    Object.keys(methods).forEach(key => {
        api[key] = methods[key].bind(null, state);
    });
    return api;
}

// any => boolean
function isVoid(value) {
    return value === undefined || value === null;
}

// (string, number) => object
function syntaxError(message, pos) {
    let err = Error(message);
    err.name = "SyntaxError";
    err.pos = pos;
    return err;
}

module.exports = {
    instance,
    isVoid,
    syntaxError
};


#!/usr/bin/env node

/**
 * Watch for changes on all directories under a given directory.
 *
 * @origin https://github.com/lighterio/lighter-common/common/fs/deep-watch.js
 * @version 0.0.4
 * @import event/emitter
 */

var fork = require('child_process').fork
var fs = require('fs')
var Emitter = require('lighter-emitter')
var dirname = require('path').dirname

/**
 * Fork a child process, and return an emitter that fires "change" events.
 */
var deepWatch = module.exports = function (dir, options) {
  options = options || {}
  options.dir = dir || process.cwd()
  var watcher = new Emitter()
  var child = fork(__filename, [JSON.stringify(options)], {
    silent: true
  })
  // When a change occurs, make sure the new version of the file gets loaded.
  child.on('message', function (path) {
    delete require.cache[path]
    watcher.emit('change', path)
  })
  return watcher
}

// Remember options and watched files.
var dir
var ignoreDir = /\/(logs?|data|xcuserdata|\.[^\/]+)$/
var ignoreFile = /\/(\.subl[\w\d]+\.tmp|[^\/]+\.swp)$/
var maxFsWatches
var maxListSize
var checkInterval
var notifyInterval
var okToNotifyAfter
var started = Date.now()
var map = {}
var list = []

/**
 * If this is being called directly, start watching.
 */
if (process.mainModule === module) {
  var options = JSON.parse(process.argv[2])
  dir = options.dir.replace(/\\/g, '/')
  ignoreDir = options.ignoreDir || ignoreDir; // Don't watch logs or leading-dot directories.
  maxFsWatches = options.maxFsWatches || 1e2; // Only call fs.watch on up to 100 files.
  maxListSize = options.maxListSize || 1e4; // Only set up 10K paths for periodic checks.
  fsWatchDelay = options.fsWatchDelay || 1e3; // Wait 1 second before starting watches.
  checkInterval = options.checkInterval || 1e2; // Check a modified date every 100ms.
  notifyInterval = options.notifyInterval || 1e3; // Don't notify more than once a second.
  okToNotifyAfter = Date.now() + notifyInterval; // Remember when it's ok to notify.
  ignoreFile = options.ignoreFile || ignoreFile; // Don't send changes for swap files, etc.
  ignoreDir = new RegExp(ignoreDir)
  ignoreFile = new RegExp(ignoreFile)
  watch(dir)
  if (checkInterval) {
    setInterval(checkDir, checkInterval)
  }
  setTimeout(startWatches, fsWatchDelay)
}

/**
 * Recurse to find directories we can watch.
 */
function watch (dir) {
  if (!ignoreDir.test(dir) && !map[dir]) {
    fs.lstat(dir, function (e, stat) {
      if (!e) {
        if (stat.isSymbolicLink()) {
          var source = dir
          fs.readlink(source, function (e, link) {
            if (!e) {
              var dest = link
              if (dest[0] !== '/') {
                while (dest.substr(0, 3) === '../') {
                  dest = dest.substr(3)
                  source = source.replace(/\/[^\/]+$/, '')
                }
                if (dest.substr(0, 2) === './') {
                  dest = dest.substr(2)
                }
                dest = source + '/' + dest
              }
              watch(dest)
            }
          })
        } else if (stat.isDirectory()) {
          addDir(dir, stat)
        } else {
          dir = dirname(dir)
          map[dir] = Math.max(map[dir], stat.mtime.getTime())
        }
      }
    })
  }
}

/**
 * Add a watchable directory to the map and list of directories we're watching.
 */
function addDir (dir, stat) {
  var mtime = stat.mtime.getTime()
  if (!map[dir] && list.length <= maxListSize) {
    map[dir] = mtime
    list.push(dir)
    clearTimeout(sortList.timer)
    sortList.timer = setTimeout(sortList, checkInterval)
    fs.readdir(dir, function (e, files) {
      if (!e) {
        files.forEach(function (file) {
          watch(dir + '/' + file)
        })
      }
    })
  }
}

/**
 * Sort the list of watched files by age.
 */
function sortList () {
  list.sort(function (a, b) {
    return map[a] > map[b] ? -1 : 1
  })
}

/**
 * Iterate over the age-prioritized list, and start fs watches.
 */
function startWatches () {
  list.forEach(function (dir, i) {
    if (i < maxFsWatches) {
      try {
        fs.watch(dir, function (op, file) {
          notify(dir + '/' + file)
        })
      } catch (e) {
        // fs.watch is known to be unstable.
      }
    }
  })
}

var i = 0
var indexes = [
  0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
  0, 1, 2, 3, 4, 10, 11, 12, 13, 14,
  0, 1, 2, 3, 4, 15, 16, 17, 18, 19,
  0, 1, 2, 3, 4, 20, 21, 22, 23, 24,
  0, 1, 2, 3, 4, 25, 26, 27, 28, 29
]

/**
 * Check a directory for changes.
 */
function checkDir () {
  var n = indexes[i]
  if (i > 44) {
    indexes[i] = (indexes[i] + 5) % list.length
  }
  i = (i + 1) % indexes.length
  var dir = list[n]
  if (dir) {
    fs.stat(dir, function (e, stat) {
      if (!e && (stat.mtime > okToNotifyAfter)) {
        fs.readdir(dir, function (e, files) {
          if (!e) {
            files.forEach(function (file) {
              var path = dir + '/' + file
              fs.stat(path, function (e, stat) {
                if (!e && (stat.mtime > okToNotifyAfter)) {
                  notify(path)
                }
              })
            })
          }
        })
      }
    })
  }
}

/**
 * Notify the master process that something changed.
 */
function notify (path) {
  var now = Date.now()
  if ((now > okToNotifyAfter) && !ignoreFile.test(path)) {
    process.send(path)
    okToNotifyAfter = now + notifyInterval
    sortList()
  }
}


/*
# Graceful

Encapsulates everything needed to shut down a worker process without interrupting
anything important, even active requests on an http server.

There are three major ways to interact with an instance of this class:

1. Kick off shutdown by calling `shutdown()` with or without the error object that
initiated the shutdown.
2. Register for notification on shutdown with `on('shutdown', fn)`. This is a good time
to close all open resources, do last bits of work.
3. Register a 'check' function with `addCheck(fn)` to delay shutdown if shutdown work
is still happening. Return true if we're okay to stop the process, false if work is still
going on. By default you only have five seconds before the process will be terminated even
if you're not ready.

*/
'use strict';

var cluster = require('cluster');
var EventEmitter = require('events').EventEmitter;
var util = require('util');

var core = require('thehelp-core');
var logShim = require('thehelp-log-shim');

var localUtil = require('./util');

/*
The `constructor` has no required parameters. Optional parameters:

+ `pollInterval` - how often to attempt to take down process by first checking all
registered 'ready to stop process' check functions
+ `timeout` - how long to wait for the 'ready to stop process' checks before taking the
process down forcefully
+ `messenger` - a `function(err, options, cb)` that gets the information supplied to the
`shutdown()` method. Defaults to `thehelp-last-ditch`.
+ `log` - a logger object: `info`, `warn` and `error` keys with the signature
`function(string)`. By default, whatever `thehelp-log-shim` gives us.

_Note: it's recommended to create an instance of this class just once per process, since
it listens for a number of process-level events. See `_setupListeners()` below._
*/
function Graceful(options) {
  /*jshint maxcomplexity: 9 */

  options = options || {};

  this.shuttingDown = false;

  this._checks = [];
  this._sending = false;

  this.pollInterval = options.pollInterval || 250;
  localUtil.verifyType('number', this, 'pollInterval');

  this.timeout = options.timeout || 5 * 1000;
  localUtil.verifyType('number', this, 'timeout');

  this.messenger = options.messenger || require('thehelp-last-ditch');
  localUtil.verifyType('function', this, 'messenger');

  this.log = options.log || logShim('thehelp-cluster:graceful');
  localUtil.verifyLog(this.log);

  var _this = this;
  this.addCheck(function areWeSending() {
    return _this._sending === false;
  });

  this._process = options._process || process;
  this._cluster = options._cluster || cluster;
  this._logPrefix = localUtil.getLogPrefix();
  this._setupListeners();

  if (Graceful.instance) {
    this.log.warn('More than one Graceful instance created in this process. ' +
      'There are now duplicate process-level wireups!');
  }
  Graceful.instance = this;
}

util.inherits(Graceful, EventEmitter);

module.exports = Graceful;

// Public methods
// ========

// A quick helper function, since other classes use `Graceful.instance` to find their
// reference to your created `Graceful` object; you don't have to keep the instance
// around. Only creates a new instance if no previous instance has been created in this
// process.
Graceful.start = function start(options) {
  if (Graceful.instance) {
    return Graceful.instance;
  }
  return new Graceful(options);
};

/*
`shutdown` is the key method for interacting with this class. When something goes
wrong, call this method with the error and any additional information (like the `url`
being serviced). The http server will be stopped, the error will be saved/sent via
`this.messenger` and we'll start the process of checking to see if we can take down the
process via `this._exit()`.

_Note: to be notified when this method is called, register for the 'shutdown' event
(`Graceful` is an `EventEmitter`):_

```
graceful.on('shutdown', function() {
  // do stuff to prepare for shutdown
})
```
*/
Graceful.prototype.shutdown = function shutdown(err, info) {
  if (!this.shuttingDown) {
    this.shuttingDown = true;
    this.error = err;

    this.log.warn(this._logPrefix + ' gracefully shutting down!');
    this._sendError(err, info);
    try {
      this.emit('shutdown');
    }
    catch (err) {
      this.log.error('Graceful: shutdown event handler threw: ' +
        core.breadcrumbs.toString(err));
    }
    this._exit();
  }
};

// `addCheck` allows you to provide a callback you can use to delay `process.exit()` until
// you are finished doing something. Return something truthy to signal your readiness for
// `process.exit()`.
Graceful.prototype.addCheck = function addCheck(check) {
  if (!check || typeof check !== 'function') {
    throw new Error('need to provide a function!');
  }
  this._checks.push(check);
};

// Helper methods
// ========

// `_sendError` uses `this.messenger` to save/send the error provided to `shutdown()`. It
// it sets `this._sending = true` so we won't take the process down before the call is
// complete.
Graceful.prototype._sendError = function _sendError(err, info) {
  var _this = this;

  if (err) {
    this._sending = true;

    this.messenger(err, info, function() {
      _this._sending = false;
    });
  }
};

// `_check` returns true if all registered check methods returned true.
Graceful.prototype._check = function _check() {
  if (!this._checks || !this._checks.length) {
    return true;
  }

  for (var i = 0, max = this._checks.length; i < max; i += 1) {
    var check = this._checks[i];

    try {
      if (!check()) {
        return false;
      }
    }
    catch (err) {
      this.log.error('Graceful: check function threw: ' + core.breadcrumbs.toString(err));
    }
  }

  return true;
};

// `_exit` _exits if `check()` returns true. Otherwise it sets an interval to continue
// trying. It also sets a timer - if we never get a successful `check()` call, we
// take down the process anyway.
Graceful.prototype._exit = function _exit() {
  var _this = this;

  this.log.info(this._logPrefix + ' calling all provided pre-exit check functions...');

  if (this._check()) {
    this._clearTimers();
    this._finalLog('info', this._logPrefix + ' passed all checks! Shutting down!');
  }
  else if (!this.interval) {
    this.interval = setInterval(function tryAgain() {
      _this._exit();
    }, this.pollInterval);

    this.timeout = setTimeout(function forceKill() {
      _this._clearTimers();
      _this._finalLog('warn', _this._logPrefix + ' checks took too long. ' +
        'Killing process now!');

    }, this.timeout);
  }
};

// `_clearTimers` properly gets rid of the timers created in `_exit()`
Graceful.prototype._clearTimers = function _clearTimers() {
  if (this.interval) {
    clearInterval(this.interval);
    this.interval = null;
  }
  if (this.timeout) {
    clearTimeout(this.timeout);
    this.timeout = null;
  }
};

/*
`_finalLog` makes a final log entry then waits until the next turn of the event loop to
call process exit, attempting to give the filesystem enough time to flush to disk.
*/
Graceful.prototype._finalLog = function _finalLog(type, message) {
  this.log[type](message);
  setTimeout(this._die.bind(this), 0);
};

// `_die` calls `process._exit()` with the right error code based on `this.error` (set in
// `shutdown()`).
Graceful.prototype._die = function _die() {
  var code = this.error ? this.error.exitCode || 1 : 0;
  this._process.exit(code);
};

// `_setupListeners` sets up some event wireups. We start the shutdown process when the
// process gets a 'SIGTERM' signal, or when the master worker disconnects. And we log
// on process exit.
Graceful.prototype._setupListeners = function _setupListeners() {
  var _this = this;
  var cluster = this._cluster;
  var process = this._process;

  process.on('SIGTERM', function gracefulShutdown() {
    _this.shutdown();
  });

  process.on('exit', function(code) {
    _this.log.warn(_this._logPrefix + ' about to exit with code ' + code);
  });

  if (cluster.worker) {
    cluster.worker.on('disconnect', function gracefulShutdown() {
      _this.shutdown();
    });
  }
};


/**
 * Keep track of mocked objects so they can all be unmocked.
 */
var mockedObjects = []

/**
 * Allow `mock-fs` to be loaded lazily and referenced.
 */
var mockFs

/**
 * Decorate an object with mocked properties.
 */
var mock = module.exports = function mock (object, mockObject) {
  var mocked = object._EXAM_MOCKED_ORIGINALS
  if (!mocked) {
    mocked = [{}, {}]
    Object.defineProperty(object, '_EXAM_MOCKED_ORIGINALS', {
      enumerable: false,
      value: mocked
    })
  }
  var pro = mocked[0]
  var own = mocked[1]
  for (var key in mockObject) {
    var index = object.hasOwnProperty(key) ? 1 : 0
    // Only remember one original, whether prototype or own.
    if (typeof pro[key] === 'undefined' && typeof own[key] === 'undefined') {
      mocked[index][key] = object[key]
    }
    object[key] = mockObject[key]
  }
  mockedObjects.push(object)
  return object
}

/**
 * Restore mocked properties to their mocked values.
 */
var unmock = mock.unmock = function unmock (object, keys) {
  // If called with no arguments, unmock all objects that have been mocked.
  if (!arguments.length) {
    mockedObjects.forEach(function (object) {
      unmock(object)
    })
    mockedObjects.length = 0
    unmock.fs()
    unmock.time()
    return
  }
  // If it's a single key, make it an array.
  if (typeof keys === 'string') {
    keys = [keys]
  }
  // If the object has original values for mocked properties, restore them.
  var mocked = object._EXAM_MOCKED_ORIGINALS
  if (mocked) {
    for (var index = 0; index < 2; index++) {
      var originals = keys || mocked[index]
      for (var key in originals) {
        if (index) {
          object[key] = originals[key]
        } else {
          delete object[key]
        }
      }
    }
    // If we weren't deleting specific keys, we no longer need the originals.
    if (!keys) {
      delete object._EXAM_MOCKED_ORIGINALS
    }
  }
  return object
}

/**
 * Decorate a mock function with chainable methods.
 */
function decorateFn (fn) {
  fn.returns = function (value) {
    fn._returns = value
    return fn
  }
  return fn
}

/**
 * Finish the execution of a mock function.
 */
function finishFn (fn) {
  return fn._returns
}

/**
 * Simple mock function: Ignore.
 */
mock.fn = mock.ignore = function () {
  var fn = function () {
    return finishFn(fn)
  }
  return decorateFn(fn)
}

/**
 * Simple mock function: Call counter.
 */
mock.count = function () {
  function fn () {
    fn.value++
    return finishFn(fn)
  }
  fn.value = 0
  return decorateFn(fn)
}

/**
 * Simple mock function: Argument setter.
 */
mock.set = function (index) {
  function fn () {
    fn.value = isNaN(index) ? arguments : arguments[index]
    return finishFn(fn)
  }
  fn.value = []
  return decorateFn(fn)
}

/**
 * Simple mock function: Argument pusher.
 */
mock.args = function (index) {
  function fn () {
    fn.value.push(isNaN(index) ? arguments : arguments[index])
    return finishFn(fn)
  }
  fn.value = []
  return decorateFn(fn)
}

/**
 * Simple mock function: String concatenator.
 */
mock.concat = function (delimiter) {
  delimiter = delimiter || ''
  function fn (data) {
    fn.value += (fn.value ? delimiter : '') + data
    return finishFn(fn)
  }
  fn.value = ''
  return decorateFn(fn)
}

/**
 * Simple mock function: Error thrower.
 */
mock.throw = function (message) {
  function fn () {
    var error = new Error(message)
    error.arguments = arguments
    fn.value.push(error)
    throw error
  }
  fn.value = []
  return decorateFn(fn)
}

// Make a reference to the real file system before mocking.
var fs = require('fs')

/**
 * Load `mock-fs` and create a temporary file system.
 */
mock.fs = function (config, newFs) {
  mockFs = require('mock-fs')
  var fs
  if (newFs) {
    fs = mockFs.fs(config)
  } else {
    mockFs(config)
    fs = require('fs')
  }
  return fs
}

/**
 * Load an array of paths, and return an object for `mock.fs`.
 */
mock.fs.load = function (paths) {
  var load = {}
  paths.forEach(function (path) {
    load[path] = fs.readFileSync(path)
  })
  return load
}

/**
 * Restore the real file system.
 */
unmock.fs = function () {
  if (mockFs) {
    mockFs.restore()
    mockFs = null
  }
}

/**
 * Make the os and cluster modules think there's just one CPU.
 */
mock.cpu = function (options) {
  options = options || {}
  if (typeof options.cpus === 'number') {
    options.cpus = unmock(require('os')).cpus().slice(0, options.cpus)
  }
  if (typeof options.hostname === 'string') {
    options.hostname = mock.fn().returns(options.hostname)
  }
  if (options.fork === false) {
    options.fork = mock.fn()
  }
  var modules = ['os', 'cluster']
  modules.forEach(function (lib) {
    lib = require(lib)
    unmock(lib)
    var mocks = {}
    for (var key in options) {
      if (typeof lib[key] === typeof options[key]) {
        mocks[key] = options[key]
      }
    }
    mock(lib, mocks)
  })
}

/**
 * Unmock the cluster and os modules.
 */
unmock.cpu = function () {
  var modules = ['os', 'cluster']
  modules.forEach(function (lib) {
    unmock(require(lib))
  })
}

// Make references to timers and the real `Date` object before mocking time.
var timers = require('timers')
timers.Date = Date

/**
 * Freeze time to a `Date` constructed with the given `value`.
 */
mock.time = function (value) {
  var date = new timers.Date(value)
  mock.time._CURRENT_TIME = date.getTime()
  mock(timers.Date, {now: MockDate.now})
  global.Date = MockDate
  global.setTimeout = getScheduler(false)
  global.setInterval = getScheduler(true)
  global.clearTimeout = getUnscheduler()
  global.clearInterval = getUnscheduler()
  return mock.time
}

/**
 * Construct a `MockDate` using a `Date` constructor value.
 */
function MockDate (value) {
  // A MockDate constructs an inner date and exposes its methods.
  var innerDate
  // If a value is specified, use it to construct a real date.
  if (arguments.length) {
    innerDate = new timers.Date(value)

  // If time isn't currently mocked, construct a real date for the real time.
  } else if (global.Date === timers.Date) {
    innerDate = new timers.Date()

  // If there's no value and time is mocked, use the current mock time.
  } else {
    innerDate = new timers.Date(mock.time._CURRENT_TIME)
  }
  Object.defineProperty(this, '_INNER_DATE', {
    enumerable: false,
    value: innerDate
  })
}

/**
 * Get the current mock time or real time in milliseconds.
 */
MockDate.now = function () {
  // If time has been unmocked, use the real Date object.
  if (mock.time._CURRENT_TIME === undefined) {
    return realNow()

  // Otherwise, use the mock time value.
  } else {
    return mock.time._CURRENT_TIME
  }
}

// `Date.parse` and `Date.UTC` are not relative to the current time.
MockDate.parse = timers.Date.parse
MockDate.UTC = timers.Date.UTC

// An instance of `MockDate` uses methods from its inner date.
var methods = ['getDate', 'getDay', 'getFullYear', 'getHours', 'getMilliseconds',
  'getMinutes', 'getMonth', 'getSeconds', 'getTime', 'getTimezoneOffset',
  'getUTCDate', 'getUTCDay', 'getUTCFullYear', 'getUTCHours',
  'getUTCMilliseconds', 'getUTCMinutes', 'getUTCMonth', 'getUTCSeconds',
  'getYear', 'setDate', 'setFullYear', 'setHours', 'setMilliseconds',
  'setMinutes', 'setMonth', 'setSeconds', 'setTime', 'setUTCDate',
  'setUTCFullYear', 'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes',
  'setUTCMonth', 'setUTCSeconds', 'setYear', 'toDateString', 'toGMTString',
  'toISOString', 'toJSON', 'toLocaleDateString', 'toLocaleString',
  'toLocaleTimeString', 'toString', 'toTimeString', 'toUTCString',
  'valueOf']
methods.forEach(function (name) {
  MockDate.prototype[name] = function () {
    return this._INNER_DATE[name].apply(this._INNER_DATE, arguments)
  }
})

/**
 * Add time to the frozen value.
 */
mock.time.add = function (time) {
  var p = /(\d+)\s*(s|seconds?)?(m|minutes?)?(h|hours?)?(d|days?)?\b/
  time = '' + time
  time.replace(p, function (match, n, s, m, h, d) {
    mock.time._CURRENT_TIME += +n * (s ? 1e3 : m ? 6e4 : h ? 36e5 : d ? 864e5 : 1)
  })
  runSchedules()
}

/**
 * Set `mock.time` to move forward at a faster or slower speed than
 * real time, based on whether `speed` is greater or less than 1.
 */
mock.time.speed = function (speed) {
  // By default, move ahead a thousand times faster than real time.
  mock.time._SPEED = speed || 1e3
  moveTime()
}

/**
 * If mock time is moving forward, schedule a time check.
 */
function moveTime () {
  if (mock.time._SPEED) {
    // Remember what the real time was before updating.
    mock.time._PREVIOUS_TIME = realNow()
    // Set time to be incremented.
    setTimeout(function () {
      var now = realNow()
      var elapsed = now - mock.time._PREVIOUS_TIME
      if (elapsed) {
        var add = elapsed * mock.time._SPEED
        mock.time.add(add)
      }
      moveTime()
    }, 0)
  }
}

/**
 * Return what `Date.now()` would return if it wasn't mocked.
 */
function realNow () {
  // We don't replace the `Date` constructor, so this is safe from stack overflow.
  return (new timers.Date()).getTime()
}

/**
 * `schedules` are the result of mocked `setTimeout` and `setInterval` calls.
 */
var schedules = []
schedules.id = 0

/**
 * The `getScheduler` returns mocks for `setTimeout` and `setInterval`.
 */
function getScheduler (isInterval) {
  return function (fn, time) {
    schedules.push({
      id: ++schedules.id,
      fn: fn,
      time: Date.now() + time,
      interval: isInterval ? time : false
    })
  }
}

/**
 * The `getUnscheduler` returns mocks for `clearTimeout` and `clearInterval`.
 */
function getUnscheduler () {
  // TODO: Create a map of IDs if the schedules array gets large.
  return function (id) {
    for (var i = 0, l = schedules.length; i < l; i++) {
      var schedule = schedules[i]
      if (schedule.id === id) {
        schedules.splice(i, 1)
        break
      }
    }
  }
}

/**
 * When `mock.time.add` is called, run schedules whose time has come.
 */
function runSchedules () {
  // Sort by descending time order.
  schedules.sort(function (a, b) {
    return b.time - a.time
  })
  // Track the soonest interval run time, in case we're already there.
  var minNewTime = Number.MAX_VALUE
  // Iterate, from the end until we reach the current mock time.
  var i = schedules.length - 1
  var schedule = schedules[i]
  while (schedule && (schedule.time <= mock.time._CURRENT_TIME)) {
    schedule.fn()
    // setTimeout schedules can be deleted.
    if (!schedule.interval) {
      schedules.splice(i, 1)

    // setInterval schedules should schedule the next run.
    } else {
      schedule.time += schedule.interval
      minNewTime = Math.min(minNewTime, schedule.time)
    }
    schedule = schedules[--i]
  }
  // If an interval schedule is in the past, catch it up.
  if (minNewTime <= mock.time._CURRENT_TIME) {
    process.nextTick(runSchedules)
  }
}

/**
 * Restore the real `Date` object and time-related functions.
 */
unmock.time = function () {
  delete mock.time._CURRENT_TIME
  delete mock.time._SPEED
  global.Date = timers.Date
  global.setTimeout = timers.setTimeout
  global.setInterval = timers.setInterval
  global.clearTimeout = timers.clearTimeout
  global.clearInterval = timers.clearInterval
  schedules.length = 0
  unmock(timers.Date, 'now')
}

/**
 * Expose `mock-fs` methods as exam/lib/mock methods.a
 */
methods = ['directory', 'file', 'symlink']
methods.forEach(function (method) {
  mock[method] = function () {
    var mockFs = require('mock-fs')
    return mockFs[method].apply(mockFs, arguments)
  }
})


// # Startup
// A class to encompass both dual master/worker cluster launch and installation of a
// top-level domain for the process.

'use strict';

var cluster = require('cluster');
var domain = require('domain');

var StatsD = require('node-statsd');

var core = require('thehelp-core');
var logShim = require('thehelp-log-shim');

var Graceful = require('./graceful');
var util = require('./util');

/*
The `constructor` requires only one parameter `worker`, a callback which
starts a worker process. Optional parameters:

+ `masterOptions` - options to be passed to the `Master` class on construction in the
default master start callback
+ `master` - a callback to start the cluster's master process
+ `errorHandler` - an alternate handler for a top-level error. Prevents `messenger` from
being called, and prevents any kind of automatic graceful shutdown.
+ `messenger` -  a `function(err, options, cb)`, defaulting to
`thehelp-last-ditch`. Passed any top-level exceptions encountered.
+ `log` - a logger object: `info`, `warn` and `error` keys with the signature
`function(string)`. By default, whatever `thehelp-log-shim` gives us.

*/
function Startup(options) {
  /*jshint maxcomplexity: 11 */

  options = options || {};

  this._logPrefix = util.getLogPrefix();

  this.worker = options.worker;
  if (!this.worker) {
    throw new Error('Need to provide a worker callback!');
  }
  util.verifyType('function', this, 'worker');

  this._stats = options.stats || new StatsD({
    prefix: process.env.THEHELP_APP_NAME + '.'
  });

  this.log = options.log || logShim('thehelp-cluster:startup');
  util.verifyLog(this.log);

  this.masterOptions = options.masterOptions;
  this.master = options.master || this._defaultMasterStart.bind(this);
  util.verifyType('function', this, 'master');

  this.graceful = options.graceful || Graceful.instance;
  // graceful supercedes messenger
  if (!this.graceful) {
    this.messenger = options.messenger || require('thehelp-last-ditch');
    util.verifyType('function', this, 'messenger');
  }
  else {
    util.verifyGraceful(this.graceful);
  }

  this._domain = domain.create();
  this._domain.on('error', this._onError.bind(this));

  this._process = options._process || process;
  this._cluster = options._cluster || cluster;
}

module.exports = Startup;

// Public methods
// ========

// `start` checks whether the current process is the master, then calls the appropriate
// `master` or `worker` in the contxt of a top-level domain.
Startup.prototype.start = function start() {
  if (this._cluster.isMaster) {
    this._stats.increment('launches.master');
    this._domain.run(this.master);
  }
  else {
    this._stats.increment('launches.worker');
    this._domain.run(this.worker);
  }
};

// Helper methods
// ========

/*
`onError` is called when the top-level domain is sent an error. Whenever this happens
it's definitely something serious, send it via the `messenger()` callback then start the
process of graceful shutdown.

If [`Graceful.instance`](./graceful.html) has been provided, its `shutdown()` method will
be called, preventing the `messenger()` handler from being called.
*/
Startup.prototype._onError = function _onError(err) {
  var _this = this;

  //Don't want the entire domain object to pollute the log entry for this error
  delete err.domain;
  delete err.domainEmitter;

  this.log.error(this._logPrefix + ' top-level domain error, taking down process: ' +
    core.breadcrumbs.toString(err));

  if (this.graceful) {
    return this.graceful.shutdown(err);
  }

  this.messenger(err, null, function() {
    _this._process.kill(_this._process.pid, 'SIGTERM');
  });
};

// `_defaultMasterStart` what starts up the master process if you don't provide your own
// `master` startup function.
Startup.prototype._defaultMasterStart = function _defaultMasterStart() {
  var Master = require('./master');
  var master = new Master(this.masterOptions);
  master.start();
};


module.exports = startGc

var gc = require("level-gc")
var looseInterval = require("loose-interval")

/**
 * Start a gc task if options dictate it. GC task will kick off
 * occasionally to reap expired versions of records.
 * @param  {Version} db      level-version instance
 * @param  {Object} options GC options
 *  - gcMaxVersions [no default] When doing GC it will only keep gcMaxVersions newest versions of each key
 *  - gcMaxAge [no default] When doing GC only keep versions where Date.now() - gcMaxAge > version
 *  - gcFreqMs [60000] How often the GC runs to apply GC rules. Only runs if a gcMax* option is set.
 *  - gcBackup [no default] A level-version instance to stream culled records into
 *  - gcCallback [no default] A callback to execute when gc sweeps complete
 * @return {LooseInterval}         A http://npm.im/loose-interval object controlling the repeated gc task.
 */
function startGc(db, options) {
  this.options = options || {}

  var freqMs = options.gcFreqMs || 60000

  var maxVersions = options.gcMaxVersions
  var maxAge = options.gcMaxAge
  var backup = options.gcBackup
  var callback = options.gcCallback

  if (maxAge || maxVersions) {
    maxAge = maxAge || Math.pow(2, 53)
    maxVersion = maxVersions || Math.pow(2, 53)

    function filter(record) {
      if (record.version != null) {
        if (Date.now() - record.version > maxAge) return true
      }
      if (record.key != this.currentKey) {
        this.currentKey = record.key
        this.currentCount = 0
      }
      return this.currentCount++ >= maxVersions
    }

    this.scanner = gc(db, filter, backup)
    return looseInterval(scanner.run.bind(scanner), freqMs, callback)
  }
}


'use strict';

// Node.js built-in

var url = require('url');

// foreign modules

var isPromise = require('is-promise');
var through2 = require('through2');

// local modules

var values = require('./values');

// this module

// pipeTransforms (stream: Readable, transforms: Transforms[]) => Readable
function pipeTransforms (stream, transforms) {
  var head, rest;
  if (!Array.isArray(transforms) || !transforms.length) {
    return stream;
  }
  head = transforms[0];
  rest = transforms.slice(1);
  // recursively pipe to remaining Transforms
  return pipeTransforms(stream.pipe(head), rest);
}

// streamify ({ index: FetcherIndex }, transform: Function) => Stream
function streamify (opts, transform) {
  return through2.obj(
    // (vfile: VinylFile, enc: String, cb: Function)
    (vfile, enc, cb) => {
      var contents = vfile.contents.toString(enc);
      var result;
      var onSuccess = (contents) => {
        vfile.contents = new Buffer(contents, enc); // eslint-disable-line node/no-deprecated-api
        // TODO: drop Node.js 4.x support, use `Buffer.from()` instead
        cb(null, vfile);
      };
      var onError = (err) => {
        cb(err);
      };
      try {
        result = transform({
          contents: contents,
          fetcher: opts.fetcher,
          filePath: vfile.path,
          index: opts.index
        });
      } catch (err) {
        onError(err);
        return;
      }
      if (isPromise(result)) {
        result
          .then(onSuccess)
          .catch(onError);
        return;
      }
      onSuccess(result);
    }
  );
}

module.exports = {

  pipeTransforms,

  /**
  @param {String} urlString to remove things like ".gz"
  @returns {String} without ".gz" in it
  */
  stripGZ: function (urlString) {
    var parsed, segments, basename;
    if (!urlString || typeof urlString !== 'string') {
      return '';
    }
    parsed = url.parse(urlString, false, true);
    if (!parsed.pathname || parsed.pathname === '/') {
      return urlString;
    }
    segments = parsed.pathname.split('/');
    basename = segments.pop();
    basename = basename.replace(/\.gz\b/g, '');
    segments.push(basename);
    parsed.pathname = segments.join('/');
    return url.format(parsed);
  },

  streamify,

  /**
  intended to be used with `Array#filter()`
  @param {string} entry from AppCache manifest
  @returns {boolean} include entry?
  */
  filterUnfetchables: function (entry) {
    var parsed = url.parse(entry, true, true);
    return !parsed.protocol || values.FETCH_PROTOCOLS.indexOf(parsed.protocol) !== -1;
  }

};


module.exports = function(GedcomX){

  /**
   * Extra information about a name form.
   *
   * @class NameFormInfo
   * @extends Base
   * @param {Object} [json]
   */
  var NameFormInfo = function(json){

    // Protect against forgetting the new keyword when calling the constructor
    if(!(this instanceof NameFormInfo)){
      return new NameFormInfo(json);
    }

    // If the given object is already an instance then just return it. DON'T copy it.
    if(NameFormInfo.isInstance(json)){
      return json;
    }

    this.init(json);
  };

  NameFormInfo.prototype = Object.create(GedcomX.Base.prototype);

  NameFormInfo._gedxClass = NameFormInfo.prototype._gedxClass = 'GedcomX.NameFormInfo';

  NameFormInfo.jsonProps = [
    'order'
  ];

  /**
   * Check whether the given object is an instance of this class.
   *
   * @memberof NameFormInfo
   * @static
   * @param {Object} obj
   * @returns {Boolean}
   */
  NameFormInfo.isInstance = function(obj){
    return GedcomX.utils.isInstance(obj, this._gedxClass);
  };

  /**
   * Initialize from JSON
   *
   * @memberof NameFormInfo
   * @param {Object}
   * @return NameFormInfo this
   */
  NameFormInfo.prototype.init = function(json){

    GedcomX.Base.prototype.init.call(this, json);

    if(json){
      this.setOrder(json.order);
    }
    return this;
  };

  /**
   * Get the order
   *
   * @memberof NameFormInfo
   * @returns {String} order
   */
  NameFormInfo.prototype.getOrder = function(){
    return this.order;
  };

  /**
   * Set the order
   *
   * @memberof NameFormInfo
   * @param {String} order
   * @returns {NameFormInfo} this
   */
  NameFormInfo.prototype.setOrder = function(order){
    this.order = order;
    return this;
  };

  /**
   * Export the object as JSON
   *
   * @memberof NameFormInfo
   * @return {Object} JSON object
   */
  NameFormInfo.prototype.toJSON = function(){
    return this._toJSON(GedcomX.Base, NameFormInfo.jsonProps);
  };

  GedcomX.NameFormInfo = NameFormInfo;

};


module.exports = function(GedcomX){

  /**
   * Class description
   *
   * @class MergeConflict
   * @extends Base
   * @param {Object} [json]
   */
  var MergeConflict = function(json){

    // Protect against forgetting the new keyword when calling the constructor
    if(!(this instanceof MergeConflict)){
      return new MergeConflict(json);
    }

    // If the given object is already an instance then just return it. DON'T copy it.
    if(MergeConflict.isInstance(json)){
      return json;
    }

    this.init(json);
  };

  MergeConflict.prototype = Object.create(GedcomX.Base.prototype);

  MergeConflict._gedxClass = MergeConflict.prototype._gedxClass = 'GedcomX.MergeConflict';

  MergeConflict.jsonProps = [
    'survivorResource',
    'duplicateResource'
  ];

  /**
   * Check whether the given object is an instance of this class.
   *
   * @memberof MergeConflict
   * @static
   * @param {Object} obj
   * @returns {Boolean}
   */
  MergeConflict.isInstance = function(obj){
    return GedcomX.utils.isInstance(obj, this._gedxClass);
  };

  /**
   * Initialize from JSON
   *
   * @memberof MergeConflict
   * @param {Object}
   * @return MergeConflict this
   */
  MergeConflict.prototype.init = function(json){

    GedcomX.Base.prototype.init.call(this, json);

    if(json){
      this.setSurvivorResource(json.survivorResource);
      this.setDuplicateResource(json.duplicateResource);
    }
    return this;
  };

  /**
   * Get the survivorResource
   *
   * @memberof MergeConflict
   * @returns {ResourceReference} survivorResource
   */
  MergeConflict.prototype.getSurvivorResource = function(){
    return this.survivorResource;
  };

  /**
   * Set the survivorResource
   *
   * @memberof MergeConflict
   * @param {ResourceReference} survivorResource
   * @returns {MergeConflict} this
   */
  MergeConflict.prototype.setSurvivorResource = function(survivorResource){
    if(survivorResource){
      this.survivorResource = GedcomX.ResourceReference(survivorResource);
    }
    return this;
  };

  /**
   * Get the duplicateResource
   *
   * @memberof MergeConflict
   * @returns {ResourceReference} duplicateResource
   */
  MergeConflict.prototype.getDuplicateResource = function(){
    return this.duplicateResource;
  };

  /**
   * Set the duplicateResource
   *
   * @memberof MergeConflict
   * @param {ResourceReference} duplicateResource
   * @returns {MergeConflict} this
   */
  MergeConflict.prototype.setDuplicateResource = function(duplicateResource){
    if(duplicateResource){
      this.duplicateResource = GedcomX.ResourceReference(duplicateResource);
    }
    return this;
  };

  /**
   * Export the object as JSON
   *
   * @memberof MergeConflict
   * @return {Object} JSON object
   */
  MergeConflict.prototype.toJSON = function(){
    return this._toJSON(GedcomX.Base, MergeConflict.jsonProps);
  };

  GedcomX.MergeConflict = MergeConflict;

};


module.exports = function(GedcomX){

  /**
   * OAuth 2 token responses
   *
   * @class OAuth2
   * @extends Base
   * @param {Object} [json]
   */
  var OAuth2 = function(json){

    // Protect against forgetting the new keyword when calling the constructor
    if(!(this instanceof OAuth2)){
      return new OAuth2(json);
    }

    // If the given object is already an instance then just return it. DON'T copy it.
    if(OAuth2.isInstance(json)){
      return json;
    }

    this.init(json);
  };

  OAuth2.prototype = Object.create(GedcomX.Base.prototype);

  OAuth2._gedxClass = OAuth2.prototype._gedxClass = 'GedcomX.OAuth2';

  OAuth2.jsonProps = [
    'access_token',
    'token_type',
    'error',
    'error_description'
  ];

  /**
   * Check whether the given object is an instance of this class.
   *
   * @memberof OAuth2
   * @static
   * @param {Object} obj
   * @returns {Boolean}
   */
  OAuth2.isInstance = function(obj){
    return GedcomX.utils.isInstance(obj, this._gedxClass);
  };

  /**
   * Initialize from JSON
   *
   * @memberof OAuth2
   * @param {Object}
   * @return OAuth2 this
   */
  OAuth2.prototype.init = function(json){

    GedcomX.Base.prototype.init.call(this, json);

    if(json){
      this.setAccessToken(json.access_token);
      this.setTokenType(json.token_type);
      this.setError(json.error);
      this.setErrorDescription(json.error_description);
    }
    return this;
  };

  /**
   * Get the access token
   *
   * @memberof OAuth2
   * @returns {String} access_token
   */
  OAuth2.prototype.getAccessToken = function(){
    return this.access_token;
  };

  /**
   * Set the access token
   *
   * @memberof OAuth2
   * @param {String} access_token
   * @returns {OAuth2} this
   */
  OAuth2.prototype.setAccessToken = function(access_token){
    this.access_token = access_token;
    return this;
  };

  /**
   * Get the token type
   *
   * @memberof OAuth2
   * @returns {String} token_type
   */
  OAuth2.prototype.getTokenType = function(){
    return this.token_type;
  };

  /**
   * Set the token type
   *
   * @memberof OAuth2
   * @param {String} token_type
   * @returns {OAuth2} this
   */
  OAuth2.prototype.setTokenType = function(token_type){
    this.token_type = token_type;
    return this;
  };

  /**
   * Get the error
   *
   * @memberof OAuth2
   * @returns {String} error
   */
  OAuth2.prototype.getError = function(){
    return this.error;
  };

  /**
   * Set the error
   *
   * @memberof OAuth2
   * @param {String} error
   * @returns {OAuth2} this
   */
  OAuth2.prototype.setError = function(error){
    this.error = error;
    return this;
  };

  /**
   * Get the error description
   *
   * @memberof OAuth2
   * @returns {String} error_description
   */
  OAuth2.prototype.getErrorDescription = function(){
    return this.error_description;
  };

  /**
   * Set the error description
   *
   * @memberof OAuth2
   * @param {String} error_description
   * @returns {OAuth2} this
   */
  OAuth2.prototype.setErrorDescription = function(error_description){
    this.error_description = error_description;
    return this;
  };

  /**
   * Export the object as JSON
   *
   * @memberof OAuth2
   * @return {Object} JSON object
   */
  OAuth2.prototype.toJSON = function(){
    return this._toJSON(GedcomX.Base, OAuth2.jsonProps);
  };

  GedcomX.OAuth2 = OAuth2;

};


let Extender = require("./extender.js");
let TReader = require("./t-reader.js");
let Tools = require("./tools.js");

let GRE_WS = /\s+/g;

// array => array
function parse(tokens) {
    let tr = TReader.create(prepareTokens(tokens));
    let nodes = [];
    while (true) {
        if (tr.accept("$")) {
            return nodes;
        }
        if (tr.accept("text")) {
            nodes.push(parseText(tr));
        } else if (tr.accept("tag_0")) {
            nodes.push(parseTag0(tr));
        } else {
            tr.expect("tag_1");
            nodes.push(parseTag1(tr));
        }
    }
}

// array => array
function prepareTokens(tokens) {
    let result = [];
    let lastToken = null;
    tokens.forEach(token => {
        let newToken = null;
        if (token.type === "text") {
            let value = token.value.replace(GRE_WS, " ").trim();
            if (value !== "") {
                if (lastToken && lastToken.type === "text") {
                    lastToken.value += value;
                } else {
                    newToken = { type: "text", start: token.start, value };
                }
            }
        } else if (token.type !== "comment") {
            newToken = token;
        }
        if (newToken) {
            result.push(newToken);
            lastToken = newToken;
        }
    });
    result.push({ type: "$" });
    return result;
}

// object => object
function parseText(tr) {
    return {
        type: "#",
        text: tr.getToken().value
    };
}

// object => object
function parseTag0(tr) {
    return initTagNode(tr.getToken());
}

// object => object
function parseTag1(tr) {
    let startToken = tr.getToken();
    let node = initTagNode(startToken);
    while (true) {
        if (tr.accept("text")) {
            node.children.push(parseText(tr));
        } else if (tr.accept("tag_0")) {
            node.children.push(parseTag0(tr));
        } else if (tr.accept("tag_1")) {
            node.children.push(parseTag1(tr));
        } else if (tr.accept("tag_2")) {
            let token = tr.getToken();
            if (token.name !== startToken.name) {
                let msg = `Unexpected closing tag "${token.name}"`;
                throw Tools.syntaxError(msg, token.start);
            }
            return node;
        } else {
            let msg = `Unclosed tag "${startToken.name}"`;
            throw Tools.syntaxError(msg, startToken.start);
        }
    }
}

// object => object
function initTagNode(token) {
    validateProps(token);
    return { type: token.name, props: token.props, children: [] };
}

// object => undefined
function validateProps(token) {
    Object.keys(token.props).forEach(key => {
        if (!validateProp(key, token.props[key])) {
            throw Tools.syntaxError(`Invalid value for "${key}" property`, token);
        }
    });
}

// (string, string) => boolean
function validateProp(key, value) {
    switch (key) {
    case "each":
        return Extender.validateEach(value);
    case "if":
    case "fi":
        return Extender.validateExpr(value);
    default:
        return true;
    }
}

module.exports = {
    parse
};


let Tools = require("./tools.js");

let P_NAME = "[_$a-zA-Z][_$a-zA-Z0-9]*";
let P_PATH = `${P_NAME}(?:\\.(?:${P_NAME}|[0-9]+))*`;
let P_NUMB = "-?[0-9]+(\\.[0-9]+)?";
let P_EXPR = `{(?:(${P_PATH})|(${P_NUMB}))}`;
let P_EACH = `{(${P_NAME}) in (${P_PATH})}`;

let GRE_WS = /\s+/g;
let GRE_EXPR = RegExp(P_EXPR, "g");

let RE_EXPR = RegExp("^" + P_EXPR + "$");
let RE_EACH = RegExp("^" + P_EACH + "$");

// string => boolean
function validateExpr(value) {
    return RE_EXPR.test(value);
}

// string => boolean
function validateEach(value) {
    return RE_EACH.test(value);
}

// (string, object) => any
function processProp(value, data) {
    return RE_EXPR.test(value)
        ? processExpr(value, data)
        : processText(value, data);
}

// (string, object) => string
function processText(value, data) {
    return value
        .replace(GRE_EXPR, m => {
            let res = processExpr(m, data);
            return Tools.isVoid(res) ? "" : res + "";
        })
        .replace(GRE_WS, " ")
        .trim();
}

// (string, object) => any
function processExpr(value, data) {
    if (value === "true") {
        return true;
    }
    if (value === "false") {
        return false;
    }
    let m = value.match(RE_EXPR);
    if (!m) {
        return null;
    }
    return m[1] ? processPath(m[1], data) : parseFloat(m[2]);
}

// (string, object) => object | null
function processEach(value, data) {
    let m = value.match(RE_EACH);
    if (!m) {
        return null;
    }
    let items = processPath(m[2], data);
    if (!Array.isArray(items)) {
        items = [];
    }
    return { item: m[1], items };
}

// (string, object) => any
function processPath(path, data) {
    return processPathArray(path.split("."), data);
}

// (array, object) => any
function processPathArray(path, data) {
    if (!data) {
        return null;
    }
    let value = data[path[0]];
    return path.length > 1 ? processPathArray(path.slice(1), value) : value;
}

module.exports = {
    validateExpr,
    validateEach,
    processProp,
    processText,
    processExpr,
    processEach
};


'use strict';

/**
 * A named event
 *
 * @param {string} name The name of the event
 * @param {object} [attributes] Custom attributes for the event
 */
function Event(name, attributes) {
    this._name = name;
    this._stopped = false;
    this._attrs = {};

    if (attributes) {
        this.setAttributes(attributes);
    }
}
var proto = Event.prototype;

/**
 * Get the event name
 *
 * @return {string} The event name
 */
proto.getName = function() {
    return this._name;
};

/**
 * Get an event attribute
 *
 * @param {string} name The attribute name
 * @return {mixed} The attribue value
 */
proto.get = function(name) {
    return this._attrs[name];
};

/**
 * Set an event attribute
 *
 * @param {string} name The attribute name
 * @param {mixed} value The attribute value
 * @return {this}
 */
proto.set = function(name, value) {
    this._attrs[name] = value;
    return this;
};

/**
 * Get an object containing all the event attributes
 *
 * @return {object} An object whose keys are attribute names and values are their values.
 */
proto.getAttributes = function() {
    return this._attrs;
};

/**
 * Set event attributes from an object whose keys are attribute names.
 * The values will be merged with existing attributes.
 *
 * @param {object} attributes The attributes to set
 */
proto.setAttributes = function(attributes) {
    for (var key in attributes) {
        if (attributes.hasOwnProperty(key)) {
            this.set(key, attributes[key]);
        }
    }
};

/**
 * Stop propagation for this event. Additional listeners will
 * not be called.
 *
 * @return {this}
 */
proto.stopPropagation = function() {
    this._stopped = true;
    return this;
};

/**
 * Check if propagation is stopped
 *
 * @return {boolean} True if propagation is stopped, false if not.
 */
proto.isPropagationStopped = function() {
    return this._stopped;
};

module.exports = Event;


'use strict';
const Buffer = require('buffer').Buffer;

// readInt32BE(value, number[, offest])

function readUInt64BE(buf, offset) {
  if (offset){
    buf = buf.slice(offset, offset + 8);
  }
  // create a hex equivalent string:
  let str = buf.toString('hex');
  str = str.split('');
  let solution = 0;
  let mul = 15;
  str.forEach((num) => {
    let dec = getDecimal(num);
    solution += dec * Math.pow(16,mul);
    mul--;
  });
  return solution;        // (value position 0 * 16^15) + (value position 1 * 16^14) + ... +  (value position 7 * 16^0)
}

module.exports = readUInt64BE;

function getDecimal(hex) {
  switch (hex) {
    case '0':
      return 0;
      break;
    case '1':
      return 1;
      break;
    case '2':
      return 2;
      break;
    case '3':
      return 3;
      break;
    case '4':
      return 4;
      break;
    case '5':
      return 5;
      break;
    case '6':
      return 6;
      break;
    case '7':
      return 7;
      break;
    case '8':
      return 8;
      break;
    case '9':
      return 9;
      break;
    case 'a':
      return 10;
      break;
    case 'b':
      return 11;
      break;
    case 'c':
      return 12;
      break;
    case 'd':
      return 13;
      break;
    case 'e':
      return 14;
      break;
    case 'f':
      return 15;
      break;
    default :
      return 0;
  }
}


let Tools = require("./tools.js");

// string => object
function create(text) {
    return Tools.instance({
        text,
        pos: 0
    }, {
        isDone,
        getPos,
        expect,
        accept,
        expectRE,
        acceptRE,
        goto
    });
}

// object => boolean
function isDone($) {
    return $.pos === $.text.length;
}

// object => number
function getPos($) {
    return $.pos;
}

// (object, string) => undefined
function expect($, str) {
    if (!accept($, str)) {
        throw Tools.syntaxError("Unexpected character", $.pos);
    }
}

// (object, object) => string
function expectRE($, regexp) {
    let str = acceptRE($, regexp);
    if (str === null) {
        throw Tools.syntaxError("Unexpected character", $.pos);
    }
    return str;
}

// (object, string) => boolean
function accept($, str) {
    if (!$.text.startsWith(str, $.pos)) {
        return false;
    }
    $.pos += str.length;
    return true;
}

// (object, object) => string | null
function acceptRE($, regexp) {
    let m = $.text.substr($.pos).match(regexp);
    if (!m) {
        return null;
    }
    let str = m[0];
    $.pos += str.length;
    return str;
}

// (object, string) => boolean
function goto($, str) {
    let index = $.text.indexOf("-->", $.pos);
    if (index === -1) {
        return false;
    }
    $.pos = index;
    return true;
}

module.exports = {
    create
};


'use strict';

// 3rd-party modules

var cheerio = require('cheerio');

// local modules

var streamify = require('../utils.js').streamify;

// this module

/*
interface StringTransformOptions {
  contents: String,
  fetcher: Fetcher,
  filePath: String,
  index: FetcherIndex
}
*/
// transform (opts: StringTransformOptions) => String
function transform (opts) {
  var contents = opts.contents;

  var $ = cheerio.load(contents);
  $('html').removeAttr('manifest'); // drop AppCache manifest attributes
  return $.html();
}

function htmlRemoveManifest (opts) {
  return streamify(opts, transform);
}

module.exports = htmlRemoveManifest;
module.exports.transform = transform;


module.exports = function(GedcomX){

  /**
   * Information about feedback for places.
   *
   * @class FeedbackInfo
   * @extends Base
   * @param {Object} [json]
   */
  var FeedbackInfo = function(json){

    // Protect against forgetting the new keyword when calling the constructor
    if(!(this instanceof FeedbackInfo)){
      return new FeedbackInfo(json);
    }

    // If the given object is already an instance then just return it. DON'T copy it.
    if(FeedbackInfo.isInstance(json)){
      return json;
    }

    this.init(json);
  };

  FeedbackInfo.prototype = Object.create(GedcomX.Base.prototype);

  FeedbackInfo._gedxClass = FeedbackInfo.prototype._gedxClass = 'GedcomX.FeedbackInfo';

  FeedbackInfo.jsonProps = [
    'status',
    'resolution',
    'details',
    'place'
  ];

  /**
   * Check whether the given object is an instance of this class.
   *
   * @memberof FeedbackInfo
   * @static
   * @param {Object} obj
   * @returns {Boolean}
   */
  FeedbackInfo.isInstance = function(obj){
    return GedcomX.utils.isInstance(obj, this._gedxClass);
  };

  /**
   * Initialize from JSON
   *
   * @memberof FeedbackInfo
   * @param {Object}
   * @return FeedbackInfo this
   */
  FeedbackInfo.prototype.init = function(json){

    GedcomX.Base.prototype.init.call(this, json);

    if(json){
      this.setStatus(json.status);
      this.setResolution(json.resolution);
      this.setDetails(json.details);
      this.setPlace(json.place);
    }
    return this;
  };

  /**
   * Get the status
   *
   * @memberof FeedbackInfo
   * @returns {String} status
   */
  FeedbackInfo.prototype.getStatus = function(){
    return this.status;
  };

  /**
   * Set the status
   *
   * @memberof FeedbackInfo
   * @param {String} status
   * @returns {FeedbackInfo} this
   */
  FeedbackInfo.prototype.setStatus = function(status){
    this.status = status;
    return this;
  };

  /**
   * Get the resolution
   *
   * @memberof FeedbackInfo
   * @returns {String} resolution
   */
  FeedbackInfo.prototype.getResolution = function(){
    return this.resolution;
  };

  /**
   * Set the resolution
   *
   * @memberof FeedbackInfo
   * @param {String} resolution
   * @returns {FeedbackInfo} this
   */
  FeedbackInfo.prototype.setResolution = function(resolution){
    this.resolution = resolution;
    return this;
  };

  /**
   * Get the details
   *
   * @memberof FeedbackInfo
   * @returns {String} details
   */
  FeedbackInfo.prototype.getDetails = function(){
    return this.details;
  };

  /**
   * Set the details
   *
   * @memberof FeedbackInfo
   * @param {String} details
   * @returns {FeedbackInfo} this
   */
  FeedbackInfo.prototype.setDetails = function(details){
    this.details = details;
    return this;
  };

  /**
   * Get the place
   *
   * @memberof FeedbackInfo
   * @returns {ResourceReference} place
   */
  FeedbackInfo.prototype.getPlace = function(){
    return this.place;
  };

  /**
   * Set the place
   *
   * @memberof FeedbackInfo
   * @param {ResourceReference} place
   * @returns {FeedbackInfo} this
   */
  FeedbackInfo.prototype.setPlace = function(place){
    if(place){
      this.place = GedcomX.ResourceReference(place);
    }
    return this;
  };

  /**
   * Export the object as JSON
   *
   * @memberof FeedbackInfo
   * @return {Object} JSON object
   */
  FeedbackInfo.prototype.toJSON = function(){
    return this._toJSON(GedcomX.Base, FeedbackInfo.jsonProps);
  };

  GedcomX.FeedbackInfo = FeedbackInfo;

};


module.exports = function(GedcomX){

  /**
   * LDS ordinance reservation
   *
   * @class Reservation
   * @extends Conclusion
   * @param {Object} [json]
   */
  var Reservation = function(json){

    // Protect against forgetting the new keyword when calling the constructor
    if(!(this instanceof Reservation)){
      return new Reservation(json);
    }

    // If the given object is already an instance then just return it. DON'T copy it.
    if(Reservation.isInstance(json)){
      return json;
    }

    this.init(json);
  };

  Reservation.prototype = Object.create(GedcomX.Conclusion.prototype);

  Reservation._gedxClass = Reservation.prototype._gedxClass = 'GedcomX.Reservation';

  Reservation.jsonProps = [
    'type',
    'status',
    'spouse',
    'father',
    'mother',
    'assignee'
  ];

  /**
   * Check whether the given object is an instance of this class.
   *
   * @memberof Reservation
   * @static
   * @param {Object} obj
   * @returns {Boolean}
   */
  Reservation.isInstance = function(obj){
    return GedcomX.utils.isInstance(obj, this._gedxClass);
  };

  /**
   * Initialize from JSON
   *
   * @memberof Reservation
   * @param {Object}
   * @return Reservation this
   */
  Reservation.prototype.init = function(json){

    GedcomX.Conclusion.prototype.init.call(this, json);

    if(json){
      this.setType(json.type);
      this.setStatus(json.status);
      this.setSpouse(json.spouse);
      this.setFather(json.father);
      this.setMother(json.mother);
      this.setAssignee(json.assignee);
    }
    return this;
  };

  /**
   * Get the type
   *
   * @memberof Reservation
   * @returns {String} type
   */
  Reservation.prototype.getType = function(){
    return this.type;
  };

  /**
   * Set the type
   *
   * @memberof Reservation
   * @param {String} type
   * @returns {Reservation} this
   */
  Reservation.prototype.setType = function(type){
    this.type = type;
    return this;
  };

  /**
   * Get the status
   *
   * @memberof Reservation
   * @returns {String} status
   */
  Reservation.prototype.getStatus = function(){
    return this.status;
  };

  /**
   * Set the status
   *
   * @memberof Reservation
   * @param {String} status
   * @returns {Reservation} this
   */
  Reservation.prototype.setStatus = function(status){
    this.status = status;
    return this;
  };

  /**
   * Get the spouse
   *
   * @memberof Reservation
   * @returns {ResourceReference} spouse
   */
  Reservation.prototype.getSpouse = function(){
    return this.spouse;
  };

  /**
   * Set the spouse
   *
   * @memberof Reservation
   * @param {ResourceReference} spouse
   * @returns {Reservation} this
   */
  Reservation.prototype.setSpouse = function(spouse){
    if(spouse){
      this.spouse = GedcomX.ResourceReference(spouse);
    }
    return this;
  };

  /**
   * Get the father
   *
   * @memberof Reservation
   * @returns {ResourceReference} father
   */
  Reservation.prototype.getFather = function(){
    return this.father;
  };

  /**
   * Set the father
   *
   * @memberof Reservation
   * @param {ResourceReference} father
   * @returns {Reservation} this
   */
  Reservation.prototype.setFather = function(father){
    if(father){
      this.father = GedcomX.ResourceReference(father);
    }
    return this;
  };

  /**
   * Get the mother
   *
   * @memberof Reservation
   * @returns {ResourceReference} mother
   */
  Reservation.prototype.getMother = function(){
    return this.mother;
  };

  /**
   * Set the mother
   *
   * @memberof Reservation
   * @param {ResourceReference} mother
   * @returns {Reservation} this
   */
  Reservation.prototype.setMother = function(mother){
    if(mother){
      this.mother = GedcomX.ResourceReference(mother);
    }
    return this;
  };

  /**
   * Get the assignee
   *
   * @memberof Reservation
   * @returns {ResourceReference} assignee
   */
  Reservation.prototype.getAssignee = function(){
    return this.assignee;
  };

  /**
   * Set the assignee
   *
   * @memberof Reservation
   * @param {ResourceReference} assignee
   * @returns {Reservation} this
   */
  Reservation.prototype.setAssignee = function(assignee){
    if(assignee){
      this.assignee = GedcomX.ResourceReference(assignee);
    }
    return this;
  };

  /**
   * Export the object as JSON
   *
   * @memberof Reservation
   * @return {Object} JSON object
   */
  Reservation.prototype.toJSON = function(){
    return this._toJSON(GedcomX.Conclusion, Reservation.jsonProps);
  };

  GedcomX.Reservation = Reservation;

};


let Tools = require("./tools.js");

// array => object
function create(tokens) {
    return Tools.instance({
        tokens,
        pos: 0
    }, {
        getToken,
        expect,
        accept
    });
}

// object => object | undefined
function getToken($) {
    return $.tokens[$.pos - 1];
}

// (object, string) => undefined
function expect($, type) {
    if (!accept($, type)) {
        let token = $.tokens[$.pos];
        throw Tools.syntaxError("Unexpected token", token.start);
    }
}

// (object, string) => boolean
function accept($, type) {
    let token = $.tokens[$.pos];
    if (token.type !== type) {
        return false;
    }
    $.pos++;
    return true;
}

module.exports = {
    create
};


// # Master
// A simple class to manage a number of worker processes via node's core `cluster` module.
// Supports graceful shutdown via the `SIGTERM` signal, as well as detection child
// processes crashing too quickly after launch.

'use strict';

var cluster = require('cluster');

var logShim = require('thehelp-log-shim');

var Graceful = require('./graceful');
var util = require('./util');

/*
The `constructor` has no required parameters. Optional parameters:

+ `graceful` - required to enable full process shutdown scenarios. Can be set either on
construction or later with `setGraceful()`. Default is `Graceful.instance`, so if you've
created an instance in this process already, it will be found automatically.
+ `spinTimeout` - how long a process needs to stay up after launch for a crash to be
considered 'normal.'
+ `delayStart` - if a process crashes too fast, how long to wait before starting another
+ `pollInterval` - when gracefully shutting down, how frequently to check for zero
remaining worker processes
+ `killTimeout` - how long to wait after sending 'SIGTERM` to follow up it with a 'SIGINT'
for a more decisive kill
+ `numberWorkers` - worker processes to start and maintain. defaults to `1` or
`process.env.THEHELP_NUMBER_WORDERS`
+ `log` - a logger object: `info`, `warn` and `error` keys with the signature
`function(string)`. By default, whatever `thehelp-log-shim` gives us.

*/
function Master(options) {
  /*jshint maxcomplexity: 12 */

  options = options || {};

  this._workers = {};
  this.shuttingDown = false;

  this.spinTimeout = options.spinTimeout || 10 * 1000;
  util.verifyType('number', this, 'spinTimeout');

  this.delayStart = options.delayStart || 60 * 1000;
  util.verifyType('number', this, 'delayStart');

  this.pollInterval = options.pollInterval || 500;
  util.verifyType('number', this, 'pollInterval');

  this.killTimeout = options.killTimeout || 7000;
  util.verifyType('number', this, 'killTimeout');

  this.numberWorkers = options.numberWorkers ||
    parseInt(process.env.THEHELP_NUMBER_WORKERS, 10) || 1;
  util.verifyType('number', this, 'numberWorkers');

  this.setGraceful(options.graceful || Graceful.instance);

  this.log = options.log || logShim('thehelp-cluster:master');
  util.verifyLog(options.log);

  this._cluster = options._cluster || cluster;
  this._cluster.on('disconnect', this._restartWorker.bind(this));

  if (Master.instance) {
    this.log.warn('More than one Master instance created in this process. ' +
      'You\'ll have more worker processes than you signed up for!');
  }
  Master.instance = this;
}

module.exports = Master;

// `start` gets all the requested worker processes started
Master.prototype.start = function start() {
  this.log.warn('Starting master');
  for (var i = 0; i < this.numberWorkers; i = i + 1) {
    this._startWorker();
  }
};

// `setGraceful` is a way to provide the reference after construction. We register with
// `Graceful` to shutdown when it starts the shutdown process, as well as to let it know
// when workers are still alive. With this in place, you won't need to call `shutdown()`
// or `stop()` yourself.
Master.prototype.setGraceful = function setGraceful(graceful) {
  if (graceful) {
    this.graceful = graceful;
    util.verifyGraceful(this.graceful);

    //default timeout for sending SIGINT is longer than timeout for Graceful force kill
    this.graceful.timeout = this.killTimeout + this.pollInterval * 2;

    this.graceful.on('shutdown', this._onShutdown.bind(this));
    this.graceful.addCheck(this._isReadyForShutdown.bind(this));
  }
};

// `stop` kills all worker processes, first using a 'SIGTERM' signal to allow for graceful
// shutdown. If the process isn't dead by `this.killTimeout` a 'SIGINT' signal is sent.
Master.prototype.stop = function stop(cb) {
  var _this = this;
  util.verifyCb(cb);

  this.log.info('Stopping all workers with SIGTERM...');

  this.shuttingDown = true;
  this._sendToAll('SIGTERM');

  var timeout = setTimeout(function forceKill() {
    timeout = null;
    _this.log.warn('Shutdown delayed; sending SIGINT to all remaining workers...');
    _this._sendToAll('SIGINT');
  }, _this.killTimeout);

  var interval = setInterval(function checkAndReturn() {
    if (!Object.keys(_this._cluster.workers).length) {

      clearInterval(interval);
      if (timeout) {
        clearTimeout(timeout);
      }

      _this.log.info('All workers gone.');
      return cb();
    }
    else {
      _this.log.info('Still some workers alive...');
    }
  }, this.pollInterval);
};

// Helper functions
// ========

// `_onShutdown` is called when `Graceful` starts shutting down. It uses `stop` to kill
// all workers, then sets `this._workersActive` back to false so `_isReadyForShutdown()`
// knows that it's safe to take the process down.
Master.prototype._onShutdown = function _onShutdown() {
  var _this = this;

  this._workersActive = true;
  this.stop(function() {
    _this._workersActive = false;
  });
};

// `_isReadyForShutdown` is registered as a `Graceful` 'check' function, letting it know
// when we're safe to shut down the process.
Master.prototype._isReadyForShutdown = function _isReadyForShutdown() {
  return !this._workersActive;
};

// `_startWorker` does a basic `cluster.fork()`, saving the result and the current time to
// `this._workers`.
Master.prototype._startWorker = function _startWorker() {
  var worker = this._cluster.fork();
  var pid = worker.process.pid;
  this._workers[pid] = {
    pid: pid,
    id: worker.id,
    start: new Date()
  };
};

// `_restartWorker` first eliminates the dead worker from `this._workers`, then either
// starts a new worker immediately, or after a delay of `this.delayStart` if the process
// wasn't alive for longer than `this.spinTimeout`.
Master.prototype._restartWorker = function _restartWorker(worker) {
  var _this = this;

  var pid = worker.process.pid;
  var data = this._workers[pid];
  delete this._workers[pid];

  if (!this.shuttingDown) {
    var now = new Date();
    var start = data ? data.start : now;
    var delta = now.getTime() - start.getTime();

    if (data && delta < this.spinTimeout) {
      this.log.error('Worker #' + worker.id + ' (pid: ' + pid +
        ') died after less than spin timeout of ' + this.spinTimeout + 'ms. Waiting ' +
        'for ' + this.delayStart + 'ms before starting replacement');

      setTimeout(function() {
        _this.log.warn('Starting delayed replacement for worker #' + worker.id);
        _this._startWorker();
      }, this.delayStart);
    }
    else {
      this.log.warn('Starting replacement for worker #' + worker.id);
      this._startWorker();
    }

    if (Object.keys(this._workers).length === 0) {
      this.log.error('No workers currently running!');
    }
  }
};

// `_sendToAll` sends the provided signal to each worker still listed in `this._workers`
Master.prototype._sendToAll = function _sendToAll(signal) {
  var keys = Object.keys(this._cluster.workers);

  for (var i = 0, max = keys.length; i < max; i += 1) {
    var key = keys[i];
    var worker = this._cluster.workers[key];
    var pid = worker.process.pid;

    if (this._workers[pid]) {
      process.kill(pid, signal);
    }
  }
};


module.exports = function(GedcomX){

  /**
   * Information about a search.
   *
   * @class SearchInfo
   * @extends Base
   * @param {Object} [json]
   */
  var SearchInfo = function(json){

    // Protect against forgetting the new keyword when calling the constructor
    if(!(this instanceof SearchInfo)){
      return new SearchInfo(json);
    }

    // If the given object is already an instance then just return it. DON'T copy it.
    if(SearchInfo.isInstance(json)){
      return json;
    }

    this.init(json);
  };

  SearchInfo.prototype = Object.create(GedcomX.Base.prototype);

  SearchInfo._gedxClass = SearchInfo.prototype._gedxClass = 'GedcomX.SearchInfo';

  SearchInfo.jsonProps = [
    'closeHits',
    'totalHits'
  ];

  /**
   * Check whether the given object is an instance of this class.
   *
   * @memberof SearchInfo
   * @static
   * @param {Object} obj
   * @returns {Boolean}
   */
  SearchInfo.isInstance = function(obj){
    return GedcomX.utils.isInstance(obj, this._gedxClass);
  };

  /**
   * Initialize from JSON
   *
   * @memberof SearchInfo
   * @param {Object}
   * @return SearchInfo this
   */
  SearchInfo.prototype.init = function(json){

    GedcomX.Base.prototype.init.call(this, json);

    if(json){
      this.setCloseHits(json.closeHits);
      this.setTotalHits(json.totalHits);
    }
    return this;
  };

  /**
   * Get the closeHits
   *
   * @memberof SearchInfo
   * @returns {Integer} closeHits
   */
  SearchInfo.prototype.getCloseHits = function(){
    return this.closeHits;
  };

  /**
   * Set the closeHits
   *
   * @memberof SearchInfo
   * @param {Integer} closeHits
   * @returns {SearchInfo} this
   */
  SearchInfo.prototype.setCloseHits = function(closeHits){
    this.closeHits = closeHits;
    return this;
  };

  /**
   * Get the totalHits
   *
   * @memberof SearchInfo
   * @returns {Integer} totalHits
   */
  SearchInfo.prototype.getTotalHits = function(){
    return this.totalHits;
  };

  /**
   * Set the totalHits
   *
   * @memberof SearchInfo
   * @param {Integer} totalHits
   * @returns {SearchInfo} this
   */
  SearchInfo.prototype.setTotalHits = function(totalHits){
    this.totalHits = totalHits;
    return this;
  };

  /**
   * Export the object as JSON
   *
   * @memberof SearchInfo
   * @return {Object} JSON object
   */
  SearchInfo.prototype.toJSON = function(){
    return this._toJSON(GedcomX.Base, SearchInfo.jsonProps);
  };

  GedcomX.SearchInfo = SearchInfo;

};


'use strict';

var assert = require('assert');
var EventEmitter = require('events').EventEmitter;
var util = require('util');
var clone = require('clone');

var async = require('async');
var debug = require('debug')('orka:pubsub');

var utils = require('./utils');

var count = 1;

// Subscription topic
function Topic(name, options, emitter) {
    this.name = name;
    this.subscribers = [];
    this.queue = [];
    this.publishedEvents = {};
    this.options = options || {};
    this.emitter = emitter;
}

var tProto = Topic.prototype;

tProto.pub = tProto.publish = function publish(data, complete, callback) {
    var topic = this.name;

    if (arguments.length === 2) {
        if (typeof complete === 'function') {
            callback = complete;
            complete = false;
        }
    }

    assert.ok(data !== undefined && data !== null, 'Data should not be undefined or null');

    var hash = typeof data === 'string' ? utils.hash(data) : (data.__publishedId = data.__publishedId || count++);
    if (this.publishedEvents[hash]) {
        return this.options.strict && utils.boom(this.emitter, new Error('data has already been published: ' +
            (typeof data === 'object' ? require('util').inspect(data, null, 2) : data)), callback);
    }

    if (this.completed) {
        var err = new Error('Error: tried to publish after complete event: ' + require('util').inspect(data, null, 2));
        err.type = 'pubsub:publish:after-complete';

        return callback ? callback(err) : this.emitter.emit('error', err);
    }

    var event;
    if (data instanceof Error) {
        debug(topic, 'publishing error', data);
        event = {
            type: 'reject',
            error: data
        };
    }
    else {
        debug(topic, 'publishing data', data);
        event = {
            type: 'resolve',
            data: data
        };
    }

    // marked as published
    this.publishedEvents[hash] = true;
    debug(topic, 'mark data as published, data:', data, ', id:', hash);

    event.type = complete ? 'complete' : event.type;

    // for future subscribers
    this.queue.push(event);
    debug(topic, 'added to queue for future subscribers', this.queue.length);
    // notify current subscribers
    this.notifyAll(event, callback);
};

tProto.once = tProto.subscribeOnce = function subscribeOnce(subscriber, callback) {
    this.subscribe(subscriber, 'once', callback);
};

/*
 * @type can be [once,all]
*/
tProto.sub = tProto.subscribe = function subscribe(subscriber, type, callback) {
    if (typeof type === 'function') {
        callback = type;
        type = undefined;
    }

    type = type || 'all';

    var _this = this;
    var topic = this.name;

    subscriber.type = type;

    _this.subscribers.push(subscriber);
    debug(topic, 'added new subscriber:', subscriber.name || 'noname', ', type:', type);

    // handle backlog for new subscriber
    var actions = this.queue.map(function map(event) {
        var data = clone(event.data);
        data && delete data.__publishedId;
        return function (next) {
            setImmediate(function () {
                debug(topic, 'backlog: notifying subscriber', subscriber.name, 'with event:', event);
                if (subscriber.type !== 'skip') {
                    subscriber(event.error, data, event.type === 'complete');
                    if (subscriber.type === 'once') {
                        subscriber.type = 'skip';
                        delete _this.subscribers;
                    }
                }
                next();
            });
        };
    });

    callback ? debug(topic, 'subscribe callback is provided') : debug(topic, 'subscribe callback is not provided');
    async.parallel(actions, function (err, result) {
        callback && callback(err, result);
        debug(topic, 'backlog: complete');
    });
};

tProto.complete = function complete(data, callback) {
    var topic = this.name;
    debug(topic, 'completing ...');
    this.pub(data, true, function (err, result) {
        debug(topic, 'completed');
        callback && callback(err, result);
    });
    this.completed = true;
};

tProto.notifyAll = function notifyAll(event, callback) {
    assert.ok(!callback || typeof callback === 'function', 'callback must be a function');
    var topic = this.name;
    var data = clone(event.data);
    data && delete data.__publishedId;

    if (this.subscribers.length) {
        debug(topic, 'notifying subscribers, total:', this.subscribers.length);

        var _this = this;
        var actions = this.subscribers.reduce(function reduce(memo, subscriber) {
            if (subscriber.type === 'skip') {
                return memo;
            }
            memo.push(function (next) {
                setImmediate(function () {
                    debug(topic, 'notifying subscriber', subscriber.name, 'with event', event);
                    subscriber.type !== 'skip' && subscriber(event.error, data, event.type === 'complete');
                    if (subscriber.type === 'once') {
                        subscriber.type = 'skip';
                        debug('removed one time subscriber, subscribers:', _this.subscribers.length);
                    }
                    next();
                });
            });
            return memo;
        }, []);

        callback ? debug(topic, 'callback is provided') : debug(topic, 'callback is not provided');
        async.parallel(actions, callback || function noop() {});
    }
    else {
        debug(topic, 'no subscribers registered');
        callback && callback();
    }
};

module.exports.Topic = Topic;

// Subscription manager
function PubSub(name, options) {
    EventEmitter.call(this);
    this.name = name;
    this.topics = {};
    this.options = options || {
        strict: true
    };
}

util.inherits(PubSub, EventEmitter);

var psProto = PubSub.prototype;

psProto.topic = function topic(topicName) {
    assert.ok(topicName, 'topic must not be undefined or empty');
    return this.topics[topicName] || (this.topics[topicName] = new Topic(topicName, this.options, this));
};

psProto.complete = function complete(data, callback) {
    var topics = this.topics;
    var actions = Object.keys(topics).map(function map(name) {
        return function (next) {
            topics[name].complete(data, next);
        };
    });

    async.parallel(actions, callback || function noop() {});
};

psProto.link = function link(fromTopic, toTopic) {
    var _this = this;
    debug('linking', 'from', fromTopic, 'to', toTopic);
    this.topic(fromTopic).sub(function topicFrom(err, data, complete) {
        _this.topic(toTopic).pub(err || data, complete, function (err) {
            err && _this.emit('error', err);
        });
    });
};

psProto.exists = function exists(topic) {
    return !!this.topics[topic];
};

psProto.reset = function reset() {
    this.topic = {};
};

module.exports.create = function create(name) {
    return new PubSub(name);
};