(function($){

  //Usage:
  //  $.gaTracker("UA-xxxx-x");                                           -->  To install GA tracking for the page and for hyperlinks, using default options.
  //  $.gaTracker("UA-xxxx-x", { options });                              -->  To install GA tracking for the page and for hyperlinks, using custom options.
  //  $.gaTracker({ code: "UA-xxxx-x", options });                        -->  To install GA tracking for the page and for hyperlinks, using custom options.
  //  $.gaTracker("http://example.com", { code: "UA-xxxx-x", options });  -->  Track a single url now, but the GA code must be in the options.
  //  $.gaTracker("http://example.com");                                  -->  Track a single url now, but options must have been passed in a former call.

  $.gaTracker = function() {

    var defaultOptions = {
      code: code,              //Your UA-tracker code.
      external: "/external/",  //The virtual path for outbound links to pass to Google Analytics. Does not actually have to exist.
      mailto: "/mailtos/",     //The virtual path for email links to pass to Google Analytics. Does not actually have to exist.
      download: "/downloads/", //The virtual path for download links to pass to Google Analytics. Does not actually have to exist.
      extensions: "",          //These extensions are file downloads. May be passed as an array or a whitespace/comma/bar-delimited string. Or even a function.
      domains: []              //These domains are also inbound. Can also be a functions that returns an array.
    };

    var defaultext = "pdf xps azw opf odt ods odp doc docx ppt pptx xls xlsx zip 7z gz rar exe torrent chm vcf kml jpg jpeg png gif svg".split(" "),
        slicer = /^(?:(?:([^:\/]+):)(?:\/*)([^:\/\?]+)(?::([0-9]+))?)?\/?([^\?]*)(?:\?([^#]*))?(?:#(.*))?$/i,
        gaqualifier = /^UA-\d+-\d+$/i,
        host = document.location.host.toLowerCase().replace(/^www\./, "");

    var log = function(e) { 
      if (typeof window.console == "object" && $.isFunction(window.console.log)) {
        console.log(e);
      }
    };
    
    //This bit is to ensure the validity and order of supplied arguments.
    var error = false, code = null, trackurl = null, options = null;
    (function(a) {
      var findcode = function() {
        error = !gaqualifier.test(options.code);
        if (!error) {
          code = options.code;
        }
        else if ($.gaTracker.options) {
          error = !gaqualifier.test($.gaTracker.options.code);
          if (!error) {
            code = options.code = $.gaTracker.options.code;
          }
        }
      };

      if (typeof a[0] == "string") {
        if (gaqualifier.test(a[0])) {
          //Usage 1 or 2
          code = a[0];
          error = !gaqualifier.test(code);
        }
        else {
          //Usage 4 or 5
          trackurl = a[0];
        }
      }
      else if (typeof a[0] == "object") {
        //Usage 3
        error = !gaqualifier.test(a[0].code);
        if (error) {
          if ($.gaTracker.options && gaqualifier.test($.gaTracker.options.code)) {
            error = false;
            code = a[0].code = $.gaTracker.options.code;
          }
        }
        code = a[0].code;
        options = a[0];
        return;
      }
      if (typeof a[1] == "object") {
        if (code) {
          //Usage 2
          options = a[1];
          if (error) { findcode(); }
        }
        else if (trackurl) {
          //Usage 4
          options = a[1];
          findcode();
        }
      }
    })(arguments);
    
    if (error) { 
      log("gaTracker failed: cannot interpret options");
      return;
    }
    
    var fixExtensions = function(e) {
      if (typeof e == "string") {
        return e.split(/\s+|,|\|/).concat(defaultext);
      }
      if ($.isFunction(e)) {
        return $.makeArray(e(defaultext));
      }
      if ($.isArray(e)) {
        return e.concat(defaultext);
      }
      return defaultext;
    };

    //See where to pull options from
    if (options === null) {
      if ($.gaTracker.options) {
        options = $.gaTracker.options;
      }
      else {
        options = defaultOptions;
        options.extensions = fixExtensions(options.extensions);
        $.gaTracker.options = options;
      }
    }
    else {
      options = $.extend(defaultOptions, options);
      
      options.domains = $.map($.isFunction(options.domains) ? options.domains() : options.domains, function(domain) {
        return domain.toLowerCase().replace(/^www\./, "");
      });
      options.domains.push(host);
      options.extensions = fixExtensions(options.extensions);
      $.gaTracker.options = options;
    }

    //A function to split a url into its constituent parts.
    var splitUrl = function(url) {
      var parts = slicer.exec(String(url));
      if (parts) {
        parts[1] = typeof parts[1] != "undefined" ? parts[1].toLowerCase() : undefined;
        parts[2] = typeof parts[2] != "undefined" ? parts[2].toLowerCase().replace(/^www\./, "") : undefined;
        parts[3] = Number(parts[3]);

        //The result object:
        var result = {
          protocol: parts[1],  //Protocol, e.g. "http", "mailto", etc. Current protocol if relative.
          host: parts[2],      //Domain excluding "www" part, or current if relative. Email-address will be here (entirely) in case of a mailto url.
          port: parts[3],      //Port, or current if relative.
          path: parts[4],      //Path, e.g. "path/to/file.php"
          query: parts[5],     //Querystring, e.g. "first=John&last=Doe"
          hash: parts[6],      //Hash (or anchor, if you want to call it that).
          relative: false      //True if relative, false if absolute.
        };
        
        var proto = document.location.protocol.replace(/:$/, ""), port = Number(document.location.port);
        result.relative = ((!result.protocol && !result.host && isNaN(result.port)) || (result.protocol == proto && result.host == host && (result.port || 80) == (port || 80)));

        if (result.relative) {
          result.protocol = proto;
          result.host = host;
          result.port = port;
        }
        else {
          result.protocol = result.protocol || "http";
          result.host = result.host || host;
          result.port = result.port || 80;
        }
        return result;
      }
      return null;
    };

    // Returns the given URL prefixed if it is:
    //    a) a link to an external site
    //    b) a mailto link
    //    c) a downloadable file
    // ...otherwise returns an empty string.
    var decorateLink = function(url){
      var trackingURL = "", parts = typeof url == "string" ? splitUrl(url) : null;

      if (parts === null) {
        return "";
      }
      if (parts.relative) {
        //Relative url - check extension
        var ext = (function(a) { return a[a.length - 1]; })(parts.path.split(".")).toLowerCase();
        if ($.inArray(ext, options.extensions) >= 0) {
          return options.download + parts.path + (parts.query ? "?" + parts.query : "") + (parts.hash ? "#" + parts.hash : "");
        }
      }
      if (parts.protocol == "mailto") {
        //Mailto link
        return options.mailto + url.substr(7);
      }
      //Check the protocol for a "browsable" destination, e.g. no file, nntp, telnet, gopher, etc.
      if (!parts.relative && $.inArray(parts.protocol, ["http", "https", "ftp", "sftp", "ftps"]) >= 0) {
        //Check if top domain isn't in the URL to be clicked
        if (parts.host != host && $.inArray(parts.host, options.domains) == -1) {
          //External link
          return options.external + url;
        }
      }

      return "";
    }

    if (trackurl) {
      if (window._gat) {
        if (!$.pageTracker) {
          $.pageTracker = _gat._getTracker(code);
          $.pageTracker._initData();
        }
        $.pageTracker._trackPageview(trackurl);
      }
    }
    else {
      //Include the external GA script in try/catch to play nice
      try {
        $.ajax({
          type: "GET",
          url: (location.protocol.toLowerCase() == "https:" ? "https://ssl" : "http://www") + ".google-analytics.com/ga.js",
          dataType: "script",
          cache: true,
          success: function() {
          
            try {
              $.pageTracker = _gat._getTracker(code);
              $.pageTracker._initData();
              $.pageTracker._trackPageview();

              $("a:not([href^=#])")
                .click(function() {
                  //Assign a click handler to each remaining link
                  var u = decorateLink($(this).attr("href") || "");
                  if (u.length > 0) {
                    $.pageTracker._trackPageview(newLink);
                  }
                });
            }
            catch(err) {
              // log any failure
              log("Failed to apply Google Analytics: " + err);
            }    
            
          }
        });
      }
      catch(err) {
        // log any failure
        log("Failed to load Google Analytics: " + err);
      }
    }
  }
})(jQuery);
