/** 
 *  Zenexity client-side tools collection file
 *  
 *  @author Victor Bartel (vba [a] zenexity.fr)
 */
(function() {
  
  if(!window.zenexity) window.zenexity = {tools : {}};
  else window.zenexity.tools = {};
  
  var zt = zenexity.tools;
  
  zt.cache = {};
  
  /**
   * @description Returns the namespace specified and creates it if it doesn't exist
   * @method namespace
   * @namespace sematic.tools
   * @param arguments {String*} 1-n namespaces to create
   * @return {Object} A reference to the last namespace object created
   *
   */
  zt.namespace = function() {
    var a = arguments, o = null, i, j, d;
    for (i = 0, len1 = a.length; i < len1; i = i + 1) {
      d = a[i].split(".");
      o = window;
      for (j = 0, len2 = d.length ; j < len2; j = j + 1) {
        o[d[j]] = o[d[j]] || {};
        o = o[d[j]];
      }
    }
    return o;
  };
  
  zt.count = function(string,substr) {
    if (!string) return 0;
    substr = zt.isRegExp(substr) ? substr : new RegExp(substr,"g");
    return string.length - string.replace(substr, '').length;
  };
  
  /**
   * @description Capitalizes entered world/sentence
   * @param s {String} world to be capitalized
   * @returns {String} Capitalized string
   * 
   */
  zt.capitalize = function(s){
    return [s.charAt(0).toUpperCase(), s.substring(1)].join('');
  };
  
  /**
   * @description Escapes html symbols in passed string
   * @param str {String} string to be escaped
   * @return {String} escaped string
   */
  zt.escapeHTML = function(str) {
    return str.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
  };
  
  /**
   * @description Unescapes html symbols in passed string
   * @param str {String} string to be unescaped
   * @return {String} unescaped string
   */
  zt.unescapeHTML = function(str) {
    return str.replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>');
  };
  
  /**
   * @description Invokes a function from indicated namespace and returns its result
   * @namespace sematic.tools
   * @param namespace {String} method name & namespace
   * @param context {Object} invocation context 
   * @param args {Array} invocation arguments
   * @return {Object|Undefined} function invocation result
   */
  zt.invoke = function(namespace,context,args) {
    var result, l = namespace.split('.');
    
    if( l.length == 1 && $.isFunction(window[namespace]) ) {
      result = window[namespace].apply(context,args);
    } else {
      var scope = window;
      $.each(l,function(i,el){
        if((i+1) == l.length && $.isFunction(scope[el])) {
          result = scope[el].apply(context,args);
        } else {
          scope = scope[el];
        }
      });
    }
    return result;
  };
  
  /**
   * @description Namespace objects getter
   * @param namespace {String} passed namespace
   * @param start {Object} start scope object
   * @return {Object} undefined if namespace doesn't exists and an object found in the namespace otherwise
   */
  zt.getNamespace = function(namespace,start) {
    var l = namespace.split('.'), scope = start || window;
    if( l.length == 1) return typeof scope[namespace] != 'undefined';
    
    for ( var i = 0, length = l.length; i < length; i++ ) {
      scope = scope[l[i]];
      if(typeof scope == 'undefined') break;
    }
    
    return scope;
  };

  /**
   * @description Returns reference function to passed namespace method
   * @namespace sematic.tools
   * @param namespace {String} method complete path
   * @param context {Object} execution context 
   * @param args {Array} execution arguments
   * @return {Object|Undefined} function calling result
   */
  zt.ref = function(namespace,context,args) {
    return function() {
      return zt.invoke(namespace,context, $.merge($.makeArray(arguments),args||[]));
    };
  };
  
  /**
   * @description Dynamically load remote script using url/namespace
   * @param desc {Object} Dependency describer
   * @param callback {Function} callback function to be called after script loading
   */
  zt.using = function(desc,callback) {
    var conf = {};
    
    conf.url = desc.url;
    conf.ref = desc.namespace;
    
    if(desc.object) conf.ref = desc.object;
    
    var obj = zt.getNamespace(conf.ref);
    
    if(desc.namespace && /^(?:\w+\.)*(?:\w+)$/gi.test(desc.namespace)) 
      conf.url = [desc.prefix,'/',desc.namespace.replace(/\./gi,'/'),'/',desc.suffix].join('');
    
    if(!obj)
      $.getScript(conf.url,function(){
        var object = zt.getNamespace(conf.ref);
        if(object && desc.init && $.isFunction(object.init)) 
          try {object.init.call(window);} catch(m){} 
        if($.isFunction(callback)) callback.call(window,object);
      });
    else
      if($.isFunction(callback)) callback.call(window,obj);
  };
  /**
   * @description Tries to call a function safely
   * @method tryToCall
   * @param f {Function*} function object to be called
   * @param c {Object*} context in which function is called
   * @param params {Array | Object*} parameters to be supplied to function call
   * @returns {Object | Undefined} function execution result if this one had place
   * 
   */
  zt.tryToCall = function(){
    var a = arguments;
    var f = a[0], c = a[1];
    
    if ($.isFunction(f)) return f.apply(c, $.makeArray(a).slice(2));
    else return false;
  };
  
  /**
   * @description Dispatch handled event to scope
   * @namespace sematic.tools
   * @param e {Event} Handled event(normalized by jQuery)
   *
   */
  zt.dispatchEvent = function(e) {
   /*
    * FIXME RDJ: j'utilise  e.preventDefault(); si et seulement si je trouve une méthode.
    *            Ca te convient comme mode de fonctionnement ?
    */
    var that = this;
    
    if($.nodeName(e.target,'img') && $.nodeName(e.target.parentNode,'a'))
      e.target = e.target.parentNode;
    
    var id = e.target.id;
    
    if(id && /\.+/.test(id.split('_')[0])) {
      var obj = {}, arr = id.match(/(?:[^\.]+\.)*[a-zA-Z]+/i)[0].split('.');
      obj.m = arr.splice(arr.length-1,1)[0];
      obj.o = zt.getNamespace(arr.join('.'),that);
      if($.isFunction(obj.o[obj.m])) {
        e.preventDefault();
        obj.o[obj.m].call(obj.o,e);
      }
    } 
    else if(id && that !== window) {
      var method = that[id.split("_")[0]];
      if($.isFunction(method)) { 
        e.preventDefault();
        method.call(that,e);
      }
    }
    
    return false;
  };
  /**
   * Tries to guess object type.
   * @param o {Object} target to get type 
   * @return {String|Undefined} return object type name or undefined
   */
  zt.guessType = function(o) {
    if (typeof o == 'undefined') return undefined;
    else return Object.prototype.toString.call(o).replace(/(\[|\]|object|\s+)/g, "");
  }
  /**
   * @description Extends an object with properties of another, that match pattern
   * @namespace sematic.tools
   * @param target {Object} target object
   * @param source {Object} source object
   * @param pattern {String|RegExp} selection pattern
   * @param inv {Boolean} Inverts search behavior if "false/not set" will select all properties that match to pattern and if "true" will select properties that not match to pattern.
   */
  zt.extendObject = function(target, source, pattern, inv) {
    var src = {};
    if(Object.prototype.toString.call(pattern) === '[object String]')
      pattern = new RegExp(pattern);
    
    $.each(source,function(k,v){
      if(!inv != !pattern.test(k)) src[k] = v;
    });
    
    $.extend(target,src);
  };
  
  /**
   * @see http://ejohn.org/blog/javascript-micro-templating/
   * @description Simple JavaScript Templating (John Resig)
   * @param str {String|HTMLElement} template source / container element
   * @param data {Object} Template data
   */
  zt.tpl = function tpl (str, data){
    data.__empty = '';
    var fn = !/\W/.test(str) ?
      zt.cache[str] = zt.cache[str] || tpl(document.getElementById(str).innerHTML) :
      function() {
        str = str.replace(/[\r\t\n]/g, " ")
          .split("<%").join("\t")
          .replace(/((^|%>)[^\t]*)'/g, "$1\r")
          .replace(/\t=(.*?)%>/g, "', obj[$.trim('$1')] || obj['__empty'],'")
          .split("\t").join("');")
          .split("%>").join("p.push('")
          .split("\r").join("\\'");

        var body = zt.sb("var p=[],print=function(){p.push.apply(p,arguments);};")
          .append("with(obj){p.push('").append(str)
          .append("');}return p.join('');");
        
        return new Function("obj",body.toString());
      }();
    return data ? fn( data ) : fn;
  };

  /**
   * Function to determine scope call stacktrace, created by Josh Goebel
   * @see http://pastie.org/253058.txt
   * @return {String} current call stacktrace
   */
  zt.getStackTrace = function () {
    
    var mode;
    try {(0)();} catch (e) {
      mode = e.stack ? 'Firefox' : window.opera ? 'Opera' : 'Other';
    }

    switch (mode) {
      case 'Firefox' : return function () {
        try {
          (0)();
        } catch (e) {
          return e.stack.replace(/^.*?\n/, '').
            replace(/(?:\n@:0)?\s+$/m, '').
            replace(/^\(/gm, '{anonymous}(').
            split("\n");
        }
      };
      // TODO: not works, need to be corrected thanks Josh Goebel !
      case 'Opera' : return function () {
        try {
          (0)();
        } catch (e) {
          var lines = e.message.split("\n"),
            ANON = '{anonymous}',
            lineRE = /Line\s+(\d+).*?in\s+(http\S+)(?:.*?in\s+function\s+(\S+))?/i,
            i,j,len;
          for (var i = 4,j = 0,len = lines.length; i < len; i += 2) {
            if (lineRE.test(lines[i])) {
              lines[j++] = (RegExp.$3 ?
                            RegExp.$3 + '()@' + RegExp.$2 + RegExp.$1 :
                            ANON + RegExp.$2 + ':' + RegExp.$1) +
                           ' -- ' + lines[i + 1].replace(/^\s+/, '');
            }
          }

          lines.splice(j, lines.length - j);
          return lines;
        }
      };
      // TODO: To test, because Josh Goebel seems to be not credible
      default : return function () {
        var curr = arguments.callee.caller,
          FUNC = 'function', ANON = "{anonymous}",
          fnRE = /function\s*([\w\-$]+)?\s*\(/i,
          stack = [],j = 0,
          fn,args,i;

        while (curr) {
          fn = fnRE.test(curr.toString()) ? RegExp.$1 || ANON : ANON;
          args = stack.slice.call(curr.arguments);
          i = args.length;

          while (i--) {
            switch (typeof args[i]) {
              case 'string'  : args[i] = '"' + args[i].replace(/"/g, '\\"') + '"'; break;
              case 'function': args[i] = FUNC; break;
            }
          }

          stack[j++] = fn + '(' + args.join() + ')';
          curr = curr.caller;
        }

        return stack;
      };
    }
  };
  
  zt.xhtml = {
    get : function(node,includeSelf) {
      var bf =  zt.sb(''); //new zt.StrBuffer();
      zt.xhtml.explore(node,bf,0,includeSelf);
      return bf.toString();
    },
    explore : function (node,bf,lvl,includeSelf) {
      if(node.nodeType == 3) {
        if ($.trim(node.nodeValue) == "" || $.trim(node.nodeValue) =="\n") return;
        bf.append( zt.escapeHTML(node.nodeValue) );
        return;
      }
      
      if(node.childNodes.length == 0 && !$(node).is('div')) {
        if (node.nodeName == "#comment") {
            bf.append ("<!--" + node.nodeValue + "-->");
        } if (node.nodeName=="A") {
            bf.append(zt.xhtml.indent(lvl)+'<a' + zt.xhtml.attrs(node)+ '></a>\n');
        } else 
            bf.append(zt.xhtml.indent(lvl)+'<' + node.nodeName.toLowerCase() + zt.xhtml.attrs(node)+ '/>\n');
        return;
      }
      
      var nodes = node.childNodes;
      if (nodes.length==1 && nodes[0].nodeType==3) {
        bf.append (zt.xhtml.indent(lvl)+'<' + node.nodeName.toLowerCase() +zt.xhtml.attrs(node)+ '>');
        zt.xhtml.explore (nodes[0],bf,lvl+1);
        bf.append('</' + node.nodeName.toLowerCase() + '>\n');
      } else {
        if (lvl != 0 || includeSelf ) bf.append (zt.xhtml.indent(lvl)+'<' + node.nodeName.toLowerCase() + zt.xhtml.attrs(node)+ '>\n');
        for (var i = 0; i < nodes.length ; i++)  {
          zt.xhtml.explore (nodes[i],bf,lvl + 1);
        }
        if (lvl!=0 || includeSelf) bf.append (zt.xhtml.indent(lvl)+'</' + node.nodeName.toLowerCase() + '>\n');
      }
    },
    attrs : function (node) {
      if (!node.attributes) return "";
      var attributes = "";
      for (var i=0; i<node.attributes.length; i++) {
        var attr = node.attributes[i];
        if (!attr.nodeValue || attr.nodeValue=="") continue;
        attributes+=" "+attr.nodeName.toLowerCase()+"=\""+attr.nodeValue+"\"";
      }
      return attributes;
    },
    indent : function (lvl) {
      var spc = "";
      while (lvl > 0) {
        spc+="  ";
        lvl--;
      }
      return spc;
    }
  };
  
  $.each(['String','Array','Number','Object','Function','RegExp'], function() {
    var type = this+'';
    zt[['is',type].join('')] = function(o) { return zt.guessType(o) == type ; }
  });
  
  /**
   * @description Extended string buffer/helper
   * @namespace sematic.tools
   *
   * @author vba@zenexity.fr
   */
  zt.StrBuffer = zt.sb = function() {
    var args = Array.prototype.slice.call(arguments,0);
    return new zt.StrBuffer.fn.init(args);
  };
  
  zt.StrBuffer.fn = zt.StrBuffer.prototype = {
    init : function(args) {
      this.setArray(args || "");
      return this;
    },
    /**
     * @description Appends new string part to current buffer
     * @param strings {String*} 1-n strings to append
     * @return {StrBuffer} current buffer
     */
    append : function(strings) {
      var than = this, args = Array.prototype.slice.call(arguments,0);
      $.each(args, function() { Array.prototype.push.call(than, this+''); });
      return this;
    },
    /**
     * @description Appends new string part to current buffer and break it
     * @param strings {Object*|String*} 1-n strings to append
     * @return {StrBuffer} current buffer
     */
    appendln : function(strings) {
      var than = this, args = Array.prototype.slice.call(arguments,0);
      $.each(args, function() { Array.prototype.push.call(than, this+'\n'); });
      return this;
    },
    /**
     * @description Converts current buffer to string
     * @return {String} buffer string representation
     */
    toString : function() {
      return Array.prototype.join.call(this, "");
    },
    get : function(index) {
      return this[index];
    },
    size : function() {
      return this.length;
    },
    setArray : function(el) {
      this.length = 0;
      Array.prototype.push.apply(this, zt.isArray(el) ? el : [el]);

      return this;
    }
  };
  
  zt.StrBuffer.fn.init.prototype = zt.StrBuffer.fn;
//  zt.StrBuffer.s = zt.StrBuffer.toString;
    
}());

// TODO: Move to dedicated module !
(function($){
  $.fn.extend({
    xhtml: function( value ) {
      var zt = zenexity.tools;
      return value === undefined ? (this[0] ? zt.xhtml.get(this[0]) : null) : this.empty().append( value );
    }
  });
})(jQuery);
