/*

  Woot Watcher AJAX code Version 0.1
  Version   0.1 8/17/2006

  Debugging note:
        This program will insert the resulting cache into the page if
        there is a section there to receive it. That section looks like
        this:

        <TEXTAREA id="cache" name="cache" rows="20" cols="80">
          cache response goes here.  Debug only, normally hidden.
        </TEXTAREA>
*/

// This is the version of this script. The web service also has an
// expected version number. If this script version is old, then the
// script will trigger a page reload. This allows us to push a browser
// refresh from the server side when we need to force changes to this
// script.
var scriptVersion=1.78;

// This stores the link to the web counter for the front page.
// You will need to replace this with a counter of your own.
var WWAL_userCounterURL = "";
if (   location.host == "wootoff.pingbot.com"
    || location.host == "www.wootoff.pingbot.com")
  {
    WWAL_userCounterURL = "http://counter.dreamhost.com/cgi-bin/Count.cgi?df=pingbot.WootOff.dat&dd=A&istrip=T&pad=F&ft=6&comma=T&st=";
  }

// This stores the path to the WootOff Cache web service. This *must* be
// on the same server as index.html or you will violate the AJAX
// security model. However, it does not need to be in the same directory
// as I have it here.
//
// We use this to build URLs based on the current form settings.
// We append the current time to keep IE from cacheing the results.
// The requestTime parameter is not actually used by the web service.
var WWAL_publishWebServiceURL = "wootcache.php?extended=1&requestTime=";

// This is the path directly to the stored cache on disk.  We will use this
// some percentage of the time to try to reduce the amount of CPUs we
// normally consume running the PHP web service.  It should still end in
// "?requestTime=" because we will add the current time to force the txt
// file not to be cached by the browser, which would be a killer.
var WWAL_publishDirectURL = "cache_extended.txt?requestTime=";

// Percentage of the time to run the web service over the direct cache.
// This is a number between 1 and 100 and should be as small as you can
// stand it (think of it as the web service only needs to be hit by x
// percentage of the users every minute). Check callWebService() to see
// how this number is used in the implementation.
var WWAL_percentageOfLiveResults = 3;

// This global is used to store the active XMLHttpRequest object
// Currently we only have and use one of these at a time.  Because of IE
// this object is destroyed and recreated for every request (you cannot use
// the IE XMLHTTP obect more than once reliably).
var WWAL_http_request = false;

// This global stores the current percentage completed.  We use this as a
// clue as to whether things are happening right now.
var WWAL_percentageCompleted = 0;

// This global stores the current age of the cache in seconds.
var WWAL_ageInSeconds = 0;

// This global stores the type of query that we are doing. Currently the
// only request type that is valid is "cache".
var WWAL_requestType = null;

// This global stores the name of the last product seen
var WWAL_lastProduct = null;

// These are the sound groups that the user can choose from. Each one
// defines a group title and four sounds (20 percent, 10 percent, 5
// percent, and new woot). The JavaScript will fill out the drop list on
// the web page from this list.  Note that these are not the names of the MP3
// files, these are the ID names from the sound-config.xml file.
var WWAL_SoundSchemes = new Array(
  new Array("subtle bells", "SubtleBells3",    "SubtleBells2",    "LoudBells2",      "LoudBells4"),
  new Array("spoken",       "Spoken20percent", "Spoken10percent", "Spoken5percent",  "SpokenWootAlarm"),
  new Array("glass",        "LoudGlass3",      "LoudGlass3",      "LoudGlass1",      "LoudGlass2"),
  new Array("loud bells",   "LoudBells5",      "LoudBells6",      "LoudBells3",      "LoudBells4"),
  new Array("loud sounds",  "AnnoyingCar",     "AnnoyingNoise",   "LoudBells1",      "AnnoyingBells"),
  new Array("subtle birds", "SubtleBirdCall4", "SubtleBirdCall3", "SubtleBirdCall1", "SubtleBirdCall2")
);

// This function is from simpler logger by Philip McCarthy, phil@chimpen.com
// It writes the log messages to firebug in FireFox.
function printfire ()
  {
    if (document.createEvent)
      {
        printfire.args = arguments;
        var ev = document.createEvent("Events");
        ev.initEvent("printfire", false, true);
        if (window.dispatchEvent)
          {
            dispatchEvent(ev);
          }
      }
  }

function logMessage (msg)
  {
    printfire("TWAL: " + msg);
  }

function logError (msg)
  {
    printfire("TWAL ERROR: " + msg);
  }

function Set_Simple_Cookie (name, value)
  {
    Set_Cookie(name, value, '', '/', '', '');
  }

function Set_Cookie (name, value, expires, path, domain, secure)
  {
    // set time, it's in milliseconds
    var today = (new Date()).getTime();

    /*
      if the expires variable is set, make the correct expires time, the
      current script below will set it for x number of days, to make it
      for hours, delete * 24, for minutes, delete * 60 * 24
    */
    if (expires)
      {
        expires = expires * 1000 * 60 * 60 * 24;
      }

    var expires_date = new Date(today + (expires));

    document.cookie = name + "=" + escape(value)
                    + (expires ? ";expires=" + expires_date.toGMTString() : "" )
                    + (path    ? ";path=" + path                          : "" )
                    + (domain  ? ";domain=" + domain                      : "" )
                    + (secure  ? ";secure"                                : "" );
  }

// this function gets the cookie, if it exists
function Get_Cookie (name)
  {
    var start = document.cookie.indexOf( name + "=" );
    var len = start + name.length + 1;
    if (   !start
        && name != document.cookie.substring(0, name.length))
      {
        return null;
      }
    if (start == -1)
      return null;

    var end = document.cookie.indexOf(";", len);

    if (end == -1)
      end = document.cookie.length;

    return unescape(document.cookie.substring(len, end));
  }

// this deletes the cookie when called
function Delete_Cookie (name, path, domain)
  {
    if (Get_Cookie(name))
      document.cookie = name + "="
                      + (path   ? ";path=" + path     : "")
                      + (domain ? ";domain=" + domain : "")
                      + ";expires=Thu, 01-Jan-1970 00:00:01 GMT";
  }

// Initialize an XMLHttpRequest().  Creates and initializes the object
// and stores it in the global WWAL_http_request variable.
function makeRequest (requestType, url)
  {
    WWAL_http_request = false;

    // Set the global state.
    WWAL_requestType = requestType;

    if (window.XMLHttpRequest) // Mozilla, Safari,...
      {
        WWAL_http_request = new XMLHttpRequest();
        if (WWAL_http_request.overrideMimeType)
          {
            WWAL_http_request.overrideMimeType('text/plain');
            // See note below about this line
          }
      }
    else if (window.ActiveXObject) // IE
      {
        try
          {
            WWAL_http_request = new ActiveXObject("Msxml2.XMLHTTP");
          }
        catch (e)
          {
            try
              {
                WWAL_http_request = new ActiveXObject("Microsoft.XMLHTTP");
              }
            catch (e)
              {}
          }
      }

    if (!WWAL_http_request)
      {
        logError('Giving up :( Cannot create an XMLHTTP instance');
        return false;
      }

    // Set the cursor to a wait cursor for the whole document while we
    // are processing the request.
    if (document.body && document.body.style)
      document.body.style.cursor = "wait";

    WWAL_http_request.onreadystatechange = alertContents;
    WWAL_http_request.open('GET', url, true);
    WWAL_http_request.send(null);
  }

function displayMessage (message)
  {
    if (message.length > 0)
      {
        errorMessageDiv = document.getElementById("errorMessage");
        errorMessageDiv.innerHTML = message;
      }
  }

function unixTimeToDate (timestamp)
  {
    return(new Date(timestamp * 1000).toLocaleString());
  }

// This function processes the audio cue checkboxes and uses soundManager
// to play any pending sounds.
function processAudioCues (product, percentComplete)
  {
    // Get all of the controlling check boxes on the page
    var enableAudioCues      = document.getElementById("enableAudioCues");
    var CueAt20PercentPlayed = document.getElementById("CueAt20PercentPlayed");
    var CueAt20Percent       = document.getElementById("CueAt20Percent");
    var CueAt10PercentPlayed = document.getElementById("CueAt10PercentPlayed");
    var CueAt10Percent       = document.getElementById("CueAt10Percent");
    var CueAt5PercentPlayed  = document.getElementById("CueAt5PercentPlayed");
    var CueAt5Percent        = document.getElementById("CueAt5Percent");
    //var NewWootCuePlayed     = document.getElementById("NewWootCuePlayed");
    var CueAtNewWoot         = document.getElementById("CueAtNewWoot");
    var audioSchemeList      = document.getElementById("AudioScheme");

    var scheme = audioSchemeList.selectedIndex;

    if (percentComplete > 20)
      {
        // See if the product changed, and don't fire on reload
        if (WWAL_lastProduct != null && product != WWAL_lastProduct)
          {
            // Is CueAtNewWoot checked?
            if (CueAtNewWoot.checked)
              {
                // Is enableAudioCues checked?
                if (enableAudioCues.checked)
                  {
                    // Play New Woot Sound
                    soundManager.play(WWAL_SoundSchemes[scheme][4]);
                  }

                // Enable playing of all timed sounds
                CueAt20PercentPlayed.value = "";
                CueAt10PercentPlayed.value = "";
                CueAt5PercentPlayed.value = "";
              }
          }

        WWAL_lastProduct = product;
        return;
      }

    if (percentComplete <= 5)
      {
        // If CueAt5PercentPlayed is blank then
        if (CueAt5PercentPlayed.value.length == 0)
          {
            // Mark all sounds as played
            CueAt20PercentPlayed.value = "1";
            CueAt10PercentPlayed.value = "1";
            CueAt5PercentPlayed.value = "1";
            // Is enableAudioCues checked?
            if (enableAudioCues.checked && CueAt5Percent.checked)
              {
                // play 5 percent sound
                soundManager.play('Spoken5percent')
              }
          }
      }
    else if (percentComplete <= 10)
      {
        // If CueAt10PercentPlayed is blank then
        if (CueAt10PercentPlayed.value.length == 0)
          {
            // Mark 20 and 10 checkboxes complete,
            CueAt20PercentPlayed.value = "1";
            CueAt10PercentPlayed.value = "1";
            // Is enableAudioCues checked?
            if (enableAudioCues.checked && CueAt10Percent.checked)
              {
                // play 10 percent sound
                soundManager.play('Spoken10percent')
              }
          }
      }
    else if (percentComplete <= 20)
      {
        // If CueAt20PercentPlayed is blank then
        if (CueAt20PercentPlayed.value.length == 0)
          {
            // Mark 20 checkboxes complete,
            CueAt20PercentPlayed.value = "1";
            // Is enableAudioCues checked?
            if (enableAudioCues.checked && CueAt20Percent.checked)
              {
                // play 20 percent sound
                soundManager.play('Spoken20percent')
              }
          }
      }
  }

// This function processes the task state XMl document.
function processCacheResults (cacheText)
  {
    // Sample extended cache version 1.1 (added condition):
    // 0                 1    2                                     3           4                              5        6                                    7                                        8    9  10   11      12 13
    // #BMSG#1155873497||24%||Hitachi LCD Entertainment Projector ||1155873480||/WantOne.aspx?WootSaleId=2395||$499.99||/Forums/ViewPost.aspx?PostID=659018||Hitachi_LCD_Entertainment_Projector_CIW||jpg||2||new||0.4879||0||1.1||

    var cache_array = cacheText.split("||");

    var expectedScriptVersion = cache_array[cache_array.length-2];

    if (expectedScriptVersion > scriptVersion)
      {
        window.location.reload(false);
      }

    // Update the current state on the page
    var productName       = document.getElementById("productName");
    var price             = document.getElementById("price");
    var condition         = document.getElementById("condition");
    var photo             = document.getElementById("photo");
    var purchaseLink      = document.getElementById("purchaseLink");
    var forumLink         = document.getElementById("forumLink");
    var photoLink         = document.getElementById("photoLink");
    var timestring        = document.getElementById("timestring");
    var timestamp         = document.getElementById("timestamp");
    var age               = document.getElementById("age");
    var percentComplete   = document.getElementById("percentComplete");
    var scriptVersionFld  = document.getElementById("scriptVersion");
    var xScriptVersionFld = document.getElementById("xScriptVersion");

    if (cache_array.length == 1)
      {
        productName.innerHTML = "An error occurred in the web service (we will try again in 30 seconds).";
        return;
      }

    purchaseLink.href = "http://www.woot.com" + cache_array[4];
    forumLink.href    = "http://www.woot.com" + cache_array[6];

    photoLink.href    = cache_array[7] + "-standard." + cache_array[8];
    photo.src         = cache_array[7] + "-thumbnail." + cache_array[8];

    condition.innerHTML = cache_array[10];

    productName.innerHTML     = cache_array[2];
    price.innerHTML           = cache_array[5];
    timestring.innerHTML      = unixTimeToDate(cache_array[3]);

    timestamp.value = cache_array[3];

    var ageInSeconds = Math.round(((new Date()).getTime()/1000)-cache_array[3]);

    age.innerHTML             = ageInSeconds;

    WWAL_ageInSeconds = ageInSeconds;

    WWAL_percentageCompleted = parseInt(cache_array[1]);

    percentage = cache_array[1];
    if (percentage == "0%")
      {
        percentage = "SOLD OUT";
        document.title = "SOLD OUT - Woot Off AJAX Client";
      }
    else if (percentage == "-1")
      {
        var progressbar = document.getElementById('progressbar');
        progressbar.style.height = 1;
        progressMeterBar.style.width = "100%";
        progressMeterBar.style.height = 1;
        progressMeterBar.style.background = "black";
        percentage = "<small>One Day,<br> One Deal&trade;</small>";
        document.title = "No Woot Off - Woot Off AJAX Client";
      }
    else
      {
        percentage += " Left";
        document.title = percentage + "- Woot Off AJAX Client";
      }

    percentComplete.innerHTML = percentage;

    var progressMeterBar = document.getElementById('progressMeterBarDone');

    progressMeterBar.style.width = cache_array[1];

    scriptVersionFld.innerHTML = scriptVersion;
    xScriptVersionFld.innerHTML = expectedScriptVersion;

    processAudioCues(productName.innerHTML, WWAL_percentageCompleted);
  }

// This is the Ajax callback function.  This is where the work of
// loading the cache starts.
function alertContents()
  {
    if (WWAL_http_request.readyState == 4)
      {
        // Set the cursor back to auto since we either got a result or
        // an error.
        document.body.style.cursor = "auto";

        if (WWAL_http_request.status == 200)
          {
            // document.write(WWAL_http_request.responseText);
            var cacheText = WWAL_http_request.responseText;

            logMessage("Request Type = " + WWAL_requestType);

            if (WWAL_requestType == "cache")
              {
                processCacheResults(cacheText);
              }
            else
              {
                processCacheResults(cacheText)
              }

            // Debugging code
            cacheField = document.getElementById("cache");
            if (cacheField != null)
              cacheField.value = cacheText;
          }
        else
          {
            logError('There was a problem with the request.');
          }
      }
  }


/*
// This implementation of callWebService uses a complex determination
// using both the current age of the cache and the current percentage
// completed to determine whether to call the server or not.
//
// Remember you can only enable one version of callWebService().
function callWebService ()
  {
    // 100-20% - reload every 30 secs
    // 20-5% - relaod every 10 secs
    // 5-1% - reload every 5 secs
    // no wootoff - reload every 4 hrs

    // checks age of cache, if older than allowed, get new data
    if (WWAL_percentageCompleted == "-1" && ageInSeconds > 240)
      {
        return(true);
      }
    else if (WWAL_percentageCompleted > 20 && ageInSeconds > 30)
      {
        return(true);
      }
    else if (WWAL_percentageCompleted > 5 && ageInSeconds > 10)
      {
        return(true);
      }
    else if (WWAL_percentageCompleted > 0 && ageInSeconds > 5)
      {
        return(true);
      }

    return(false);
  }
*/

// This implementation of callWebService only uses a random value to
// decide when to call the web service. It provides a relatively
// guaranteed amount of pressure on the server (protecting the server).
//
// The temptation here is to call the web service more often if the cache
// gets old, however, if the web server is having problems keeping up
// (as evidenced by the fact that it is not keeping the cache updated
// properly) then calling the web service more often is going to have
// the opposite effect of putting more undue pressure on the web server.
// During a WootOff it is imperative that the server be protected at all
// times.
//
// Remember you can only enable one version of callWebService().
function callWebService ()
  {
    if (WWAL_percentageCompleted == "-1") // No WootOff
      {
        // There is little server pressure when there is no Wootoff, so
        // just call the web service all the time.
        return(true);
      }

    // During a WootOff we want to control the pressure on the server,
    // so 1/10th or even 1/20th of the time (depending on the value of
    // WWAL_percentageOfLiveResults) we call the web service, the other
    // 9/10ths or 19/20ths of the time we pull the cache file directly
    // from the web browser. This should be enough to keep the cache
    // updated while only running the web service 1/10th the time and
    // saving server resources.

    var rnd = Math.floor(Math.random()*100)+1; // returns 1-100

    return(rnd < WWAL_percentageOfLiveResults);
  }

// This implementation calls again in 30 seconds if the woot has just
// started and in 15 seconds if the woot is near completion.  It returns
// milliseconds, so to delay 15 seconds you will return 15,000.
//
// Remember you can only enable one version of calculateTimeout().
function calculateTimeout ()
  {
    if (WWAL_percentageCompleted < 15)
      return(15000);

    return(30000);
  }

/*
// This implementation uses the following table to determine how long to wait:
//
// 100-20% - reload every 30 secs
// 20-5% - relaod every 10 secs
// 5-1% - reload every 5 secs
// no wootoff - reload every 4 hrs
//
// Remember you can only enable one version of calculateTimeout().
function calculateTimeout ()
  {
    if (WWAL_percentageCompleted > 20)
      return(30000);
    else if (WWAL_percentageCompleted > 5)
      return(10000);
    else if (WWAL_percentageCompleted == "-1")
      return(240000);

    return(5000);
  }
*/

// This implementation calls again in 30 seconds if the woot has just
// started and in 15 seconds if the woot is near completion.  It returns
// milliseconds, so to delay 15 seconds you will return 15,000.
// This method cannot return zero.
//
// Remember you can only enable one version of calculateTimeout().
function calculateTimeout ()
  {
    if (WWAL_percentageCompleted < 15)
      return(15000);
    else if (WWAL_percentageCompleted == "-1")
      return(300000); // 5 minutes

    return(30000);
  }

// This function updates the cache and then schedules a call back on
// itself. How it updates the cache and how often it checks back on the
// cache are determined by the methods callWebService() and
// calculateTimeout(). You can provide custom implementations of those
// two methods to change the behavior of the client (make it more
// reponsive, putting more pressure on the server, or make it less
// responsive but also easier on the server, or find your own middle
// ground).
function updateCache ()
  {
    logMessage("Getting Cache");

    // We call the callWebService() method which contains the current
    // algorithm on how often to call the web service versus calling
    // the direct URL (which bypasses the PHP web service and goes
    // directly via the web server saving server resources).  If you
    // want to experiment with new ways of making this determination,
    // write your own version of callWebService() and include it,
    // commenting out the one above.
    if (callWebService())
      makeRequest("cache", WWAL_publishWebServiceURL + (new Date().getTime()));
    else
      makeRequest("cache", WWAL_publishDirectURL + (new Date().getTime()));

    // We call calculateTimeout to find out how long to wait until
    // updating the cache again. This method might implement a number of
    // different algorithms for determining what a reasonable time is to
    // wait. If you want to experiment with new ways of making this
    // determination, write your own version of calculateTimeout() and
    // include it, commenting out the one above.
    setTimeout('updateCache();', calculateTimeout());
  }

function updateWebCounter ()
  {
    // Update the web counter to show the current number of unique users
    var webCounter = document.getElementById("webCounter");
    var newUrl = WWAL_userCounterURL + (new Date().getTime() % 100000);
    webCounter.src = newUrl;

    // Update the web counter every 5 minutes
    setTimeout('updateWebCounter();', 300000);
  }

function updateAge ()
  {
    var timestamp = document.getElementById("timestamp");
    var age       = document.getElementById("age");

    lastTimeStamp = parseInt(timestamp.value);

    if (lastTimeStamp < 100)
      return;

    var ageInSeconds = Math.round(((new Date()).getTime()/1000)-lastTimeStamp);
    age.innerHTML = ageInSeconds;

    WWAL_ageInSeconds = ageInSeconds;

    setTimeout('updateAge();', 1000);
  }

function initializeAudioScheme ()
  {
    var audioSchemeList = document.getElementById("AudioScheme");

    for (i = 0; i < WWAL_SoundSchemes.length; i++)
      {
        audioSchemeList.options[i] = new Option(WWAL_SoundSchemes[i][0], i);
      }

    var enableAudioCues = document.getElementById("enableAudioCues");

    var scheme = Get_Cookie("audioScheme")
    if (scheme != null)
      {
        audioSchemeList.selectedIndex = scheme;
      }

    if (enableAudioCues.checked)
      audioCuesEnabled()
  }

function audioCuesEnabled ()
  {
    soundManager.play('CuesEnabled');
    //soundManager.play('Spoken20percent');
  }

function testSound (index)
  {
    var audioSchemeList = document.getElementById("AudioScheme");

    var scheme = audioSchemeList.selectedIndex;

    soundManager.play(WWAL_SoundSchemes[scheme][index]);
  }

function audioSchemeChanged ()
  {
    var audioSchemeList = document.getElementById("AudioScheme");

    var scheme = audioSchemeList.selectedIndex;

    Set_Simple_Cookie("audioScheme", scheme)
  }

setTimeout('updateCache(); updateAge(); initializeAudioScheme(); updateWebCounter();', 1000);

