function BrowserCap() {
  // An experiment in site usability/user behavior studies
  // Inspired by yugop.surface.com
  var self = this;
  this.mouse = null; // mouse
  this.history = null;
  this.timer = new Date().getTime(); // record time of initialization (for reference)
  this.submitted = 0;

  this.History = function(x,y) {
    var h = this;
    this.resolution = 16; // "window" in which to record 1 event
    this.frames = 0;
    this.next = 0;
    this.prev = -1; // "previous"
    this.timeStart = new Date().getTime();
    this.timeNow = null;
    // x += document.body.scrollTop; // include any scrolling
    this.x = [x];
    this.y = [y];
    this.time = [20]; // time of each event
    this.click = []; // array of values (event number) when click was registered
    this.clickType = []; // left/right, scroll wheel etc.

    this.addEvent = function(x,y,button,e) {
      // window.status = Math.abs(this.x[this.next]-x) + ' || ' + Math.abs(this.y[this.next]-y) + ' > ' + this.resolution);
      offX = (document.documentElement.scrollLeft||window.scrollX||document.body.scrollLeft);
      offY = (document.documentElement.scrollTop||window.scrollY||document.body.scrollTop);
      if (!isNaN(offX)) x += offX;
      if (!isNaN(offY)) y += offY;
      if ((e.type=='mouseup' || e.type=='mousedown') || (Math.abs(h.x[h.next]-x)>h.resolution || Math.abs(h.y[h.next]-y)>h.resolution)) {
        if (e.type=='mouseup') { // ensure button resets to 0 on mouseup
          button = 0;
        } else if (e.type=='mousedown' && !button) { // Mozilla may use 0 when button down
          button = 1;
        }
        h.next++;
        h.prev++;
        h.frames++;
        h.x[h.next] = x; // +document.body.scrollTop;// -h.x[h.prev]; // relative to previous value, including Y scrolling
        h.y[h.next] = y; // -h.y[h.prev];

        h.timeNow = new Date().getTime();
        h.time[h.next] = h.timeNow-h.timeStart; // (new Date().getTime())-h.time[0]-h.time[h.prev];
	
        h.timeStart = new Date().getTime();
        if (!isNaN(button) && button) {
          h.click[h.click.length] = h.next;
          h.clickType[h.clickType.length] = button;
          target = e.target||e.srcElement;
          if (target) {
            detail = target.nodeName;
            if (target.href) detail += ' href: '+target.href;
            if (target.id) detail += ' id: '+target.id;
            if (target.src) detail += ' src: '+target.src;
            // if target.onclick?
            h.clickType[h.clickType.length-1] += ' ('+detail+')';
            
          }
        }
        if (this.frames>=1024) {
          self.stopRecord();
          self.sendXML();
        }
      } else {
        return false;
      }
    }
  }
  
  this.addEventHandler = null;
  this.removeEventHandler = null;
  
  this.addEventHandlerDOM = function(o,eventType,eventHandler,eventBubble) {
    o.addEventListener(eventType,eventHandler,eventBubble);
  }

  this.removeEventHandlerDOM = function(o,eventType,eventHandler,eventBubble) {
    o.removeEventListener(eventType,eventHandler,eventBubble);
  }
  
  this.addEventHandlerIE = function(o,eventType,eventHandler) { // IE workaround
    if (!eventType.indexOf('on')+1) eventType = 'on'+eventType;
    o.attachEvent(eventType,eventHandler); // Note addition of "on" to event type
  }
  
  this.removeEventHandlerIE = function(o,eventType,eventHandler) {
    if (!eventType.indexOf('on')+1) eventType = 'on'+eventType;
    o.detachEvent(eventType,eventHandler);
  }
  
  if (document.addEventListener) {
    // DOM event handler method
    this.addEventHandler = this.addEventHandlerDOM;
    this.removeEventHandler = this.removeEventHandlerDOM;
  } else if (document.attachEvent) {
    // IE event handler method
    this.addEventHandler = this.addEventHandlerIE;
    this.removeEventHandler = this.removeEventHandlerIE;
  } else {
    // not supported
    this.addEventHandler = function() {}
  }
  
  this.getMouse = function(e) {
    // grab mouse from event
    // 'this' within the context of element that event was attached to - eg. 'document' object
    e = e||event;
    self.mouse = e;
    // detach event handler
    self.removeEventHandler(document,'mousemove',self.getMouse,false);
    self.history = new self.History(self.mouse.clientX,self.mouse.clientY);
    setTimeout('browserCap.startRecord()',20);
  }
  
  this.startRecord = function() {
    this.addEventHandler(document,'mousemove',this.recordEvent,false);
    this.addEventHandler(document,'mousedown',this.recordEvent,false);
    this.addEventHandler(document,'mouseup',this.recordEvent,false);
  }
  
  this.stopRecord = function() {
    this.removeEventHandler(document,'mousemove',this.recordEvent,false);
    this.removeEventHandler(document,'mousedown',this.recordEvent,false);
    this.removeEventHandler(document,'mouseup',this.recordEvent,false);
  }

  this.disable = function() {
    this.removeEventHandler(window,(document.all?'beforeunload':'unload'),this.sendOnExit,false);
  }

  this.recordEvent = function(e) {
    // pass data to History object
    self.history.addEvent(self.mouse.clientX,self.mouse.clientY,self.mouse.button,e||event);
  }

  this.sendXML = function(onExit) {
    if (!this.submitted && self.history) {
      this.submitted = 1;
    } else {
      return false;
    }

    function addNode(name,data) {
      return '<'+name+'>'+(data||'null')+'</'+name+'>\n';
    }

    var bcHistory = self.history;
    var xmlhttp = null;
    var xmlDoc = '<?xml version="1.0" encoding="UTF-8"?>\n\n';

    xmlDoc += '<browserCap>\n';
    // xmlDoc += addNode('date',new Date().getTime());
    xmlDoc += addNode('referrer',document.referrer);
    xmlDoc += addNode('frames',bcHistory.frames);
    xmlDoc += addNode('screenx',(document.documentElement.clientWidth||document.body.clientWidth||window.innerWidth));
    xmlDoc += addNode('screeny',(document.documentElement.clientHeight||document.body.clientHeight||window.innerHeight));
    xmlDoc += addNode('x',bcHistory.x.join(','));
    xmlDoc += addNode('y',bcHistory.y.join(','));
    xmlDoc += addNode('click',bcHistory.click.join(','));
    xmlDoc += addNode('clickType',bcHistory.clickType.join(','));
    xmlDoc += addNode('time',bcHistory.time.join(','));
    xmlDoc += '</browserCap>';

    try {
      xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
    } catch(e) {
      try {
        xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
      } catch(E) {
        xmlhttp = null;
      }
    }
    if (!xmlhttp) {
      try {
        xmlhttp = new XMLHttpRequest();
        xmlhttp.overrideMimeType("text/xml"); // So mozilla doesn't crash
      } catch(e) {
        xmlhttp = false;
      }
    }
    if (xmlhttp) {
      try {
        xmlhttp.open('POST','/interactive/browsercap/browsercap.php?r='+parseInt(Math.random()*1078576),(onExit?false:true));
        xmlhttp.onreadystatechange = function() {
          if (xmlhttp.readyState == 4) {
            // submit successful (xmlhttp.responseText)
          }
        }
        xmlhttp.setRequestHeader('Content-Type', 'text/xml');
        xmlhttp.send(xmlDoc);
      } catch(e) {
        // d'oh!
      }
    }
  }

  this.sendOnExit = function() {
    self.sendXML(true);
  }

  // initial listen for mouse grab and other events
  this.addEventHandler(document,'mousemove',this.getMouse,false);
  this.addEventHandler(window,(document.all?'beforeunload':'unload'),this.sendOnExit,false);

}

var browserCap = new BrowserCap();