/**
 * Fragments.js
 *
 * Released under LGPL License.
 * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This module reads and applies html fragments from/to dom nodes.
 *
 * @class tinymce.undo.Fragments
 * @private
 */
define(
  'tinymce.core.undo.Fragments',
  [
    "tinymce.core.util.Arr",
    "tinymce.core.html.Entities",
    "tinymce.core.undo.Diff"
  ],
  function (Arr, Entities, Diff) {
    var getOuterHtml = function (elm) {
      if (elm.nodeType === 1) {
        return elm.outerHTML;
      } else if (elm.nodeType === 3) {
        return Entities.encodeRaw(elm.data, false);
      } else if (elm.nodeType === 8) {
        return '<!--' + elm.data + '-->';
      }

      return '';
    };

    var createFragment = function (html) {
      var frag, node, container;

      container = document.createElement("div");
      frag = document.createDocumentFragment();

      if (html) {
        container.innerHTML = html;
      }

      while ((node = container.firstChild)) {
        frag.appendChild(node);
      }

      return frag;
    };

    var insertAt = function (elm, html, index) {
      var fragment = createFragment(html);
      if (elm.hasChildNodes() && index < elm.childNodes.length) {
        var target = elm.childNodes[index];
        target.parentNode.insertBefore(fragment, target);
      } else {
        elm.appendChild(fragment);
      }
    };

    var removeAt = function (elm, index) {
      if (elm.hasChildNodes() && index < elm.childNodes.length) {
        var target = elm.childNodes[index];
        target.parentNode.removeChild(target);
      }
    };

    var applyDiff = function (diff, elm) {
      var index = 0;
      Arr.each(diff, function (action) {
        if (action[0] === Diff.KEEP) {
          index++;
        } else if (action[0] === Diff.INSERT) {
          insertAt(elm, action[1], index);
          index++;
        } else if (action[0] === Diff.DELETE) {
          removeAt(elm, index);
        }
      });
    };

    var read = function (elm) {
      return Arr.filter(Arr.map(elm.childNodes, getOuterHtml), function (item) {
        return item.length > 0;
      });
    };

    var write = function (fragments, elm) {
      var currentFragments = Arr.map(elm.childNodes, getOuterHtml);
      applyDiff(Diff.diff(currentFragments, fragments), elm);
      return elm;
    };

    return {
      read: read,
      write: write
    };
  }
);