/**
 * Fragments.js
 *
 * Released under LGPL License.
 * Copyright (c) 1999-2016 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/undo/Fragments", [
	"tinymce/util/Arr",
	"tinymce/html/Entities",
	"tinymce/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.map(elm.childNodes, getOuterHtml);
	};

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