/*the following line is used for checking source against javascript lint*/
/*global window, document, encodeURIComponent, Image, event, self, HTMLDocument */

if (!window.Msn) { window.Msn = {}; } // Future proof in case the framework is released.

Msn.Gtracking = new function() {
  var elC = null; // the ctag DOM element
  var my = this; // save a self-reference
  var i = null; // image object we'll use to make tracking request
  
  // shortcuts
  var d = document;
  var w = window;
  
  // debug flag
  // override with window.gTrackDebug upon initialization
  // or call the public SetDebug method
  var dbg = false;

  // tracking url base
  // override with <meta name="g-link" content="{delay};{url}" />
  var tb = "http://g.msn.com/_0USHP/22";
  
  // default dl for set timeout (ms)
  // override with <meta name="g-link" content="{delay};{url}" />
  var dl = 1000;  
  
  // exluded domains
  // override with window.gTrackExclude upon initialization
  var ex = [ "g.msn.com" ];
  
  if (w.gTrackExclude) {
    ex = w.gTrackExclude;     
  }
  
  if ( w.gTrackDebug ) {
    // force true/false
    dbg = (w.gTrackDebug !== false);
  }
  
  // url format regular expression (rfc-2396)
  // ar[1] = scheme:
  // ar[2] = scheme
  // ar[3] = //server
  // ar[4] = server
  // ar[5] = path
  // ar[6] = ?params
  // ar[7] = params
  // ar[8] = #fragment
  // ar[9] = fragment
  var reUrl = /^(([^:\/?#]+)\:)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/;
    
  var ub = null;                // the value of the href attribute of the base tag when present
  var di = '';                  // domain id, pulled from page's ctag src attribute
  var pi = '';                  // logical property, pulled from page's ctag src attribute
  var ps = '';                  // property specific, pulled from page's ctag src attribute
  var tp = '';                  // tp, pulled from page's ctag src attribute
  var dst = null;               // destination url: the href of the link

  var tid = null;               // Active Timer

  var tw = self; // Window that should be navigated
  
  // if an element comes through with this ID, we won't OOB it
  // because it's part of our work-around for IE not sending a
  // referrer when setting the window.location value to the dest url.
  var nt = "gt_no_oob";
    
  function track() {
    try {
      init();
      
      // be sure NOT to clear out any of the private fields that
      // we use to keep state while waiting for the tracking image to load.
      // if this is a non-link click, we still want any link clicks in the
      // pipeline to finish uninterrupted.
      var tgt = null; // target url
      var hl = null;  //headline: the text of the link or the alt text in case of image
      var cm = '';  //content module: the container of the link, including identification and context in tree
      var ce = -1;  //content element: 1-based ordinal position of link within parent (0 means error, negative means not set yet)
      var gt1 = '';  //campaign id: optional value stored in dest url
      
      var tn = null; // target node -- the node that will navigate
      
      // loop from the source element up to the body
      var el = event.srcElement;
      if ( el.id != nt ) {
        while (el && el.tagName != "BODY") {
          switch (el.tagName) {
            case "A":
            case "AREA":
              if ( !tn ) {
                // clear the timeout now in case the user is clicking links
                // really fast. We only want to activate the last one
                i = null;
                w.clearTimeout(tid);
                
                // this is the node that will navigate us
                tn = el;

                // get the destination url for the link
                tgt = getDst(el.getAttribute("href"));
                // if it's null, we don't want to track it
                if ( !tgt ) {
                  return;
                }
                
                // if we haven't set the headline yet, do it now
                if (!hl) {
                  hl = el.innerText;
                  if ( !hl ) {
                    hl = el.getAttribute("alt");
                  }
                }
                
                // get the optional campaign id from the
                // destination query string (if any).
                // REVIEW: when we move to the new platform, we should
                // pull this from the REF attribute of the anchor
                gt1 = getGT1( tgt );
                
                // assume that we will be navigating our own window
                tw = self;
                
                // shift+click opens new browser or tab
                // ctrl+click opens new tab in mozilla
                if ( event.shiftKey ||
                  (event.ctrlKey && 
                  // the next tests are taken from Msn.Browser.IsMozilla.
                  // I don't include msnmoz.js for known IE browsers, so I can't
                  // rely on that method being available
                  typeof d.implementation != 'undefined' && 
                  typeof d.implementation.createDocument != 'undefined' && 
                  typeof HTMLDocument!='undefined') ) {
                  tw = null;
                }
                
                // determine if this is a targeted link
                var sTarget = el.getAttribute("target");
                if (tw && sTarget) {
                  // we are a targeted link. If the target of this link would
                  // unload our page, we will need to cancel the default action
                  // until AFTER we have returned from our logging, at which point
                  // we will then navigate the window. If our target is not in our
                  // parent chain, then we can let the link proceed while we log it,
                  // since it won't affect our page.
                  while( tw && sTarget != tw.name ) {
                    tw = ( tw === tw.parent ? null : tw.parent);
                  }
                }
              }
              break;
            case "IMG":
              if ( !hl ) {
                hl = el.getAttribute("alt");
              }
              break;
          }
          
          if (el.id) {
            // if we have a target node but the ce hasn't been set yet
            if ( tn && ce < 0 ) {
              // calculate the navigable content element index of tn under el
              ce = getCe( el, tn );
            }

            // prepend the id to the chain (if any)
            cm = ">" + el.id + cm;
          }
          
          // next parent in the DOM
          el = el.parentElement;
        }
        
        //no destination found, what's the point in tracking?
        if ( tgt && event.returnValue !== false ) { 
          // save the target url in the closure dst value
          dst = tgt;
          
          //remove leading '>' from cm (if any)
          if (cm.length > 0) {
            cm = cm.substring(1);
          }
          else {
            // no ids? must be on the <body> element now.
            // if no id, use a default value
            cm = (!el || !el.id ? "html" : el.id);

            // calculate the ce
            ce = getCe( el, tn );
          }
          
          // build the tracking url
          // the parameters are the destination url,
          // followed by a double-ampersand and the g-parameters.
          // munge the base now instead of when we grab the base
          // because what we add depends on the debug mode now.
          // DO NOT URL-ENCODE the DESTINATION URL. iDSS needs it raw.
          var tu = mb(tb) + 
                  "?" + dst +
                  "&&ps=" + ps + 
                  "&pi=" + pi + 
                  "&di=" + di + 
                  "&ce=" + (ce > 0 ? ce : 0) +
                  "&gt1=" + gt1 +
                  "&cm=" + encodeURIComponent(cm) +
                  "&hl=" + encodeURIComponent(hl) + 
                  "&su=" + tp;  // the target page on the ctag is the source page for the glink

          if ( dbg ) {
            w.alert( "Tracking: " + pu(tu) );
          }
          
          // create a new image object that 
          // we will use to log the event
          i = new Image();
          
          // if we have a target window, we will unload our page if we
          // let the default action continue, so we have to interrupt it
          // and wait for the tracking to finish, error, or timeout
          if ( tw ) {
            // we have a target window. That means if we let the default
            // action continue, then we will cause our page to unload.
            // instead we will cancel the default, then navigate the target
            // window when the logging finishes (or times-out).
            event.returnValue = false;
            i.onload = iOL;
            i.onerror = iOE;
            tid = w.setTimeout(iTO, dl);
          }
          
          // kick off the track request
          i.src = tu;
        }
      }
    }
    catch(e) {
      // only alert if debug flag is set; otherwise silent
      if ( dbg ) {
        w.alert("Error: " + e.name + "\n" + e.message);
      }
    }
  }
  
  function iOL() {
    // image is loaded
    // this points to the img, since it fired the event
    // only navigate if this is the current image,
    if ( i && this === i ) {
      doNav();
    }
  }
  
  function iOE() {
    // error loading the img
    // this points to the img, since it fired the event
    // only navigate if this is the current image,
    if ( i && this === i ) {
      doNav( "Track Error" );
    }
  }
  
  function iTO() {
    // timeout; navigate.
    // this points to the window, since it fired the timeout event
    i = null; // clear the image object
    doNav( "Track Timeout" );
  }

  function doNav( msg ) {
    w.clearTimeout( tid );
    if ( tw ) {
      if ( dbg ) {
        // debug: just show where we WOULD be navigating
        w.alert( (msg?msg + '\n':'') + "Navigate: " + pu(dst) );
      }
      else if ( document.all ) {
        // navigate the target window
        try {
          // we want to set the href of an anchor with a special id
          // to our target, then fire the click method. If the element
          // already exists, we'll just use it.
          var a = tw.document.getElementById(nt);
          if ( !a ) {
            // otherwise we need to create a new element
            a = tw.document.createElement("a"); 
            // with the appropriate id
            a.id = nt;
            // and add it to the end of the document
            tw.document.body.appendChild(a);
          }
          else if ( a.removeAttribute ) {
            // make SURE there's no target attribute
            a.removeAttribute( "target" );
          }
          // set the destination url and fire the click.
          // because it has the "special" id, the OOB script
          // won't go into infinite recursion. 
          // IE will send the appropriate referrer header.
          a.href = dst;
          a.click();
        }
        catch(e) {
          // just set the location. this is the last resort because
          // IE won't send a referrer when we do this -- but at least
          // the user will get to where they want to go.
          tw.location = dst;
        }
      }
      else {
        // just set the location. most browsers (non-IE) will correctly
        // send the referrer anyway
        tw.location = dst;
      }
    }
  }
  
  // this function takes the base glink url and makes sure
  // the namespace is prepended with an underscore (if it isn't already)
  // so the g-server doesn't redirect when we call them
  function mb(url) {
    // this regular expression will grab the second-to-last group
    // in the url path. So http://server/namespace/linkid will
    // grab the namespace portion
    var re = /([^\/]+)\/[^\/\?]+\/?(\?.*)?$/;
    var arr = re.exec( url );
    // if we matched and the namespace portion doesn't already
    // begin with an underscore...
    if ( arr ) {
      var nm = arr[1], fc = nm.charAt(0);
      if ( dbg && nm.substring(0,3) != "_1_" ) {
        // add the _1_ in if no underscore present already,
        // or just _1 if it already starts with and underscore
        url = url.replace( nm, (fc=='_' ? '_1' : '_1_')+nm );
      }
      else if ( fc != "_" ) {
        // add the underscore in
        url = url.replace( nm, '_'+nm );
      }
    }
    return url;
  }
  
  // returns the numeric portion of any GT1 parameter
  // on the passed URL (or an empty string if none present)
  function getGT1(u) {
    var re = /[\?\&]GT1=(\d+)/i;
    var ar = re.exec( u );
    return (ar ? ar[1] : '');
  }
  
  // break a url into a "pretty" string with line-breaks
  // so Firefox can display it in a readable manner
  function pu( u ) {
    var s = '';
    // ar[1] = scheme:
    // ar[2] = scheme
    // ar[3] = //server
    // ar[4] = server
    // ar[5] = path
    // ar[6] = ?params
    // ar[7] = params
    // ar[8] = #fragment
    // ar[9] = fragment
    var ar = reUrl.exec( u );
    if ( !ar ) {
      // can't break it up -- just return what we've got
      s = u;
    }
    else if ( !ar[7] ) {
      // no params; optional fragment
      s = ar[1] + ar[3] + ar[5] + (ar[8] ? ar[8] : '');
    }
    else {
      // break params onto separate lines
      s = ar[1] + ar[3] + ar[5] +
        '\n\t?' + ar[7].replace( /\&/g, '\n\t&' ) +
        (ar[8] ? '\n\t' + ar[8] : '');
    }
    return s;
  }

  //function to prepend base href when present and relevant for the url passed
  function getDst(su) {
    var u = null;
    // ar[1] = scheme:
    // ar[2] = scheme
    // ar[3] = //server
    // ar[4] = server
    // ar[5] = path
    // ar[6] = ?params
    // ar[7] = params
    // ar[8] = #fragment
    // ar[9] = fragment
    var ar = reUrl.exec( su );
    if ( !ar ) {
      // if we can't parse it for some strange reason, 
      // just return what we were given
      u = su;
    }
    else if ( (ar[4] && noLog(ar[4].toLowerCase())) || 
      (ar[2] && ar[2].toLowerCase() == "javascript") ) {
      // we have a server and it's on the exclude list,
      // or we have a scheme and it's javascript, then
      // we want to not log this click -- return null
      u = null;
    }
    else if ( !ub || ar[2] ) {
        // if we don't have a base url, it doesn't matter if
        // this is relative or not. Just return what we got.
        // or if there is a scheme, we must be absolute.
        u = su;
    }
    else {
      // we have a base, but no scheme, then must be relative.
      // return our base + the url
      u = ub + su;
    }
    return u;
  }
  
  function noLog( s ) {
    // walk through our domain exclusion list (if any) and
    // return true if the domain passed in is in the list,
    // indicating that we don't want to log this click.
    if ( ex && ex.length ) {
      // check each exclude item against the domain
      for(var n=0; n < ex.length; ++n) {
        // if we have a match, we want to exclude this domain
        if ( s === ex[n] ) {
          return true;
        }
      }
    }
    return false;
  }

  // itemizes all <a> and <area> elements under rt until it finds tn,
  // then returns the index of tn within that set
  function getCe(rt, tn) {
    // if we have no root, it's an error
    if ( !rt ) {
      return 0;
    }
    if ( rt == tn ) {
      // this element itself has an id -- so we are by definition
      // the first element within the CM ended by this id
      return 1;
    }
    // the recursive df function will return the positive non-zero index 
    // of the target node under rt. If it can't find tn, it will return a
    // number less than 1.
    var ce = df( rt, tn );
    
    // if df returned a negative number, then
    // we didn't find the node -- which is an error (return 0)
    return (ce > 0 ? ce : 0);
  }
  
  // recursive depth-first tree search function. 
  // we're going to keep a running count of navigable elements (<a> or <area>) by
  // starting with -1 and counting DOWN.
  // cn = current node; tn = target node; x = current index (negative)
  function df( cn, tn, x ) {
    // if x is less than zero: negative of the running count of navigable elements (<a> and <area>)
    // if x is greater than zero: we found the element, pop out of our recursive run
    // if x is zero, null, or undefined: first call; set x to -1
    if ( !x ) {
      x = -1;
    }
    
    // loop through each child node of cn.
    // if x is greater than zero, then we found the target; pop out and unwind the stack
    for(var i = 0; x < 0 && i < cn.childNodes.length; ++i) {
      var c = cn.childNodes[i];
      if ( c.nodeType == 1 ) {
        // this node is an element. If it's our target element,
        // then we need to return the opposite of the current index, which is
        // negative. Therefore we'll return a positive number and unwind the stack
        if ( c == tn ) {
          return -x;
        }
        
        // this isn't the target node. But if this node has an id, then we
        // don't want to recurse down into it, because items below it will
        // have their own cm/ce combinations
        if ( !c.id ) {
          // if this element is an <a> or an <area> element
          // (and thereby navigable), we want to increment our
          // counter
          switch( c.tagName ) {
            case 'A':
            case 'AREA':
              // don't count <a> or <area> with no href attribute -- they aren't navigable items.
              // also skip javascript: urls
              var href = c.getAttribute("href");
              if ( href && href.indexOf("javascript:") !== 0 ) {
                // we're counting DOWN
                --x;
              }
              break;
          }
          // recurse through the child depth-first, passing 
          // our current index
          x = df( c, tn, x );
        }
      }
    }
    return x;
  }

  //returns the querystring value from the (p)ath for the (t)erm provided
  function qsVal(p, t) {
    var re = new RegExp("[?&]" + t + "=([^&]+)", "i");
    var ar = re.exec(p);
    return (ar ? ar[1] : '');
  }

  // sets global tracking variables from querystring used in page's c tag
  function init() {
    if ( elC === null ) {
      // get the c-tag, if any
      elC = d.getElementById("ctag");
      if (elC) {
        var s = elC.getAttribute("src");
        di = qsVal( s, 'di' );
        pi = qsVal( s, 'pi' );
        ps = qsVal( s, 'ps' );
        tp = qsVal( s, 'tp' );
      }
      else {
        // assign a non-null value so the init block won't
        // run for every single click
        elC = '';
      }
      
      // see if there's a g-link meta element
      var ms = d.getElementsByTagName("META");
      for(var i=0; i < ms.length; ++i) {
        var nm = ms[i].getAttribute("name");
        if ( nm && nm.toLowerCase() == "g-link" ) {
          // the track base is the content attribute of the meta element
          var c = ms[i].getAttribute("content");
          if ( c ) {
            // the format should be {delay};{url}. 
            // Either one may be missing, but if there is no delay,
            // the string should start with the semicolon
            var ca = c.split(';');
            if ( ca[0] ) {
              var y = parseInt(ca[0]);
              if ( !isNaN(y) ) {
                dl = y;
              }
            }
            if ( ca[1] ) {
              tb = ca[1];
            }
          }
          break;
        }
      }
      
      // get all the <base> element(s) in our doc (if any)
      var elBase = d.getElementsByTagName("BASE");
      // if there's any, there should be only one
      if (elBase.length == 1) {
        ub = elBase[0].getAttribute("href");
      }
    }
  }
  
  this.SetDebug = function( flag ) {
    // force true/false
    dbg = (flag !== false);
  };
  
  this.oncreate = function() {
    if ( d.attachEvent ) {
      d.attachEvent("onclick",track);
      w.attachEvent("onunload", my.ondestroy);
    }
  };

  this.ondestroy = function() {
    if ( d.detachEvent ) {
      w.detachEvent("onunload",my.ondestroy);
      d.detachEvent("onclick",track);
    }
  };
  
  this.oncreate();
  
  return this;
};


