// UserTime's measurement collection. This code is derived from Steve Souders' episodes.js


// Don't overwrite pre-existing instances of the object
var UT = UT || {};

// logging
UT.log=function(m){
  //if (typeof window.console != "undefined") { console.debug(m) } // To log every message to console, as long as console is available:
  if(window.location.href.indexOf('utlogger') !=-1){ console.debug(m) } // To only log when a parameter is added to the URL
};

UT.init = function() {
  UT.domain = UT.domain == null ? "http://usertimeapp.com" : UT.domain;
  UT.done = false;
  UT.domready = false;
  UT.marks = {};
  UT.measures = {};
  UT.starts = {};
  UT.numScripts = 0;
  UT.numScriptsInHead = 0;
  UT.numInlineScripts = 0;
  UT.numStylesheets = 0;
  UT.numImages = 0;
  UT.numIncompleteImages = 0;
  UT.slowImages = {};
  UT.bindDomReady();
  UT.findStartTime();
  UT.handleEpisodeMessage("UT:mark:firstbyte:" + UT.firstbyte);
  UT.handleEpisodeMessage("UT:measure:firstbyte:backendstarttime:firstbyte");
  UT.addEventListener("load", function() {
    // Punting on perceivedrendertime -- it needs to be calculated somehow. The measurement might not ultimately go here.
    UT.handleEpisodeMessage("UT:measure:perceivedrendertime:firstbyte");
    UT.handleEpisodeMessage("UT:measure:pageready:firstbyte");
    UT.handleEpisodeMessage("UT:done");
  }, false);
  UT.addEventListener("beforeunload", UT.beforeUnload, false);
  UT.countsTimer = setTimeout(UT.getCounts,1500);
  UT.countsDone = false;
  UT.auditCookieExpire = 30; // X minutes between audits for 1 user session
  UT.auditSampleRate = 3; // Every 1 of X pages will be eligible for audit. Once a page is sampled, another one won't be until +UT.assets.cookieExpire+  
};

/*
 * space out audits -- don't do them on the first pageview, and respect the audited cookie
 */
UT.auditEligible = function() {
  if (1 == Math.floor(Math.random()*UT.auditSampleRate+1)) {
    return !document.cookie.match('UTAudited=true');
  }
};

/*
 * Forces UT to request an audit next run. Used for debugging. Note: server may still decide not to do an audit.
 */
UT.resetAudit = function() {
  var date = new Date();
  date.setTime(date.getTime()-(1000));
  var expires = "; expires="+date.toGMTString();
  document.cookie = "UTAudited="+expires+"; path=/";
  return true
};

// Parse an UT message and perform the desired function.
UT.handleEpisodeMessage = function(message) {
  var handlerStartTime=new Date().getTime();
  var aParts = message.split(':');
  if ("UT" === aParts[0]) {
    var action = aParts[1];
    if ("init" === action) {
      // "UT:init"
      UT.init();
    }
    else if ("mark" === action) {
      // "UT:mark:markName[:markTime]"
      var markName = aParts[2];
      UT.marks[markName] = parseInt(aParts[3] || Number(new Date()));
    }
    else if ("measure" === action) {
      // "UT:measure:episodeName[:startMarkName|startEpochTime[:endMarkName|endEpochTime]]"
      var episodeName = aParts[2];

      // If no startMarkName is specified, assume it's the same as the episode name.
      var startMarkName = ( "undefined" != typeof(aParts[3]) ? aParts[3] : episodeName );
      // If the startMarkName doesn't exist, assume it's an actual time measurement.

      var startEpochTime = ( "undefined" != typeof(UT.marks[startMarkName]) ? UT.marks[startMarkName] :
                             ( ("" + startMarkName) === parseInt(startMarkName) ? startMarkName : undefined ) );

      var endEpochTime = ( "undefined" === typeof(aParts[4]) ? Number(new Date()) :
                           ( "undefined" != typeof(UT.marks[aParts[4]]) ? UT.marks[aParts[4]] : aParts[4] ) );

      if (startEpochTime) {
        UT.measures[episodeName] = parseInt(endEpochTime - startEpochTime);
        UT.starts[episodeName] = parseInt(startEpochTime);
      }
    }
    else if ("done" === action) {
      // "UT:done"
      UT.done = true;

      setTimeout(UT.sendBeacon,0);
    }
    var now= new Date().getTime();
    UT.log("Since start: "+(now-UT.firstbyte)+"ms. In handler: "+(now-handlerStartTime)+"ms. "+message);
  }
};

// gather some additional information. ACL added; this is experimental.
UT.getCounts=function(){
  UT.countsDone=true;

  // num scripts: inline vs external source (with a src attribute) and in head.
  // We only count external scripts in head.
  var scripts=document.getElementsByTagName('script');
  var numScripts=scripts.length;
  var head=document.getElementsByTagName('head')[0];
  for(i=0;i<numScripts; i++){
    s=scripts[i];
    if(s.src == '') {
      UT.numInlineScripts+=1;
    }else {
      UT.numScripts+=1;
      if(s.parentNode==head){UT.numScriptsInHead+=1}
    }
  }

  // num stylesheets
  UT.numStylesheets=document.styleSheets.length;

  // num images
  var images=document.images;
  UT.numImages=images.length;

  // num unloaded Images
  for(i=0;i<UT.numImages;i++){
    image=images[i];
    //UT.log(image.src +" is " + (image.complete ? 'complete' : 'incomplete') );
    if(!image.complete){
      // Note, the slow_image_id isn't strictly necessary right now.break We use it
      // to remember *when* we started timing the image, so we can calculate duration when the image finally loads.
      // But, we only take one sample right now, so there's not actually any variation in the starting time.
//      image.slow_image_id=i;
      UT.numIncompleteImages+=1;
//      UT.slowImages[i]=[image,Number(new Date())]; // { arbitrary number: [dom element, time now] }
//
//      image.onerror = image.onabort = image.onload = function(){
//        duration = Number(new Date()) - UT.slowImages[this.slow_image_id][1];
//      };
    }
  }
};

// Return an object of episode names and their corresponding durations.
UT.getMeasures = function() {
  return UT.measures;
};

// Return an object of episode names and their corresponding durations.
UT.getStarts = function() {
  return UT.starts;
};

// Construct a querystring of episodic time measurements and send it to the specified URL.
UT.sendBeacon = function() {
  var url = UT.domain+"/beacon.js";
  var measures = UT.getMeasures();
  var sTimes = "";
  for (var key in measures) {
    sTimes += "," + key + ":" + measures[key];
  }

  // fetch the counts if they haven't already been done
  if(!UT.countsDone){
    UT.getCounts();
  }

  if (sTimes) {
    // strip the leading ","
    sTimes = sTimes.substring(1);

    url += "?ets=" + sTimes;

    url += "&counts="+UT.numScripts+"."+UT.numScriptsInHead+"."+UT.numInlineScripts+"."+
            UT.numStylesheets+"."+UT.numImages+"."+UT.numIncompleteImages;
    if (UT.key) {
    url += "&key="+UT.key;
    }
    url += "&title="+encodeURIComponent(document.title);
    // add the label, if provided
    if (UT.label != null && UT.label != '') {
        url += '&label=' + encodeURIComponent(UT.label);
    }
    if (UT.auditEligible()){url += "&audit_eligible=1";UT.log('audit eligible')}
    if (window.location.href.indexOf('utforceaudit') !=-1){url +="&force_audit=1";UT.log('forcing audit')}

    // Send the beacon.
    var s = document.createElement("script");
    s.src = url;
    s.type="text/javascript";
    document.getElementsByTagName("head")[0].appendChild(s);
  }
  return "";
};

// Use various techniques to determine the time at which this page started.
UT.findStartTime = function() {
  var aCookies = document.cookie.split(' ');
  for (var i = 0; i < aCookies.length; i++) {
    if (0 === aCookies[i].indexOf("UT=")) {
      var aSubCookies = aCookies[i].substring("UT=".length).split('&');
      var startTime, bReferrerMatch;
      for (var j = 0; j < aSubCookies.length; j++) {
        if (0 === aSubCookies[j].indexOf("s=")) {
          startTime = aSubCookies[j].substring(2);
        }
        else if (0 === aSubCookies[j].indexOf("r=")) {
          // Some browsers return the semi-colon at the end of each stored
          // value. Remove it before we perform referrer matching to make it
          // work reliably.
          if (aSubCookies[j][aSubCookies[j].length - 1] == ';') {
            aSubCookies[j] = aSubCookies[j].substring(0, aSubCookies[j].length - 1);
          }
          var startPage = aSubCookies[j].substring(2, aSubCookies[j].length);
          bReferrerMatch = ( encodeURIComponent(document.referrer) == startPage );
        }
      }
      if (bReferrerMatch && startTime) {
        UT.handleEpisodeMessage("UT:mark:backendstarttime:" + startTime);
      }
    }
  }
};

// Set a cookie when the page unloads. Consume this cookie on the next page to get a "start time".
UT.beforeUnload = function(e) {
  document.cookie = "UT=s=" + Number(new Date()) + "&r=" + encodeURIComponent(document.location) + "; path=/";
};

// Wrapper for FF's window.addEventListener and IE's window.attachEvent.
UT.addEventListener = function(sType, callback, bCapture) {
  if ("undefined" != typeof(window.attachEvent)) {
    return window.attachEvent("on" + sType, callback);
  }
  else if (window.addEventListener) {
    return window.addEventListener(sType, callback, bCapture);
  }
};

// Add a domready measurement. This event occurs before all images and other
// referenced binaries have finished loading. It uses the DOMContentLoaded
// event in browsers that support it, and an emulation of that for Internet
// Explorer.
// Shamelessly copied from jQuery.
UT.bindDomReady = function() {
  // Mozilla, Opera and webkit nightlies currently support this event
  if (document.addEventListener) {
    // Use the handy event callback
    document.addEventListener("DOMContentLoaded", function() {
      document.removeEventListener("DOMContentLoaded", arguments.callee, false);
      UT.domIsReady();
    }, false);

    // If IE event model is used
  } else if (document.attachEvent) {
    // ensure firing before onload,
    // maybe late but safe also for iframes
    document.attachEvent("onreadystatechange", function() {
      if (document.readyState === "complete") {
        document.detachEvent("onreadystatechange", arguments.callee);
        UT.domIsReady();
      }
    });

    // If IE and not an iframe
    // continually check to see if the document is ready
    if (document.documentElement.doScroll && window == window.top) (function() {
      if (UT.domready) return;

      try {
        // If IE is used, use the trick by Diego Perini
        // http://javascript.nwbox.com/IEContentLoaded/
        document.documentElement.doScroll("left");
      } catch(error) {
        setTimeout(arguments.callee, 0);
        return;
      }

      // and execute any waiting functions
      UT.domIsReady();
    })();
  }

  // A fallback to window.onload, that will always work
  UT.addEventListener("load", function() {
    UT.domIsReady();
  }, false);
};

UT.domIsReady = function() {
  if (!UT.domready) {
    UT.domready = true;
    UT.handleEpisodeMessage("UT:measure:domready:firstbyte");
  }
};

UT.headDone = function(){
  UT.handleEpisodeMessage("UT:measure:headdone:firstbyte");
};


// Initialize everything (init is defined at top)
UT.init();

