<!--
/*
   Filename: countdown.js
   Author:   Manuel Navarro
   Description: Used to countdown to a specific date, includes a skipWeekEnds setting as well.
 */

// Number of countdown objects.
// If this variable does not exist then create it.
if(typeof(countDownNumID)=="undefined")
   var countDownNumID = 0;

var countDown =
function(endDate, formatMsg, finalMsg, startCount, skipWeekEnds, skipHolidays,
          leadZero, staticClock, dateFormat, countOver)
{
   this.endDate = (endDate == null) ? false : endDate;   // Find ending date
   this.formatMsg = (formatMsg == null) ?
            "<b>%d%</b> Days, <b>%h%</b>:<b>%m%</b>:<b>%s%</b> Left" :
            formatMsg; // Format default message
   this.staticClock = (staticClock == null) ? false : staticClock;    // Clock does not update by default
   this.finalMsg = (finalMsg == null) ?  "No more time left!" : finalMsg;
   this.startCount = (startCount == null) ? true : startCount;    // Start count right away? True or false value
   this.countOver = (countOver == null) ? false : countOver;      // Allow the counter to start counting over target date
   this.skipWeekEnds = (skipWeekEnds == null) ? false : skipWeekEnds;   // Makes the counter skip weekends
   this.skipHolidays = (skipHolidays == null) ? false : skipHolidays;   // Make the counter skip hHolidays

   this.startDate = null;        // (Advanced option) Starts count from a specific start date.
   this.sDate = null;            // Used to hold startDate value so we can modify it later
   this.leadZero = true;         // (Advanced option) Show leading zeroes on one digit numbers
   this.countStep = 1;           // (Advanced option) Number of seconds it takes to update the count
   this.setNewTime = null;       // (Advanced option) Sets the current date clock to a specific time.
   // Format date output for current date
   this.dateFormat = (dateFormat == null) ? "dddd mmmm dd yyyy hh:MM:ss TT" : dateFormat;
   this.countBack = false;       // Shows that the counter is counting backwards. IE. Specified Date has pasted.

   // Counter properties
   this.countRate = null;
   this.tickTimer = null;
   this.spanObj = null;
   this.modCount = null;

   this.start =
   function()
   {
      var objID;

      if(!this.tickTimer)
      {
         // Create object that holds the countdown
         document.write("<span id='countdown_" + countDownNumID + "' class='countdown'></span>");
         // Return object to a variable
         objID = "countdown_" + countDownNumID++;
      }
      else
         clearTimeout(this.tickTimer);

      try
      {
         if(!this.holder)
            this.holder = (document.all) ?
                              document.all[objID] : document.getElementById(objID);

         // Setup startDate as a real date object
         this.sDate = (this.startDate) ? new Date(this.startDate) : new Date();
         if(isNaN(this.sDate.getTime())) this.sDate = new Date();     // Set correct date to sDate

         this.endDate = new Date(this.endDate);    // Set end date as a Date object

         // Round the counter steps to the nearest largest number
         this.countStep = Math.ceil(this.countStep)

         var msg = this.formatMsg;
         // Determine if the user only wants to display days left count
         this.onlyShowDays = ( (msg.indexOf("%d%") != -1) && 
                               (msg.indexOf("%h%") + msg.indexOf("%m%") + msg.indexOf("%s%")) == -3 );

         this.countRate = ( (Math.abs(this.countStep) -1) * 1000 ) + 990;  // Converts countStep into milliseconds

         this.setupEndDate();

         // Start the count:
         this.doCount();
      }
      catch(e)
      {
         this.handleError(e);
      }
   };

   this.setupEndDate =
   function()
   {
      var offsetBy = 0;
      this.setNewSeconds(this.endDate);         // Change time left into seconds left.

      if(this.skipWeekEnds)
         offsetBy += this.calcSkipWeekEnds();   // Skip weekends

      if(this.skipHolidays)
         offsetBy += this.calcSkipHolidays();   // Skip holidays

      var newTarget = new Date(this.endDate);
      newTarget.setDate(newTarget.getDate() - offsetBy);
      this.setNewSeconds(newTarget);
   };

   // Returns current date, handles setting up new time if specified
   this.getCurDate =
   function()
   {
      var dNow = this.sDate;

      if(this.setNewTime != null)
         dNow = new Date(dNow.toDateString() + " " + this.setNewTime);

      return dNow;
   };

   this.setNewSeconds =
   function(dTarget)
   {
      var dNow = this.getCurDate();    // Find current date

      // Find first offset of dates.
      var dOffset = new Date(dTarget - dNow);
      this.dateToSecs = Math.floor(dOffset / 1000);

      // Test to make sure this date is not already past
      //    If it has then check to see if we can Count OVER the past date,
      //    and start the count from there, then set countBack to true
      if((this.dateToSecs <= 0) && this.countOver)
      {
         dOffset = new Date(dNow - dTarget);
         this.dateToSecs = Math.floor(dOffset.getTime() / 1000);
         this.countBack = true;
         this.countStep = Math.abs(this.countStep);         // Make the count steps forward
      }
      else
         this.countStep = (Math.abs(this.countStep) * -1)   // Make the count steps backwards

      // If dOffset is not a true number, throw an error
      if(isNaN(dOffset)) throw new errObj(10, "[dOffset]", "Contains an Invalid Date/Time value.");
   };

   // Calculates new time based on skipped weekends.
   this.calcSkipWeekEnds =
   function()
   {
      try
      {
         // Find out how many days are left first
         var curDaysLeft = parseInt(this.calcSecs(86400, 100000, false));

         var curDate = this.sDate;

         var newDaysLeft = 0;

         // Only look for a new date if the number of days left is less than or
         //    equal to 5 and the date is not on Monday, OR
         //    if newDaysLeft is more than or equal to 5 OR
         //    countBack is true, meaning the counter is going backwards, and
         //    newDaysLeft is less than or equal to 5 and the date is not on Friday.
         if(
            (curDaysLeft <= 5 && curDate.getDay() != 1) ||
            (curDaysLeft >= 5) ||
            (this.countBack && curDaysLeft <= 5 && curDate.getDay() != 5)
            )
            newDaysLeft = curDaysLeft - Math.ceil(curDaysLeft - ((curDaysLeft/7)*2));

         return newDaysLeft;
      }
      catch(e)
      {
         this.handleError(e);
      }
   };

   // Calculate skipped holidays
   this.calcSkipHolidays =
   function()
   {
      var startDate = this.sDate;     // Get start date
      var endDate = this.endDate;   // Get end date
      var cdHD = countDown.Holidays;   // Find holiday array
      if(typeof(cdHD) == "undefined") return;   // Return if array does not exist or
      if(cdHD.length < 0) return;               // the length is less than 0

      var tmpDate,
          startYear = startDate.getFullYear(),
          endYear = endDate.getFullYear(),
          offset = 0,
          midTest;

      // If the start year is more than the end year, swap the two and swap the start and end years
      if(startYear > endYear)
      {
         startDate = this.endDate;
         endDate = this.sDate;
         startYear = startDate.getFullYear();
         endYear = endDate.getFullYear();
      }

      var years = Math.abs(endYear - startYear),   // Find # of years in between start and end date
          start = Math.floor(startDate.getTime()/1000),  // Find start, and
          end = Math.floor(endDate.getTime()/1000);      //   end dates in seconds

      for(var y = 0; y <= years; y++)  // Loop through the years
      {
         for(var i = 0; i < cdHD.length; i++)   // Loop through each holiday
         {
            // Set new holiday date
            tmpDate = new Date(cdHD[i] + "/" + (startYear + y))
            // Find midTest in seconds
            midTest = Math.floor(tmpDate.getTime()/1000);
            
            if( (midTest >= start) && (midTest <= end))
               offset++;      // Increase number of days to skip (offset days)

            if(midTest >= end) break;  // End this loop if the midTest is past the end date
         }
      }

      return offset;
   };

   // Timed counter
   this.doCount =
   function()
   {
      // If the date is past then display final message and exit
      if(this.dateToSecs <= 0)
      {
         if(!this.countOver)
         {
            var span = this.holder;
            span.innerHTML = this.finalMsg;
            span.className += " done";
            return true;
         } else {
            this.setupEndDate();
         }
      }

      // Start the display.
      var daysLeft = this.calcSecs(86400, 100000, false);
      // If formatMsg does not include hours, minutes, or seconds, add one to this variable
      daysLeft = (this.onlyShowDays) ? parseInt(daysLeft) + 1 : daysLeft;
      daysLeft = (this.leadZero && daysLeft < 10) ? "0" + daysLeft : daysLeft;
      var msg = this.formatMsg.replace(/%d%/g, daysLeft);
      msg = msg.replace(/%h%/g, this.calcSecs(3600, 24));
      msg = msg.replace(/%m%/g, this.calcSecs(60, 60));
      msg = msg.replace(/%s%/g, this.calcSecs(1, 60));

      // Replace %date% with todays date
      msg = msg.replace(/%date%/g, this.formatDate());

      this.holder.innerHTML = msg;

      if(!this.staticClock)
      {
         this.dateToSecs += this.countStep;     // Add steps to time
         var tmr = this;
         this.tickTimer = setTimeout(function() { tmr.doCount(); }, this.countRate);
         // Increase current time
         this.sDate.setSeconds(this.sDate.getSeconds() + Math.abs(this.countStep));
      }
   };

   // Calculate seconds
   this.calcSecs =
   function(num1, num2, leadZero)
   {
      var secs = this.dateToSecs;
      var leadZero = (leadZero == null) ? this.leadZero : leadZero
      var newSec = ((Math.floor(secs/num1))%num2).toString();
      if(leadZero && newSec.length < 2) newSec = "0" + newSec;
      return newSec;
   };

   this.formatDate =
   function()
   {
      var curDate = this.sDate;
      var date = this.dateFormat;
      // Get month, day, year
      var m = curDate.getMonth();
      var d = curDate.getDate();
      var y = curDate.getFullYear() + "";

      var wd = curDate.getDay();    // Week day
      // Get hour, minute, seconds
      var HH = curDate.getHours();        // 24 hour format
      var h = (HH > 12) ? HH - 12 : (HH == 0) ? 12 : HH;    // Format short hour
      var MM = curDate.getMinutes();
      var ss = curDate.getSeconds();
      var TT = (HH >= 12) ? "PM" : "AM";

      //HH = (HH < 10) ? "&nbsp;" + HH : HH;
      //h = (h < 10) ? "&nbsp;" + h : h;
      MM = (MM < 10) ? "0" + MM : MM;
      ss = (ss < 10) ? "0" + ss : ss;     // Add leading zero to one digit numbers

      // Find if we should display a full weekday name, and a full month name
      if(date.indexOf("dddd") > -1) wd += 7;
      var arr_m = (date.indexOf("mmmm") > -1) ? m += 12 : m;

      // Create regular expressions:
      var reWeek  = /(\b)(d{3,3}|d{4,4})(\b)/g;
      var reMonth = /(\b)(m{3,3}|m{4,4})(\b)/g;

      // Format year:
      date = date.replace("yyyy", y);
      date = date.replace("yy", y.substring(2,4));

      var cD = countDown;

      // Format Year, Month, Day, Hour, Minute, Seconds
      date = date.replace(reMonth, cD.WM.monthName[arr_m]);
      date = date.replace(/\b(m{2,2})\b/g, m+1);
      date = date.replace(reWeek, cD.WM.weekDay[wd]);
      date = date.replace(/\b(d{2,2})\b/g, d);
      date = date.replace(/\b(h{2,2})\b/g, h);
      date = date.replace(/\b(H{2,2})\b/g, HH);
      date = date.replace(/\b(M{2,2})\b/g, MM);
      date = date.replace(/\b(s{2,2})\b/g, ss);
      date = date.replace(/\b(T{2,2})\b/g, TT);

      return date;
   };

   // Error trapping
   this.handleError =
   function(e)
   {
      var msg = "An error occurred:\n\n";
      msg += e.number + " - " + e.message + ":\n    " + e.description;
      alert(msg);
   };

   // Start this function if startCount is set to true
   if(this.startCount) this.start();
};

// Week, Month Long & Short Names
countDown.WM =
   {
      weekDay:   [ "Sun","Mon","Tues","Wed","Thur","Fri","Sat",
                   "Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday" ],
      monthName: [ "Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec",
                   "January", "February", "March", "April", "May", "June", "July", "August",
                   "September", "October", "November", "December" ]
   };

// Holidays to skip, only add month and date, year will be added later.
// Format should be MM/DD where MM is the month and DD is the day.
countDown.Holidays =
   [  "1/1", "1/21",  "2/12",  "2/18",  "3/31",  "5/26", "6/4",
      "9/1", "10/13", "11/11", "11/27", "11/28", "12/25"  ];

// Error object required.
if(typeof(errObj) == "undefined")
   var errObj =
   function(num, msg, desc)
   {
      this.number = (num == null) ? -1 : num;
      this.description = (desc == null) ? "Invalid procedure" : desc;
      this.message = (msg == null) ? "[Object]" : msg;
   };

// -->

