'use strict';

Object.defineProperty(exports, '__esModule', {
  value: true
});

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; };

exports['default'] = patch;

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

var _render = require('./render');

var _render2 = _interopRequireDefault(_render);

var _updateProps = require('./update-props');

var _updateProps2 = _interopRequireDefault(_updateProps);

function patch(oldVirtualNode, newVirtualNode, options) {
  var oldNode = oldVirtualNode.domNode;
  if (virtualNodesAreEqual(oldVirtualNode, newVirtualNode)) {
    var newNode = undefined;
    if (newVirtualNode.text != null) {
      oldNode.nodeValue = newVirtualNode.text;
      newNode = oldNode;
    } else {
      if (typeof newVirtualNode.tag === 'function') {
        newNode = updateComponent(oldVirtualNode, newVirtualNode, options);
      } else {
        updateChildren(oldNode, oldVirtualNode.children, newVirtualNode.children, options);
        (0, _updateProps2['default'])(oldNode, oldVirtualNode, newVirtualNode, options);
        newNode = oldNode;
      }
    }
    newVirtualNode.domNode = oldNode;
    if (newNode !== oldNode && oldNode.parentNode) {
      oldNode.parentNode.replaceChild(newNode, oldNode);
    }
    return newNode;
  } else {
    var parentNode = oldNode.parentNode;
    var nextSibling = oldNode.nextSibling;
    removeVirtualNode(oldVirtualNode, options && options.refs);
    var newNode = (0, _render2['default'])(newVirtualNode, options);
    if (parentNode) parentNode.insertBefore(newNode, nextSibling);
    newVirtualNode.domNode = newNode;
    return newNode;
  }
}

function updateComponent(oldVirtualNode, newVirtualNode, options) {
  var component = oldVirtualNode.component;
  var oldProps = oldVirtualNode.props;
  var newProps = newVirtualNode.props;
  var newChildren = newVirtualNode.children;

  newVirtualNode.component = component;
  if (options && options.refs) {
    var refs = options.refs;
    var oldRefName = oldProps && oldProps.ref;
    var newRefName = newProps && newProps.ref;
    if (newRefName !== oldRefName) {
      if (oldRefName && refs[oldRefName] === component) delete refs[oldRefName];
      if (newRefName) refs[newRefName] = component;
    }
    if (newRefName) {
      newProps = _extends({}, newProps);
      delete newProps.ref;
    }
  }
  component.update(newProps || {}, newChildren);
  return component.element;
}

function updateChildren(parentElement, oldChildren, newChildren, options) {
  var oldStartIndex = 0;
  var oldEndIndex = oldChildren.length - 1;
  var oldStartChild = oldChildren[0];
  var oldEndChild = oldChildren[oldEndIndex];

  var newStartIndex = 0;
  var newEndIndex = newChildren.length - 1;
  var newStartChild = newChildren[0];
  var newEndChild = newChildren[newEndIndex];

  var oldIndicesByKey = undefined;

  while (oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex) {
    if (!oldStartChild) {
      oldStartChild = oldChildren[++oldStartIndex];
    } else if (!oldEndChild) {
      oldEndChild = oldChildren[--oldEndIndex];
    } else if (virtualNodesAreEqual(oldStartChild, newStartChild)) {
      patch(oldStartChild, newStartChild, options);
      oldStartChild = oldChildren[++oldStartIndex];
      newStartChild = newChildren[++newStartIndex];
    } else if (virtualNodesAreEqual(oldEndChild, newEndChild)) {
      patch(oldEndChild, newEndChild, options);
      oldEndChild = oldChildren[--oldEndIndex];
      newEndChild = newChildren[--newEndIndex];
    } else if (virtualNodesAreEqual(oldStartChild, newEndChild)) {
      patch(oldStartChild, newEndChild, options);
      parentElement.insertBefore(oldStartChild.domNode, oldEndChild.domNode.nextSibling);
      oldStartChild = oldChildren[++oldStartIndex];
      newEndChild = newChildren[--newEndIndex];
    } else if (virtualNodesAreEqual(oldEndChild, newStartChild)) {
      patch(oldEndChild, newStartChild, options);
      parentElement.insertBefore(oldEndChild.domNode, oldStartChild.domNode);
      oldEndChild = oldChildren[--oldEndIndex];
      newStartChild = newChildren[++newStartIndex];
    } else {
      if (!oldIndicesByKey) oldIndicesByKey = mapOldKeysToIndices(oldChildren, oldStartIndex, oldEndIndex);
      var key = getKey(newStartChild);
      var oldIndex = key ? oldIndicesByKey[key] : null;
      if (oldIndex == null) {
        parentElement.insertBefore((0, _render2['default'])(newStartChild, options), oldStartChild.domNode);
        newStartChild = newChildren[++newStartIndex];
      } else {
        var oldChildToMove = oldChildren[oldIndex];
        patch(oldChildToMove, newStartChild, options);
        oldChildren[oldIndex] = undefined;
        parentElement.insertBefore(oldChildToMove.domNode, oldStartChild.domNode);
        newStartChild = newChildren[++newStartIndex];
      }
    }
  }

  if (oldStartIndex > oldEndIndex) {
    var subsequentElement = newChildren[newEndIndex + 1] ? newChildren[newEndIndex + 1].domNode : null;
    for (var i = newStartIndex; i <= newEndIndex; i++) {
      parentElement.insertBefore((0, _render2['default'])(newChildren[i], options), subsequentElement);
    }
  } else if (newStartIndex > newEndIndex) {
    for (var i = oldStartIndex; i <= oldEndIndex; i++) {
      var child = oldChildren[i];
      if (child) removeVirtualNode(child, options && options.refs);
    }
  }
}

function removeVirtualNode(virtualNode, refs) {
  var removeDOMNode = arguments.length <= 2 || arguments[2] === undefined ? true : arguments[2];
  var domNode = virtualNode.domNode;
  var props = virtualNode.props;
  var children = virtualNode.children;
  var component = virtualNode.component;

  var ref = props && props.ref;
  if (component) {
    if (refs && ref && refs[ref] === component) delete refs[ref];
    if (typeof component.destroy === 'function') component.destroy();
  } else {
    if (refs && ref && refs[ref] === domNode) delete refs[ref];
    if (children) {
      for (var i = 0; i < children.length; i++) {
        removeVirtualNode(children[i], refs, false);
      }
    }
  }

  if (removeDOMNode) domNode.remove();
}

function removeRefs(virtualNode, refs) {
  for (var i = 0; i < children.length; i++) {
    removeRefs(children[i], refs);
  }
}

function virtualNodesAreEqual(oldVirtualNode, newVirtualNode) {
  return getKey(oldVirtualNode) === getKey(newVirtualNode) && oldVirtualNode.tag === newVirtualNode.tag;
}

function getKey(virtualNode) {
  return virtualNode.props ? virtualNode.props.key : undefined;
}

function mapOldKeysToIndices(children, startIndex, endIndex) {
  var oldIndicesByKey = {};
  for (var i = startIndex; i <= endIndex; i++) {
    var key = getKey(children[i]);
    if (key) oldIndicesByKey[key] = i;
  }
  return oldIndicesByKey;
}
module.exports = exports['default'];