// ZFC JavaScript library

function ZFC(pageTime, upstreamLabel, upstreamHost, upstreamURI)
{
  // Mark the time of construction
  this.startTime = (new Date).getTime();

  // Store the time at which the page was generated
  this.pageTime = pageTime;

  // store the upstream label
  this.upstreamLabel = upstreamLabel;

  // store the upstream Host header
  this.upstreamHost = upstreamHost;

  // Store the upstream URI
  this.upstreamURI = upstreamURI;

  // Method to serialize a 32-bit unsigned int in protobuf varint format
  this.writeVarint = function(s, value)
  {
    do { 
      if (value >= 0x80) {
        s += String.fromCharCode((value & 0x7f) | 0x80);
      } else {
        s += String.fromCharCode(value);
      }
    } while ((value >>>= 7));
    return s;
  }

  // Method to serialize a protobuf field in varint format
  this.writeVarintField = function(s, field, value)
  {
    s = this.writeVarint(s, field << 3);
    s = this.writeVarint(s, value);
    return s;
  }

  // Method to serialize a protobuf field in binary format
  this.writeBinaryField = function(s, field, value)
  {
    s = this.writeVarint(s, field << 3 | 2);
    s = this.writeVarint(s, value.length);
    s += value;
    return s;
  }

  // Method to serialize a date (represented as milliseconds since Jan
  // 1, 1970) as a pair of varints with given field numbers.  Sadly,
  // we can't just serialize it as a uint64, since JavaScript's bit
  // operators truncate the operands to 32 bits.
  this.writeDTimeFields = function(s, field1, field2, date)
  {
    var sval = '' + date;
    var time = sval.substring(0, sval.length - 3);
    var msec = sval.substring(sval.length - 3, sval.length);
    s = this.writeVarintField(s, field1, time);
    s = this.writeVarintField(s, field2, msec);
    return s;
  }

  // Method to base64 encode a string
  this.base64Encode = function(s)
  {
    var e ="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
    var c0, c1, c2;

    var len = s.length;
    var pos = 0;
    var enc = '';

    while (len > 2) {
      c0 = s.charCodeAt(pos++);
      c1 = s.charCodeAt(pos++);
      c2 = s.charCodeAt(pos++);
      enc += e.charAt((c0 >>> 2) & 0x3f);
      enc += e.charAt(((c0 & 0x03) << 4) | (c1 >>> 4));
      enc += e.charAt(((c1 & 0x0f) << 2) | (c2 >>> 6));
      enc += e.charAt(c2 & 0x3f);
      len -= 3;
    }

    if (len) {
      c0 = s.charCodeAt(pos++);
      enc += e.charAt((c0 >>> 2) & 0x3f);
      if (len == 1) {
        enc += e.charAt((c0 & 0x03) << 4);
        enc += '=';
      } else {
        c1 = s.charCodeAt(pos++);
        enc += e.charAt(((c0 & 0x03) << 4) | (c1 >>> 4));
        enc += e.charAt((c1 & 0x0f) << 2);
      }
      enc += '=';
    }

    return enc;
  }

  // Method to build the tracking pixel URL
  this.trackerURL = function()
  {
    var msg = '';
    msg = this.writeBinaryField(msg, 1, location.href);
    msg = this.writeBinaryField(msg, 2, this.upstreamLabel);
    msg = this.writeBinaryField(msg, 3, this.upstreamHost);
    msg = this.writeBinaryField(msg, 4, this.upstreamURI);
    msg = this.writeDTimeFields(msg, 5, 6, this.pageTime);
    msg = this.writeDTimeFields(msg, 7, 8, this.startTime);
    msg = this.writeDTimeFields(msg, 9, 10, (new Date).getTime());
    if (screen.width) {
      msg = this.writeVarintField(msg, 11, screen.width);
    }
    if (screen.height) {
      msg = this.writeVarintField(msg, 12, screen.height);
    }
    if (screen.colorDepth) {
      msg = this.writeVarintField(msg, 13, screen.colorDepth);
    }
    if (document.referrer) {
      msg = this.writeBinaryField(msg, 14, document.referrer);
    }
    return msg;
  }

  // Method to fetch the tracking pixel
  this.track = function(root)
  {
    var arg = this.base64Encode(this.trackerURL());
    var url = '<img src="' + root + '?' + arg + '">';
    document.write(url);
  }
}
