/** 
 * (smt)2 simple mouse tracking - auxiliary functions (smt-aux-2.0.0.js)
 * Copyleft (cc) 2006-2009 Luis Leiva
 * Release date: July 19th 2009
 * http://smt.speedzinemedia.com  
 * @class smt2-aux
 * @version 2.0.0
 * @author Luis Leiva 
 * @license Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses. 
 */

var smtAuxFn = {
  /**
   * Overrides (smt) tracking options object with custom-provided options object
   * @return void
   * @param {object} smtOptionsObj
   * @param {object} customOptionsObj
   * @see <code>smtOpt</code> object either in <code>smtRecord</code> or <code>smtReplay</code> classes 
   */
  overrideTrackingOptions: function(smtOptionsObj, customOptionsObj) 
  {
    for (var prop in smtOptionsObj) {
      if (customOptionsObj.hasOwnProperty(prop) && customOptionsObj[prop]) {
        smtOptionsObj[prop] = customOptionsObj[prop];
      }
    }
  },
  /**
   * Allows recording/replaying the mouse path over Flash objects.
   * A Flash movie may display above all the layers on the HTML apge, 
   * regardless of the stacking order ("z-index") of those layers.   
   * Using a WMODE value of "opaque" or "transparent" will prevent a Flash movie from playing in the topmost layer 
   * and allow you to adjust the layering of the movie within other layers of the HTML document.
   * However, to avoid possible performance issues, it's best use the "opaque" mode.   
   * Note: The WMODE parameter is supported only on some browser/Flash Player version combinations. 
   * If the WMODE parameter is not supported, the Flash movie will always display on top.
   * @return void  
   */
  allowTrackingOnFlashObjects: function()
  {
    var obj = document.getElementsByTagName("object");  
    for (var i = 0, t = obj.length; i < t; ++i) {
      var param = document.createElement("param");
      param.setAttribute("name", "wmode");
      param.setAttribute("value","opaque");
      obj[i].appendChild(param);
    }
    
    var embed = document.getElementsByTagName("embed");
    for (var j = 0, u = embed.length; j < u; ++j) {
      embed[j].setAttribute("wmode", "opaque");
      // recording on some browsers is tricky (replaying is ok, though)
      if (!/MSIE/i.test(navigator.userAgent)) {
        /* Some browsers sets the wmode correctly, 
         * but once the SWF object is instantiated you can't update its properties.
         * So, replace the old Flash object with the new one ;)       
         */
        var cloned = embed[j].cloneNode(true);
        embed[j].parentNode.replaceChild(cloned, embed[j]);
      }
    }
  },
  /**
   * Traces a message in the debug console, if available. 
   * @return void
   * @param {string} type   message type: "log" (default), "info", or "error"
   * @param {mixed}  msg    message text (anything to trace)
   */
  log: function(msg, type)
  {
    if (!type) { type = "log"; }
    // check if console is available
    if (!window.console) { return false; }
    // display messages in the console
    switch (type) {
      case 'log':
        console.log(msg);
        break;
      case 'info':
        console.info(msg);
        break;
      case 'error':
        console.error(msg);
        break;
      default:
        console.log(msg);
        break;
      }
  },
  /**
   * Checks the DOM-ready initialization in modern browsers.
   * This method was introduced by Dean Edwards/Matthias Miller/John Resig (dean.edwards.name/outofhanwell.com/jquery.com)
   * and it is discussed in http://dean.edwards.name/weblog/2006/06/again/
   * It's 2009, and fortunately nowadays there are only 2 types of modern browsers: W3C standards and Internet Explorer.  
   * @return void
   * @param {function} callback   the function to be called on DOM load
   */
  onDOMload: function(callback)
  {
    if (arguments.callee.done) { return; }
    // flag this function so we don't do the same thing twice
    arguments.callee.done = true;
    
    // Firefox, Opera, Webkit-based browsers (Chrome, Safari)...
    if (document.addEventListener) {
      document.addEventListener('DOMContentLoaded', callback, false);
      document.removeEventListener('DOMContentLoaded', callback, false);
    }
    // Internet Explorer ¬¬
    else if (document.attachEvent) {
      try {
        document.write("<scr"+"ipt id=__ie_onload defer=true src=//:><\/scr"+"ipt>");
      	var script = document.getElementById("__ie_onload"); 
      	script.onreadystatechange = function() {
          if (this.readyState === 'complete') { callback(); }
      	};
      } catch(e) {}
    }
    // fallback: old browsers use the window.onload event
    this.addEvent(window, 'load', callback);
  },
  /**
   * Gets the browser's window size (aka 'the viewport')     
   * @return {object} window dimmensions - object with 2 properties: width {integer}, and height {integer}
   */
  getWindowSize: function() 
  {
    var d = document;
    var w = (window.innerWidth) ? window.innerWidth
          	: (d.documentElement && d.documentElement.clientWidth) ? d.documentElement.clientWidth
          	: (d.body && d.body.clientWidth) ? d.body.clientWidth 
            : 0;
  	var h = (window.innerHeight) ? window.innerHeight
          	: (d.documentElement && d.documentElement.clientHeight) ? d.documentElement.clientHeight
          	: (d.body && d.body.clientHeight) ? d.body.clientHeight 
            : 0;
    return { width: w, height: h };
  },
  /**
   * Gets the browser window's offsets.
   * @return {object} window offsets - object with 2 properties: x {integer}, and y {integer}
   * @deprecated     
   */
  getWindowPosition: function()
  {
    var d = document;
    var xpos = (window.pageXOffset) ? window.pageXOffset
            	 : (d.documentElement && d.documentElement.scrollLeft) ? d.documentElement.scrollLeft
            	 : (d.body && d.body.scrollLeft) ? d.body.scrollLeft 
               : 0;
    var ypos = (window.pageYOffset) ? window.pageYOffset
            	 : (d.documentElement && d.documentElement.scrollTop) ? d.documentElement.scrollTop
            	 : (d.body && d.body.scrollTop) ? d.body.scrollTop 
               : 0;
    
    return { x: xpos, y: ypos };
  },
  /**
   * Gets the document's size.       
   * @return {object} document dimmensions - object with 2 properties: width {integer}, and height {integer}  
   */
  getDocumentSize: function() 
  {
    var d = document;
  	var w = (window.innerWidth && window.scrollMaxX) ? window.innerWidth + window.scrollMaxX
          	: (d.body && d.body.scrollWidth > d.body.offsetWidth) ? d.body.scrollWidth
          	: (d.body && d.body.offsetWidth) ? d.body.offsetWidth
            : 0;
  	var h = (window.innerHeight && window.scrollMaxY) ? window.innerHeight + window.scrollMaxY
          	: (d.body && d.body.scrollHeight > d.body.offsetHeight) ? d.body.scrollHeight
          	: (d.body && d.body.offsetHeight) ? d.body.offsetHeight
            : 0;
    	
    return { width: w, height: h };
  },
  /**
   * Gets the max value from both window (viewport's size) and document's size, adding offsets.
   * MSHTML (Trident), Firefox (Gecko), Opera (Presto), and Safari (Webkit) support offsetHeight on document.body or on document.documentElement. 
   * The difficulty is figuring out which one is reliable, because it depends on the environment. 
   * As usual, the environment includes the browser, its version, and the rendering mode of the document.     
   * @return {object} viewport dimmensions object with 2 properties: width {integer}, and height {integer}  
   */
  getPageSize: function()
  {
    var win = this.getWindowSize(),
        doc = this.getDocumentSize();

    // find max values from this group
    var w = (doc.width < win.width) ? win.width : doc.width;
    var h = (doc.height < win.height) ? win.height : doc.height;
    // however, some browsers in some situations should return more space available
    var d = document.documentElement || document.body;
    // verify offsets...
    if (h < d.offsetHeight) { h = d.offsetHeight; }
    if (w < d.offsetWidth) { w = d.offsetWidth; }
    // ...again
    if (h < d.scrollHeight) { h = d.scrollHeight; }
    if (w < d.scrollWidth) { w = d.scrollWidth; }

    return { width: w, height: h };
  },
  /**
   * Gets the max z-index level available in the page.
   * @return {integer} z-index level     
   * @param {object} e    DOM element (default: document)
   * @autor Jason J. Jaeger (greengeckodesign.com)   
   */
  getNextHighestDepth: function(e) 
  {
    var highestIndex = 0;
    var currentIndex = 0;
    var elementArray = [];
    // check all elements in page ...
    if (document.getElementsByTagName) {
      elementArray = document.getElementsByTagName('*');
    } else if (e.getElementsByTagName) {
      elementArray = document.getElementsByTagName('*');
    }
    // ... and iterate
    for (var i = 0, l = elementArray.length; i < l; ++i) {
      if (elementArray[i].currentStyle) {
        currentIndex = parseFloat(elementArray[i].currentStyle.zIndex);
      } else if (window.getComputedStyle) {
        currentIndex = parseFloat(document.defaultView.getComputedStyle(elementArray[i],null).getPropertyValue('z-index'));
      }
      if (currentIndex > highestIndex) { highestIndex = currentIndex; }
    }

    return highestIndex + 1;
  },
  /**
   * Add event listeners unobtrusively.
   * @return void     
   * @param obj   {object}    Object to add listener(s) to
   * @param type  {string}    Event type
   * @param type  {function}  Function to execute
   * @autor John Resig (jquery.com)   
   */
  addEvent: function(obj, type, fn) 
  {
  	if (obj.addEventListener) {
      obj.addEventListener(type, fn, false);
    } else if (obj.attachEvent)	{
  		obj["e"+type+fn] = fn;
  		obj[type+fn] = function(){ obj["e"+type+fn](window.event); };
  		obj.attachEvent("on"+type, obj[type+fn]);
  	}
  },
  /**
   * Rounds a number to a given digits accuracy.
   * @return {float}
   * @param number  {float} input number
   * @param type    {float} precision digits
   */
  roundTo: function(number,digits)
  {
    //var d = digits || 2;
    var exp = 100; // faster, because (smt)2 precision is the same for all computations!
    /* in 'taliban mode' that would be ok:
     * <code>var exp = Math.pow(10,digits);</code>
     * or even this, avoiding the pow function:
     * <code>for (var i = 0, exp = 1; i < digits.length; ++i, exp *= 10) {}</code>          
     */
    return Math.round(exp*number)/exp; 
  },
  /**
   * Creates an XML/HTTP request to provide async communication with the server.
   * @return {object} XHR object
   * @autor Peter-Paul Koch (quirksMode.org)
   */
  createXMLHTTPObject: function() 
  {
    var xmlhttp = false;
    // current AJAX flavours
    var XMLHttpFactories = [
      function(){ return new XMLHttpRequest(); },
      function(){ return new ActiveXObject("Msxml2.XMLHTTP"); },
      function(){ return new ActiveXObject("Msxml3.XMLHTTP"); },
      function(){ return new ActiveXObject("Microsoft.XMLHTTP"); }
    ];
    // check AJAX flavour
    for (var i = 0; i < XMLHttpFactories.length; ++i) {
      try {
        xmlhttp = XMLHttpFactories[i]();
      } catch(e) { continue; }
      break;
    }
      
    return xmlhttp;
  },
  /** 
   * Makes an asynchronous XMLHTTP request (XHR) via GET or POST.
   * Inspired on Peter-Paul Koch's XMLHttpRequest function.   
   * @return void
   * @param {object} reqSetup   Request properties
   * @config {string} url Request URL
   * @config {function} [callback] Response function
   * @config {string} [postdata]  POST vars in the form "var1=name&var2=name..."
   * @config {object} [xmlhttp]   A previous XMLHTTP object can be reused        
   */ 
  sendAjaxRequest: function(setup)
  {
    // create XHR object (or reuse it)
    var request = (setup.xmlhttp) ? setup.xmlhttp : this.createXMLHTTPObject();
    if (!request) { return; }
    
    var method = (setup.postdata) ? "POST" : "GET";
    // start request
    request.open(method, setup.url, true);
    // post requests must set the correct content type
    if (setup.postdata) {
      request.setRequestHeader('Content-Type', "application/x-www-form-urlencoded");
    }
    // check for the 'complete' request state
    request.onreadystatechange = function(){
      if (this.readyState == 4 && typeof setup.callback === 'function') {
        // send server response to callback function
        setup.callback(request.responseText);
      }
    };
    // send request
    request.send(setup.postdata);
  },
  /** 
   *  Browser detection object. This function is used only for browser stats.
   *  As its autor said:
   *  "The more this script is used, the less likely it is to stay current.
   *  In order to keep this script up-to-date you should use it as little as possible."
   *  @autor Peter-Paul Koch (quirksMode.org)   
   *  @deprecated      
   */
  browserDetect: {
    /**
     * Launches the browser detect script.
     * @return void
     */
  	init: function() 
    {
  		this.browser = this.searchString(this.dataBrowser) || "Other";
  		this.version = this.searchVersion(navigator.userAgent) || this.searchVersion(navigator.appVersion) || "0";
  		this.OS = this.searchString(this.dataOS) || "Other";
  	},
  	/**
     * Performs an identity search on provided data object.
     * @return {string}       browser identity     
     * @param {object} data   dataBrowser identity
     */
  	searchString: function(data) 
    {
  		for (var i = 0; i < data.length; ++i)	{
  			var dataString = data[i].string;
  			var dataProp = data[i].prop;
  			this.versionSearchString = data[i].versionSearch || data[i].identity;
  			if (dataString) {
  				if (dataString.indexOf(data[i].subString) != -1) {
  				  return data[i].identity;
          }
  			} else if (dataProp) {
          return data[i].identity;
        }
  		}
  	},
  	/**
     * Performs a version search on provided data string.
     * @return {float}              browser version     
     * @param {string} dataString   string to parse
     */
  	searchVersion: function(dataString) 
    {
  		var index = dataString.indexOf(this.versionSearchString);
  		if (index == -1) { return; }
  		return parseFloat(dataString.substring(index + this.versionSearchString.length+1));
  	},
  	/**
     * Common browsers array.
     */
  	dataBrowser: 
    [
  	  { prop: window.opera,                                 identity: "Opera"     },
  		{ string: navigator.vendor,    subString: "Apple",    identity: "Safari"    },
  		{ string: navigator.vendor,    subString: "iCab",     identity: "iCab"      },
  		{ string: navigator.vendor,    subString: "KDE",      identity: "Konqueror" },
  		{ string: navigator.vendor,    subString: "Camino",   identity: "Camino"    },
  		{ string: navigator.userAgent, subString: "Chrome",   identity: "Chrome"    },
  		{ string: navigator.userAgent, subString: "Flock",    identity: "Flock"     },
  		{ string: navigator.userAgent, subString: "Firefox",  identity: "Firefox"   },
  		{	string: navigator.userAgent, subString: "Netscape", identity: "Netscape"  },
  		{ string: navigator.userAgent, subString: "OmniWeb",  identity: "OmniWeb",   versionSearch: "OmniWeb/" },
  		{ string: navigator.userAgent, subString: "MSIE",     identity: "IExplorer", versionSearch: "MSIE"     },
  		{ string: navigator.userAgent, subString: "Gecko",    identity: "Mozilla",   versionSearch: "rv"       },
  		{ string: navigator.userAgent, subString: "Mozilla",  identity: "Netscape",  versionSearch: "Mozilla"  }
  	],
  	/**
     * Common Operating Systems.
     */
  	dataOS: 
    [
  		{ string: navigator.platform,  subString: "Win",      identity: "Windows"     },
  		{ string: navigator.platform,  subString: "Mac",      identity: "Mac"         },
  		{ string: navigator.platform,  subString: "Linux",    identity: "Linux"       },
  		{ string: navigator.userAgent, subString: "iPhone",   identity: "iPhone/iPod" },
  		{ string: navigator.userAgent, subString: "Chrome",   identity: "ChromeOS"    } 
  	]
  },
  /** 
   *  Cookies management object.
   *  This cookies object allows you to store and retrieve cookies easily.
   *  Cookies can be picked up by any other web pages in the correct domain. 
   *  Cookies are set to expire after a certain length of time.
   */
  cookies: {
    /**
     * Stores a cookie variable.
     * @return void     
     * @param {string} name
     * @param {mixed}  value 
     * @param {string} expiredays (optional) default: no expire
     * @param {string} domainpath (optional) default: root domain
     */
    setCookie: function(name,value,expiredays,domainpath) 
    {
      var path = domainpath || "/";
      var expires = "";
      if (expiredays) {
    		var date = new Date();
    		date.setTime(date.getTime() + (expiredays*24*60*60*1000)); // ms
    		expires = "; expires=" + date.toGMTString();
    	}
      document.cookie = name +"="+ escape(value) + expires +"; path=" + path;
    },
    /**
     * Retrieves a cookie variable.
     * @return {string}       cookie value, or false on failure     
     * @param {string} name   cookie name
     */
    getCookie: function(name) 
    {
      var cStart,cEnd;
      if (document.cookie.length > 0) {
        cStart = document.cookie.indexOf(name+"=");
        if (cStart != -1) {
          cStart = cStart + name.length + 1; 
          cEnd   = document.cookie.indexOf(";", cStart);
          if (cEnd == -1) {
            cEnd = document.cookie.length;
          }
            
          return unescape(document.cookie.substring(cStart, cEnd));
        }
      }
      return false;
    },
    /**
     * Checks if a cookie exists.
     * @return {boolean}      true on success, or false on failure
     * @param {string} name   cookie name    
     */
    checkCookie: function(name) 
    {
      var c = this.getCookie(name);
      return (c);
    }
  },
  /** 
   * Core for tracking widgets.
   * The word "widget" stands for *any* HTML DOM element on the page.        
   */
  widget: {
    /** 
     * Finds the first available element with an ID.
     * Traversing count starts from current element to node parents.
     * This function should be registered on mouse move/down events.   
     * @return {object}            DOM node element
     * @param {object}   e         DOM event
     * @param {function} callback  response function             
     */ 
    findDOMElement: function(e,callback) 
    {
      if (!e) { e = window.event; }
      // find the element
      var t = e.target || e.srcElement;
      // defeat Safari bug
      if (t.nodeType == 3) { t = t.parentNode; }
      // if the element has no ID, travese the DOM in reverse (find its parents)
      var check = (t.id) ? this.getID(t) : this.getParents(t);
      if (check) {
        callback(check);
      }
    },
    /** 
     * Gets the element's id.
     * @return {string}     DOM node ID
     * @param {object}  o   DOM node element        
     */
    getID: function(o) 
    {
      // do not track HTML and BODY nodes
      if (o.nodeName == 'HTML' || o.nodeName == 'BODY') { return null; }
      return o.nodeName + "#" + o.id;
    },
    /** 
     * Gets the element's class. 
     * If that element has more than one CSS class, return only the first class name found.
     * @return {string}     DOM node class
     * @param {object}  o   DOM node element        
     */  
    getClass: function(o) 
    {
      // do not track HTML and BODY nodes
      if (o.nodeName == 'HTML' || o.nodeName == 'BODY') { return null; }
      // if the element has no class, return its node name only
      return (o.className) ? o.nodeName + "." + o.className.split(" ")[0] : o.nodeName;
    },
    /** 
     * Gets all node parents until a node with ID is found.
     * @return {string}     CSS selector string (intended to use in other scripts).
     * @param {object}  o   DOM node element        
     */   
    getParents: function(o) 
    {
      // store current node first
      var elem = (o.id) ? this.getID(o) : this.getClass(o);
      //var list = (elem) ? [elem] : [];
      var list = [elem];
      // get all parents until find an element with ID
      while (o.parentNode) {
        o = o.parentNode;
        // store only element nodes
        if (o.nodeType == 1) {
          if (o.id) {
            elem = this.getID(o);
            list.unshift(elem);
            // if parent has an ID, end finding
            return list.join(" ");
          } else {
            elem = this.getClass(o);
            list.unshift(elem);
          } 
          if (o == parent) {
            // #document reached
            return list.join(" ");
          }
        }
      }
      return list.join(" ");
    }
  },
  /**
   *  Array methods -- without extending the Array prototype (best practice). 
   *  Note: These Array methods would only work for a completely 'dense' array. 
   *  If you're working with sparse arrays, then you should pre-process them:
   *  <code> 
   *    var narray = [];
   *    for (var i = 0; i < array.length; ++i) {
   *      if (array[i] != null) { narray.push(array[i]); }
   *    }
   *    // now narray is converted in a 'dense' array
   *  </code>   
   */
  array: {
    /**
     * Gets the max element in a numeric array. 
     * @return {float}        array max value
     * @param {array} array 
     * @author John Resig
     */
    max: function(array) 
    {
      return Math.max.apply(Math, array);
    },
    /**
     * Gets the min element in a numeric array.
     * @return {float}        array min value
     * @param {array} array 
     * @author John Resig
     */
    min: function(array) 
    {
      return Math.max.apply(Math, array);
    },
    /**
     * Sums all elements in a numeric array.
     * @return {float}        array sum
     * @param {array} array   
     */
    sum: function(array) 
    {
      var s = 0;
      for (var i = 0, l = array.length; i < l; ++i) {
        s += array[i];
      }
      return s; 
    },
    /**
     * Appends any supplied arguments to the end of the array, in the order given.
     * @return {integer}        new length of the array
     * @param {array} array
     * @param {mixed} content   anything to push to array  
     * @author Ash Searle (tweaked by Luis Leiva)
     * @link http://hexmen.com/blog/ 
     * @license http://creativecommons.org/licenses/by/2.5/
     */
    push: function(array, content) 
    {
      var args = Array.prototype.slice.call(content);
      var n = array.length >>> 0;
      for (var i = 0; i < args.length; ++i) {
    	 array[n] = args[i];
    	 n = n + 1 >>> 0;
      }
      array.length = n;
      return n;
    },
    /**
     * Removes the last element of the array and returns it.
     * @return {mixed}        element removed (if any). Otherwise return <code>undefined</code>
     * @param {array} array 
     * @author Ash Searle
     * @link http://hexmen.com/blog/ 
     * @license http://creativecommons.org/licenses/by/2.5/
     */
    pop: function(array) 
    {
      var n = array.length >>> 0, value;
      if (n) {
      	value = array[--n];
      	delete array[n];
      }
      array.length = n;
      return value;
    },
    /**
     * Removes element(s) in desired position(s) from input array and returns the modified array.
     * @return {array}          modified array
     * @param {array}   array
     * @param {integer} from    start index
     * @param {integer} to      end index
     * @author John Resig 
     */
    remove: function(array, from, to) 
    {
      var rest = array.slice((to || from) + 1 || array.length);
      array.length = (from < 0) ? array.length + from : from;
      return array.push.apply(array, rest);
    },
    /**
     * Gets the index of the first element that matches search value.
     * @return {integer}          index if found, or -1 otherwise
     * @param {array}   array
     * @param {mixed}   search    element to search for
     * @param {integer} from      start index      
     * @link http://snipplr.com/view/3355/arrayindexof/   
     */
    indexOf: function(array, search, from) 
    {
      for (var i = (from || 0), total = array.length; i < total; ++i) {
        // strict types should use === comparison
        if (array[i] == search) {
          return i;
        }
      }
      return -1;
    }
  }
  
};