/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.
 */

/**
 * Implementation of Prototype's Insertion class with Dojo
 *
 * @version $Id: insertion.js 448479 2006-09-21 07:33:36Z crossley $
 */

dojo.provide("cocoon.ajax.insertion");
dojo.require("dojo.dom");

cocoon.ajax.insertion = {};

dojo.lang.mixin(cocoon.ajax.insertion, {

	/**
	 * Inserts before the reference node
	 */
    before: function (refElt, content) {
        return cocoon.ajax.insertionHelper.insert(refElt, content, function(refElt, newElt) {
            refElt.parentNode.insertBefore(newElt, refElt);
        });
    },

	/**
	 * Inserts after the reference node
	 */
    after: function (refElt, content) {
        return cocoon.ajax.insertionHelper.insert(refElt, content, function(refElt, newElt) {
            // There's no node.insertAfter...
            if (refElt.nextSibling) {
                refElt.parentNode.insertBefore(newElt, refElt.nextSibling);
            } else {
                refElt.parentNode.appendChild(newElt);
            }
        });
    },

    /**
     * Inserts at the top of the reference node children (i.e. as the first child)
     */
    top: function (refElt, content) {
        return cocoon.ajax.insertionHelper.insert(refElt, content, function(refElt, newElt) {
            if (refElt.firstChild) {
                refElt.insertBefore(newElt, refElt.firstChild);
            } else {
                refElt.appendChild(newElt);
            }
        })
    },

    /**
     * Inserts at the bottom of the reference node children (i.e. as the last child)
     */
    bottom: function (refElt, content) {
        return cocoon.ajax.insertionHelper.insert(refElt, content, function(refElt, newElt) {
            refElt.appendChild(newElt);
        })
    },

    /**
     * Inserts as the contents of the reference node (i.e. replaces all children)
     */
    inside: function (refElt, content) {
        return cocoon.ajax.insertionHelper.insert(refElt, content, function(refElt, newElt) {
            // Destroy and remove all children
            while (refElt.hasChildNodes()) {
                var firstChild = refElt.firstChild;
                if (firstChild.nodeType == dojo.dom.ELEMENT_NODE) {
                    cocoon.ajax.insertionHelper.destroy(firstChild);
                }
                refElt.removeChild(firstChild);
            }

            // Insert the new one
            refElt.appendChild(newElt);
        })
    },

    /**
     * Replaces the reference element
     */
    replace: function (refElt, content) {
        return cocoon.ajax.insertionHelper.insert(refElt, content, function(refElt, newElt) {
            refElt.parentNode.replaceChild(newElt, refElt);
            cocoon.ajax.insertionHelper.destroy(refElt);
        })
    }
});

cocoon.ajax.insertionHelper = {};

dojo.lang.mixin(cocoon.ajax.insertionHelper, {
	/**
	 * Imports an element into a document, taking care of using the correct implementation
	 * so that the browser interprets it as displayable XML.
	 *
	 * Any <script> in the imported node are collected and for post-import evaluation
	 * FIXME: only inline script are considered, but not <script src="">
	 *
	 * @return { element: <em>imported_element</em>, scripts: <em>array_of_script_text</em> }
	 */
    importNode: function(node, targetDoc) {
	    if(node.xml || dojo.lang.isString(node)) {
	        // IE or text
	        var text = dojo.lang.isString(node) ? node : node.xml;
	        var div = targetDoc.createElement("DIV");

	        // Code below heavily inspired by the Prototype library
	        var scriptExpr = "(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)";
            var textWithoutScripts = text.replace(new RegExp(scriptExpr, 'img'), '');

            // Internet Explorer can't handle empty <textarea .../> elements, so replace
            // them with <textarea></textarea>
            var textareaExpr = "(<textarea[^<]*)\/>";
            var textAreaMatches = textWithoutScripts.match(textareaExpr);
            var textFixed = textWithoutScripts.replace(new RegExp(textareaExpr, 'img'), "$1></textarea>");

            // Update screen with removed scripts
	        div.innerHTML = textFixed;

	        var matchAll = new RegExp(scriptExpr, 'img');
	        var matchOne = new RegExp(scriptExpr, 'im');
	        var allMatches = text.match(matchAll);
	        var scripts = new Array();
	        if (allMatches) {
		        for (var i = 0; i < allMatches.length; i++) {
		            scripts.push(allMatches[i].match(matchOne)[1]);
		        }
		    }
	        return { element: dojo.dom.getFirstChildElement(div), scripts: scripts };

	    } else {
	        var scripts = new Array();
	        var element = this._importDomNode(node, targetDoc, scripts);
	        return { element: element, scripts: scripts }
	    }
	},

	/**
	 * DOM implementation of importNode, recursively creating nodes.
	 * Scripts are collected in the "scripts" parameter
	 */
	_importDomNode: function(node, targetDoc, scripts) {
	    switch(node.nodeType) {
	        case dojo.dom.ELEMENT_NODE:
	            if (node.nodeName.toLowerCase() == "script") {
	                // Collect scripts
	                scripts.push(node.firstChild && node.firstChild.nodeValue);
	                return;
	            }
	            var element = targetDoc.createElement(node.nodeName);
	            //var element = targetDoc.createElementNS(node.namespaceURI, node.nodeName);
	            var attrs = node.attributes;
	            for (var i = 0; i < attrs.length; i++) {
	                var attr = attrs[i];
	                element.setAttribute(attr.nodeName, attr.nodeValue);
	                //element.setAttributeNS(attr.namespaceURI, attr.nodeName, attr.nodeValue);
	            }
	            var children = node.childNodes;
	            for (var j = 0; j < children.length; j++) {
	                var imported = this._importDomNode(children[j], targetDoc, scripts);
	                if (imported) element.appendChild(imported);
	            }
	            return element;
	        break;

	        case dojo.dom.TEXT_NODE:
	            return targetDoc.createTextNode(node.nodeValue);
	        break;

	        case dojo.dom.CDATA_SECTION_NODE:
	            return targetDoc.createTextNode(node.nodeValue);
	        break;
	    }
	},

	_runScripts: function(imported) {
        // Evaluate scripts
        for (var i = 0; i < imported.scripts.length; i++) {
            eval(imported.scripts[i]);
        }
	},

    insert: function(refElt, content, insertFunc) {
        refElt = dojo.byId(refElt, content);
        var imports = this.importNode(content, refElt.ownerDocument);
        insertFunc(refElt, imports.element);
        this._runScripts(imports);
        return this.parseDojoWidgets(imports.element);
    },

    destroy: function(element) {
	    var widget = dojo.widget.byNode(element);
	    if (widget) {
	        // Dojo will destroy all its children
	        widget.destroy(true, true);
	    } else {
	        // Recurse until we eventually find a widget
	        var children = element.childNodes;
	        for (var i = 0; i < children.length; i++) {
	            var child = children[i];
	            if (child.nodeType == dojo.dom.ELEMENT_NODE) {
	                this.destroy(child);
	            }
	        }
	    }
	},

	parseDojoWidgets: function(element) {
	    // Find a parent widget (if any) so that Dojo can maintain its widget tree
	    var parentWidget = this.findParentWidget(element);
		var parser = new dojo.xml.Parse();

		// FIXME: parser.parseElement() parses the _children_ of an element, whereas we want here
		// the element itself to be parsed. Parsing its parent is not an option as we don't want
		// to parse the siblings. So place it in a temporary div that we'll trash afterwards.
		var div = document.createElement("DIV");
		element.parentNode.replaceChild(div, element);
		div.appendChild(element);
		var frag = parser.parseElement(div, null, true);
		dojo.widget.getParser().createComponents(frag, parentWidget);
		// Get again the first child of the div, which may no more be the original one
		// if it's a widget
		element = div.firstChild;
		div.parentNode.replaceChild(element, div);
		parentWidget && parentWidget.onResized();

		return element;
	},

	findParentWidget: function(element) {
	    var parent = element.parentNode;
	    var widget;
	    while (parent && !widget) {
	        var widget = dojo.widget.byNode(parent);
	        if (widget) {
	            return widget;
	        }
	        parent = parent.parentNode;
	    }
	}
});
