/*!
 * jQuery Taconite plugin - A port of the Taconite framework by Ryan Asleson and
 *     Nathaniel T. Schutta: http://taconite.sourceforge.net/
 *
 * Examples and documentation at: http://malsup.com/jquery/taconite/
 * Copyright (c) 2007-2010 M. Alsup
 * Dual licensed under the MIT and GPL licenses:
 * http://www.opensource.org/licenses/mit-license.php
 * http://www.gnu.org/licenses/gpl.html
 * Thanks to Kenton Simpson for contributing many good ideas!
 *
 * @version: 3.09  01-MAR-2010
 * @requires jQuery v1.2.6 or later
 */

(function($) {
  var version = '3.09';

  $.taconite = function(xml) { processDoc(xml); };

  $.taconite.debug = 0;  // set to true to enable debug logging to window.console.log
  $.taconite.defaults = {
    cdataWrap: 'div'
  };

  // add 'replace' and 'replaceContent' plugins (conditionally)
  if (typeof $.fn.replace == 'undefined')
    $.fn.replace = function(a) { return this.after(a).remove(); };
  if (typeof $.fn.replaceContent == 'undefined')
    $.fn.replaceContent = function(a) { return this.empty().append(a); };

  $.expr[':'].taconiteTag = function(a) { return a.taconiteTag === 1; };

  var _httpData = $.httpData; // original jQuery httpData function

  // replace jQuery's httpData method
  $.httpData = $.taconite.detect = function(xhr, type) {
    var ct = xhr.getResponseHeader('content-type');
    if ($.taconite.debug) {
      log('[AJAX response] content-type: ', ct, ';  status: ', xhr.status, ' ', xhr.statusText, ';  has responseXML: ', xhr.responseXML != null);
      log('type arg: ' + type);
      // log('responseXML: '+  xhr.responseXML);
    }
    var data = _httpData(xhr, type); // call original method
    if (data && data.documentElement) {
      $.taconite(data);
    }
    else { 
      log('jQuery core httpData returned: ' + data);
      log('httpData: response is not XML (or not "valid" XML)');
    }
    return data;
  };

  // allow auto-detection to be enabled/disabled on-demand
  $.taconite.enableAutoDetection = function(b) {
    $.httpData = b ? $.taconite.detect : _httpData;
  };

  var logCount = 0;
  function log() {
    if (!$.taconite.debug || !window.console || !window.console.log) return;
    if (!logCount++)
      log('Plugin Version: ' + version);
    window.console.log('[taconite] ' + [].join.call(arguments,''));
  };

  function processDoc(xml) { 
    var status = true, ex;
    try {
      if (typeof xml == 'string') {
        xml = convert(xml);
      }
      if (!xml) {
        log('$.taconite invoked without valid document; nothing to process');
        return false;
      }
		
      var root = xml.documentElement.tagName;
      log('XML document root: ', root);
		
      var taconiteDoc = $('taconite', xml)[0];
			
      if (!taconiteDoc) {
        log('document does not contain <taconite> element; nothing to process');
        return false;
      }
		
      $.event.trigger('taconite-begin-notify', [taconiteDoc]);
      status = go(taconiteDoc); 
    } catch(e) {
      status = ex = e;
    }
    $.event.trigger('taconite-complete-notify', [xml, !!status, status === true ? null : status]);
    if (ex) throw ex;
  };

  // convert string to xml document
  function convert(s) {
    var doc;
    log('attempting string to document conversion');
    try {
      if (window.DOMParser) {
        var parser = new DOMParser();
        doc = parser.parseFromString(s, 'text/xml');
      }
      else {
        doc = $("<xml>")[0];
        doc.async = 'false';
        doc.loadXML(s);
      }
    }
    catch(e) {
      if (window.console && window.console.error)
        window.console.error('[taconite] ERROR parsing XML string for conversion: ' + e);
      throw e;
    }
    var ok = doc && doc.documentElement && doc.documentElement.tagName != 'parsererror';
    log('conversion ', ok ? 'successful!' : 'FAILED');
    return doc;
  };


  function go(xml) {
    var trimHash = { wrap: 1 };

    try {
      var t = new Date().getTime();
      // process the document
      process(xml.childNodes);
      $.taconite.lastTime = (new Date().getTime()) - t;
      log('time to process response: ' + $.taconite.lastTime + 'ms');
    } catch(e) {
      if (window.console && window.console.error)
        window.console.error('[taconite] ERROR processing document: ' + e);
      throw e;
    }
    return true;
    
    // process the taconite commands    
    function process(commands) {
      var doPostProcess = 0;
      for(var i=0; i < commands.length; i++) {
        if (commands[i].nodeType != 1)
          continue; // commands are elements
        var cmdNode = commands[i], cmd = cmdNode.tagName;
        if (cmd == 'eval') {
          var js = (cmdNode.firstChild ? cmdNode.firstChild.nodeValue : null);
          log('invoking "eval" command: ', js);
          if (js) $.globalEval(js);
          continue;
        }
        var q = cmdNode.getAttribute('select');
        var jq = $(q);
        if (!jq[0]) {
          log('No matching targets for selector: ', q);
          continue;
        }
        var cdataWrap = cmdNode.getAttribute('cdataWrap') || $.taconite.defaults.cdataWrap;

        var a = [];
        if (cmdNode.childNodes.length > 0) {
          doPostProcess = 1;
          for (var j=0,els=[]; j < cmdNode.childNodes.length; j++) {
            els[j] = createNode(cmdNode.childNodes[j]);
          }
          a.push(trimHash[cmd] ? cleanse(els) : els);
        }

        // remain backward compat with pre 2.0.9 versions
        var n = cmdNode.getAttribute('name');
        var v = cmdNode.getAttribute('value');
        if (n !== null) a.push(n);
        if (v !== null) a.push(v);

        // @since: 2.0.9: support arg1, arg2, arg3...
        for (var j=1; true; j++) {
          v = cmdNode.getAttribute('arg'+j);
          if (v === null)
            break;
          // support numeric primitives
          var n = Number(v);
          if (v == n)
            v = n;
          a.push(v);
        }

        if ($.taconite.debug) {
          var args = '';
          if (els)
            args = '...';
          else {
            for (var k=0; k < a.length; k++) {
              if (k > 0)
                args += ',';
              var val = a[k];
              var isString = typeof val == 'string';
              if (isString)
                args += '\'';
              args += val;							
              if (isString)
                args += '\'';
            }
          }
          log("invoking command: $('", q, "').", cmd, '('+ args +')');
        }
        jq[cmd].apply(jq,a);
      }
      // apply dynamic fixes
      if (doPostProcess) 
        postProcess();
    
      function postProcess() {
        if ($.browser.mozilla) return; 
        // post processing fixes go here; currently there is only one:
        // fix1: opera, IE6, Safari/Win don't maintain selected options in all cases (thanks to Karel Fučík for this!)
        $('select:taconiteTag').each(function() {
            var sel = this;
            $('option:taconiteTag', this).each(function() {
                this.setAttribute('selected','selected');
                this.taconiteTag = null;
                if (sel.type == 'select-one') {
                  var idx = $('option',sel).index(this);
                  sel.selectedIndex = idx;
                }
              });
            this.taconiteTag = null;
          });
      };
        
      function cleanse(els) {
        for (var i=0, a=[]; i < els.length; i++)
          if (els[i].nodeType == 1) a.push(els[i]);
        return a;
      };
        
      function createNode(node) {
        var type = node.nodeType;
        if (type == 1) return createElement(node);
        if (type == 3) return fixTextNode(node.nodeValue);
        if (type == 4) return handleCDATA(node.nodeValue);
        return null;
      };
        
      function handleCDATA(s) {
        var el = document.createElement(cdataWrap);
        el.innerHTML = s;
            
        // remove wrapper node if possible
        var $el = $(el), $ch = $el.children();
        if ($ch.size() == 1)
          return $ch[0];
        return el;
      };
        
      function fixTextNode(s) {
        if ($.browser.msie) s = s.replace(/\n/g, '\r').replace(/\s+/g, ' ');
        return document.createTextNode(s);
      };
        
      function createElement(node) {
        var e, tag = node.tagName.toLowerCase();
        // some elements in IE need to be created with attrs inline
        if ($.browser.msie) {
          var type = node.getAttribute('type');
          if (node.attributes.length) {
            if (tag == 'table' || type == 'radio' || type == 'checkbox' || tag == 'button' || 
                (tag == 'select' && node.getAttribute('multiple'))) {
              e = document.createElement('<' + tag + ' ' + copyAttrs(null, node, true) + '>');
            }
          }
        }
        if (!e) {
          e = document.createElement(tag);
          // copyAttrs(e, node, tag == 'option' && $.browser.safari);
          copyAttrs(e, node);
        }
            
        // IE fix; colspan must be explicitly set
        if ($.browser.msie && tag == 'td') {
          var colspan = node.getAttribute('colspan');
          if (colspan) e.colSpan = parseInt(colspan);
        }

        // IE fix; script tag not allowed to have children
        if($.browser.msie && !e.canHaveChildren) {
          if(node.childNodes.length > 0)
            e.text = node.text;
        }
        else {
          for(var i=0, max=node.childNodes.length; i < max; i++) {
            var child = createNode (node.childNodes[i]);
            if(child) e.appendChild(child);
          }
        }
        if (! $.browser.mozilla) {
          if (tag == 'select' || (tag == 'option' && node.getAttribute('selected')))
            e.taconiteTag = 1;
        }
        return e;
      };
        
      function copyAttrs(dest, src, inline) {
        for (var i=0, attr=''; i < src.attributes.length; i++) {
          var a = src.attributes[i], n = $.trim(a.name), v = $.trim(a.value);
          if (inline) attr += (n + '="' + v + '" ');
          else if (n == 'style') { // IE workaround
            dest.style.cssText = v;
            dest.setAttribute(n, v);
          }
          else $.attr(dest, n, v);
        }
        return attr;
      };
    };
  };

})(jQuery);

