/**************************************************************
 *      Rick.js ver.0.1                                       *
 *      by Takanashi Ginpei                                   *
 *      http://nest.0xff.biz/                                 *
 *                                                            *
 *  2007/08/04  ...                                           *
 **************************************************************/


// Returned values of 'element.nodeType'
NodeTypes = {
    element:                1,  // ELEMENT_NODE
    attribute:              2,  // ATTRIBUTE_NODE
    text:                   3,  // TEXT_NODE
    cdata:                  4,  // CDATA_SECTION_NODE
    entityReference:        5,  // ENTITY_REFERENCE_NODE
    entity:                 6,  // ENTITY_NODE
    processingInstruction:  7,  // PROCESSING_INSTRUCTION_NODE
    comment:                8,  // COMMENT_NODE
    document:               9,  // DOCUMENT_NODE
    doctype:                10, // DOCUMENT_TYPE_NODE
    documentFragment:       11, // DOCUMENT_FRAGMENT_NODE
    notation:               12  // NOTATION_NODE
};

/**
 * The methods to assign to Rick Object.
 */
RickMethods = {
    /**
     * [BETA] Extend properties. This method is wrapping 'Object.extend' of prototype.js.
     * @param obj       The object to be extended.
     */
    extend: function(obj) {
	Object.extend(this, obj);
    },

    /**
     * Create new element node and insert.
     * @param name      A tagName.
     * @param options   A object including properties: ID, class name, and text.
     * @param attrs     A list of attributes.
     * @return The created element.
     */
    create: function(name, options, attrs, pos) {
        var child = new Rick(name, options, attrs);
        this.add(child, pos);
	return child;
    },

    /**
     * Insert a child node.
     * @param child     The child node.
     * @param pos       Position to insert. If this is undefined, insert it into last.
     * @return The added element.
     */
    add: function(child, pos) {
        if(typeof(child) == 'string') {
            child = document.createTextNode(child);
	}

	if(pos == undefined || pos >= this.childNodes.length) {
	    this.appendChild(child);
	} else {
	    this.insertBefore(child, this.childNodes[pos]);
	}

	return child;
    },

    /**
     * Remove child node from position.
     * @param pos       Position of child node for removing. If this is undefined, remove last node.
     */
    removeAt: function(pos) {
	if(pos == undefined)
	    pos = this.childNodes.length -1;
	this.removeChild(this.childNodes[pos]);
    },

    /**
     * Remove all child nodes.
     */
    removeAll: function() {
	while(this.hasChildNodes()) {
	    this.removeAt(0);
	}
    },

    /**
     * Remove ownself, bye-bye.
     */
    removeMe: function() {
	this.parentNode.removeChild(this);
    },

    /**
     * Copy all child nodes to the target object.
     * @param target    The target object to copy this.
     */
    copyTo: function(target) {
	var children = this.childNodes;
	for(var i=0; i<children.length; i++) {
	    target.add(children[i].cloneNode(true));
	}
    },

    /**
     * Get value of first text node. It returns X if no text nodes were found.
     * @return  The text.
     */
    getText: function() {
        for(var i=0; i<this.childNodes.length; i++) {
            if(this.childNodes[i].nodeType == NodeTypes.text) {
                return this.childNodes[i].nodeValue;
	    }
	}
        return '';
    },

    /**
     * Set a text to this element after all child nodes will be removed.
     * @param text      A text to set.
     */
    setText: function(text) {
	this.removeAll();
        this.add(text);
    },

    /**
     * Remove text nodes including only spaces.
     * @return this     This element after removing.
     */
    compact: function() {
	var children = this.childNodes;
	for(var i=children.length-1; i>=0; i--) { // Search from last.
	    if(children[i].nodeType == NodeTypes.text) {
		if(children[i].nodeValue.match(/^[ \t\r\n]*$/) != null) {
		    Element.remove(children[i]);
		}
	    }
	}
	return this;
    },


    /**
     * Check whether to have the class name. This method is wrapping 'Element.hasClassName' of prototype.js.
     * @param className The class name for check.
     * @return  true/false
     */
    hasClassName: function(className) {
        return Element.hasClassName(this, className);
    },

    /**
     * Check whether this element is a child node of the target node. This method is wrapping 'Element.childOf' of prototype.js.
     * @return  true/false
     */
    isChildOf: function(parent) {
        return Element.childOf(this, parent);
    },

    /**
     * Get queried child nodes. This method get from only child.
     * @param filter    A filter like: [tagName][#ID][.className][.className]...
     * @return  The child nodes that has been filtered.
     */
    get: function(filter) {
	return this.filter(this.childNodes, filter);
    },

    /**
     * Get queried descendant nodes. This method get from all descendant.
     * @param filter    A filter like: [tagName][#ID][.className][.className]...
     * @return  The descendant nodes that has been filtered.
     */
    getAll: function(filter) {
	return this.filter(this.descendants(), filter);
    },

    /**
     * Filter nodes.
     * @param nodes     The nodes to be filtered.
     * @param filter    A filter like: [tagName][#ID][.className][.className]...
     * @return  The nodes that has been filtered.
     */
    filter: function(nodes, filter) {
	var elements = new Array();

	// Filter by node-type.
	for(var i=0; i<nodes.length; i++) {
	    if(nodes[i].nodeType == NodeTypes.element) {
		elements.push(nodes[i]);
	    }
	}

	if(elements.length == 0) {
	    return elements;
	}

	// Filter by tag name.
	var tag = filter.match(/(.*?)(?:[#.]|$)/)[1];
	if(tag != '') {
	    var pick = new Array();
	    for(var i=0; i<elements.length; i++) {
		if(elements[i].nodeName.toLowerCase() == tag.toLowerCase()) {
		    pick.push(elements[i]);
		}
	    }
	    elements = pick;
	}

	// Filter by ID.
	if(filter.indexOf('#') >= 0) {
	    var id = filter.match(/.*?#(.*?)(?:\.|$)/)[1];
	    var pick = new Array();
	    for(var i=0; i<elements.length; i++) {
		if(elements[i].id == id) {
		    pick.push(elements[i]);
		}
	    }
	    elements = pick;
	}

	// Filter by class name.
	if(filter.indexOf('.') >= 0) {
	    var classNameList = filter.substr(filter.indexOf('.')+1, filter.length).split('.');
	    for(var j=0; j<classNameList.length; j++) {
		var pick = new Array();
		for(var i=0; i<elements.length; i++) {
		    if(Element.hasClassName(elements[i], classNameList[j])) {
			pick.push(elements[i]);
		    }
		}
		elements = pick;
	    }
	}

	return elements;
    }, // get

    /**
     * [BETA] Replace own to new node.
     * @param newNode   The old node.
     * @param newNode   The new node.
     * @return  The old node.
     */
    replaceNode: function(oldNode, newNode) {
	var bak = oldNode.cloneNode(true);
	this.insertBefore(newNode, oldNode);
	this.removeChild(oldNode);
	return bak;
    },

    /**
     * Set event handler.
     */
    observe: function(eventName, func) {
	Element.observe(this, eventName, func);
    },

    ricked: [true] // Whether this is added Rick methods.
}

/**
 * Add Rick methods.
 * @param element       A element to add.
 */
var MakeRick = function(element, force) {
    if(!element.ricked || force) {
	Object.extend(element, RickMethods);
    }
    return element;
}

/**
 * Add Rick methods to element and its descendant.
 */
var MakeRickAll = function(root, force) {
    MakeRick(root, force);

    var descendant = $(root).descendants();
    for(var i=0; i<descendant.length; i++) {
	MakeRick(descendant[i]);
    }
    return root;
}

var Rick = function(name, options, attrs) {
    var element = document.createElement(name);

    // Add all attributes.
    for(attr in attrs)
        element.setAttribute(attr, attrs[attr]);

    if(options) {
        // Add ID.
        if(options.id)
            element.id = options.id;

        // Add class name.
        if(options.className)
            element.className = options.className;

        // Add text node.
        if(options.text)
            element.appendChild(document.createTextNode(options.text));
    }

    // Add methods.
    return MakeRick(element);
}

