/* eslint max-len:0 */

/**
 * @fileoverview Disallows or enforces spaces inside of parentheses.
 * @author Jonathan Rajavuori
 * @copyright 2014 David Clark. All rights reserved.
 * @copyright 2014 Jonathan Rajavuori. All rights reserved.
 */
'use strict';

const astUtils = require('eslint/lib/ast-utils');

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

module.exports = {
  meta: {
    docs: {
      description: 'enforce consistent spacing inside parentheses',
      category: 'Stylistic Issues',
      recommended: false,
      url: 'https://eslint.org/docs/rules/space-in-parens'
    },

    fixable: 'whitespace',

    schema: [
      {
        enum: [ 'always', 'never' ]
      },
      {
        type: 'object',
        properties: {
          exceptions: {
            type: 'array',
            items: {
              enum: [ '{}', '[]', '()', 'empty' ]
            },
            uniqueItems: true
          }
        },
        additionalProperties: false
      }
    ]
  },

  create( context ) {

    const MISSING_SPACE_MESSAGE  = 'There must be a space inside this paren.';
    const REJECTED_SPACE_MESSAGE = 'There should be no spaces inside this paren.';
    const ALWAYS                 = context.options[ 0 ] === 'always';
    const exceptionsArrayOptions = ( context.options[ 1 ] && context.options[ 1 ].exceptions ) || [];
    const options                = {};
    let exceptions;

    if ( exceptionsArrayOptions.length ) {
      options.braceException   = exceptionsArrayOptions.indexOf('{}') !== -1;
      options.bracketException = exceptionsArrayOptions.indexOf('[]') !== -1;
      options.parenException   = exceptionsArrayOptions.indexOf('()') !== -1;
      options.empty            = exceptionsArrayOptions.indexOf('empty') !== -1;
    }

    /**
     * Produces an object with the opener and closer exception values
     * @param {Object} opts The exception options
     * @returns {Object} `openers` and `closers` exception values
     * @private
     */
    function getExceptions() {
      const openers = [];
      const closers = [];

      if ( options.braceException ) {
        openers.push('{');
        closers.push('}');
      }

      if ( options.bracketException ) {
        openers.push('[');
        closers.push(']');
      }

      if ( options.parenException ) {
        openers.push('(');
        closers.push(')');
      }

      if ( options.empty ) {
        openers.push(')');
        closers.push('(');
      }

      return {
        openers,
        closers
      };
    }

    //--------------------------------------------------------------------------
    // Helpers
    //--------------------------------------------------------------------------
    const sourceCode = context.getSourceCode();

    /**
     * Determines if a token is one of the exceptions for the opener paren
     * @param {Object} token The token to check
     * @returns {boolean} True if the token is one of the exceptions for the opener paren
     */
    function isOpenerException( token ) {
      return token.type === 'Punctuator' && exceptions.openers.indexOf( token.value ) >= 0;
    }

    /**
     * Determines if a token is one of the exceptions for the closer paren
     * @param {Object} token The token to check
     * @returns {boolean} True if the token is one of the exceptions for the closer paren
     */
    function isCloserException( token ) {
      return token.type === 'Punctuator' && exceptions.closers.indexOf( token.value ) >= 0;
    }

    /**
     * Determines if an opener paren should have a missing space after it
     * @param {Object} left The paren token
     * @param {Object} right The token after it
     * @returns {boolean} True if the paren should have a space
     */
    function shouldOpenerHaveSpace( left, right ) {
      if ( sourceCode.isSpaceBetweenTokens( left, right ) ) {
        return false;
      }

      if ( ALWAYS ) {
        if ( astUtils.isClosingParenToken( right ) ) {
          return false;
        }
        return !isOpenerException( right );
      }
      return isOpenerException( right );
    }

    /**
     * Determines if an closer paren should have a missing space after it
     * @param {Object} left The token before the paren
     * @param {Object} right The paren token
     * @returns {boolean} True if the paren should have a space
     */
    function shouldCloserHaveSpace( left, right ) {
      if ( astUtils.isOpeningParenToken( left ) ) {
        return false;
      }

      if ( sourceCode.isSpaceBetweenTokens( left, right ) ) {
        return false;
      }

      if ( ALWAYS ) {
        return !isCloserException( left );
      }
      return isCloserException( left );
    }

    /**
     * Determines if an opener paren should not have an existing space after it
     * @param {Object} left The paren token
     * @param {Object} right The token after it
     * @returns {boolean} True if the paren should reject the space
     */
    function shouldOpenerRejectSpace( left, right ) {
      if ( right.type === 'Line' ) {
        return false;
      }

      if ( !astUtils.isTokenOnSameLine( left, right ) ) {
        return false;
      }

      if ( !sourceCode.isSpaceBetweenTokens( left, right ) ) {
        return false;
      }

      if ( ALWAYS ) {
        return isOpenerException( right );
      }
      return !isOpenerException( right );
    }

    /**
     * Determines if an closer paren should not have an existing space after it
     * @param {Object} left The token before the paren
     * @param {Object} right The paren token
     * @returns {boolean} True if the paren should reject the space
     */
    function shouldCloserRejectSpace( left, right ) {
      if ( astUtils.isOpeningParenToken( left ) ) {
        return false;
      }

      if ( !astUtils.isTokenOnSameLine( left, right ) ) {
        return false;
      }

      if ( !sourceCode.isSpaceBetweenTokens( left, right ) ) {
        return false;
      }

      if ( ALWAYS ) {
        return isCloserException( left );
      }
      return !isCloserException( left );
    }

    //--------------------------------------------------------------------------
    // Public
    //--------------------------------------------------------------------------

    return {
      Program: function checkParenSpaces( node ) {
        exceptions = getExceptions();
        const tokens = sourceCode.tokensAndComments;

        const enclosers = {
          openers: [ '(', ...exceptions.openers.filter( opener => opener !== ')' ) ],
          closers: [ ')', ...exceptions.closers.filter( closer => closer !== '(' ) ]
        };

        const encloserExceptions = [ '=', ',' ];

        tokens.forEach( ( token, i ) => {
          const prevToken = tokens[ i - 1 ];
          const nextToken = tokens[ i + 1 ];

          if ( !astUtils.isOpeningParenToken( token ) && !astUtils.isClosingParenToken( token ) ) {
            return;
          }

          // idiomatic exceptions...

          // Don't require space before single string literal
          if ( token.value === '(' && ( nextToken.type === 'String' || nextToken.type === 'Template' ) ) {
            let closer = tokens[ i + 2 ];
            if ( closer.value === ')' ) {
              if ( sourceCode.isSpaceBetweenTokens( token, nextToken ) ) {
                context.report({
                  node,
                  loc: token.loc.end,
                  message: REJECTED_SPACE_MESSAGE,
                  fix( fixer ) {
                    return fixer.removeRange([ token.range[ 1 ], nextToken.range[ 0 ] ]);
                  }
                });
              }
              return;
            }
          }

          // Don't require space after single string literal
          if ( token.value === ')' && ( prevToken.type === 'String' || prevToken.type === 'Template' ) ) {
            let opener = tokens[ i - 2 ];
            if ( opener.value === '(' ) {
              if ( sourceCode.isSpaceBetweenTokens( prevToken, token ) ) {
                context.report({
                  node,
                  loc: token.loc.start,
                  message: REJECTED_SPACE_MESSAGE,
                  fix( fixer ) {
                    return fixer.removeRange([ prevToken.range[ 1 ], token.range[ 0 ] ]);
                  }
                });
              }
              return;
            }
          }

          // Require space before function
          if ( token.value === '(' && nextToken.value === 'function' ) {
            if ( !sourceCode.isSpaceBetweenTokens( token, nextToken ) ) {
              context.report({
                node,
                loc: token.loc.end,
                message: MISSING_SPACE_MESSAGE,
                fix( fixer ) {
                  return fixer.insertTextAfter( token, ' ' );
                }
              });
            }
            return;
          }

          // Require a space for multiple object/array literals and default parameters
          const isOpenParenAndNotEmpty = (
            token.value === '(' &&
            !astUtils.isClosingParenToken( nextToken )
          );

          if ( isOpenParenAndNotEmpty ) {
            let nestedLevels = 0;
            for ( let j = i; j < tokens.length; j++ ) {
              let tokenToCheck = tokens[ j ];

              if ( tokenToCheck.type === 'Punctuator' ) {
                if ( enclosers.openers.includes( tokenToCheck.value ) ) {
                  nestedLevels += 1;
                } else if ( enclosers.closers.includes( tokenToCheck.value ) ) {
                  nestedLevels -= 1;
                  if ( nestedLevels === 0 ) {
                    break;
                  }
                } else if ( nestedLevels === 1 && encloserExceptions.includes( tokenToCheck.value ) ) {
                  if ( !sourceCode.isSpaceBetweenTokens( token, nextToken ) ) {
                    context.report({
                      node,
                      loc: token.loc.end,
                      message: MISSING_SPACE_MESSAGE,
                      fix( fixer ) {
                        return fixer.insertTextAfter( token, ' ' );
                      }
                    });
                  }
                  return;
                }
              }
            }
          }

          // Require a space for multiple object/array literals and default parameters
          const isCloseParenAndNotEmpty = (
            token.value === ')' &&
            !astUtils.isOpeningParenToken( prevToken )
          );

          if ( isCloseParenAndNotEmpty ) {
            let nestedLevels = 0;
            for ( let j = i; j > 0; j-- ) {
              let tokenToCheck = tokens[ j ];

              if ( tokenToCheck.type === 'Punctuator' ) {
                if ( enclosers.closers.includes( tokenToCheck.value ) ) {
                  nestedLevels += 1;
                } else if ( enclosers.openers.includes( tokenToCheck.value ) ) {
                  nestedLevels -= 1;
                  if ( nestedLevels === 0 ) {
                    break;
                  }
                } else if ( nestedLevels ===  1 && encloserExceptions.includes( tokenToCheck.value ) ) {
                  if ( !sourceCode.isSpaceBetweenTokens( prevToken, token ) ) {
                    context.report({
                      node,
                      loc: token.loc.start,
                      message: MISSING_SPACE_MESSAGE,
                      fix( fixer ) {
                        return fixer.insertTextBefore( token, ' ' );
                      }
                    });
                  }
                  return;
                }
              }
            }
          }

          // end idiomatic exceptions

          if ( token.value === '(' && shouldOpenerHaveSpace( token, nextToken ) ) {
            context.report({
              node,
              loc: token.loc.end,
              message: MISSING_SPACE_MESSAGE,
              fix( fixer ) {
                return fixer.insertTextAfter( token, ' ' );
              }
            });
          } else if ( token.value === '(' && shouldOpenerRejectSpace( token, nextToken ) ) {
            context.report({
              node,
              loc: token.loc.end,
              message: REJECTED_SPACE_MESSAGE,
              fix( fixer ) {
                return fixer.removeRange([ token.range[ 1 ], nextToken.range[ 0 ] ]);
              }
            });
          } else if ( token.value === ')' && shouldCloserHaveSpace( prevToken, token ) ) {
            context.report({
              node,
              loc: token.loc.start,
              message: MISSING_SPACE_MESSAGE,
              fix( fixer ) {
                return fixer.insertTextBefore( token, ' ' );
              }
            });
          } else if ( token.value === ')' && shouldCloserRejectSpace( prevToken, token ) ) {
            context.report({
              node,
              loc: token.loc.start,
              message: REJECTED_SPACE_MESSAGE,
              fix( fixer ) {
                return fixer.removeRange([ prevToken.range[ 1 ], token.range[ 0 ] ]);
              }
            });
          }
        });
      }
    };
  }
};


(function( global ) {

  'use strict';

  var $$iterator, ArrayIteratorPrototype;

  // don't trample native promises if they exist
  if ( 'Promise' in global && typeof global.Promise.all === 'function' ) {
    if ( typeof module === 'object' && typeof module.exports === 'object' ) {
      module.exports = global.Promise;
    }
    return;
  }

  // set a value as non-configurable and non-enumerable
  function defineInternal ( obj, key, val ) {
    Object.defineProperty(obj, key, {
      configurable: false,
      enumerable: false,
      writable: true,
      value: val
    });
  }

  // set a value as configurable and non-enumerable
  function defineConfigurable ( obj, key, val ) {
    Object.defineProperty(obj, key, {
      configurable: true,
      enumerable: false,
      writable: true,
      value: val
    });
  }

  // From the ES6 spec (http://people.mozilla.org/~jorendorff/es6-draft.html)

  // 6 ECMAScript Data Types and Values
  function Type ( x ) {
    switch ( typeof x ) {
      case 'undefined':
      case 'boolean':
      case 'string':
      case 'number':
        return typeof x;
      default:
        if ( x === null ) {
          return 'null';
        }
        if ( typeof Symbol === 'function' && x instanceof Symbol ) {
          return 'symbol';
        }
        return 'object';
    }
  }

  // 6.1.5.1 Well-Known Symbols (iterator key)
  $$iterator = typeof Symbol !== 'undefined' && Symbol.iterator ||
    '_shim_iterator_';

  // 7.1.4 ToInteger
  function ToInteger ( argument ) {
    var number = +argument;
    if ( number !== number ) {
      return 0;
    }
    if ( number === 0 || number === Infinity || number === -Infinity ) {
      return number;
    }
    return ( number >= 0 ? 1 : -1 ) * Math.floor(Math.abs(number));
  }

  // 7.1.12 ToString
  function ToString ( argument ) {
    return typeof argument === 'string' ? argument : String(argument);
  }

  // 7.1.13 ToObject
  function ToObject ( argument ) {
    if ( argument == null ) {
      throw TypeError();
    }
    switch ( typeof argument ) {
      case 'boolean':
      case 'number':
      case 'string':
        return Object(argument);
      default:
        return argument;
    }
  }

  // 7.1.15 ToLength
  function ToLength ( argument ) {
    var len = ToInteger(argument);
    return len <= 0 ? 0 : Math.min(len, Math.pow(2, 53) - 1);
  }

  // 7.2.2 IsCallable
  function IsCallable ( argument ) {
    return typeof argument === 'function';
  }

  // 7.2.3 SameValue( x, y )
  function SameValue ( x, y ) {
    if ( typeof x !== typeof y ) {
      return false;
    }
    if ( Type(x) === 'undefined' ) {
      return true;
    }
    if ( Type(x) === 'number' ) {
      if ( x !== x && y !== y ) {
        return true;
      }
      if ( x === 0 ) {
        return 1 / x === 1 / y;
      }
    }
    return x === y;
  }

  // 7.2.5 IsConstructor
  // this is an ES6 abstract operation, and it's not really
  // possible in JS, but this should be good enough
  function IsConstructor ( obj ) {
    return typeof obj === 'function';
  }

  // 7.4.1 GetIterator ( obj )
  // not a real shim, but it works
  function GetIterator ( obj ) {
    var iterator = obj[$$iterator]();
    if ( Type(obj) !== 'object' ) {
      throw TypeError();
    }
    return iterator;
  }

  // 7.4.2 IteratorNext ( iterator, value )
  function IteratorNext ( iterator, value ) {
    var result = iterator.next(value);
    if ( Type(result) !== 'object' ) {
      throw TypeError();
    }
    return result;
  }

  // 7.4.3 IteratorComplete ( iterResult )
  function IteratorComplete ( iterResult ) {
    if ( Type(iterResult) !== 'object' ) {
      throw TypeError();
    }
    return Boolean(iterResult.done);
  }

  // 7.4.4 IteratorValue ( iterResult )
  function IteratorValue ( iterResult ) {
    if ( Type(iterResult) !== 'object' ) {
      throw TypeError();
    }
    return iterResult.value;
  }

  // 7.4.5 IteratorStep ( iterator )
  function IteratorStep ( iterator ) {
    var result = IteratorNext(iterator);
    return IteratorComplete(result) === true ? false : result;
  }

  // 7.4.6 CreateIterResultObject ( value, done )
  function CreateIterResultObject ( value, done ) {
    if ( Type(done) !== 'boolean' ) {
      throw TypeError();
    }
    return {value: value, done: done};
  }

  // 8.4.1 EnqueueTask ( queueName, task, arguments)
  // not a real shim, but good enough
  function EnqueueTask ( task, args ) {
    if ( typeof setImmediate === 'function' ) {
      setImmediate(function() {
        task.apply(null, args);
      });
    } else {
      setTimeout(function() {
        task.apply(null, args);
      }, 0);
    }
  }

  // 22.1.5.1 CreateArrayIterator Abstract Operation
  function CreateArrayIterator ( array, kind ) {
    var O = ToObject(array),
      iterator = Object.create(ArrayIteratorPrototype);
    defineInternal(iterator, '[[IteratedObject]]', O);
    defineInternal(iterator, '[[ArrayIteratorNextIndex]]', 0);
    defineInternal(iterator, '[[ArrayIteratorKind]]', kind);
    return iterator;
  }

  // 22.1.3.29 Array.prototype.values ( )
  if ( typeof Array.prototype.values !== 'function' ) {
    defineConfigurable( Array.prototype, 'values', function values() {
      var O = ToObject(this);
      return CreateArrayIterator(O, 'value');
    });
  }

  // 22.1.3.30 Array.prototype [ @@iterator ] ( )
  if ( typeof Array.prototype[ $$iterator ] === 'undefined' ) {
    defineConfigurable( Array.prototype, $$iterator, Array.prototype.values );
  }

  // 22.1.5.2 The %ArrayIteratorPrototype% Object
  ArrayIteratorPrototype = {};

  // 22.1.5.2.1 %ArrayIteratorPrototype%. next()
  ArrayIteratorPrototype.next = function () {
    var O = this, a, index, itemKind, lenValue, len,
      elementKey, elementValue, result;
    if ( Type(O) !== 'object' ) {
      throw TypeError();
    }
    a = O['[[IteratedObject]]'];
    if ( Type(a) === 'undefined' ) {
      return CreateIterResultObject(undefined, true);
    }
    index = O['[[ArrayIteratorNextIndex]]'];
    itemKind = O['[[ArrayIteratorKind]]'];
    lenValue = a.length;
    len = ToLength(lenValue);
    if ( index >= len ) {
      defineInternal(O, '[[IteratedObject]]', undefined);
      return CreateIterResultObject(undefined, true);
    }
    defineInternal(O, '[[ArrayIteratorNextIndex]]', index + 1);
    if ( itemKind.indexOf('value') !== -1 ) {
      elementKey = ToString(index);
      elementValue = a[elementKey];
    }
    if ( itemKind.indexOf('key+value') !== -1 ) {
      result = [index, elementValue];
      return CreateIterResultObject(result, false);
    } else if ( itemKind.indexOf('key') !== -1 ) {
      return CreateIterResultObject(index, false);
    }
    if ( itemKind.indexOf('value') === -1 ) {
      throw TypeError();
    }
    return CreateIterResultObject(elementValue, false);
  };

  // 22.1.5.2.2 %ArrayIteratorPrototype% [ @@iterator ] ( )
  ArrayIteratorPrototype[$$iterator] = function () {
    return this;
  };

  // 25.4.1.1.1 IfAbruptRejectPromise (value, capability)
  function IfAbruptRejectPromise ( value, capability ) {
    try {
      capability['[[Reject]]'].call(undefined, [value]);
    } catch ( e ) {
      return e;
    }
    return capability;
  }

  // 25.4.1.3 CreateRejectFunction ( promise )
  function CreateRejectFunction ( promise ) {
    var reject = new PromiseReject();
    defineInternal(reject, '[[Promise]]', promise);
    return reject;
  }

  // 25.4.1.3.1 Promise Reject Functions
  function PromiseReject () {
    return function F ( reason ) {
      var promise = F['[[Promise]]'], reactions;
      if ( Type(promise) !== 'object' ) {
        throw TypeError();
      }
      if ( promise['[[PromiseStatus]]'] !== 'unresolved' ) {
        return undefined;
      }
      reactions = promise['[[PromiseRejectReactions]]'];
      defineInternal(promise, '[[PromiseResult]]', reason);
      defineInternal(promise, '[[PromiseResolveReactions]]', undefined);
      defineInternal(promise, '[[PromiseRejectReactions]]', undefined);
      defineInternal(promise, '[[PromiseStatus]]', 'has-rejection');
      return TriggerPromiseReactions(reactions, reason);
    };
  }

  // 25.4.1.4 CreateRejectFunction ( promise )
  function CreateResolveFunction ( promise ) {
    var resolve = new PromiseResolve();
    defineInternal(resolve, '[[Promise]]', promise);
    return resolve;
  }

  // 25.4.1.4.1 Promise Resolve Functions
  function PromiseResolve () {
    return function F ( resolution ) {
      var promise = F['[[Promise]]'], reactions;
      if ( Type(promise) !== 'object' ) {
        throw TypeError();
      }
      if ( promise['[[PromiseStatus]]'] !== 'unresolved' ) {
        return undefined;
      }
      reactions = promise['[[PromiseResolveReactions]]'];
      defineInternal(promise, '[[PromiseResult]]', resolution);
      defineInternal(promise, '[[PromiseResolveReactions]]', undefined);
      defineInternal(promise, '[[PromiseRejectReactions]]', undefined);
      defineInternal(promise, '[[PromiseStatus]]', 'has-resolution');
      return TriggerPromiseReactions(reactions, resolution);
    };
  }

  // 25.4.1.5 NewPromiseCapability ( C )
  function NewPromiseCapability ( C ) {
    var promise;
    if ( !IsConstructor(C) ) {
      throw TypeError();
    }
    try {
      promise = Object.create(C.prototype);
    } catch ( e ) {
      return e;
    }
    return CreatePromiseCapabilityRecord(promise, C);
  }

  // 25.4.1.5.1 CreatePromiseCapabilityRecord( promise, constructor )
  function CreatePromiseCapabilityRecord ( promise, constructor ) {
    var promiseCapability = {}, executor, constructorResult;
    defineInternal(promiseCapability, '[[Promise]]', promise);
    defineInternal(promiseCapability, '[[Resolve]]', undefined);
    defineInternal(promiseCapability, '[[Reject]]', undefined);
    executor = new GetCapabilitiesExecutor();
    defineInternal(executor, '[[Capability]]', promiseCapability);
    try {
      constructorResult = constructor.call(promise, executor);
    } catch ( e ) {
      return e;
    }
    if ( !IsCallable(promiseCapability['[[Resolve]]']) ) {
      throw TypeError();
    }
    if ( !IsCallable(promiseCapability['[[Reject]]']) ) {
      throw TypeError();
    }
    if ( typeof constructorResult === 'object' &&
      !SameValue(promise, constructorResult) ) {
      throw TypeError();
    }
    return promiseCapability;
  }

  // 25.4.1.5.2 GetCapabilitiesExecutor Functions
  function GetCapabilitiesExecutor () {
    return function F ( resolve, reject ) {
      var promiseCapability = F['[[Capability]]'];
      if ( Type(promiseCapability['[[Resolve]]']) !== 'undefined' ) {
        throw TypeError();
      }
      if ( Type(promiseCapability['[[Reject]]']) !== 'undefined' ) {
        throw TypeError();
      }
      defineInternal(promiseCapability, '[[Resolve]]', resolve);
      defineInternal(promiseCapability, '[[Reject]]', reject);
    };
  }

  // 25.4.1.6 IsPromise ( x )
  function IsPromise ( x ) {
    if ( Type(x) !== 'object' ) {
      return false;
    }
    if ( Type(x['[[PromiseStatus]]']) === 'undefined' ) {
      return false;
    }
    return true;
  }

  // 25.4.1.7 TriggerPromiseReactions ( reactions, argument )
  function TriggerPromiseReactions ( reactions, argument ) {
    reactions.forEach(function( reaction ) {
      EnqueueTask(PromiseReactionTask, [reaction, argument]);
    });
  }

  // 25.4.1.8 UpdatePromiseFromPotentialThenable ( x, promiseCapability )
  function UpdatePromiseFromPotentialThenable ( x, promiseCapability ) {
    var then, rejectResult, thenCallResult;
    if ( Type(x) !== 'object' ) {
      return 'not a thenable';
    }
    try {
      then = x.then;
    } catch ( e ) {
      rejectResult = promiseCapability['[[Reject]]'].call(undefined, e);
      return null;
    }
    if ( !IsCallable(then) ) {
      return 'not a thenable';
    }
    try {
      thenCallResult = then.call(x, promiseCapability['[[Resolve]]'],
        promiseCapability['[[Reject]]']
      );
    } catch ( e ) {
      rejectResult = promiseCapability['[[Reject]]'].call(undefined, e);
      return null;
    }
    return null;
  }

  // 25.4.2.1 PromiseReactionTask( reaction, argument )
  function PromiseReactionTask ( reaction, argument ) {
    var promiseCapability = reaction['[[Capabilities]]'],
      handler = reaction['[[Handler]]'],
      handlerResult, selfResolutionError, updateResult;
    try {
      handlerResult = handler.call(undefined, argument);
    } catch ( e ) {
      return promiseCapability['[[Reject]]'].call(undefined, e);
    }
    if ( SameValue(handlerResult, promiseCapability['[[Promise]]']) ) {
      selfResolutionError = TypeError();
      return promiseCapability['[[Reject]]']
        .call(undefined, selfResolutionError);
    }
    updateResult = UpdatePromiseFromPotentialThenable(handlerResult,
      promiseCapability
    );
    if ( updateResult === 'not a thenable' ) {
      return promiseCapability['[[Resolve]]'].call(undefined, handlerResult);
    }
    return undefined;
  }

  // 25.4.3.1 Promise ( executor )
  function Promise ( executor ) {
    var promise = this;
    if ( !IsCallable(executor) ) {
      throw TypeError('Invalid executor');
    }
    if ( Type(promise) !== 'object' ) {
      throw TypeError('Invalid promise');
    }
    if ( Type(promise['[[PromiseStatus]]']) !== 'undefined' ) {
      throw TypeError();
    }
    defineInternal(this, '[[PromiseConstructor]]', Promise);
    return InitializePromise(promise, executor);
  }

  // 25.4.3.1.1 InitializePromise( promise, executor )
  function InitializePromise ( promise, executor) {
    var resolve, reject, completion, status;
    if ( Type(promise['[[PromiseStatus]]']) !== 'undefined' ) {
      throw TypeError();
    }
    if ( !IsCallable(executor) ) {
      throw TypeError();
    }
    defineInternal(promise, '[[PromiseStatus]]', 'unresolved');
    defineInternal(promise, '[[PromiseResolveReactions]]', []);
    defineInternal(promise, '[[PromiseRejectReactions]]', []);
    resolve = CreateResolveFunction(promise);
    reject = CreateRejectFunction(promise);
    try {
      completion = executor.call(undefined, resolve, reject);
    } catch ( e ) {
      try {
        status = reject.call(undefined, e);
      } catch ( e ) {
        return e;
      }
    }
    return promise;
  }

  // 25.4.4.1 Promise.all ( iterable )
  Promise.all = function ( iterable ) {
    var C = this, promiseCapability, iterator, values,
      remainingElementsCount, index, next, resolveResult,
      nextValue, nextPromise, resolveElement, result;
    try {
      promiseCapability = NewPromiseCapability(C);
    } catch ( e ) {
      return e;
    }
    try {
      iterator = GetIterator(iterable);
    } catch ( e ) {
      return IfAbruptRejectPromise(e, promiseCapability);
    }
    values = [];
    remainingElementsCount = {'[[value]]': 0};
    index = 0;
    while ( true ) {
      try {
        next = IteratorStep(iterator);
      } catch ( e ) {
        return IfAbruptRejectPromise(e, promiseCapability);
      }
      if ( next === false ) {
        if ( index === 0 ) {
          try {
            resolveResult = promiseCapability['[[Resolve]]']
              .call(undefined, values);
          } catch ( e ) {
            return e;
          }
        }
        return promiseCapability['[[Promise]]'];
      }
      try {
        nextValue = IteratorValue(next);
      } catch ( e ) {
        return IfAbruptRejectPromise(e, promiseCapability);
      }
      try {
        nextPromise = C.cast(nextValue);
      } catch ( e ) {
        return IfAbruptRejectPromise(e, promiseCapability);
      }
      resolveElement = new PromiseAllResolveElementFunction();
      defineInternal(resolveElement, '[[Index]]', index);
      defineInternal(resolveElement, '[[Values]]', values);
      defineInternal(resolveElement, '[[Capabilities]]', promiseCapability);
      defineInternal(resolveElement, '[[RemainingElements]]',
        remainingElementsCount
      );
      try {
        result = nextPromise.then(resolveElement,
          promiseCapability['[[Reject]]']
        );
      } catch ( e ) {
        return IfAbruptRejectPromise(e, promiseCapability);
      }
      index++;
      remainingElementsCount['[[value]]']++;
    }
  };

  // 25.4.4.1.1 Promise.all Resolve Element Functions
  function PromiseAllResolveElementFunction () {
    return function F ( x ) {
      var index = F['[[Index]]'],
        values = F['[[Values]]'],
        promiseCapability = F['[[Capabilities]]'],
        remainingElementsCount = F['[[RemainingElements]]'];
      try {
        values[index] = x;
      } catch ( e ) {
        return IfAbruptRejectPromise(e, promiseCapability);
      }
      remainingElementsCount['[[value]]']--;
      if ( remainingElementsCount['[[value]]'] === 0 ) {
        promiseCapability['[[Resolve]]'].call(undefined, values);
      }
      return undefined;
    };
  }

  // 25.4.4.2 Promise.cast ( x )
  Promise.cast = function ( x ) {
    var C = this,
      promiseCapability,
      resolveResult,
      constructor;
    if ( IsPromise(x) ) {
      constructor = x['[[PromiseConstructor]]'];
      if ( SameValue(constructor, C) ) {
        return x;
      }
    }
    try {
      promiseCapability = NewPromiseCapability(C);
    } catch ( e ) {
      return e;
    }
    try {
      resolveResult = promiseCapability['[[Resolve]]'].call(undefined, x);
    } catch ( e ) {
      return e;
    }
    return promiseCapability['[[Promise]]'];
  };

  // 25.4.4.4 Promise.race ( iterable )
  Promise.race = function ( iterable ) {
    var C = this, promiseCapability, iterator, nextValue, nextPromise, next;
    try {
      promiseCapability = NewPromiseCapability(C);
    } catch ( e ) {
      return e;
    }
    try {
      iterator = GetIterator(iterable);
    } catch ( e ) {
      return IfAbruptRejectPromise(e, promiseCapability);
    }
    while ( true ) {
      try {
        next = IteratorStep(iterator);
      } catch ( e ) {
        return IfAbruptRejectPromise(e, promiseCapability);
      }
      if ( next === false ) {
        return promiseCapability['[[Promise]]'];
      }
      try {
        nextValue = IteratorValue(next);
      } catch ( e ) {
        return IfAbruptRejectPromise(e, promiseCapability);
      }
      try {
        nextPromise = C.cast(nextValue);
      } catch ( e ) {
        return IfAbruptRejectPromise(e, promiseCapability);
      }
      try {
        nextPromise.then(promiseCapability['[[Resolve]]'],
          promiseCapability['[[Reject]]']
        );
      } catch ( e ) {
        return IfAbruptRejectPromise(e, promiseCapability);
      }
    }
  };

  // 25.4.4.5 Promise.reject ( r )
  Promise.reject = function ( r ) {
    var C = this, promiseCapability, rejectResult;
    try {
      promiseCapability = NewPromiseCapability(C);
    } catch ( e ) {
      return e;
    }
    try {
      rejectResult = promiseCapability['[[Reject]]'].call(undefined, r);
    } catch ( e ) {
      return e;
    }
    return promiseCapability['[[Promise]]'];
  };

  // 25.4.4.6 Promise.resolve ( x )
  Promise.resolve = function ( x ) {
    var C = this, promiseCapability, resolveResult;
    try {
      promiseCapability = NewPromiseCapability(C);
    } catch ( e ) {
      return e;
    }
    try {
      resolveResult = promiseCapability['[[Resolve]]'].call(undefined, x);
    } catch ( e ) {
      return e;
    }
    return promiseCapability['[[Promise]]'];
  };

  // 25.4.5.1 Promise.prototype.catch ( onRejected )
  Promise.prototype['catch'] = function ( onRejected ) {
    var promise = this;
    return promise.then(undefined, onRejected);
  };

  // 25.4.5.3 Promise.prototype.then ( onFulfilled , onRejected )
  Promise.prototype.then = function ( onFulfilled , onRejected ) {
    var promise = this,
      C, promiseCapability, rejectionHandler, fulfillmentHandler,
      resolutionHandler, resolveReaction, rejectReaction, resolution;
    if ( !IsPromise(promise) ) {
      throw TypeError();
    }
    try {
      C = promise.constructor;
    } catch ( e ) {
      return e;
    }
    try {
      promiseCapability = NewPromiseCapability(C);
    } catch ( e ) {
      return e;
    }
    if ( IsCallable(onRejected) ) {
      rejectionHandler = onRejected;
    } else {
      rejectionHandler = new ThrowerFunction();
    }
    if ( IsCallable(onFulfilled) ) {
      fulfillmentHandler = onFulfilled;
    } else {
      fulfillmentHandler = new IdentityFunction();
    }
    resolutionHandler = new PromiseResolutionHandlerFunction();
    defineInternal(resolutionHandler, '[[Promise]]', promise);
    defineInternal(resolutionHandler, '[[FulfillmentHandler]]',
      fulfillmentHandler
    );
    defineInternal(resolutionHandler, '[[RejectionHandler]]',
      rejectionHandler
    );
    resolveReaction = {
      '[[Capabilities]]': promiseCapability,
      '[[Handler]]': resolutionHandler
    };
    rejectReaction = {
      '[[Capabilities]]': promiseCapability,
      '[[Handler]]': rejectionHandler
    };
    if ( promise['[[PromiseStatus]]'] === 'unresolved' ) {
      promise['[[PromiseResolveReactions]]'].push(resolveReaction);
      promise['[[PromiseRejectReactions]]'].push(rejectReaction);
    }
    if ( promise['[[PromiseStatus]]'] === 'has-resolution' ) {
      resolution = promise['[[PromiseResult]]'];
      EnqueueTask(PromiseReactionTask, [resolveReaction, resolution]);
    }
    if ( promise['[[PromiseStatus]]'] === 'has-rejection' ) {
      resolution = promise['[[PromiseResult]]'];
      EnqueueTask(PromiseReactionTask, [rejectReaction, resolution]);
    }
    return promiseCapability['[[Promise]]'];
  };

  // 25.4.5.3.1 Identity Functions
  function IdentityFunction () {
    return function F ( x ) {
      return x;
    };
  }

  // 25.4.5.3.2 PromiseResolutionHandlerFunctions
  function PromiseResolutionHandlerFunction () {
    return function F ( x ) {
      var promise = F['[[Promise]]'],
        fulfillmentHandler = F['[[FulfillmentHandler]]'],
        rejectionHandler = F['[[RejectionHandler]]'],
        selfResolutionError, C, promiseCapability, updateResult;
      if ( SameValue(x, promise) ) {
        selfResolutionError = TypeError();
        return rejectionHandler.call(undefined, selfResolutionError);
      }
      C = promise['[[PromiseConstructor]]'];
      try {
        promiseCapability = NewPromiseCapability(C);
      } catch ( e ) {
        return e;
      }
      try {
        updateResult = UpdatePromiseFromPotentialThenable(x,
          promiseCapability
        );
      } catch ( e ) {
        return e;
      }
      if ( updateResult !== 'not a thenable') {
        return promiseCapability['[[Promise]]'].then(fulfillmentHandler,
          rejectionHandler
        );
      }
      return fulfillmentHandler.call(undefined, x);
    };
  }

  // 25.4.5.3.3 Thrower Functions
  function ThrowerFunction () {
    return function F ( e ) {
      throw e;
    };
  }

  // export the Promise constructor
  if ( typeof module === 'object' && typeof module.exports === 'object' ) {
    module.exports = Promise;
  } else {
    global.Promise = Promise;
  }

}( typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : this ) ); //jshint ignore:line


/*! trigger - v1.1.1 - 2013-05-22
* Copyright (c) 2013 ESHA Research; Licensed MIT, GPL */
;(function(window, document, $) {

    function trigger(){ return _.manual.apply(this, arguments); }
    trigger.add = function(){ return _.add.apply(this, arguments); };

    var _ = trigger._ = {
        version: "1.1.1",
        prefix: 'data-',// will change to '' if !<html data-trigger="true" ...>
        splitRE: / (?![^\[\]]*\])+/g,
        noClickRE: /^(select|textarea)$/,
        noEnterRE: /^(textarea|button)$/,
        buttonRE: /^(submit|button|reset)$/,
        boxRE: /^(checkbox|radio)$/,
        // custom event stuff
        all: function(target, sequence, t) {//t==trigger (usually a 'click'ish event)
            sequence = sequence.split(_.splitRE);
            for (var i=0, e, props; i<sequence.length && (!e||!e.isSequenceStopped()); i++) {
                props = _.parse(sequence[i]);
                if (props) {
                    props.sequence = sequence;
                    if (e){ props.previousEvent = e; }
                    if (t){ props.trigger = t; }
                    _.controls(props, target, sequence, i);
                    e = _.event(target, props);
                }
            }
            return e;
        },
        controls: function(props, target, sequence, i, stopped) {
            props.resumeSequence = function(t) {
                if (stopped) {
                    stopped = false;
                    _.all(target, sequence.slice(i+1).join(' '), t||props.trigger);
                }
            };
            props.stopSequence = function(promise) {
                if (stopped !== false) {// after resume, can't stop again
                    stopped = true;
                    return promise && promise.then(this.resumeSequence);
                }
            };
            props.isSequenceStopped = function(){ return !!stopped; };
        },
        parse: function(type) {
            if (!type){ return; }
            var e = { text: type },
                colon, pound, bracket;
            if ((pound = type.indexOf('#', type.indexOf(']'))) > 0) {
                e.tags = type.substring(pound+1).split('#');
                type = type.substring(0, pound);
                for (var i=0,m=e.tags.length; i<m; i++) {
                    e[e.tags[i]] = true;
                }
            }
            if ((bracket = type.indexOf('[')) > 0) {
                e.constants = JSON.parse(type.substring(bracket).replace(/'/g,'"'));
                type = type.substring(0, bracket);
            }
            if ((colon = type.indexOf(':')) > 0) {
                e.category = type.substring(0, colon);
                type = type.substring(colon+1);
            }
            e.type = type;
            return e;
        },
        event: function(target, props) {
            var e = document.createEvent('HTMLEvents');
            e.initEvent(props.type, true, true);
            for (var key in props) {// copy props w/ext hook
                e[_.prop(key)] = props[key];
            }
            target.dispatchEvent(e);
            return e;
        },
        // native DOM and event stuff
        listen: function(e) {
            var el = e.target, attr,
                type = e.type,
                key = type.indexOf('key') === 0 ? e.which || e.keyCode || '' : '',
                special = _.special[type+key];
            if (el && special) {
                type = special(e, el, el.nodeName.toLowerCase());
                if (!type){ return; }// special said to ignore it!
            }
            el = _.find(el, type),
            attr = _.attr(el, type);
            if (attr) {
                _.all(el, attr, e);
                if (type === 'click' && !_.boxRE.test(el.type)) {
                    e.preventDefault();
                }
            }
        },
        on: function(type, fn) {
            if (!_.on[type]) {// no dupes!
                _.on[type] = fn;
                if ($ && $.event){ $(document).on(type, fn); }// allows non-native triggers
                else { document.addEventListener(type, fn); }
            }
        },
        attr: function(el, type) {
            return (el && el.getAttribute && el.getAttribute(_.prefix+type))||'';
        },
        find: function(el, type) {
            return !el || _.attr(el, type) ? el : _.find(el.parentNode, type);
        },
        // special event handlers
        special: {
            click: function(e, el, name) {// if click attr or not editable (i.e. not focusing click)
                return (_.attr(el, e.type) ||
                        (!el.isContentEditable && !_.noClickRE.test(name) &&
                         (name !== 'input' || _.buttonRE.test(el.type)))) &&
                       'click';
            },
            keyup13: function(e, el, name) {// if fitting attr or not already accessible (i.e. enter !== click)
                return (_.attr(el, 'key-enter') && 'key-enter') ||// enter sometimes fits better than click
                       (_.attr(el, e.type) && e.type) ||// return keyup type for keyup attr
                       (!el.isContentEditable && !_.noEnterRE.test(name) &&
                        !(name === 'a' && el.getAttribute('href')) && _.buttonRE.test(el.type)) &&
                       'click';// convert to a click
            }
        },
        // extension hooks
        manual: function(el, sequence) {
            if (typeof el === "string"){ sequence = el; el = document; }
            return _.all(el, sequence || _.attr(el, 'click'));
        },
        add: function() {
            for (var i=0,m=arguments.length; i<m; i++){ _.on(arguments[i], _.listen); }
        },
        prop: function(prop) {// add custom properties to jQuery's event system
            if ($ && $.event && !_.prop[prop]) {
                _.prop[prop] = true;
                $.event.props.push(prop);
            }
            return prop;
        },
        init: function() {
            _.add('click','keyup');
            var el = document.documentElement;
            if (_.attr(el, 'trigger') !== 'true'){ _.prefix = ''; }
            _.add.apply(_, _.attr(el, 'trigger-add').split(' '));
        }
    };
    // connect to the outside world
    _.init();
    if (typeof module !== 'undefined' && module.exports) {
        module.exports = trigger;
    } else {
        window.trigger = trigger;
    }
})(window, document, window.jQuery);


/* eslint max-len:0 */

/**
 * @fileoverview Disallows or enforces spaces inside computed properties.
 * @author Jamund Ferguson
 * @copyright 2015 Jamund Ferguson. All rights reserved.
 */

'use strict';

let astUtils = require('eslint/lib/ast-utils');

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

module.exports = function( context ) {
  let sourceCode = context.getSourceCode();
  let propertyNameMustBeSpaced = context.options[ 0 ] === 'always'; // default is "never"

  //--------------------------------------------------------------------------
  // Helpers
  //--------------------------------------------------------------------------

  /**
   * Reports that there shouldn't be a space after the first token
   * @param {ASTNode} node - The node to report in the event of an error.
   * @param {Token} token - The token to use for the report.
   * @param {Token} tokenAfter - The token after `token`.
   * @returns {void}
   */

  function reportNoBeginningSpace( node, token, tokenAfter ) {
    context.report({
      node: node,
      loc: token.loc.start,
      message: 'There should be no space after \'' + token.value + '\'',
      fix: function( fixer ) {
        return fixer.removeRange([ token.range[ 1 ], tokenAfter.range[ 0 ] ]);
      }
    });
  }

  /**
   * Reports that there shouldn't be a space before the last token
   * @param {ASTNode} node - The node to report in the event of an error.
   * @param {Token} token - The token to use for the report.
   * @param {Token} tokenBefore - The token before `token`.
   * @returns {void}
   */

  function reportNoEndingSpace( node, token, tokenBefore ) {
    context.report({
      node: node,
      loc: token.loc.start,
      message: 'There should be no space before \'' + token.value + '\'',
      fix: function( fixer ) {
        return fixer.removeRange([ tokenBefore.range[ 1 ], token.range[ 0 ] ]);
      }
    });
  }

  /**
   * Reports that there should be a space after the first token
   * @param {ASTNode} node - The node to report in the event of an error.
   * @param {Token} token - The token to use for the report.
   * @returns {void}
   */

  function reportRequiredBeginningSpace( node, token ) {
    context.report({
      node: node,
      loc: token.loc.start,
      message: 'A space is required after \'' + token.value + '\'',
      fix: function( fixer ) {
        return fixer.insertTextAfter( token, ' ' );
      }
    });
  }

  /**
   * Reports that there should be a space before the last token
   * @param {ASTNode} node - The node to report in the event of an error.
   * @param {Token} token - The token to use for the report.
   * @returns {void}
   */

  function reportRequiredEndingSpace( node, token ) {
    context.report({
      node: node,
      loc: token.loc.start,
      message: 'A space is required before \'' + token.value + '\'',
      fix: function( fixer ) {
        return fixer.insertTextBefore( token, ' ' );
      }
    });
  }

  /**
   * Returns a function that checks the spacing of a node on the property name
   * that was passed in.
   * @param {String} propertyName The property on the node to check for spacing
   * @returns {Function} A function that will check spacing on a node
   */

  function checkSpacing( propertyName ) {
    return function( node ) {
      if ( !node.computed ) {
        return;
      }

      let property = node[ propertyName ];

      let before = context.getTokenBefore( property );
      let first = context.getFirstToken( property );
      let last = context.getLastToken( property );
      let after = context.getTokenAfter( property );

      // idiomatic exceptions
      if ( first === last && ( first.type === 'String' || first.type === 'Template' ) ) {
        if ( sourceCode.isSpaceBetweenTokens( before, first ) ) {
          reportNoBeginningSpace( node, before, first );
        }
        if ( sourceCode.isSpaceBetweenTokens( last, after ) ) {
          reportNoEndingSpace( node, after, last );
        }
        return;
      }

      if ( astUtils.isTokenOnSameLine( before, first ) ) {
        if ( propertyNameMustBeSpaced ) {
          if ( !sourceCode.isSpaceBetweenTokens( before, first ) && astUtils.isTokenOnSameLine( before, first ) ) {
            reportRequiredBeginningSpace( node, before );
          }
        } else {
          if ( sourceCode.isSpaceBetweenTokens( before, first ) ) {
            reportNoBeginningSpace( node, before, first );
          }
        }
      }

      if ( astUtils.isTokenOnSameLine( last, after ) ) {
        if ( propertyNameMustBeSpaced ) {
          if ( !sourceCode.isSpaceBetweenTokens( last, after ) && astUtils.isTokenOnSameLine( last, after ) ) {
            reportRequiredEndingSpace( node, after );
          }
        } else {
          if ( sourceCode.isSpaceBetweenTokens( last, after ) ) {
            reportNoEndingSpace( node, after, last );
          }
        }
      }
    };
  }

  //--------------------------------------------------------------------------
  // Public
  //--------------------------------------------------------------------------

  return {
    Property: checkSpacing('key'),
    MemberExpression: checkSpacing('property')
  };

};

module.exports.schema = [
  {
    enum: [ 'always', 'never' ]
  }
];


import debug from 'debug'
import fetch from 'isomorphic-fetch'
import Leaflet from 'leaflet'
import Transitive from 'transitive-js'
import 'leaflet-transitivelayer'

import Browsochrones from './lib'

debug.enable('*')

Leaflet.Icon.Default.imagePath = 'node_modules/leaflet/dist/images/'

const b = new Browsochrones({webpack: false}) // set to true if using webpack to bundle
const baseUrl = 'https://dz69bcpxxuhn6.cloudfront.net/indy-baseline-v6'
const gridUrl = 'https://dz69bcpxxuhn6.cloudfront.net/indy-baseline-z9/intgrids'
const cutoff = 60 // minutes
const map = Leaflet.map('map', {
  center: [39.766667, -86.15],
  maxZoom: 18,
  inertia: false, // recommended when using a transitive layer
  zoom: 12,
  zoomAnimation: false
})
Leaflet.tileLayer('https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png', {
  attribution: '© OpenStreetMap, © CARTO'
}).addTo(map)
const lonlat = {lat: 39.766667, lon: -86.15}
Leaflet.marker(lonlat).addTo(map)

const LAT_DEGREE = 111111 // meters
const MIN_DISTANCE = 250 // meters
const MAX_DISTANCE = 5000 // meters
const destinationLonlat = generateDestinationLonLat(lonlat)
Leaflet.marker(destinationLonlat).addTo(map)

run()
  .catch((err) => {
    console.error(err.message)
    console.error(err.stack)
  })

async function run () {
  const query = await fetch(baseUrl + '/query.json').then((res) => res.json())
  const stopTrees = await fetch(baseUrl + '/stop_trees.dat').then((res) => res.arrayBuffer())
  const grid1 = await fetch(gridUrl + '/Jobs_total.grid').then((res) => res.arrayBuffer())
  const grid2 = await fetch(gridUrl + '/Workers_total.grid').then((res) => res.arrayBuffer())
  console.log('fetched initial data')

  await b.setQuery(query)
  await b.setStopTrees(stopTrees)
  const jobsGridInfo = await b.putGrid({id: 'jobs', grid: grid1})
  const workforceGridInfo = await b.putGrid({id: 'workforce', grid: grid2})
  console.log('jobs grid: ', jobsGridInfo)
  console.log('workforce grid: ', workforceGridInfo)
  await b.setTransitiveNetwork(query.transitiveData)
  console.log('initialized browsochrones')

  const point = b.pixelToOriginPoint({pixel: map.project(lonlat), currentZoom: map.getZoom()})
  const data = await fetch(baseUrl + '/' + (point.x | 0) + '/' + (point.y | 0) + '.dat').then((res) => res.arrayBuffer())
  console.log('fetched origin point')

  const originInfo = await b.setOrigin({data: data.slice(0), point})
  console.log('origin set', originInfo)

  await b.generateSurface({gridId: 'jobs'})
  await b.generateSurface({gridId: 'workforce'})
  console.log('generated surfaces')

  const destinationPoint = b.pixelToOriginPoint({pixel: map.project(destinationLonlat), currentZoom: map.getZoom()})
  const destinationData = await b.generateDestinationData({
    from: point,
    to: destinationPoint
  })
  console.log('generated destination data for', destinationPoint, destinationData)

  const surfaceLayer = new Leaflet.GridLayer()
  surfaceLayer.createTile = b.createTile // automatically bound to the instance
  surfaceLayer.addTo(map)
  console.log('surface layer added to map')

  const isochrone = await b.getIsochrone({cutoff}) // minutes
  const isoLayer = Leaflet.geoJson(isochrone, {
    style: {
      weight: 3,
      color: '#f00',
      opacity: 1,
      fillColor: '#222',
      fillOpacity: 0.3
    }
  })
  isoLayer.addTo(map)
  console.log('isolayer added to map', isochrone)

  const transitiveLayer = new Leaflet.TransitiveLayer(new Transitive({
    data: destinationData.transitive
  }))
  map.addLayer(transitiveLayer)
  // see leaflet.transitivelayer issue #2
  transitiveLayer._refresh()
  console.log('transitive layer added to the map')

  const jobAccess = await b.getAccessibilityForGrid({gridId: 'jobs', cutoff})
  console.log('job access', jobAccess)
  const workforceAccess = await b.getAccessibilityForGrid({gridId: 'workforce', cutoff})
  console.log('workforce access', workforceAccess)
}

/**
 * Get a random point offset from the origin
 */
function generateDestinationLonLat ({
  lat,
  lon
}) {
  const latOffset = (getDistance() / LAT_DEGREE) * getSign()
  const lonOffset = (getDistance() / (LAT_DEGREE * Math.cos(lat))) * getSign()
  return {
    lat: lat + latOffset,
    lon: lon + lonOffset
  }
}

function getDistance () {
  return Math.random() * MAX_DISTANCE + MIN_DISTANCE
}

function getSign () {
  return (Math.random() * 2) > 1 ? 1 : -1
}


'use strict';

module.exports = function () {

	var fs = require('fs');
	var argv = require('yargs').argv;
	var extend = require('extend');

	var globalValues = {};
	var baseValues = {};
	var valuesStack = [];

	valuesStack.push(globalValues);
	valuesStack.push(baseValues);

	//
	// Debug only function to get the current state of the value stack.
	// NOTE: This deep copies the data so that it cannot be modified externally this
	// ensures the managed configuration is immutable.
	//
	this.getValueStackCopy = function () {
		return extend(true, [], valuesStack);
	};

	//
	// Set a value by key at the top level of the key/value staci.
	//
	this.set = function (key, value) {
		var keyParts = key.split(':');
		var hash = valuesStack[valuesStack.length-1];

		for (var i = 0; i < keyParts.length-1; ++i) {
			var subKey = keyParts[i];
			hash = hash[subKey];
			if (typeof(hash) === 'undefined') {
				hash = {};
				hash[subKey] = hash;
			}
		}

		var lastKeyPartIndex = keyParts.length-1;
		var lastSubKey = keyParts[lastKeyPartIndex];

		if (typeof(value) === 'object') {
			hash[lastSubKey] = extend(true, {}, value);
		}
		else {
			hash[lastSubKey] = value;
		}
	};

	//
	// Clear a key from the top level of the key/value stack.
	//
	this.clear = function (key) {
		var keyParts = key.split(':');
		var hash = valuesStack[valuesStack.length-1];

		for (var i = 0; i < keyParts.length-1; ++i) {
			var subKey = keyParts[i];
			hash = hash[subKey];
			if (typeof(hash) === 'undefined') {
				return;
			}
		}

		var lastKeyPartIndex = keyParts.length-1;
		var lastSubKey = keyParts[lastKeyPartIndex];
		delete hash[lastSubKey];
	};

	//
	// Set a global key/value.
	//
	this.setGlobal = function (key, value) {
		globalValues[key] = value;
	};

	//
	// Clear a global key/value.
	//
	this.clearGlobal = function (key) {
		delete globalValues[key];
	};

	//
	// Get a value from the specified object.
	// Returns undefined if not found.
	//
	var getValue = function (key, hash) {

		var value = hash[key];
		if (typeof(value) !== 'undefined') {
			return value;
		}

		return undefined;
	};

	//
	// Get a nested value from the specified object.
	// Returns undefined if not found.
	//
	var getNestedValue = function (keyParts, hash) {

		for (var i = 0; i < keyParts.length-1; ++i) {
			hash = getValue(keyParts[i], hash);
			if (typeof(hash) === 'undefined') {
				return undefined;
			}
		}

		var lastKeyPartIndex = keyParts.length-1;
		return getValue(keyParts[lastKeyPartIndex], hash)
	};

	//
	// Get a value by key.
	// Key can reference a nested value.
	// Searches all levels in the key/value stack.
	// Returns undefined if the key does not exist anywhere.
	//
	this.get = function (key) {

		var keyParts = key.split(':');

		for (var i = valuesStack.length-1; i >= 0; --i) {
			var value = getNestedValue(keyParts, valuesStack[i]);
			if (typeof(value) !== 'undefined') {
				return value;
			}
		}

		return undefined;
	};

	//
	// Push a set of key/values on the key/value stack.
	//
	this.push = function (values) {
		valuesStack.push(extend(true, {}, values));
	};

	//
	// Pop a set of values from the stack.
	//
	this.pop = function () {
		if (valuesStack.length > 2) {
			valuesStack.pop();
		}
		else {
			// Can't push the base levels of the stack.
		}
	};

	//
	// Load a json file and push it on the key/value stack.
	//
	this.pushJsonFile = function (filePath) {
		this.push(JSON.parse(fs.readFileSync(filePath, 'utf8')));
	};

	//
	// Push arguments onto the key/value stack.
	//
	this.pushArgv = function () {
		this.push(argv);
	};

	//
	// Push environment variables onto the key/value stack.
	//
	this.pushEnv = function () {
		this.push(process.env);
	};

	//
	// Get the current configuration as a JavaScript object.
	//
	this.toObject = function () {
		var base = {};

		valuesStack.forEach(function (value) {
			base = extend(base, value);
		});

		return base;
	};
};


module.exports = {
  authors: authors,
  nameAndEmail: nameAndEmail
}

var pkg = require('../package.json')
var async = require('async')
var request = require('request')
var exec = require('child_process').exec
var rateLimitExceeded = false
var log

// returns array of strings like
//    "firstname lastname <email@host.com>"
// - first contributions are first in the list
// - contains duplicates
// - might not have a real email
// - might not have real name
function gitAuthors (cb) {
  return exec('git log --pretty="%an <%ae>"', function (er, stdout, stderr) {
    if (er || stderr) throw new Error(er || stderr)
    return cb(null, stdout.split('\n').reverse())
  })
}

function nameAndEmail (cb) {
  return gitAuthors(function (er, ppl) {
    var uppl = unique(ppl)
    cb(er, uppl)
  })
}

function unique (list) {
  var seen = {}
  list.forEach(function (e) {
    if (e) seen[e] = 1
  })
  return Object.keys(seen)
}

// look for the email first. if no results, look for the name.
function lookupGithubLogin (p, print, callback) {
  var apiURI = 'https://api.github.com/search/users?q='
  var options = { json: true, headers: { 'user-agent': pkg.name + '/' + pkg.version } }
  if (process.env.OAUTH_TOKEN) {
    options.headers['Authorization'] = 'token ' + process.env.OAUTH_TOKEN.trim()
  }
  function cb (err, p) {
    callback(err, p)
  }
  if (print) process.stdout.write('.')

  request(apiURI + encodeURIComponent(p.email + ' in:email type:user'), options, onEmail)
  function onEmail (err, data) {
    rateLimitExceeded = rateLimitExceeded || data.body.message
    if (!err && data.body.items && data.body.items[0]) {
      p.login = data.body.items[0].login
      return cb(err, p)
    }
    request(apiURI + encodeURIComponent(p.name + ' in:fullname type:user'), options, onName)
  }
  function onName (err, data) {
    rateLimitExceeded = rateLimitExceeded || data.body.message
    if (!err && data.body.items && data.body.items[0]) {
      p.login = data.body.items[0].login
      return cb(err, p)
    }
    cb(err, p)
  }
}

function toData (ppl) {
  return ppl
    .map(function (p) {
      return {
        name: p.substring(0, p.indexOf('<')).trim(),
        email: p.substring(p.indexOf('<') + 1, p.indexOf('>')).trim()
      }
    })
}

function toMarkdown (p) {
  var str = '- '
  if (p.login && p.login !== 'undefined') {
    str = str + '[' + p.name + ']' + '(https://github.com/' + p.login + ')'
    if (p.name !== p.login) str += ' aka `' + p.login + '`'
  } else {
    str = str + p.name
  }
  return str
}

function authors (path, print, cb) {
  cb = cb || function (err) { if (err) log(err.stack) }
  log = function log () {
    if (print) console.log.apply(console.log, arguments)
  }
  process.chdir(path)

  nameAndEmail(function (er, ppl) {
    var list = toData(ppl)
    var tasks = []

    if (print) {
      log('Fetching ' + list.length + ' logins from github based on' + ' email/name...')
    }
    list.forEach(function (p) {
      tasks.push(async.apply(lookupGithubLogin, p, print))
    })
    async.parallel(tasks, function (err, results) {
      var seen = {}
      var uresults = results
        .map(function (p) {
          if (seen[p.login]) return null
          seen[p.login] = true
          return p
        })
        .filter(function (p) {
          return p
        })
      if (rateLimitExceeded) {
        log('\nWarning: GitHub API rate limit exceeded! The result might be incomplete!\n')
        if (!process.env.OAUTH_TOKEN) {
          log('Hint: Set the "OAUTH_TOKEN" environment variable to increase your limit.\n')
        }
      }
      if (print) {
        log('')
        log('## Contributors')
        log('Ordered by date of first contribution.' +
          ' [Auto-generated](https://github.com/dtrejo/node-authors) on ' +
          new Date().toUTCString() + '.')
        log('')
        log(uresults.map(toMarkdown).join('\n'))
        log('')
      }
      cb(err, uresults)
    })
  })
}


/*
Copyright 2014 Lectorius, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/** @suppress {duplicate} */
var forge = forge || require('node-forge');

// Define keyczar_util as a module that can be loaded both by node require and a browser
var keyczar_util = {};

(function() {
'use strict';

keyczar_util.KEYHASH_LENGTH = 4;
var MODE_CBC = 'CBC';

keyczar_util.VERSION_BYTE = '\x00';

// Unpacks Keyczar's output format
function _unpackOutput(message) {
    if (message.charAt(0) != keyczar_util.VERSION_BYTE) {
        throw new Error('Unsupported version byte: ' + message.charCodeAt(0));
    }

    var keyhash = message.substr(1, keyczar_util.KEYHASH_LENGTH);
    message = message.substr(1 + keyczar_util.KEYHASH_LENGTH);
    return {keyhash: keyhash, message: message};
}

function _packOutput(keyhash, message) {
    if (keyhash.length != keyczar_util.KEYHASH_LENGTH) {
        throw new Error('Invalid keyhash length: ' + keyhash.length);
    }

    return keyczar_util.VERSION_BYTE + keyhash + message;
}

function _checkKeyHash(keyhash, unpackedMessage) {
    if (unpackedMessage.keyhash != keyhash) {
        var primaryHex = forge.util.bytesToHex(keyhash);
        var actualHex = forge.util.bytesToHex(unpackedMessage.keyhash);
        throw new Error('Mismatched keyhash (primary: ' +
            primaryHex + ' actual: ' + actualHex + ')');
    }
}

/** Copied from forge.pki.js because it is not public */
keyczar_util._bnToBytes = function(b) {
    // prepend 0x00 if first byte >= 0x80
    var hex = b.toString(16);
    if(hex[0] >= '8') {
        hex = '00' + hex;
    }
    return forge.util.hexToBytes(hex);
};

function _bnToBase64(b) {
    return keyczar_util.encodeBase64Url(keyczar_util._bnToBytes(b));
}

// Hack to support URL-safe base64 (base64url) from:
// http://tools.ietf.org/html/rfc4648
// TODO: Directly encode/decode this alphabet instead of search and replacing?
// TODO: Patch Forge to use window.btoa/atob if available?
keyczar_util.decodeBase64Url = function(message) {
    message = message.replace(/-/g, '+').replace(/_/g, '/');

    // Add missing padding (=): 3 bytes of padding is an error, but if
    // length % 4 == 1, add 3 bytes of padding; the error is caught later
    var padding = '!!==';
    var remainder_bytes = message.length % 4;
    if (remainder_bytes > 0) {
        if (remainder_bytes == 1) {
            throw new Error("Invalid base64: incorrect input length");
        }
        message += padding.substring(remainder_bytes);
    }

    return forge.util.decode64(message);
};

keyczar_util.encodeBase64Url = function(message) {
    message = forge.util.encode64(message);

    // remove padding
    var endIndex = message.length-1;
    while (message.charAt(endIndex) == '=') {
        endIndex -= 1;
    }
    message = message.substring(0, endIndex+1);

    return message.replace(/\+/g, '-').replace(/\//g, '_');
};

function _stripLeadingZeros(bytes) {
    var nonZeroIndex = 0;
    while (nonZeroIndex < bytes.length && bytes.charAt(nonZeroIndex) == '\x00') {
        nonZeroIndex += 1;
    }
    return bytes.substring(nonZeroIndex);
}

keyczar_util._encodeBigEndian = function(number) {
    if (!(0 <= number && number <= 2147483647)) {
        throw new Error('number is out of range: ' + number);
    }
    if ((number & 0xffffffff) != number) {
        throw new Error('number is not a 32-bit integer? ' + number);
    }
    var b1 = String.fromCharCode((number >> 24) & 0xff);
    var b2 = String.fromCharCode((number >> 16) & 0xff);
    var b3 = String.fromCharCode((number >> 8) & 0xff);
    var b4 = String.fromCharCode(number & 0xff);
    return b1 + b2 + b3 + b4;
};

keyczar_util._decodeBigEndian = function(byteString) {
    if (byteString.length < 4) {
        throw new Error('byteString too short: ' + byteString.length);
    }
    var firstByte = byteString.charCodeAt(0);
    if (firstByte & 0x80) {
        throw new Error('Cannot decode negative number; initial byte = ' + firstByte.toString(16));
    }

    var b1 = firstByte << 24;
    var b2 = byteString.charCodeAt(1) << 16;
    var b3 = byteString.charCodeAt(2) << 8;
    var b4 = byteString.charCodeAt(3);
    return b1 | b2 | b3 | b4;
};

// Returns an Array of byte strings from an input byte string.
// Equivalent to org.keyczar.util.Util.lenPrefixUnpack
keyczar_util._unpackByteStrings = function(bytes) {
    var numByteStrings = keyczar_util._decodeBigEndian(bytes);
    var index = 4;

    var output = [];
    for (var i = 0; i < numByteStrings; i++) {
        var length = keyczar_util._decodeBigEndian(bytes.substring(index));
        index += 4;
        output.push(bytes.substring(index, index + length));
        index += length;
    }

    // checks if the final string was truncated
    if (index > bytes.length) {
        throw new Error('Malformed input: not enough data!');
    }
    return output;
};

// Packs an Array of byte strings into a byte string, length prefixed (32-bit big endian)
// Equivalent to org.keyczar.util.Util.lenPrefixUnpack
keyczar_util._packByteStrings = function(listOfBytes) {
    var output = [];
    // total number of strings
    output.push(keyczar_util._encodeBigEndian(listOfBytes.length));

    for (var i = 0; i < listOfBytes.length; i++) {
        // string length
        output.push(keyczar_util._encodeBigEndian(listOfBytes[i].length));
        // the string itself
        // TODO: Check that the string doesn't include out of range values?
        output.push(listOfBytes[i]);
    }

    return output.join('');
};

function _hashBigNumber(md, bigNumber) {
    var bytes = keyczar_util._bnToBytes(bigNumber);
    bytes = _stripLeadingZeros(bytes);

    md.update(keyczar_util._encodeBigEndian(bytes.length));
    md.update(bytes);
}

// Returns the keyhash for an RSA public key.
function _rsaHash(publicKey) {
    var md = forge.md.sha1.create();

    // hash:
    // 4-byte big endian length
    // "magnitude" of the public modulus (trim all leading zero bytes)
    // same for the exponent
    _hashBigNumber(md, publicKey.n);
    _hashBigNumber(md, publicKey.e);
    var digest = md.digest();
    return digest.getBytes(keyczar_util.KEYHASH_LENGTH);
}

function _rsaPublicKeyToKeyczarObject(publicKey) {
    return {
        modulus: _bnToBase64(publicKey.n),
        publicExponent: _bnToBase64(publicKey.e),
        size: publicKey.n.bitLength()
    };
}

// Returns the JSON string representing publicKey in Keyczar's format.
keyczar_util._rsaPublicKeyToKeyczarJson = function(publicKey) {
    var obj = _rsaPublicKeyToKeyczarObject(publicKey);
    return JSON.stringify(obj);
};

function _bytesToBigInteger(bytes) {
    var buffer = forge.util.createBuffer(bytes);
    var hex = buffer.toHex();
    return new forge.jsbn.BigInteger(hex, 16);
}

keyczar_util._base64ToBn = function(s) {
    var decoded = keyczar_util.decodeBase64Url(s);
    return _bytesToBigInteger(decoded);
};

keyczar_util._privateKeyToKeyczarObject = function(key) {
    var obj = {
        publicKey: _rsaPublicKeyToKeyczarObject(key),

        privateExponent: _bnToBase64(key.d),
        primeP: _bnToBase64(key.p),
        primeQ: _bnToBase64(key.q),
        primeExponentP: _bnToBase64(key.dP),
        primeExponentQ: _bnToBase64(key.dQ),
        crtCoefficient: _bnToBase64(key.qInv),

        size: key.q.bitLength() + key.p.bitLength()
    };

    if (obj.size != obj.publicKey.size) {
        throw new Error("Incorrect calculation of private key size? " + obj.size + " != " + obj.publicKey.size);
    }

    return obj;
};

keyczar_util._rsaPrivateKeyToKeyczarJson = function(key) {
    var obj = keyczar_util._privateKeyToKeyczarObject(key);
    return JSON.stringify(obj);
};

function _mdForSignature(message) {
    var mdObject = forge.md.sha1.create();
    mdObject.update(message);
    // Keyczar signature format appends the version byte
    // https://code.google.com/p/keyczar/wiki/SignatureFormat
    mdObject.update(keyczar_util.VERSION_BYTE);
    return mdObject;
}

// Returns a key object for an RSA key.
function _makeRsaKey(rsaKey) {
    var key = {
        keyhash: _rsaHash(rsaKey),
        size: rsaKey.n.bitLength()
    };

    key.encrypt = function(plaintext) {
        // needed to make this work with private keys
        var tempKey = forge.pki.setRsaPublicKey(rsaKey.n, rsaKey.e);
        var ciphertext = tempKey.encrypt(plaintext, 'RSA-OAEP');
        return _packOutput(key.keyhash, ciphertext);
    };

    key.verify = function(message, signature) {
        signature = _unpackOutput(signature);
        _checkKeyHash(key.keyhash, signature);

        var digest = _mdForSignature(message).digest().getBytes();

        // needed to make this work with private keys
        var tempKey = forge.pki.setRsaPublicKey(rsaKey.n, rsaKey.e);
        return tempKey.verify(digest, signature.message);
    };

    return key;
}

keyczar_util.publicKeyFromKeyczar = function(serialized) {
    var obj = JSON.parse(serialized);
    var modulus = keyczar_util._base64ToBn(obj.modulus);
    var exponent = keyczar_util._base64ToBn(obj.publicExponent);
    var rsaKey = forge.pki.setRsaPublicKey(modulus, exponent);

    var key = _makeRsaKey(rsaKey);

    key.toJson = function() {
        return keyczar_util._rsaPublicKeyToKeyczarJson(rsaKey);
    };
    return key;
};

keyczar_util.privateKeyFromKeyczar = function(serialized) {
    var obj = JSON.parse(serialized);

    // public key parts
    var n = keyczar_util._base64ToBn(obj.publicKey.modulus);
    var e = keyczar_util._base64ToBn(obj.publicKey.publicExponent);

    // private key parts
    var d = keyczar_util._base64ToBn(obj.privateExponent);
    var p = keyczar_util._base64ToBn(obj.primeP);
    var q = keyczar_util._base64ToBn(obj.primeQ);
    var dP = keyczar_util._base64ToBn(obj.primeExponentP);
    var dQ = keyczar_util._base64ToBn(obj.primeExponentQ);
    var qInv = keyczar_util._base64ToBn(obj.crtCoefficient);
    var rsaKey = forge.pki.setRsaPrivateKey(n, e, d, p, q, dP, dQ, qInv);

    var key = _makeRsaKey(rsaKey);

    key.decrypt = function(message) {
        message = _unpackOutput(message);
        _checkKeyHash(key.keyhash, message);
        return rsaKey.decrypt(message.message, 'RSA-OAEP');
    };

    key.sign = function(message) {
        var md = _mdForSignature(message);
        var signature = rsaKey.sign(md);

        return _packOutput(key.keyhash, signature);
    };

    /** Returns a JSON string containing the public part of this key. */
    key.exportPublicKeyJson = function() {
        return keyczar_util._rsaPublicKeyToKeyczarJson(rsaKey);
    };

    key.toJson = function() {
        return keyczar_util._rsaPrivateKeyToKeyczarJson(rsaKey);
    };

    return key;
};

function constantTimeEquals(s1, s2) {
    if (s1.length != s2.length) {
        throw new Error('Invalid arguments: strings must be the same length');
    }

    var equals = 1;
    for (var i = 0; i < s1.length; i++) {
        equals &= s1.charAt(i) == s2.charAt(i);
    }
    return equals;
}

// Returns a Keyczar AES key object from the serialized JSON representation.
keyczar_util.aesFromKeyczar = function(serialized) {
    var obj = JSON.parse(serialized);
    if (obj.mode != MODE_CBC) {
        throw new Error('Unsupported cipher mode: ' + obj.mode);
    }

    var keyBytes = keyczar_util.decodeBase64Url(obj.aesKeyString);
    if (keyBytes.length != obj.size/8) {
        throw new Error('Mismatched key sizes: ' + keyBytes.length + ' != ' + (obj.size/8));
    }

    var hmacBytes = keyczar_util.decodeBase64Url(obj.hmacKey.hmacKeyString);
    if (hmacBytes.length != obj.hmacKey.size/8) {
        throw new Error('Mismatched hmac key sizes: ' +
            hmacBytes.length + ' != ' + (obj.hmacKey.size/8));
    }

    return keyczar_util._aesFromBytes(keyBytes, hmacBytes);
};

keyczar_util._aesFromBytes = function(keyBytes, hmacBytes) {
    var aesObject = forge.aes.createEncryptionCipher(keyBytes);
    var mdObject = forge.md.sha1.create();
    var hmacObject = forge.hmac.create();
    hmacObject.start(mdObject, hmacBytes);

    // calculate the keyhash
    var md = forge.md.sha1.create();
    md.update(keyczar_util._encodeBigEndian(keyBytes.length));
    md.update(keyBytes);
    md.update(hmacBytes);
    var keyhash = md.digest().getBytes(keyczar_util.KEYHASH_LENGTH);

    var key = {
        keyhash: keyhash
    };
    key.encrypt = function(input) {
        // generate a random IV
        var iv = forge.random.getBytes(keyBytes.length);

        // TODO: cache the cipher object?
        var cipher = forge.aes.startEncrypting(keyBytes, iv, null);
        cipher.update(new forge.util.ByteBuffer(input));
        var success = cipher.finish();
        if (!success) {
            throw new Error('AES encryption failed');
        }

        var output = _packOutput(key.keyhash, iv + cipher.output.getBytes());

        // compute the HMAC over the entire message
        hmacObject.start(null, null);
        hmacObject.update(output);
        var hmac = hmacObject.getMac();
        return output + hmac.getBytes();
    };

    key.decrypt = function(message) {
        var unpacked = _unpackOutput(message);
        _checkKeyHash(key.keyhash, unpacked);

        // check the HMAC over the entire message
        var hmac = message.substring(message.length - mdObject.digestLength);
        hmacObject.start(null, null);
        hmacObject.update(message.substring(0, message.length - mdObject.digestLength));
        var hmacPrime = hmacObject.getMac().getBytes();

        if (!constantTimeEquals(hmac, hmacPrime)) {
            throw new Error('Decryption failed: HMAC does not match');
        }

        // split the message into parts
        var iv = unpacked.message.substring(0, keyBytes.length);
        var ciphertext = unpacked.message.substring(keyBytes.length, unpacked.message.length - mdObject.digestLength);

        var cipher = forge.aes.startDecrypting(keyBytes, iv, null);
        cipher.update(new forge.util.ByteBuffer(ciphertext));
        var success = cipher.finish();
        if (!success) {
            throw new Error('Decryption failed: AES error?');
        }
        return cipher.output.getBytes();
    };

    key.toJson = function() {
        var data = {
            aesKeyString: keyczar_util.encodeBase64Url(keyBytes),
            size: keyBytes.length*8,
            mode: MODE_CBC,

            hmacKey: {
                hmacKeyString: keyczar_util.encodeBase64Url(hmacBytes),
                size: hmacBytes.length*8
            }
        };
        return JSON.stringify(data);
    };

    // Returns a byte string containing the key/hmac bytes, using _packByteStrings.
    // Used by keyczar.createSessionCrypter().
    key.pack = function() {
        return keyczar_util._packByteStrings([keyBytes, hmacBytes]);
    };

    return key;
};

if (typeof module !== 'undefined' && module.exports) {
    module.exports = keyczar_util;
}
})();


/*
 * grunt-node-version
 * https://github.com/jking90/grunt-node-version
 *
 * Copyright (c) 2013 Jimmy King
 * Licensed under the MIT license.
 */

'use strict';

var semver = require('semver'),
    prompt = require('prompt'),
    stripColorCodes = require('stripcolorcodes'),
    childProcess = require('child_process');

module.exports = function(grunt) {

  grunt.registerTask('node_version', 'A grunt task to ensure you are using the node version required by your project\'s package.json', function() {

    var expected = semver.validRange(grunt.file.readJSON('package.json').engines.node),
        actual = semver.valid(process.version),
        result = semver.satisfies(actual, expected),
        done = this.async(),
        home = process.env.HOME,
        locals = [],
        remotes = [],
        bestMatch = '',
        nvmUse = '',
        nvmPath = home + '/.nvm/nvm.sh',
        options = this.options({
          alwaysInstall: false,
          errorLevel: 'fatal',
          globals: [],
          maxBuffer: 200*1024,
          nvm: true,
          override: ''
        }),
        nvmInit = '. ' + nvmPath + ' && ',
        cmdOpts = {
          cwd: process.cwd(),
          env: process.env,
          maxBuffer: options.maxBuffer
        };

    // Apply override if specified
    if (options.override) {
      expected = semver.validRange(options.override);
    }

    // Extend grunt-exec
    var extendExec = function() {
      if (!result) {
        var exec = grunt.config.get('exec');

        for (var key in exec) {
          exec[key].cmd = nvmUse + ' && ' + exec[key].cmd;
        }

        grunt.config.set('exec', exec);
      }
    };

    // Validate options
    if (options.errorLevel !== 'warn' &&
        options.errorLevel !== 'fatal') {
      grunt.fail.warn('Expected node_version.options.errorLevel to be \'warn\' or \'fatal\', but found ' + options.errorLevel);
    }

    // Check for engine version in package.json
    if (!expected) {
      grunt.fail.warn('You must define a node verision in your project\'s `package.json` file.\nhttps://npmjs.org/doc/json.html#engines');
    }

    var printVersion = function(using) {
      grunt.log.writeln('Switched from node v' + actual + ' to ' + using);
      grunt.log.writeln('(Project requires node ' + expected + ')');
    };

    // Check for NVM
    var checkNVM = function(callback) {
      var command = '. ' + nvmPath;

      childProcess.exec(command, cmdOpts, function(err, stdout, stderr) {
        if (stderr.indexOf('No such file or directory') !== -1) {
          if (nvmPath === home + '/.nvm/nvm.sh') {
            nvmPath = home + '/nvm/nvm.sh';
            nvmInit = '. ' + nvmPath + ' && ';
            checkNVM(callback);
          } else {
            grunt[options.errorLevel]('Expected node ' + expected + ', but found v' + actual + '\nNVM does not appear to be installed.\nPlease install (https://github.com/creationix/nvm#installation), or update the NVM path.');
          }
        } else {
          callback();
        }
      });
    };

    // Check for globally required packages
    var checkPackages = function (packages) {
      var thisPackage;

      if (packages.length) {
        thisPackage = packages.pop();

        var command = nvmUse + ' && npm ls -g ' + thisPackage;

        childProcess.exec(command, cmdOpts,function(err, stdout, stderr) {
          if (err) { throw err ;}

          if (stdout.indexOf('â”€ (empty)') !== -1) {
            npmInstall(thisPackage, function() {
              checkPackages(packages);
            });
          } else {
            checkPackages(packages);
          }
        });

      } else {
        done();
      }
    };

    // Install missing packages
    var npmInstall = function(thisPackage, callback) {
      var command = nvmUse + ' && npm install -g ' + thisPackage;

      childProcess.exec(command, cmdOpts,function(err, stdout, stderr) {
        if (err) { throw err ;}
        grunt.verbose.writeln(stdout);
        grunt.log.oklns('Installed ' + thisPackage);
        callback();
      });
    };

    // Prompt to install
    var askInstall = function() {
      prompt.start();

      var prop = {
        name: 'yesno',
        message: 'You do not have any node versions installed that satisfy this project\'s requirements ('.white + expected.yellow + '). Would you like to install the latest compatible version? (y/n)'.white,
        validator: /y[es]*|n[o]?/,
        required: true,
        warning: 'Must respond yes or no'
      };

      prompt.get(prop, function (err, result) {
        result = result.yesno.toLowerCase();
        if (result === 'yes' ||
            result === 'y') {
          nvmInstall();
        } else {
          grunt[options.errorLevel]('Expected node v' + expected + ', but found ' + actual);
        }
      });

    };

    // Install latest compatible node version
    var nvmInstall = function() {
      nvmLs('remote', function() {

        bestMatch = semver.maxSatisfying(remotes, expected);
        nvmUse = nvmInit + 'nvm use ' + bestMatch;

        var command = nvmInit + 'nvm install ' + bestMatch;

        childProcess.exec(command, cmdOpts,function(err, stdout, stderr) {
          if (err) { throw err ;}
          var nodeVersion = stdout.split(' ')[3];
          grunt.log.ok('Installed node v' + bestMatch);
          printVersion(nodeVersion);
          extendExec();
          checkPackages(options.globals);
        });
      });
    };

    // Check for available node versions
    var nvmLs = function(loc, callback) {
      var command = nvmInit + 'nvm ls';

      if (loc === 'remote') {
        command += '-remote';
      }

      childProcess.exec(command, cmdOpts, function(err, stdout, stderr) {
        var data = stripColorCodes(stdout.toString()).replace(/\s+/g, '|'),
            available = data.split('|');

        for (var i = 0; i < available.length; i++) {
          // Trim whitespace
          available[i] = available[i].replace(/\s/g, '');
          // Validate
          var ver = semver.valid(available[i]);
          if (ver) {
            if (loc === 'remote') {
              remotes.push(ver);
            } else if (loc === 'local') {
              locals.push(ver);
            }
          }
        }

        callback();
      });
    };

    // Check for compatible node version
    var checkVersion = function() {
      // Make sure a node version is intalled that satisfies
      // the projects required engine. If not, prompt to install.
      nvmLs('local', function() {
        var matches = semver.maxSatisfying(locals, expected);

        if (matches) {
          bestMatch = matches;
          nvmUse = nvmInit + 'nvm use ' + bestMatch;

          childProcess.exec(nvmUse, cmdOpts,function(err, stdout, stderr) {
            printVersion(stdout.split(' ')[3]);
            extendExec();
            checkPackages(options.globals);
          });
        } else {
          if (options.alwaysInstall) {
            nvmInstall();
          } else {
            askInstall();
          }
        }
      });
    };

    if (result === true) {
      grunt.log.writeln('Using node ' + actual);
      grunt.log.writeln('(Project requires node ' + expected + ')');
      checkPackages(options.globals);
    } else {
      if (!options.nvm) {
        grunt[options.errorLevel]('Expected node ' + expected + ', but found v' + actual);
      } else {
        checkNVM(checkVersion);
      }
    }

  });

};


/** @module certainty */
var ProxyBase = require('./proxy');
var subjectFactory = require('./factoryBase');

/** A subject which executes its assertions on each element of an iterable.
    @param {Subject} The subject responsible for creating this.
    @param {Array} elements List of elements to test.
    @param {string} elementName What noun to use when referring to elements ('item', 'child', etc.)
    @param {bool} opt_noIndex If true, don't include the element index in the failure message.
    @constructor
    @extends ProxyBase
  */
function EachSubject(subject, elements, elementName, opt_noIndex) {
  ProxyBase.call(this);
  this.elementSubjects = [];
  for (var i = 0; i < elements.length; ++i) {
    var es = subjectFactory.newSubject(subject.failureStrategy, elements[i]);
    es.named(elementName + (opt_noIndex ? '' : (' ' + i)) + ' of ' + subject.describe());
    es.failureMessage = subject.failureMessage;
    this.elementSubjects.push(es);
  }
}
EachSubject.prototype = Object.create(ProxyBase.prototype);
EachSubject.prototype.constructor = ProxyBase;

/** Called for a proxied method; invokes the method once for every element in the list. */
EachSubject.prototype.invoke = function(methodName, args) {
  for (var i = 0; i < this.elementSubjects.length; ++i) {
    var subject = this.elementSubjects[i];
    this.elementSubjects[i] = this.getMethod(subject, methodName).apply(subject, args);
  }
  return this;
};

module.exports = EachSubject;


/** @module certainty */
var Subject = require('./subject');
var DeferredSubject = require('./deferredSubject');
var subjectFactory = require('./factoryBase');

/** A class which functions as a subject, but doesn't actually execute any assertions until
    the promise has resolved. Once the promise succeeds, any method calls will be played back
    on the result value of the promise.
    @param {PromiseSubject} subject The subject wrapping the promise.
    @param {Promise} promise The promise being tested.
    @constructor
    @extends DeferredSubject
  */
function EventualSubject(subject, promise) {
  DeferredSubject.call(this);
  this.subject = subject;
  var self = this;
  this.promise = promise.then(
    function(value) {
      // Play back the recorded calls on a new subject which is created based on the resolved
      // value of the promise.
      var valueSubject = subjectFactory.newSubject(subject.failureStrategy, value);
      valueSubject.failureMessage = subject.failureMessage;
      self.run(valueSubject);
      return value;
    },
    function(reason) {
      subject.fail('Expected promise ' + subject.describe() +
        ' to succeed, but failed with ' + this.format(reason) + '.');
      return reason;
    }
  );
  // Make this object a thenable
  this.then = this.promise.then.bind(this.promise);
  this.catch = this.promise.catch.bind(this.promise);
}
EventualSubject.prototype = Object.create(DeferredSubject.prototype);
EventualSubject.prototype.constructor = DeferredSubject;

/** Subject subclass which provides assertion methods for promise types.
    @param {FailureStrategy} failureStrategy The failure strategy to use when an assertion fails.
    @param {*} value The value being checked.
    @constructor
    @extends Subject
*/
function PromiseSubject(failureStrategy, value) {
  Subject.call(this, failureStrategy, value);
  // Make this object a thenable
  this.then = this.value.then.bind(this.value);
  this.catch = this.value.catch.bind(this.value);
}
PromiseSubject.prototype = Object.create(Subject.prototype);
PromiseSubject.prototype.constructor = PromiseSubject;

/** Ensure that the promise eventually succeeds.
    @return {PromiseSubject} `this` for chaining.
*/
PromiseSubject.prototype.succeeds = function () {
  var self = this;
  return this.value.catch(
    function (reason) {
      self.fail('Expected promise ' + self.describe() + ' to succeed, but failed with ' +
        this.format(reason) + '.');
      return reason;
    });
};

/** Ensure that the promise eventually succeeds with the specified value.
    @param {*} expected The value that the promise must resolve to.
    @return {PromiseSubject} `this` for chaining.
*/
PromiseSubject.prototype.succeedsWith = function (expected) {
  var self = this;
  return this.value.then(
    function (value) {
      if (value !== expected) {
        self.fail('Expected promise ' + self.describe() + ' to resolve to ' +
          this.format(expected) + ' actual value was ' + this.format(value) + '.');
      }
      return value;
    },
    function (reason) {
      self.fail('Expected promise ' + self.describe() + ' to succeed, but failed with ' +
        this.format(reason) + '.');
      return reason;
    });
};

/** Ensure that the promise eventually fails.
    @return {PromiseSubject} `this` for chaining.
*/
PromiseSubject.prototype.fails = function () {
  var self = this;
  return this.value.then(
    function (value) {
      self.fail('Expected promise ' + self.describe() + ' to fail, but succeeded with ' +
        this.format(value) + '.');
      return value;
    },
    function (reason) {
      return reason;
    });
};

/** Ensure that the promise eventually fails with the specified reason.
    @param {*} expected The expected reason for rejection.
    @return {PromiseSubject} `this` for chaining.
*/
PromiseSubject.prototype.failsWith = function (expected) {
  var self = this;
  return this.value.then(
    function (value) {
      self.fail('Expected promise ' + self.describe() + ' to fail, but succeeded with ' +
        this.format(value) + '.');
      return value;
    },
    function (reason) {
      if (reason !== expected) {
        self.fail('Expected promise ' + self.describe() + ' to be rejected with ' +
          this.format(expected) + ' actual reason was ' + this.format(reason) + '.');
      }
      return reason;
    });
};

/** Ensure that the promise succeds, and returns a 'deferred subject' - an object that allows
    assertions methods to be called, but doesn't actually execute the assertions until after
    the promise has resolved.
    @return {DeferredSubject} The deferred subject.
*/
PromiseSubject.prototype.eventually = function () {
  return new EventualSubject(this, this.value);
};

module.exports = PromiseSubject;