/*
 * Utilities: A classic collection of JavaScript utilities
 * Copyright 2112 Matthew Eernisse (mde@fleegix.org)
 *
 * 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.
 *
*/

/*
This is a very simple buffer for a predetermined set of events. It is unbounded.
It forwards all arguments to any outlet emitter attached with sync().

Example:
    var source = new Stream()
      , dest = new EventEmitter()
      , buff = new EventBuffer(source)
      , data = '';
    dest.on('data', function (d) { data += d; });
    source.writeable = true;
    source.readable = true;
    source.emit('data', 'abcdef');
    source.emit('data', '123456');
    buff.sync(dest);
*/

/**
  @name EventBuffer
  @namespace EventBuffer
  @constructor
*/

var EventBuffer = function (src, events) {
  // By default, we service the default stream events
  var self = this
    , streamEvents = ['data', 'end', 'error', 'close', 'fd', 'drain', 'pipe'];
  this.events = events || streamEvents;
  this.emitter = src;
  this.eventBuffer = [];
  this.outlet = null;
  this.events.forEach(function (name) {
    self.emitter.addListener(name, function () {
      self.proxyEmit(name, arguments);
    });
  });
};

EventBuffer.prototype = new (function () {
  /**
    @name EventBuffer#proxyEmit
    @public
    @function
    @description Emit an event by name and arguments or add it to the buffer if
                 no outlet is set
    @param {String} name The name to use for the event
    @param {Array} args An array of arguments to emit
  */
  this.proxyEmit = function (name, args) {
    if (this.outlet) {
      this.emit(name, args);
    }
    else {
      this.eventBuffer.push({name: name, args: args});
    }
  };

  /**
    @name EventBuffer#emit
    @public
    @function
    @description Emit an event by name and arguments
    @param {String} name The name to use for the event
    @param {Array} args An array of arguments to emit
  */
  this.emit = function (name, args) {
    // Prepend name to args
    var outlet = this.outlet;
    Array.prototype.splice.call(args, 0, 0, name);
    outlet.emit.apply(outlet, args);
  };

  /**
    @name EventBuffer#sync
    @public
    @function
    @description Flush the buffer and continue piping new events to the outlet
    @param {Object} outlet The emitter to send events to
  */
  this.sync = function (outlet) {
    var buffer = this.eventBuffer
      , bufferItem;
    this.outlet = outlet;
    while ((bufferItem = buffer.shift())) {
      this.emit(bufferItem.name, bufferItem.args);
    }
  };
})();
EventBuffer.prototype.constructor = EventBuffer;

module.exports.EventBuffer = EventBuffer;


var util = require('util')

var c1 = 0xcc9e2d51
var c2 = 0x1b873593

function multiply (a, b) {
    var aHigh = (a >> 16) & 0xffff
    var aLow = a & 0xffff
    var bHigh = (b >> 16) & 0xffff
    var bLow = b & 0xffff
    var high = ((aHigh * bLow) + (aLow * bHigh)) & 0xffff
    return (high << 16) + (aLow * bLow)
}

// We don't use `>>> 0`. We let the values negate. The only use of addition in
// Murmur uses the result of a multiplication, which will be converted to
// unsigned integer by our 16-bit at a time multiplication.

function fmix32 (hash) {
    hash ^= hash >>> 16
    hash = multiply(hash, 0x85ebca6b)
    hash ^= hash >>> 13
    hash = multiply(hash, 0xc2b2ae35)
    hash ^= hash >>> 16
    return hash
}

// With this, unused, function we always make sure we have an unsigned integer
// value, but it's not absolutely necessary. We're only interested in the
// integer value when we perform addition or write the value to our buffer. We
// do not do this within Murmur's mix function. I'm leaving it in place for a
// benchmark where I can gauge the cost of `>>> 0`.

function fmix32_pure (hash) {
    hash = (hash ^ (hash >>> 16)) >>> 0
    hash = multiply(hash, 0x85ebca6b)
    hash = (hash ^ (hash >>> 13)) >>> 0
    hash = multiply(hash, 0xc2b2ae35)
    hash = (hash ^ (hash >>> 16)) >>> 0
    return hash
}

function rotl32 (number, bits) {
    return ((number << bits) | (number >>> 32 - bits)) >>> 0
}

function murmur (buffer, start, end) {
    var hash = 0
    var length = end - start

    var count = length / 4
    var remainder = length % 4

    for (var i = 0; i < count; i++) {
        var k1 =  buffer[i * 4 + start] +
                 (buffer[i * 4 + 1 + start] <<  8) +
                 (buffer[i * 4 + 2 + start] << 16) +
                 (buffer[i * 4 + 3 + start] << 24)

        k1 = multiply(k1, c1)
        k1 = rotl32(k1, 15)
        k1 = multiply(k1, c2)

        hash ^= k1
        hash = rotl32(hash, 13)
        hash = multiply(hash, 5) + 0xe6546b64
    }
    length += count * 4

    var k1 = 0

    switch (remainder) {
    case 3: k1 ^= buffer[i + 2 + start] << 16
    case 2: k1 ^= buffer[i + 1 + start] << 8
    case 1: k1 ^= buffer[i + 0 + start]
        k1 = multiply(k1, c1)
        k1 = rotl32(k1, 15)
        k1 = multiply(k1, c2)
        hash ^= k1
    }

    hash ^= length + remainder

    hash = fmix32(hash) >>> 0

    return new Buffer([
        hash >>> 24 & 0xff,
        hash >>> 16 & 0xff,
        hash >>> 8 & 0xff,
        hash & 0xff
    ])
}

module.exports = murmur


var path = require('path')
var assert = require('assert')
var fs = require('fs')

var cadence = require('cadence')

var mkdirp = require('mkdirp')

var Cache = require('magazine')

var Appender = require('./appender')
var Cursor = require('./cursor')

var Journalist = require('./journalist')

var Interrupt = require('interrupt').createInterrupter('b-tree')
var Turnstile = require('turnstile')

function compare (a, b) { return a < b ? -1 : a > b ? 1 : 0 }

// TODO Branch and leaf size, can we just sort that out in a call to balance?
function Strata (options) {
    this.options = options
    this.options.comparator = options.comparator || compare
    this.journalist = new Journalist(this, options)

    this.housekeeper = new Turnstile
    this.writer = new Turnstile
    this._cursors = []
}

Strata.prototype.create = cadence(function (async, options) {
    this.instance = 0
    var directory = this.options.directory
    async(function () {
        fs.stat(directory, async())
    }, function (stat) {
        Interrupt.assert(stat.isDirectory(), 'create.not.directory', { directory: directory })
        fs.readdir(this.journalist.directory, async())
    }, function (files) {
        Interrupt.assert(files.filter(function (f) {
            return ! /^\./.test(f)
        }).length == 0, 'create.directory.not.empty', { directory: directory })
    }, function () {
        mkdirp(path.resolve(directory, 'pages'), 0o755, async())
    }, function () {
        mkdirp(path.resolve(directory, 'instance', '0'), async())
    }, function () {
        async(function () {
            mkdirp(path.resolve(directory, 'pages', '0.0'), 0o755, async())
        }, function () {
            var appender = new Appender(path.resolve(directory, 'pages', '0.0', '0.0'))
            async(function () {
                appender.append({ method: 'insert', index: 0, value: { id: '0.1' } }, async())
            }, function () {
                appender.end(async())
            })
        }, function () {
            mkdirp(path.resolve(directory, 'pages', '0.1'), 0o755, async())
        }, function () {
            new Appender(path.resolve(directory, 'pages', '0.1', '0.0')).end(async())
        }, function () {
            this.journalist.magazine.hold(-1, { items: [{ id: '0.0' }], right: null  })
        })
    })
})

Strata.prototype.open = cadence(function (async) {
    this.journalist.magazine.hold(-1, { items: [{ id: '0.0' }]  })
    async(function () {
        fs.stat(this.options.directory, async())
    }, function () {
        fs.readdir(path.join(this.options.directory, 'instance'), async())
    }, function (files) {
        files = files.filter(function (file) {
            return /^\d+$/.test(file)
        }).map(function (file) {
            return +file
        }).sort(function (left, right) {
            return right - left
        })
        Interrupt.assert(files.length != 0, 'instance.missing')
        this.instance = files[0] + 1
        async(function () {
            mkdirp(path.resolve(this.options.directory, 'instance', String(this.instance)), async())
        }, function () {
            async.forEach([ files ], function (file) {
                fs.rmdir(path.resolve(this.options.directory, 'instance', String(file)), async())
            })
        })
    })
})

Strata.prototype.close = cadence(function (async) {
    return
    // TODO that's a lot of indirection.
    var cartridge = this.sheaf.metaRoot.cartridge, lock = cartridge.value.page.lock

    lock.unlock()
    // TODO
    lock.dispose()

    cartridge.release()

    var purge = this.sheaf.magazine.purge()
    while (purge.cartridge) {
        purge.cartridge.remove()
        purge.next()
    }
    purge.release()

    assert(!this.sheaf.magazine.count, 'pages still held by cache')
})

Strata.prototype.cursor = cadence(function (async, key) {
    // We hold onto all cartridges until we're done, even retries, so we're
    // going to end up holding cartridges two or more times, but we'll make
    // progress eventually and release everything.
    async(function () {
        this.journalist.descend(key, -1, 0, async())
    }, function (descent) {
        var cartridge = descent.cartridges.pop()
        descent.cartridges.forEach(function (cartridge) { cartridge.release() })
        return new Cursor(this.journalist, key, cartridge, descent.index)
    })
})

Strata.prototype.purge = function (downTo) {
    var purge = this.journalist.magazine.purge()
    while (purge.cartridge && this.journalist.magazine.heft > downTo) {
        purge.cartridge.remove()
        purge.next()
    }
    purge.release()
}

module.exports = Strata


'use strict'

var processNodes = require('./process_nodes')
var keyMap = require('./key_map')

var markerKey = keyMap.marker
var metaKey = keyMap.meta
var isMarkerLastKey = keyMap.isMarkerLast
var hasDefinitionKey = keyMap.hasDefinition
var isBoundToParentKey = keyMap.isBoundToParent
var replaceAttributeKey = keyMap.replaceAttribute
var retainElementKey = keyMap.retainElement
var memoizedObjectKey = keyMap.memoizedObject

// Fixed constant for text node type.
var TEXT_NODE = 3

// Element tag names for elements that should update data on change.
var updateTags = [ 'INPUT', 'TEXTAREA' ]

module.exports = bindKeys

/**
 * Define getters & setters. This function is the internal entry point to a lot
 * of functionality.
 *
 * @param {*} [scope]
 * @param {Object} obj
 * @param {Object} def
 * @param {Node} parentNode - This is not the same as
 * `Node.prototype.parentNode`, this is the internal parent node if the key
 * was bound to its parent.
 * @param {Array} path
 */
function bindKeys (scope, obj, def, parentNode, path) {
  var meta, key

  if (typeof obj !== 'object' || obj === null)
    throw new TypeError(
      'Invalid type of value "' + obj + '", object expected.')

  Object.defineProperty(obj, memoizedObjectKey, {
    value: {},
    configurable: true
  })

  Object.defineProperty(obj, metaKey, {
    value: {},
    configurable: true
  })

  meta = obj[metaKey]

  for (key in def) {
    meta[key] = {
      keyPath: {
        key: key,
        root: path.root,
        target: obj
      },
      activeNodes: [],
      previousValues: [],

      // Assign the current marker relevant to this object. This is in case of
      // arrays of objects.
      currentMarker: def[key][markerKey],

      valueIsArray: null
    }

    bindKey(scope, obj, def, key, parentNode)
  }
}

// This is an internal function that's used for defining the getters and
// setters.
function bindKey (scope, obj, def, key, parentNode) {
  var memoizedObject = obj[memoizedObjectKey]
  var meta = obj[metaKey][key]
  var branch = def[key]
  var node = branch[0]
  var change = !branch[hasDefinitionKey] && branch[1]
  var definition = branch[hasDefinitionKey] && branch[1]
  var mount = branch[2]
  var isMarkerLast = branch[isMarkerLastKey]

  // Temporary keys.
  var keyPath = meta.keyPath
  var activeNodes = meta.activeNodes
  var previousValues = meta.previousValues
  var valueIsArray = meta.valueIsArray

  // For initialization, call this once.
  if (branch[isBoundToParentKey]) parentSetter(obj[key])
  else setter(obj[key])

  Object.defineProperty(obj, key, {
    get: getter,
    set: branch[isBoundToParentKey] ? parentSetter : setter,
    enumerable: true,
    configurable: true
  })

  function getter () { return memoizedObject[key] }

  // Special case for binding same node as parent.
  function parentSetter (x) {
    var previousValue = memoizedObject[key]
    var returnValue

    // Optimistically set the memoized value, so it persists even if an error
    // occurs after this point.
    memoizedObject[key] = x

    // Check for no-op.
    if (x === previousValue) return x

    // Need to qualify this check for non-empty value.
    if (definition && x !== null && x !== void 0)
      bindKeys(scope, x, definition, parentNode, keyPath)

    else if (change) {
      returnValue = change(parentNode, x,
        previousValue === void 0 ? null : previousValue, keyPath)
      if (returnValue !== void 0)
        changeValue(parentNode, returnValue, branch[replaceAttributeKey])
    }

    return x
  }

  function setter (x) {
    var value, marker, currentNode
    var a, b, i, j

    // Optimistically set the memoized value, so it persists even if an error
    // occurs after this point.
    memoizedObject[key] = x

    valueIsArray = meta.valueIsArray = Array.isArray(x)
    value = valueIsArray ? x : [ x ]

    for (i = 0, j = Math.max(previousValues.length, value.length);
      i < j; i++) {
      a = value[i]
      b = previousValues[i]
      currentNode = !a || a !== b ? replaceNode(a, b, i) : null
      marker = meta.currentMarker

      if (currentNode)
        if (isMarkerLast) {
          marker.parentNode.appendChild(currentNode)
          marker.parentNode.appendChild(marker)
        }
        else marker.parentNode.insertBefore(currentNode,
          getNextNode(i + 1, activeNodes) || marker)
    }

    // Reset length to current values, implicitly deleting indices and
    // allowing for garbage collection.
    if (value.length !== previousValues.length)
      previousValues.length = activeNodes.length = value.length

    // Assign array mutator methods if we get an array.
    if (valueIsArray) {
      // Some mutators such as `sort`, `reverse`, `fill`, `copyWithin` are
      // not present here. That is because they trigger the array index
      // setter functions by assigning on them internally.

      // These mutators may alter length.
      value.pop = pop
      value.push = push
      value.shift = shift
      value.unshift = unshift
      value.splice = splice

      // Handle array index assignment.
      for (i = 0, j = value.length; i < j; i++)
        defineIndex(value, i)
    }

    return x
  }

  function defineIndex (array, i) {
    var value = array[i]

    Object.defineProperty(array, i, {
      get: function () { return value },
      set: function (x) {
        var a, b, marker, currentNode

        value = x
        a = array[i]
        b = previousValues[i]

        if (a !== b) currentNode = replaceNode(a, b, i)
        marker = meta.currentMarker

        if (currentNode)
          if (isMarkerLast) {
            marker.parentNode.appendChild(currentNode)
            marker.parentNode.appendChild(marker)
          }
          else marker.parentNode.insertBefore(currentNode,
            getNextNode(i + 1, activeNodes) || marker)
      },
      enumerable: true,
      configurable: true
    })
  }

  function removeNode (value, previousValue, i) {
    var marker = meta.currentMarker
    var activeNode = activeNodes[i]
    var returnValue

    delete previousValues[i]

    if (activeNode) {
      delete activeNodes[i]

      if (valueIsArray) keyPath.index = i
      else delete keyPath.index

      if (change)
        returnValue = change(activeNode, null, previousValue, keyPath)
      else if (definition && mount) {
        keyPath.target = previousValue
        returnValue = mount(activeNode, null, previousValue, keyPath)
      }

      // If a change or mount function returns the retain element symbol,
      // skip removing the element from the DOM.
      if (returnValue !== retainElementKey)
        marker.parentNode.removeChild(activeNode)
    }
  }

  // The return value of this function is a Node to be added, otherwise null.
  function replaceNode (value, previousValue, i) {
    var activeNode = activeNodes[i]
    var currentNode = node
    var returnValue

    // Cast values to null if undefined.
    if (value === void 0) value = null
    if (previousValue === void 0) previousValue = null

    // If value is null, just remove the Node.
    if (value === null) {
      removeNode(null, previousValue, i)
      return null
    }

    if (valueIsArray) keyPath.index = i
    else delete keyPath.index

    previousValues[i] = value

    if (definition) {
      if (activeNode) removeNode(value, previousValue, i)
      currentNode = processNodes(scope, node, definition)
      keyPath.target = valueIsArray ? value[i] : value
      bindKeys(scope, value, definition, currentNode, keyPath)
      if (mount) {
        keyPath.target = value
        mount(currentNode, value, null, keyPath)
      }
    }

    else {
      currentNode = activeNode || node.cloneNode(true)

      if (change) {
        returnValue = change(currentNode, value, previousValue, keyPath)
        if (returnValue !== void 0)
          changeValue(currentNode, returnValue, branch[replaceAttributeKey])
      }
      else {
        // Add default update behavior. Note that this event does not get
        // removed, since it is assumed that it will be garbage collected.
        if (previousValue === null &&
          ~updateTags.indexOf(currentNode.tagName))
          currentNode.addEventListener('input',
            updateChange(branch[replaceAttributeKey], keyPath, key))

        changeValue(currentNode, value, branch[replaceAttributeKey])
      }

      // Do not actually add an element to the DOM if it's only a change
      // between non-empty values.
      if (activeNode) return null
    }

    activeNodes[i] = currentNode

    return currentNode
  }

  // Below are optimized array mutator methods. They have to exist within
  // this closure. Note that the native implementations of these methods do
  // not trigger setter functions on array indices.

  function pop () {
    var i = this.length - 1
    var previousValue = previousValues[i]
    var value = Array.prototype.pop.call(this)

    removeNode(null, previousValue, i)
    previousValues.length = activeNodes.length = this.length

    return value
  }

  function push () {
    var i = this.length
    var j = i + arguments.length
    var marker, currentNode

    // Passing arguments to apply is fine.
    var value = Array.prototype.push.apply(this, arguments)

    for (j = i + arguments.length; i < j; i++) {
      currentNode = replaceNode(this[i], null, i)
      marker = meta.currentMarker
      if (currentNode)
        if (isMarkerLast) {
          marker.parentNode.appendChild(currentNode)
          marker.parentNode.appendChild(marker)
        }
        else marker.parentNode.insertBefore(currentNode, marker)
      defineIndex(this, i)
    }

    return value
  }

  function shift () {
    removeNode(null, previousValues[0], 0)

    Array.prototype.shift.call(previousValues)
    Array.prototype.shift.call(activeNodes)

    return Array.prototype.shift.call(this)
  }

  function unshift () {
    var i = this.length
    var j, k, marker, currentNode

    // Passing arguments to apply is fine.
    var value = Array.prototype.unshift.apply(this, arguments)

    Array.prototype.unshift.apply(previousValues, arguments)
    Array.prototype.unshift.apply(activeNodes, Array(k))

    for (j = 0, k = arguments.length; j < k; j++) {
      currentNode = replaceNode(arguments[j], null, j)
      marker = meta.currentMarker
      if (currentNode)
        if (isMarkerLast) {
          marker.parentNode.appendChild(currentNode)
          marker.parentNode.appendChild(marker)
        }
        else marker.parentNode.insertBefore(currentNode,
          getNextNode(arguments.length, activeNodes) || marker)
    }

    for (j = i + arguments.length; i < j; i++) defineIndex(this, i)

    return value
  }

  function splice (start, count) {
    var insert = []
    var i, j, k, value, marker, currentNode, shouldAppend

    for (i = start, j = start + count; i < j; i++)
      removeNode(null, previousValues[i], i)

    for (i = 2, j = arguments.length; i < j; i++)
      insert.push(arguments[i])

    // Passing arguments to apply is fine.
    Array.prototype.splice.apply(previousValues, arguments)

    // In this case, avoid setting new values.
    Array.prototype.splice.apply(activeNodes,
      [ start, count ].concat(Array(insert.length)))

    value = Array.prototype.splice.apply(this, arguments)
    shouldAppend = start - count >= this.length - 1

    for (i = start + insert.length - 1, j = start; i >= j; i--) {
      currentNode = replaceNode(insert[i - start], null, i)
      marker = meta.currentMarker
      if (currentNode)
        if (isMarkerLast && shouldAppend) {
          marker.parentNode.appendChild(currentNode)
          marker.parentNode.appendChild(marker)
        }
        else marker.parentNode.insertBefore(currentNode,
          getNextNode(start + insert.length, activeNodes) || marker)
    }

    k = insert.length - count

    if (k < 0)
      previousValues.length = activeNodes.length = this.length

    else if (k > 0)
      for (i = this.length - k, j = this.length; i < j; i++)
        defineIndex(this, i)

    return value
  }
}

// Default behavior when a return value is given for a change function.
function changeValue (node, value, attribute) {
  var firstChild

  switch (attribute) {
  case 'textContent':
    firstChild = node.firstChild
    if (firstChild && !firstChild.nextSibling &&
      firstChild.nodeType === TEXT_NODE)
      firstChild.textContent = value
    else node.textContent = value
    break
  case 'checked':
    node.checked = Boolean(value)
    break
  case 'value':
    // Prevent some misbehavior in certain browsers when setting a value to
    // itself, i.e. text caret not in the correct position.
    if (node.value !== value) node.value = value
    break
  default:
    break
  }
}

// Find next node in a potentially sparse array.
function getNextNode (index, activeNodes) {
  var i, j, nextNode

  for (i = index, j = activeNodes.length; i < j; i++)
    if (activeNodes[i]) {
      nextNode = activeNodes[i]
      break
    }

  return nextNode
}

// Internal event listener to update data on input change.
function updateChange (targetKey, path, key) {
  var target = path.target
  var index = path.index
  var replaceKey = key

  if (typeof index === 'number') {
    target = target[key]
    replaceKey = index
  }

  return function handleChange (event) {
    target[replaceKey] = event.target[targetKey]
  }
}


'use strict'

var processNodes = require('./process_nodes')
var bindKeys = require('./bind_keys')
var keyMap = require('./key_map')
var helpers = require('./helpers')
var rehydrate = require('./rehydrate')
var featureCheck = require('./feature_check')

var helper
var isArray = Array.isArray
var hasDefinitionKey = keyMap.hasDefinition
var replaceAttributeKey = keyMap.replaceAttribute
var isBoundToParentKey = keyMap.isBoundToParent
var isProcessedKey = keyMap.isProcessed
var markerKey = keyMap.marker

// Element tag names which should have value replaced.
var replaceValue = [ 'INPUT', 'PROGRESS' ]

// Input types which use the "checked" attribute.
var replaceChecked = [ 'checkbox', 'radio' ]

// A list of features to check for upon instantiation.
var features = [
  // ECMAScript features.
  [ Object, 'defineProperty' ],

  // DOM features. Missing `contains` since apparently it is not on
  // the Node.prototype in Internet Explorer.
  [ 'document', 'createTreeWalker' ],
  [ 'Node', 'prototype', 'cloneNode' ],
  [ 'Node', 'prototype', 'normalize' ],
  [ 'Node', 'prototype', 'insertBefore' ],
  [ 'Node', 'prototype', 'isEqualNode' ],
  [ 'Node', 'prototype', 'removeChild' ]
]

// Symbol for retaining an element instead of removing it.
Object.defineProperty(simulacra, 'retainElement', {
  enumerable: true, value: keyMap.retainElement
})

// Option to use comment nodes as markers.
Object.defineProperty(simulacra, 'useCommentNode', {
  get: function () { return processNodes.useCommentNode },
  set: function (value) { processNodes.useCommentNode = value },
  enumerable: true
})

// Assign helpers on the main export.
for (helper in helpers) simulacra[helper] = helpers[helper]

module.exports = simulacra

/**
 * Bind an object to the DOM.
 *
 * @param {Object} obj
 * @param {Object} def
 * @param {Node} [matchNode]
 * @return {Node}
 */
function simulacra (obj, def, matchNode) {
  var document = this ? this.document : window.document
  var Node = this ? this.Node : window.Node
  var node, query

  // Before continuing, check if required features are present.
  featureCheck(this || window, features)

  if (obj === null || typeof obj !== 'object' || isArray(obj))
    throw new TypeError('First argument must be a singular object.')

  if (!isArray(def))
    throw new TypeError('Second argument must be an array.')

  if (typeof def[0] === 'string') {
    query = def[0]
    def[0] = document.querySelector(query)
    if (!def[0]) throw new Error(
      'Top-level Node "' + query + '" could not be found in the document.')
  }
  else if (!(def[0] instanceof Node)) throw new TypeError(
    'The first position of the top-level must be either a Node or a CSS ' +
    'selector string.')

  if (!def[isProcessedKey]) {
    // Auto-detect template tag.
    if ('content' in def[0]) def[0] = def[0].content

    def[0] = def[0].cloneNode(true)
    cleanNode(this, def[0])
    ensureNodes(def[0], def[1])
    setProperties(def)
  }

  node = processNodes(this, def[0], def[1])

  bindKeys(this, obj, def[1], node, { root: obj })

  if (matchNode) {
    rehydrate(this, obj, def[1], node, matchNode)
    return matchNode
  }

  return node
}

/**
 * Internal function to mutate string selectors into Nodes and validate that
 * they are allowed.
 *
 * @param {Element} parentNode
 * @param {Object} def
 */
function ensureNodes (parentNode, def) {
  var adjacentNodes = []
  var i, j, key, query, branch, boundNode, matchedNodes
  var adjacentNode, adjacentKey

  if (typeof def !== 'object') throw new TypeError(
    'The second position must be an object.')

  for (key in def) {
    branch = def[key]

    // Change function or definition object bound to parent.
    if (typeof branch === 'function' || (typeof branch === 'object' &&
      branch !== null && !Array.isArray(branch)))
      def[key] = branch = [ parentNode, branch ]

    // Cast CSS selector string to array.
    else if (typeof branch === 'string') def[key] = branch = [ branch ]

    else if (!Array.isArray(branch))
      throw new TypeError('The binding on key "' + key + '" is invalid.')

    // Dereference CSS selector string to actual DOM element.
    if (typeof branch[0] === 'string') {
      query = branch[0]

      // Match all nodes for the selector, pick the first and remove the rest.
      matchedNodes = parentNode.querySelectorAll(query)

      if (!matchedNodes.length) throw new Error(
        'An element for selector "' + query + '" was not found.')

      for (i = 1, j = matchedNodes.length; i < j; i++)
        matchedNodes[i].parentNode.removeChild(matchedNodes[i])

      branch[0] = matchedNodes[0]
    }
    else if (!branch[0])
      throw new TypeError(
        'The first position on key "' + key +
        '" must be a CSS selector string.')

    // Auto-detect template tag.
    if ('content' in branch[0]) branch[0] = branch[0].content

    boundNode = branch[0]

    if (typeof branch[1] === 'object' && branch[1] !== null) {
      Object.defineProperty(branch, hasDefinitionKey, { value: true })
      if (branch[2] && typeof branch[2] !== 'function')
        throw new TypeError('The third position on key "' + key +
          '" must be a function.')
    }
    else if (branch[1] && typeof branch[1] !== 'function')
      throw new TypeError('The second position on key "' + key +
        '" must be an object or a function.')

    // Special case for binding to parent node.
    if (parentNode === boundNode) {
      Object.defineProperty(branch, isBoundToParentKey, { value: true })
      if (branch[hasDefinitionKey]) ensureNodes(boundNode, branch[1])
      else if (typeof branch[1] === 'function')
        setReplaceAttribute(branch, boundNode)
      else console.warn( // eslint-disable-line
        'A change function was not defined on the key "' + key + '".')
      setProperties(branch)
      continue
    }

    adjacentNodes.push([ key, boundNode ])

    if (!parentNode.contains(boundNode))
      throw new Error('The bound DOM element must be either ' +
        'contained in or equal to the element in its parent binding.')

    if (branch[hasDefinitionKey]) {
      ensureNodes(boundNode, branch[1])
      setProperties(branch)
      continue
    }

    setReplaceAttribute(branch, boundNode)
    setProperties(branch)
  }

  // Need to loop again to invalidate containment in adjacent nodes, after the
  // adjacent nodes are found.
  for (key in def) {
    boundNode = def[key][0]
    for (i = 0, j = adjacentNodes.length; i < j; i++) {
      adjacentKey = adjacentNodes[i][0]
      adjacentNode = adjacentNodes[i][1]

      if (adjacentNode.contains(boundNode) && adjacentKey !== key)
        throw new Error(
          'The element for key "' + key + '" is contained in the ' +
          'element for the adjacent key "' + adjacentKey + '".')
    }
  }

  setProperties(def)
}

// Internal function to strip empty text nodes.
function cleanNode (scope, node) {
  // A constant for showing text nodes.
  var showText = 0x00000004
  var document = scope ? scope.document : window.document
  var treeWalker = document.createTreeWalker(
    node, showText, processNodes.acceptNode, false)
  var textNode

  while (treeWalker.nextNode()) {
    textNode = treeWalker.currentNode
    textNode.textContent = textNode.textContent.trim()
  }

  node.normalize()
}

function setReplaceAttribute (branch, boundNode) {
  Object.defineProperty(branch, replaceAttributeKey, {
    value: ~replaceValue.indexOf(boundNode.nodeName) ?
      ~replaceChecked.indexOf(boundNode.type) ?
        'checked' : 'value' : 'textContent'
  })
}

function setProperties (obj) {
  Object.defineProperty(obj, isProcessedKey, { value: true })
  Object.defineProperty(obj, markerKey, { value: null, writable: true })
}


'use strict'

var keyMap = require('./key_map')

var isBoundToParentKey = keyMap.isBoundToParent
var markerKey = keyMap.marker
var matchedNodeKey = keyMap.matchedNode
var templateKey = keyMap.template
var isMarkerLastKey = keyMap.isMarkerLast

// A fixed constant for `NodeFilter.SHOW_ALL`.
var showAll = 0xFFFFFFFF

// Option to use comment nodes as markers.
processNodes.useCommentNode = false

// Avoiding duplication of compatibility hack.
processNodes.acceptNode = acceptNode

module.exports = processNodes

/**
 * Internal function to remove bound nodes and replace them with markers.
 *
 * @param {*} [scope]
 * @param {Node} node
 * @param {Object} def
 * @return {Node}
 */
function processNodes (scope, node, def) {
  var document = scope ? scope.document : window.document
  var key, branch, result, mirrorNode, parent, marker, indices
  var i, j, treeWalker, orderedKeys

  result = def[templateKey]

  if (!result) {
    node = node.cloneNode(true)

    indices = []

    matchNodes(scope, node, def)

    orderedKeys = Object.keys(def).sort(function (a, b) {
      var nodeA = def[a][0][matchedNodeKey]
      var nodeB = def[b][0][matchedNodeKey]

      if (nodeA && nodeB)
        return nodeA.index - nodeB.index

      return 0
    })

    for (i = 0; i < orderedKeys.length; i++) {
      key = orderedKeys[i]
      branch = def[key]
      if (branch[isBoundToParentKey]) continue

      result = branch[0][matchedNodeKey]
      indices.push(result.index)
      mirrorNode = result.node
      parent = mirrorNode.parentNode

      // This value is memoized so that `appendChild` can be used instead of
      // `insertBefore`, which is a performance optimization.
      if (mirrorNode.nextElementSibling === null)
        branch[isMarkerLastKey] = true

      if (processNodes.useCommentNode) {
        marker = parent.insertBefore(
          document.createComment(' end "' + key + '" '), mirrorNode)
        parent.insertBefore(
          document.createComment(' begin "' + key + '" '), marker)
      }
      else marker = parent.insertBefore(
        document.createTextNode(''), mirrorNode)

      branch[markerKey] = marker

      parent.removeChild(mirrorNode)
    }

    Object.defineProperty(def, templateKey, {
      value: {
        node: node.cloneNode(true),
        indices: indices
      }
    })
  }
  else {
    node = result.node.cloneNode(true)
    indices = result.indices
    i = 0
    j = 0

    treeWalker = document.createTreeWalker(
      node, showAll, acceptNode, false)

    for (key in def) {
      branch = def[key]
      if (branch[isBoundToParentKey]) continue

      while (treeWalker.nextNode()) {
        if (i === indices[j]) {
          branch[markerKey] = treeWalker.currentNode
          i++
          break
        }
        i++
      }

      j++
    }
  }

  return node
}

/**
 * Internal function to find and set matching DOM nodes on cloned nodes.
 *
 * @param {*} [scope]
 * @param {Node} node
 * @param {Object} def
 */
function matchNodes (scope, node, def) {
  var document = scope ? scope.document : window.document
  var treeWalker = document.createTreeWalker(
    node, showAll, acceptNode, false)
  var nodes = []
  var i, j, key, currentNode, childWalker
  var nodeIndex = 0

  // This offset is a bit tricky, it's used to determine the index of the
  // marker in the processed node, which depends on whether comment nodes
  // are used and the count of child nodes.
  var offset = processNodes.useCommentNode ? 1 : 0

  for (key in def) nodes.push(def[key][0])

  while (treeWalker.nextNode() && nodes.length) {
    for (i = 0, j = nodes.length; i < j; i++) {
      currentNode = nodes[i]
      if (treeWalker.currentNode.isEqualNode(currentNode)) {
        Object.defineProperty(currentNode, matchedNodeKey, {
          value: {
            index: nodeIndex + offset,
            node: treeWalker.currentNode
          }
        })
        if (processNodes.useCommentNode) offset++
        childWalker = document.createTreeWalker(
          currentNode, showAll, acceptNode, false)
        while (childWalker.nextNode()) offset--
        nodes.splice(i, 1)
        break
      }
    }

    nodeIndex++
  }
}

// A crazy Internet Explorer workaround.
function acceptNode () { return 1 }
acceptNode.acceptNode = acceptNode


'use strict'

var processNodes = require('./process_nodes')
var bindKeys = require('./bind_keys')
var keyMap = require('./key_map')

var hasDefinitionKey = keyMap.hasDefinition
var isBoundToParentKey = keyMap.isBoundToParent
var markerKey = keyMap.marker
var metaKey = keyMap.meta
var acceptNode = processNodes.acceptNode

// A fixed constant for `NodeFilter.SHOW_ELEMENT`.
var whatToShow = 0x00000001

// Fixed constant for comment node type.
var COMMENT_NODE = 8

module.exports = rehydrate

/**
 * Rehydration of existing DOM nodes by recursively checking equality.
 *
 * @param {*} scope
 * @param {Object} obj
 * @param {Object} def
 * @param {Node} node
 * @param {Node} matchNode
 */
function rehydrate (scope, obj, def, node, matchNode) {
  var document = scope ? scope.document : window.document

  var key, branch, x, value, change, definition, mount, keyPath
  var meta, valueIsArray, activeNodes, index, treeWalker, currentNode

  for (key in def) {
    branch = def[key]
    meta = obj[metaKey][key]
    change = !branch[hasDefinitionKey] && branch[1]
    definition = branch[hasDefinitionKey] && branch[1]
    mount = branch[2]
    keyPath = meta.keyPath

    if (branch[isBoundToParentKey]) {
      x = obj[key]

      if (definition && x !== null && x !== void 0)
        bindKeys(scope, x, definition, matchNode, keyPath)
      else if (change)
        change(matchNode, x, null, keyPath)

      continue
    }

    activeNodes = meta.activeNodes
    if (!activeNodes.length) continue

    valueIsArray = meta.valueIsArray
    x = valueIsArray ? obj[key] : [ obj[key] ]
    index = 0
    treeWalker = document.createTreeWalker(
      matchNode, whatToShow, acceptNode, false)

    while (index < activeNodes.length && treeWalker.nextNode()) {
      currentNode = activeNodes[index]

      if (treeWalker.currentNode.isEqualNode(currentNode)) {
        activeNodes.splice(index, 1, treeWalker.currentNode)

        value = x[index]

        if (valueIsArray) keyPath.index = index
        else delete keyPath.index

        if (definition) {
          rehydrate(scope, value, definition,
            currentNode, treeWalker.currentNode)

          if (mount) {
            keyPath.target = value
            mount(treeWalker.currentNode, value, null, keyPath)
          }
        }
        else if (change)
          change(treeWalker.currentNode, value, null, keyPath)

        index++
      }
    }

    if (index !== activeNodes.length) throw new Error(
      'Matching nodes could not be found on key "' + key + '", expected ' +
      activeNodes.length + ', found ' + index + '.')

    // Rehydrate marker node.
    currentNode = treeWalker.currentNode

    // Try to re-use comment node.
    if (processNodes.useCommentNode &&
      currentNode.nextSibling !== null &&
      currentNode.nextSibling.nodeType === COMMENT_NODE)
      branch[markerKey] = currentNode.nextSibling
    else branch[markerKey] = currentNode.parentNode.insertBefore(
      document.createTextNode(''), currentNode.nextSibling)
  }
}


'use strict'

var htmlParser = require('htmlparser2')
var select = require('css-select')
var serialize = require('dom-serializer')

// These symbols are not re-usable.
var renderFnKey = symbol('renderFn')
var insertionPathKey = symbol('insertionPath')
var insertionOrderKey = symbol('insertionOrder')
var matchedNodeKey = symbol('matchedNode')
var hasDefinitionKey = symbol('hasDefinition')
var parentBindingsKey = symbol('parentBindings')

var hasSymbol = typeof Symbol === 'function'
var handlerOptions = { withDomLvl1: true }

// Entities are a performance killer! However, this is potentially unsafe.
var serializeOptions = { decodeEntities: false }

var escapeMap = {
  '&': '&',
  '<': '<',
  '>': '>',
  '"': '"',
  '\'': '''
}

var elementExtension = {
  addEventListener: { value: noop },
  removeEventListener: { value: noop },
  querySelector: { value: noop },
  querySelectorAll: { value: function () { return [] } },
  setAttribute: { value: function (key, value) {
    this.attribs[key] = value
  } },
  innerHTML: {
    get: function () {
      return this.children[0].data
    },
    set: function (html) {
      this.children = [ { data: html } ]
      return html
    },
    configurable: true
  },
  textContent: {
    get: function () {
      return this.children[0].data
    },
    set: function (text) {
      this.children = [ { data: htmlEscape(text) } ]
      return text
    },
    configurable: true
  },
  classList: {
    get: function () {
      var self = this

      return {
        add: function (cls) {
          if (!('class' in self.attribs)) self.attribs['class'] = cls
          else if (self.attribs['class'].match(new RegExp(cls))) return
          else self.attribs['class'] += ' ' + cls
        },
        toggle: function (cls) {
          if (self.attribs['class'].match(new RegExp(cls))) this.remove(cls)
          else this.add(cls)
        },
        remove: function (cls) {
          self.attribs['class'] = (self.attribs['class'] || '')
            .replace(new RegExp(' *' + cls + '\b'), '').trim()
        }
      }
    },
    configurable: true
  }
}

var attributeAliases = {
  checked: 'checked',
  className: 'class',
  placeholder: 'placeholder',
  value: 'value',
  id: 'id',
  href: 'href',
  src: 'src',
  action: 'action',
  required: 'required',
  disabled: 'disabled'
}

var booleanAttributes = [
  'checked', 'required', 'disabled'
]

var key

for (key in attributeAliases)
  extendElement(
    key,
    attributeAliases[key],
    ~booleanAttributes.indexOf(key))

module.exports = render

/**
 * Internal function for rendering strings. The benchmark to beat is plain old
 * string concatenation and for loops. Although this won't be faster, it should
 * work with more or less the same functionality as the DOM version.
 *
 * Only a tiny subset of the actual DOM is implemented here, so it may not work
 * for all front-end functionality.
 *
 * @param {Object} obj
 * @param {Object} def
 * @param {String} [html]
 * @return {String}
 */
function render (obj, def, html) {
  var i, nodes, handler, parser, element, elementPrototype

  // If given bindings with a root node, pick only the binding keys.
  if (Array.isArray(def)) def = def[1]

  // Generating the render function is processing intensive. Skip if possible.
  if (renderFnKey in def) return def[renderFnKey](obj)

  // Callback API looks weird. This is actually synchronous, not asynchronous.
  handler = new htmlParser.DomHandler(function (error, result) {
    if (error) throw error
    nodes = result
  }, handlerOptions)

  parser = new htmlParser.Parser(handler)
  parser.write(html)
  parser.end()

  for (i = nodes.length; i--;)
    if (nodes[i].type === 'tag') {
      element = nodes[i]
      break
    }

  if (!element) throw new Error('No element found!')

  elementPrototype = Object.getPrototypeOf(element)
  Element.prototype = elementPrototype
  Object.defineProperties(elementPrototype, elementExtension)

  processDefinition(def, nodes)
  def[renderFnKey] = makeRender(def, nodes)

  return def[renderFnKey](obj)
}

function processDefinition (def, nodes) {
  var insertionIndices = []
  var key, branch, selector, matches, match

  for (key in def) {
    branch = def[key]

    if (typeof branch === 'function') {
      if (!(parentBindingsKey in def)) def[parentBindingsKey] = []
      def[parentBindingsKey].push(key)
      continue
    }

    if (!Array.isArray(branch)) def[key] = branch = [ branch ]
    selector = branch[0]

    if (typeof selector !== 'string') throw new Error('Selector on "' +
      key + '" must be a string.')

    matches = select(selector, nodes)

    if (!matches.length) throw new Error('Matching node not found on "' +
      key + '".')

    match = matches[0]
    normalize(match)
    locateMatch(branch, nodes, match, [])
    insertionIndices.push(branch[insertionPathKey])
    branch[matchedNodeKey] = match
    removeMatches(nodes, matches)

    if (typeof branch[1] === 'object') {
      branch[hasDefinitionKey] = true
      processDefinition(branch[1], match.children)
    }
  }

  def[insertionOrderKey] = Object.keys(def)
    .filter(function (key) {
      return insertionPathKey in def[key]
    })
    .sort(function (a, b) {
      return def[a][insertionPathKey][0] - def[b][insertionPathKey][0]
    })

  // Optimization: serialize static nodes in advance.
  serializeNodes(nodes, insertionIndices)
}

function serializeNodes (nodes, insertionIndices) {
  var i, j, node, matches = []

  for (i = 0, j = insertionIndices.length; i < j; i++)
    matches.push(insertionIndices[i][0])

  for (i = 0, j = nodes.length; i < j; i++) {
    node = nodes[i]
    if (!~matches.indexOf(i))
      nodes[i] = {
        data: serialize(normalize(node), serializeOptions)
      }
    else if ('children' in node && node.children.length)
      serializeNodes(node.children, insertionIndices.map(sliceIndex))
    else if ('data' in node)
      normalize(node)
  }

  function sliceIndex (index) {
    return index.slice(1)
  }
}

function normalize (node) {
  if ('children' in node && node.children.length)
    node.children.forEach(normalize)
  else if ('data' in node)
    node.data = node.data.trim()
  return node
}

function locateMatch (branch, nodes, match, path) {
  var i, j, node

  for (i = 0, j = nodes.length; i < j; i++) {
    node = nodes[i]

    if (match === node) {
      path.push(i)
      branch[insertionPathKey] = path
      return true
    }

    if ('children' in node && node.children.length) {
      path.push(i)
      if (!locateMatch(branch, node.children, match, path)) path.pop()
      else return true
    }
  }

  return false
}

function removeMatches (nodes, matches) {
  var i, node

  for (i = nodes.length; i--;) {
    node = nodes[i]
    if (~matches.indexOf(node)) {
      nodes.splice(i, 1)
      continue
    }
    if ('children' in node && node.children.length)
      removeMatches(node.children, matches)
  }
}

function makeRender (def, nodes) {
  return function (obj) {
    var dom = fillTemplate(obj, def, nodes, obj)
    var html = serialize(dom, serializeOptions)
    return html
  }
}

function fillTemplate (obj, def, nodes, root) {
  var clonedNodes = []
  var path = {
    root: root,
    target: obj
  }
  var key, value, branch, node, change, returnValue, isArray
  var nestedDef, parentKey
  var i, j, k, l

  // Clone nodes and children recursively.
  for (i = 0, j = nodes.length; i < j; i++)
    clonedNodes.push(cloneNode(nodes[i]))

  for (i = def[insertionOrderKey].length; i--;) {
    key = def[insertionOrderKey][i]
    path.key = key

    value = obj[key]
    if (value === null || value === void 0) continue

    isArray = Array.isArray(value)
    if (!isArray) value = [ value ]

    branch = def[key]

    for (j = value.length; j--;) {
      // Clone current node recursively.
      node = cloneNode(branch[matchedNodeKey])

      if (hasDefinitionKey in branch) {
        nestedDef = branch[1]
        if (parentBindingsKey in nestedDef)
          for (k = 0, l = nestedDef[parentBindingsKey].length; k < l; k++) {
            parentKey = nestedDef[parentBindingsKey][k]
            path.key = parentKey
            nestedDef[parentKey](node, value[j][parentKey], null, path)
          }

        node.children = fillTemplate(value[j], nestedDef, node.children, root)
        change = branch[2]
      }
      else change = branch[1]

      // Apply change function.
      if (change) {
        if (isArray) path.index = j
        else delete path.index
        returnValue = change(node, value[j], null, path)
      }
      else returnValue = value[j]

      if (!(hasDefinitionKey in branch) && returnValue !== void 0)
        if (node.name === 'input' || node.name === 'progress')
          if (node.attribs.type === 'checkbox' ||
            node.attribs.type === 'radio') {
            if (returnValue) node.attribs.checked = true
          }
          else node.attribs.value = htmlEscape(returnValue)
        else node.children = [ { data: htmlEscape(returnValue) } ]

      insertNode(node, clonedNodes, branch[insertionPathKey])
    }
  }

  return clonedNodes
}

function insertNode (node, nodes, path) {
  var i, j, currentNodes = nodes

  for (i = 0, j = path.length - 1; i < j; i++)
    currentNodes = currentNodes[path[i]].children

  currentNodes.splice(path[j], 0, node)
}

function cloneNode (node) {
  var i, j, key, clonedNode

  if ('data' in node) return node

  clonedNode = new Element()
  clonedNode.type = node.type
  clonedNode.name = node.name
  clonedNode.children = []
  clonedNode.attribs = {}

  if ('children' in node)
    for (i = 0, j = node.children.length; i < j; i++)
      clonedNode.children.push(cloneNode(node.children[i]))

  for (key in node.attribs)
    clonedNode.attribs[key] = node.attribs[key]

  return clonedNode
}

function htmlEscape (html) {
  return typeof html === 'string' ?
    html.replace(/[&<>"']/g, replacer) : html
}

function replacer (char) {
  return escapeMap[char]
}

function extendElement (key, attribute, isBoolean) {
  elementExtension[key] = {
    get: function () {
      return this.attribs[attribute]
    },
    set: function (value) {
      if (isBoolean && !value) delete this.attribs[attribute]
      else this.attribs[attribute] = htmlEscape(value)
      return value
    },
    configurable: true
  }
}

function symbol (description) {
  return hasSymbol ?
    Symbol(description) : '__' + description + '__'
}

function noop () {}

// Internal type.
function Element () {}


'use strict'

module.exports = featureCheck

/**
 * Check if capabilities are available, or throw an error.
 *
 * @param {*} globalScope
 */
function featureCheck (globalScope, features) {
  var i, j, k, l, feature, path

  for (i = 0, j = features.length; i < j; i++) {
    path = features[i]

    if (typeof path[0] === 'string') {
      feature = globalScope

      for (k = 0, l = path.length; k < l; k++) {
        if (!(path[k] in feature)) throw new Error('Missing ' +
          path.slice(0, k + 1).join('.') + ' feature which is required.')

        feature = feature[path[k]]
      }
    }

    else {
      feature = path[0]

      for (k = 1, l = path.length; k < l; k++) {
        if (k > 1) feature = feature[path[k]]

        if (typeof feature === 'undefined') throw new Error('Missing ' +
          path[0].name + path.slice(1, k + 1).join('.') +
          ' feature which is required.')
      }
    }
  }
}


var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
  return typeof obj;
} : function (obj) {
  return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};

var _extends = Object.assign || function (target) {
  for (var i = 1; i < arguments.length; i++) {
    var source = arguments[i];

    for (var key in source) {
      if (Object.prototype.hasOwnProperty.call(source, key)) {
        target[key] = source[key];
      }
    }
  }

  return target;
};

var isString = function isString(val) {
  return val && typeof val === 'string' && val.length > 0;
};
var isPObject = function isPObject(val) {
  return val && (typeof val === 'undefined' ? 'undefined' : _typeof(val)) === 'object' && val !== null && val.constructor === Object && Object.keys(val).length > 0;
};

var hyphenate = function hyphenate(str) {
  return str.replace(/\B([A-Z])/g, '-$1').toLowerCase();
};
// eslint-disable-next-line no-restricted-globals
var isNumber = function isNumber(val) {
  return typeof val === 'number' && isFinite(val);
};

/**
 * Create String from BEM entitys
 * @param {Object} entitys BEM entitys
 * @param {String} entitys.block Block
 * @param {String} [entitys.el] Element
 * @param {Object<string>} [entitys.mods] Modifiers
 * @param {String} [entitys.mixin] Mixin
 * @param {Object<string>} delimiters Delimiters for BEM entitys
 * @returns {String}
 */
function bemNames(entitys, delimiters) {
  var resultString = '';
  var names = entitys || { mods: {}, mixin: '' };
  var delims = _extends({
    ns: '',
    el: '__',
    mod: '--',
    modVal: '-'
  }, delimiters);
  var mixin = isString(names.mixin) ? ' ' + names.mixin : '';

  if (!names.block) return '';
  resultString = delims.ns ? delims.ns + names.block : names.block;

  if (names.el) resultString += delims.el + names.el;

  if (isPObject(names.mods)) {
    resultString += Object.keys(names.mods).reduce(function (prev, name) {
      var val = names.mods[name];
      /* eslint-disable no-param-reassign */
      if (val === true) {
        prev += ' ' + resultString + delims.mod + name;
      } else if (isString(val) || isNumber(val)) {
        prev += ' ' + resultString + delims.mod + name + delims.modVal + names.mods[name];
      }
      /* eslint-enable no-param-reassign */

      return prev;
    }, '');
  }
  return resultString + mixin;
}

function bemCn(block) {
  var opt = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : { delimiters: {} };

  var options = _extends({
    hyphenate: false
  }, opt, {
    delimiters: _extends({}, opt.delimiters)
  });
  if (!isString(block)) return '';

  return function entitys(elem, mods, mix) {
    var resultObj = {
      block: block,
      el: '',
      mods: {},
      mixin: ''
    };

    if (isPObject(mods)) resultObj.mods = mods;

    if (isString(elem)) {
      resultObj.el = elem;
    } else if (isPObject(elem)) {
      resultObj.mods = elem;
    }

    if (isString(mods)) {
      resultObj.mixin = resultObj.mixin.length > 0 ? resultObj.mixin + ' ' + mods : resultObj.mixin + mods;
    }
    if (isString(mix)) {
      resultObj.mixin = resultObj.mixin.length > 0 ? resultObj.mixin + ' ' + mix : resultObj.mixin + mix;
    }

    if (options.hyphenate) {
      return hyphenate(bemNames(resultObj, options.delimiters));
    }

    return bemNames(resultObj, options.delimiters);
  };
}

var vuePlugin = {
  install: function install(Vue) {
    var config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : { delimiters: {} };

    var cfg = _extends({
      hyphenate: false,
      methodName: 'b'
    }, config, {
      delimiters: _extends({
        ns: '',
        el: '__',
        mod: '--',
        modVal: '-'
      }, config.delimiters)
    });

    Vue.mixin({
      created: function created() {
        var block = this.$options.block || this.$options.name;
        var generator = null;

        if (typeof block !== 'string') return;

        generator = bemCn(block, cfg);
        this[cfg.methodName] = function () {
          return generator.apply(undefined, arguments);
        };
      }
    });
  }
};

export default vuePlugin;
//# sourceMappingURL=vue-bem-cn.es6.js.map


const fs = require('fs')

// const { blue } = require('./log.js')

function write (dest, code) {
  return new Promise(function (resolve, reject) {
    fs.writeFile(dest, code, function (err) {
      if (err) return reject(err)
      // console.log(blue(dest) + ' ' + getSize(code))
      resolve(code)
    })
  })
}

// function getSize (code) {
//   return (code.length / 1024).toFixed(2) + 'kb'
// }

module.exports = write


const path = require('path');
const yaml = require('js-yaml');
const merge = require('deepmerge');

const CONFIG_PATH = '.github';
const BASE_KEY = '_extends';
const BASE_REGEX = new RegExp(
  '^' +
  '(?:([a-z\\d](?:[a-z\\d]|-(?=[a-z\\d])){0,38})/)?' + // org
  '([-_.\\w\\d]+)' + // project
  '(?::([-_./\\w\\d]+\\.ya?ml))?' + // filename
    '$',
  'i'
);
const DEFAULT_BASE = '.github';

/**
 * Decodes and parses a YAML config file
 *
 * @param {string} content Base64 encoded YAML contents
 * @returns {object} The parsed YAML file as native object
 */
function parseConfig(content) {
  return yaml.safeLoad(Buffer.from(content, 'base64').toString()) || {};
}

/**
 * Merges an array of configs
 *
 * @param {array<config>} configs The configs to merge
 * @param {object} options https://github.com/KyleAMathews/deepmerge#options
 * @returns {object} The merged configuration
 */
function deepMergeConfigs(configs, options) {
  return merge.all(configs.filter(config => config), options);
}

/**
 * Loads a file from GitHub
 *
 * @param {Context} context A Probot context
 * @param {object} params Params to fetch the file with
 * @returns {Promise<object>} The parsed YAML file
 * @async
 */
async function loadYaml(context, params) {
  try {
    const response = await context.github.repos.getContents(params);
    return parseConfig(response.data.content);
  } catch (e) {
    if (e.code === 404) {
      return null;
    }

    throw e;
  }
}

/**
 * Computes parameters for the repository specified in base
 *
 * Base can either be the name of a repository in the same organization or
 * a full slug "organization/repo".
 *
 * @param {object} params An object containing owner, repo and path
 * @param {string} base A string specifying the base repository
 * @returns {object} The params of the base configuration
 */
function getBaseParams(params, base) {
  if (typeof base !== 'string') {
    throw new Error(`Invalid repository name in key "${BASE_KEY}"`);
  }

  const match = base.match(BASE_REGEX);
  if (match == null) {
    throw new Error(`Invalid repository name in key "${BASE_KEY}": ${base}`);
  }

  return {
    owner: match[1] || params.owner,
    repo: match[2],
    path: match[3] || params.path,
  };
}

/**
 * Loads the specified config file from the context's repository
 *
 * If the config file contains a top-level key "_extends", it is merged
 * with a config of the same name in the repository specified by the
 * _extends key.  The repository of the base configuration can either be
 * given as "repository" or as "organization/repository".
 *
 * If the config file does not exist in the context's repository, `null`
 * is returned. If the base repository does not exist or does not contain
 * the config file, it is ignored.
 *
 * If a default config is given, it is merged with the config from the
 * repository, if it exists.
 *
 * @param {Context} context A Probot context
 * @param {string} fileName Name of the config file
 * @param {object} defaultConfig A default config that is merged in
 * @param {object} deepMergeOptions from deepmerge
 * @returns {object} The merged configuration
 * @async
 */
async function getConfig(context, fileName, defaultConfig, deepMergeOptions) {
  const filePath = path.posix.join(CONFIG_PATH, fileName);
  const params = context.repo({
    path: filePath,
  });

  const config = await loadYaml(context, params);
  let baseRepo;
  if (config == null) {
    baseRepo = DEFAULT_BASE;
  } else if (config != null && BASE_KEY in config) {
    baseRepo = config[BASE_KEY];
    delete config[BASE_KEY];
  }

  let baseConfig;
  if (baseRepo) {
    const baseParams = getBaseParams(params, baseRepo);
    baseConfig = await loadYaml(context, baseParams);
  }

  if (config == null && baseConfig == null && !defaultConfig) {
    return null;
  }

  return deepMergeConfigs(
    [defaultConfig, baseConfig, config],
    deepMergeOptions
  );
}

module.exports = getConfig;


'use strict'

function isFunction (funktion) {
  return typeof funktion === 'function'
}

// Default to complaining loudly when things don't go according to plan.
var logger = console.error.bind(console)

// Sets a property on an object, preserving its enumerability.
// This function assumes that the property is already writable.
function defineProperty (obj, name, value) {
  var enumerable = !!obj[name] && obj.propertyIsEnumerable(name)
  Object.defineProperty(obj, name, {
    configurable: true,
    enumerable: enumerable,
    writable: true,
    value: value
  })
}

// Keep initialization idempotent.
function shimmer (options) {
  if (options && options.logger) {
    if (!isFunction(options.logger)) logger("new logger isn't a function, not replacing")
    else logger = options.logger
  }
}

function wrap (nodule, name, wrapper) {
  if (!nodule || !nodule[name]) {
    logger('no original function ' + name + ' to wrap')
    return
  }

  if (!wrapper) {
    logger('no wrapper function')
    logger((new Error()).stack)
    return
  }

  if (!isFunction(nodule[name]) || !isFunction(wrapper)) {
    logger('original object and wrapper must be functions')
    return
  }

  var original = nodule[name]
  var wrapped = wrapper(original, name)

  defineProperty(wrapped, '__original', original)
  defineProperty(wrapped, '__unwrap', function () {
    if (nodule[name] === wrapped) defineProperty(nodule, name, original)
  })
  defineProperty(wrapped, '__wrapped', true)

  defineProperty(nodule, name, wrapped)
  return wrapped
}

function massWrap (nodules, names, wrapper) {
  if (!nodules) {
    logger('must provide one or more modules to patch')
    logger((new Error()).stack)
    return
  } else if (!Array.isArray(nodules)) {
    nodules = [nodules]
  }

  if (!(names && Array.isArray(names))) {
    logger('must provide one or more functions to wrap on modules')
    return
  }

  nodules.forEach(function (nodule) {
    names.forEach(function (name) {
      wrap(nodule, name, wrapper)
    })
  })
}

function unwrap (nodule, name) {
  if (!nodule || !nodule[name]) {
    logger('no function to unwrap.')
    logger((new Error()).stack)
    return
  }

  if (!nodule[name].__unwrap) {
    logger('no original to unwrap to -- has ' + name + ' already been unwrapped?')
  } else {
    return nodule[name].__unwrap()
  }
}

function massUnwrap (nodules, names) {
  if (!nodules) {
    logger('must provide one or more modules to patch')
    logger((new Error()).stack)
    return
  } else if (!Array.isArray(nodules)) {
    nodules = [nodules]
  }

  if (!(names && Array.isArray(names))) {
    logger('must provide one or more functions to unwrap on modules')
    return
  }

  nodules.forEach(function (nodule) {
    names.forEach(function (name) {
      unwrap(nodule, name)
    })
  })
}

shimmer.wrap = wrap
shimmer.massWrap = massWrap
shimmer.unwrap = unwrap
shimmer.massUnwrap = massUnwrap

module.exports = shimmer


/**
 * @file hexo-pwa entry
 * @author pengxing(pengxing@baidu.com)
 */

'use strict';

/* globals hexo */
const fs = require('fs');
const path = require('path');
const tpl = require('lodash.template');

const manifestGenerator = require('./lib/manifest');
const serviceWorkerGenerator = require('./lib/serviceWorker');

let compiledSWRegTpl;
let asyncLoadPageJSTpl;

if (hexo.config.pwa.manifest) {
  // generate manifest json file
  hexo.extend.generator.register('manifest', manifestGenerator);
}

if (hexo.config.pwa.serviceWorker) {
  // get sw register code and compile
  let swTpl = fs.readFileSync(path.resolve(__dirname, './templates/swRegister.tpl.js'));
  compiledSWRegTpl = tpl(swTpl)({path: hexo.config.pwa.serviceWorker.path + '?t=' + Date.now()});
  // generate service worker file
  hexo.extend.generator.register('serviceWorker', serviceWorkerGenerator);
}

if (hexo.config.pwa.asyncLoadPage) {
  // get async load page js tpl
  asyncLoadPageJSTpl = fs.readFileSync(path.resolve(__dirname, './templates/asyncLoadPage.tpl.js'));
}

// inject manifest into html files
hexo.extend.filter.register('after_render:html', data => {
  if (!data) {
    return;
  }

  // inject code into pages
  if (hexo.config.pwa.manifest) {
    data = injectManifest(data);
  }

  if (hexo.config.pwa.serviceWorker) {
    data = injectSWRegister(data);
  }

  if (hexo.config.pwa.asyncLoadPage) {
    data = injectAsyncLoadPageJS(data);
  }

  return data;
}, hexo.config.pwa.priority || 10);

/**
 * inject manifest html fragment into page code
 *
 * @param {string} data html
 * @return {string}
 */
function injectManifest(data) {
  let manifestHtml = `<head><link rel=manifest href=${hexo.config.pwa.manifest.path}>`;

  if (data.indexOf(manifestHtml) === -1) {
    data = data.replace('', manifestHtml);
  }

  return data;
}

/**
 * inject service worker registion fragment into page code
 *
 * @param {string} data html
 * @return {string}
 */
function injectSWRegister(data) {
  let swHtml = `<script>${compiledSWRegTpl}</script></body>`;

  if (data.indexOf(compiledSWRegTpl) === -1) {
    data = data.replace('', swHtml);
  }

  return data;
}

/**
 * inject async load page js fragment
 *
 * @param {string} data html
 * @return {string}
 */
function injectAsyncLoadPageJS(data) {
  let injectHtml = `<script>${asyncLoadPageJSTpl}</script>`;

  if (data.indexOf(injectHtml) === -1) {
    data = data.replace('', injectHtml);
  }

  return data;
}


'use strict';

/* Dependencies. */
var has = require('has');
var toH = require('hast-to-hyperscript');
var tableCellStyle = require('@mapbox/hast-util-table-cell-style');

/* Expose `rehype-react`. */
module.exports = rehype2react;

/**
 * Attach a react compiler.
 *
 * @param {Unified} processor - Instance.
 * @param {Object?} [options]
 * @param {Object?} [options.components]
 *   - Components.
 * @param {string?} [options.prefix]
 *   - Key prefix.
 * @param {Function?} [options.createElement]
 *   - `h()`.
 */
function rehype2react(options) {
  var settings = options || {};
  var createElement = settings.createElement;
  var components = settings.components || {};

  this.Compiler = compiler;

  /* Compile HAST to React. */
  function compiler(node) {
    if (node.type === 'root') {
      if (node.children.length === 1 && node.children[0].type === 'element') {
        node = node.children[0];
      } else {
        node = {
          type: 'element',
          tagName: 'div',
          properties: node.properties || {},
          children: node.children
        };
      }
    }

    return toH(h, tableCellStyle(node), settings.prefix);
  }

  /* Wrap `createElement` to pass components in. */
  function h(name, props, children) {
    var component = has(components, name) ? components[name] : name;
    return createElement(component, props, children);
  }
}


'use strict';
var child_process = require('child_process');
var crossSpawn = require('cross-spawn');
var ChildProcessPromise = require('./ChildProcessPromise');
var ChildProcessError = require('./ChildProcessError');

var slice = Array.prototype.slice;

/**
 * Promise wrapper for exec, execFile
 *
 * @param {String} method
 * @param {...*} args
 * @return {Promise}
 */
function doExec(method, args) {
    var cp;
    var cpPromise = new ChildProcessPromise();
    var reject = cpPromise._cpReject;
    var resolve = cpPromise._cpResolve;

    var finalArgs = slice.call(args, 0);
    finalArgs.push(callback);

    cp = child_process[method].apply(child_process, finalArgs);

    function callback(err, stdout, stderr) {
        if (err) {
            var commandStr = args[0] + (Array.isArray(args[1]) ? (' ' + args[1].join(' ')) : '');
            err.message += ' `' + commandStr + '` (exited with error code ' + err.code + ')';
            err.stdout = stdout;
            err.stderr = stderr;
            var cpError = new ChildProcessError(err.message, err.code, child_process, stdout, stderr);
            reject(cpError);
        } else {
            resolve({
                childProcess: cp,
                stdout: stdout,
                stderr: stderr
            });
        }
    }

    cpPromise.childProcess = cp;

    return cpPromise;
}

/**
 * `exec` as Promised
 *
 * @param {String} command
 * @param {Object} options
 * @return {Promise}
 */
function exec() {
    return doExec('exec', arguments);
}

/**
 * `execFile` as Promised
 *
 * @param {String} command
 * @param {Array} args
 * @param {Object} options
 * @return {Promise}
 */
function execFile() {
    return doExec('execFile', arguments);
}

/**
 * `spawn` as Promised
 *
 * @param {String} command
 * @param {Array} args
 * @param {Object} options
 * @return {Promise}
 */
function doSpawn(method, command, args, options) {
    var result = {};

    var cp;
    var cpPromise = new ChildProcessPromise();
    var reject = cpPromise._cpReject;
    var resolve = cpPromise._cpResolve;

    var successfulExitCodes = (options && options.successfulExitCodes) || [0];

    cp = method(command, args, options);

    // Don't return the whole Buffered result by default.
    var captureStdout = false;
    var captureStderr = false;

    var capture = options && options.capture;
    if (capture) {
        for (var i = 0, len = capture.length; i < len; i++) {
            var cur = capture[i];
            if (cur === 'stdout') {
                captureStdout = true;
            } else if (cur === 'stderr') {
                captureStderr = true;
            }
        }
    }

    result.childProcess = cp;

    if (captureStdout) {
        result.stdout = '';

        cp.stdout.on('data', function(data) {
            result.stdout += data;
        });
    }

    if (captureStderr) {
        result.stderr = '';

        cp.stderr.on('data', function(data) {
            result.stderr += data;
        });
    }

    cp.on('error', reject);

    cp.on('close', function(code) {
        if (successfulExitCodes.indexOf(code) === -1) {
            var commandStr = command + (args.length ? (' ' + args.join(' ')) : '');
            var message = '`' + commandStr + '` failed with code ' + code;
            var err = new ChildProcessError(message, code, cp);

            if (captureStderr) {
                err.stderr = result.stderr.toString();
            }

            if (captureStdout) {
                err.stdout = result.stdout.toString();
            }

            reject(err);
        } else {
            result.code = code;
            resolve(result);
        }
    });

    cpPromise.childProcess = cp;

    return cpPromise;
}

function spawn(command, args, options) {
    return doSpawn(crossSpawn, command, args, options);
}

function fork(modulePath, args, options) {
    return doSpawn(child_process.fork, modulePath, args, options);
}

exports.exec = exec;
exports.execFile = execFile;
exports.spawn = spawn;
exports.fork = fork;