/**
 * Bookmark.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
 */

define("tinymce.lists.core.Bookmark", [
	"global!tinymce.dom.DOMUtils.DOM",
	"tinymce.lists.core.NodeType",
	"tinymce.lists.core.Range"
], function (DOM, NodeType, Range) {
	/**
	 * Returns a range bookmark. This will convert indexed bookmarks into temporary span elements with
	 * index 0 so that they can be restored properly after the DOM has been modified. Text bookmarks will not have spans
	 * added to them since they can be restored after a dom operation.
	 *
	 * So this: <p><b>|</b><b>|</b></p>
	 * becomes: <p><b><span data-mce-type="bookmark">|</span></b><b data-mce-type="bookmark">|</span></b></p>
	 *
	 * @param  {DOMRange} rng DOM Range to get bookmark on.
	 * @return {Object} Bookmark object.
	 */
	var createBookmark = function (rng) {
		var bookmark = {};

		var setupEndPoint = function (start) {
			var offsetNode, container, offset;

			container = rng[start ? 'startContainer' : 'endContainer'];
			offset = rng[start ? 'startOffset' : 'endOffset'];

			if (container.nodeType === 1) {
				offsetNode = DOM.create('span', {'data-mce-type': 'bookmark'});

				if (container.hasChildNodes()) {
					offset = Math.min(offset, container.childNodes.length - 1);

					if (start) {
						container.insertBefore(offsetNode, container.childNodes[offset]);
					} else {
						DOM.insertAfter(offsetNode, container.childNodes[offset]);
					}
				} else {
					container.appendChild(offsetNode);
				}

				container = offsetNode;
				offset = 0;
			}

			bookmark[start ? 'startContainer' : 'endContainer'] = container;
			bookmark[start ? 'startOffset' : 'endOffset'] = offset;
		};

		setupEndPoint(true);

		if (!rng.collapsed) {
			setupEndPoint();
		}

		return bookmark;
	};

	var resolveBookmark = function (bookmark) {
		function restoreEndPoint (start) {
			var container, offset, node;

			var nodeIndex = function (container) {
				var node = container.parentNode.firstChild, idx = 0;

				while (node) {
					if (node === container) {
						return idx;
					}

					// Skip data-mce-type=bookmark nodes
					if (node.nodeType !== 1 || node.getAttribute('data-mce-type') !== 'bookmark') {
						idx++;
					}

					node = node.nextSibling;
				}

				return -1;
			};

			container = node = bookmark[start ? 'startContainer' : 'endContainer'];
			offset = bookmark[start ? 'startOffset' : 'endOffset'];

			if (!container) {
				return;
			}

			if (container.nodeType === 1) {
				offset = nodeIndex(container);
				container = container.parentNode;
				DOM.remove(node);
			}

			bookmark[start ? 'startContainer' : 'endContainer'] = container;
			bookmark[start ? 'startOffset' : 'endOffset'] = offset;
		}

		restoreEndPoint(true);
		restoreEndPoint();

		var rng = DOM.createRng();

		rng.setStart(bookmark.startContainer, bookmark.startOffset);

		if (bookmark.endContainer) {
			rng.setEnd(bookmark.endContainer, bookmark.endOffset);
		}

		return Range.normalizeRange(rng);
	};

	return {
		createBookmark: createBookmark,
		resolveBookmark: resolveBookmark
	};
});

