/*
 * yui-ext 0.33
 * Copyright(c) 2006, Jack Slocum.
 */

/**
 * @class YAHOO.ext.grid.XMLDataModel
 * This is an implementation of a DataModel used by the Grid. It works 
 * with XML data. 
 * <br>Example schema from Amazon search:
 * <pre><code>
 * var schema = {
 *     tagName: 'Item',
 *     id: 'ASIN',
 *     fields: ['Author', 'Title', 'Manufacturer', 'ProductGroup']
 * };
 * </code></pre>
 * @extends YAHOO.ext.grid.LoadableDataModel
 * @constructor
 * @param {Object} schema The schema to use
 * @param {XMLDocument} xml An XML document to load immediately
*/
YAHOO.ext.grid.XMLDataModel = function(schema, xml){
    YAHOO.ext.grid.XMLDataModel.superclass.constructor.call(this, YAHOO.ext.grid.LoadableDataModel.XML);
    /**@private*/
    this.schema = schema;
    this.xml = xml;
    if(xml){
        this.loadData(xml);
    }
    this.idSeed = 0;
};
YAHOO.extendX(YAHOO.ext.grid.XMLDataModel, YAHOO.ext.grid.LoadableDataModel, {
    
    getDocument: function(){
       return this.xml;    
    },
    
    /**
     * Overrides loadData in LoadableDataModel to process XML
     * @param {XMLDocument} doc The document to load
     * @param {<i>Function</i>} callback (optional) callback to call when loading is complete
     * @param {<i>Boolean</i>} keepExisting (optional) true to keep existing data
     * @param {<i>Number</i>} insertIndex (optional) if present, loaded data is inserted at the specified index instead of overwriting existing data
     */
    loadData: function(doc, callback, keepExisting, insertIndex){
    	this.xml = doc;
    	var idField = this.schema.id;
    	var fields = this.schema.fields;
    	if(this.schema.totalTag){
    	    this.totalCount = null;
    	    var totalNode = doc.getElementsByTagName(this.schema.totalTag);
    	    if(totalNode && totalNode.item(0) && totalNode.item(0).firstChild) {
                var v = parseInt(totalNode.item(0).firstChild.nodeValue, 10);
                if(!isNaN(v)){
                    this.totalCount = v;
                }
        	}
    	}
    	var rowData = [];
    	var nodes = doc.getElementsByTagName(this.schema.tagName);
        if(nodes && nodes.length > 0) {
    	    for(var i = 0; i < nodes.length; i++) {
    	        var node = nodes.item(i);
    	        var colData = [];
    	        colData.node = node;
    	        colData.id = this.getNamedValue(node, idField, String(++this.idSeed));
    	        for(var j = 0; j < fields.length; j++) {
    	            var val = this.getNamedValue(node, fields[j], "");
    	            if(this.preprocessors[j]){
    	                val = this.preprocessors[j](val);
    	            }
    	            colData.push(val);
    	        }
    	        rowData.push(colData);
    	    }
        }
        if(keepExisting !== true){
           YAHOO.ext.grid.XMLDataModel.superclass.removeAll.call(this);
    	}
    	if(typeof insertIndex != 'number'){
    	    insertIndex = this.getRowCount();
    	}
        YAHOO.ext.grid.XMLDataModel.superclass.insertRows.call(this, insertIndex, rowData);
        if(typeof callback == 'function'){
        	callback(this, true);
        }
        this.fireLoadEvent();
    },
    
    /**
     * Adds a row to this DataModel and syncs the XML document
     * @param {String} id The id of the row, if null the next row index is used
     * @param {Array} cellValues The cell values for this row
     * @return {Number} The index of the new row (if the model is sorted this index may not be accurate)
     */
    addRow: function(id, cellValues){
        var node = this.createNode(this.xml, id, cellValues);
        cellValues.id = id || ++this.idSeed;
        cellValues.node = node;
        return YAHOO.ext.grid.XMLDataModel.superclass.addRow.call(this, cellValues);
    },
    
    /**
     * Inserts a row into this DataModel and syncs the XML document
     * @param {Number} index The index to insert the row
     * @param {String} id The id of the row, if null the next row index is used
     * @param {Array} cellValues The cell values for this row
     * @return {Number} The index of the new row (if the model is sorted this index may not be accurate)
     */
    insertRow: function(index, id, cellValues){
        var node = this.createNode(this.xml, id, cellValues);
        cellValues.id = id || ++this.idSeed;
        cellValues.node = node;
        return YAHOO.ext.grid.XMLDataModel.superclass.insertRow.call(this, index, cellValues);
    },
    
    /**
     * Removes the row from DataModel and syncs the XML document
     * @param {Number} index The index of the row to remove
     */
    removeRow: function(index){
        var node = this.data[index].node;
        node.parentNode.removeChild(node);
        YAHOO.ext.grid.XMLDataModel.superclass.removeRow.call(this, index, index);
    },
    
    getNode: function(rowIndex){
        return this.data[rowIndex].node;
    },
    
    /**
     * Override this method to define your own node creation routine for when new rows are added.
     * By default this method clones the first node and sets the column values in the newly cloned node.
     * In many instances this will not work and you will have to create the node manually.
     * @param {XMLDocument} xmlDoc The xml document being used by this model
     * @param {String/Number} id The row id
     * @param {Array} colData The column data for the new node
     * @return {XMLNode} The created node
     */
    createNode: function(xmlDoc, id, colData){
        var template = this.data[0].node;
        var newNode = template.cloneNode(true);
        var fields = this.schema.fields;
        for(var i = 0, len = fields.length; i < len; i++){
            var nodeValue = colData[i];
            if(this.postprocessors[i]){
                nodeValue = this.postprocessors[i](nodeValue);
            }
            this.setNamedValue(newNode, fields[i], nodeValue);
        }
        if(id){
            this.setNamedValue(newNode, this.schema.idField, id);
        }
        template.parentNode.appendChild(newNode);
        return newNode;
    },
    
    /**
     * @private
     * Convenience function looks for value in attributes, then in children tags - also 
     * normalizes namespace matches (ie matches ns:tag, FireFox matches tag and not ns:tag).
     */
    getNamedValue: function(node, name, defaultValue){
    	if(!node || !name){
    		return defaultValue;
    	}
    	var nodeValue = defaultValue;
        var attrNode = node.attributes.getNamedItem(name);
        if(attrNode) {
        	nodeValue = attrNode.value;
        } else {
            var childNode = node.getElementsByTagName(name);
            if(childNode && childNode.item(0) && childNode.item(0).firstChild) {
                nodeValue = childNode.item(0).firstChild.nodeValue;
        	}else{
        	    // try to strip namespace for FireFox
        	    var index = name.indexOf(':');
        	    if(index > 0){
        	        return this.getNamedValue(node, name.substr(index+1), defaultValue);
        	    }
        	}
        }
        return nodeValue;
    },
    
    /**
     * @private
     * Convenience function set a value in the underlying xml node.
     */
    setNamedValue: function(node, name, value){
    	if(!node || !name){
    		return;
    	}
    	var attrNode = node.attributes.getNamedItem(name);
        if(attrNode) {
        	attrNode.value = value;
        	return;
        }
        var childNode = node.getElementsByTagName(name);
        if(childNode && childNode.item(0) && childNode.item(0).firstChild) {
            childNode.item(0).firstChild.nodeValue = value;
        }else{
    	    // try to strip namespace for FireFox
    	    var index = name.indexOf(':');
    	    if(index > 0){
    	        this.setNamedValue(node, name.substr(index+1), value);
    	    }
    	}
    },
    
    /**
     * Overrides DefaultDataModel.setValueAt to update the underlying XML Document
     * @param {Object} value The new value
     * @param {Number} rowIndex
     * @param {Number} colIndex
     */
    setValueAt: function(value, rowIndex, colIndex){
        var node = this.data[rowIndex].node;
        if(node){
            var nodeValue = value;
            if(this.postprocessors[colIndex]){
                nodeValue = this.postprocessors[colIndex](value);
            }
            this.setNamedValue(node, this.schema.fields[colIndex], nodeValue);
        }
        YAHOO.ext.grid.XMLDataModel.superclass.setValueAt.call(this, value, rowIndex, colIndex);
    },
    
    /**
     * Overrides getRowId in DefaultDataModel to return the ID value of the specified node. 
     * @param {Number} rowIndex
     * @return {Number}
     */
    getRowId: function(rowIndex){
        return this.data[rowIndex].id;
    },
    
    addRows : function(rowData){   
        for(var j = 0, len = rowData.length; j < len; j++){
           var cellValues = rowData[j];
           var id = ++this.idSeed; 
           var node = this.createNode(this.xml, id, cellValues);       
           cellValues.node=node;
           cellValues.id = cellValues.id || id;
           YAHOO.ext.grid.XMLDataModel.superclass.addRow.call(this,cellValues);
        }
    },   

   insertRows : function(index, rowData){
       // copy original array so it is not reversed 
       rowData = rowData.slice(0).reverse();
       for(var j = 0, len = rowData.length; j < len; j++){
          var cellValues = rowData[j];
          var id = ++this.idSeed; 
          var node = this.createNode(this.xml, id, cellValues);
          cellValues.id = cellValues.id || id;
          cellValues.node = node;
          YAHOO.ext.grid.XMLDataModel.superclass.insertRow.call(this, index, cellValues);
       }
   }
});

YAHOO.ext.grid.XMLQueryDataModel = function(){
   YAHOO.ext.grid.XMLQueryDataModel.superclass.constructor.apply(this, arguments);
};
YAHOO.extendX(YAHOO.ext.grid.XMLQueryDataModel, YAHOO.ext.grid.XMLDataModel, {
    getNamedValue: function(node, name, defaultValue){
    	if(!node || !name){
    		return defaultValue;
    	}
    	var nodeValue = defaultValue;
    	var childNode = cssQuery(name, node);
    	if(childNode && childNode[0]) {
            nodeValue = childNode[0].firstChild.nodeValue;
    	}
        return nodeValue;
    }
});