/** A Schedule Class for reading and displaying radio schedules in a variety of formats
* @class Schedule
* @param {String} xmlFile 
* @param {jQuery} options

  <ol>
 	  <li [class="current"]>
			<div class="time">
	 			<span class="start">00:00</span>
				<span class="to"> - </span>
				<span class="end">12:00</span>
			</div>
			<div class="show"><a href="/show/url/"><img src="/show-img.jpg"/> Show Name</a></div>
			<div class="host"><a href="/host/url/"><img src="/host-img.jpg"/> Host Name</a></div>
		</li>
  </ol>
 *
 */
	
function Schedule(xmlDoc, opts)
{
  var self = this;
  this.error = false;
  this.shows = new Array();
  this.xml = xmlDoc;
  
  this.options = $.extend({
    debug : true,
    fxSpeed : 'slow',
    dowSeparator: ',',
    view: "today", // (today, now, week, month,...)
		gmtOffset: 0,
    now : new Date()
  }, opts);
  
  this.parseXML(this.xml);
};
  
$.extend(Schedule.prototype, {
    
		/**
		* Core method to convert the xml shows document into Show objects.
		* @method parseXML
		* @param {XMLDocument} xml	The xml to parse
		*/
    parseXML : function(xml)
    {
      var self = this, o = this.options;
      var YYYYMMDD = /(\d{4})[-_\s]?(\d{2})[-_\s]?(\d{2})/;
      var shows = $("shows",xml);
      o.gmtOffset = parseInt(shows[0].getAttribute("gmtoffset"));
      o.now = new Date(o.now.getTime()+(o.gmtOffset + (new Date().getTimezoneOffset())/60)*1000*60*60);
      
      // Loop over each show
      $("show",xml).each(
        function(id){
          var show = $("show",xml).get(id);

          // For Each Host of the Show
          var hostArr = new Array();
          $("host",show).each(function(id){
            var host = $("host",show).get(id);
            hostArr[hostArr.length] = new ShowHost(host.getAttribute("name"),host.getAttribute("link"),host.getAttribute("image"));
          });
          
          // For each scheduled show-date, get the time and day(s) it runs.
          // and create a new Show object for each during this week
          $("date",show).each(
            function(id){
            
              var date = $("date",show).get(id);
              if(!date) return;
              var stime = new ClockTime(date.getAttribute("start"));
              var etime = new ClockTime(date.getAttribute("end"));
              
              // Recurring shows
              // for each set day (mon,tue,wed,etc), create a ShowDate for the show for this week
              var days = date.getAttribute("days");
              if(!empty(days)) {
                days = days.split(o.dowSeparator);
                
                for(var i=0; i<days.length; i++) {
                
                  var day = o.now.add(Date.DAY,Date[days[i].toUpperCase()]-o.now.getDay()); // subtract the day of the week to reset to sunday, then move the date forward from there
                  var start = new Date(day.getFullYear(),day.getMonth(),day.getDate(),stime.hour,stime.minute);
                  if(etime.hour < stime.hour) day = day.add(Date.DAY,1); // end time goes into the next day's morning
                  var end = new Date(day.getFullYear(),day.getMonth(),day.getDate(),etime.hour,etime.minute);
                  var showDate = new ShowDate( start, end, true ); //log(start,end);
                  var theShow = new Show(show.getAttribute("name"),showDate,show.getAttribute("link"),show.getAttribute("image"),hostArr);
                  
                  self.shows.push(theShow);
                }
              }
              
              // Handle specific date (e.g. non-recurring)
              // Day format expected: YYYY-MM-DD
              var day = date.getAttribute("day");
              if(!empty(day)) {
                day = YYYYMMDD.exec(day);
                // log(show.getAttribute("name"), day);
                
                if(day && day.length == 4) { // year, month, day are present
                
                  day = new Date( day[1], parseInt(day[2])-1, day[3] );
                  var start = new Date(day.getFullYear(),day.getMonth(),day.getDate(),stime.hour,stime.minute);
                  if(etime.hour < stime.hour) day = day.add(Date.DAY,1); // end time goes into the next day's morning
                  var end = new Date(day.getFullYear(),day.getMonth(),day.getDate(),etime.hour,etime.minute);
                  var showDate = new ShowDate( start, end, false); //log(start,end);
                  var theShow = new Show(show.getAttribute("name"),showDate,show.getAttribute("link"),show.getAttribute("image"),hostArr);
                  
                  self.shows.push(theShow);
                  
                }
              }
              
            } // function(id)
            
          ); // each

        } // function
      ); // each show
      
      // Sort the shows by start date and recurring status.
      // Shows that don't recur come first in the sort order.
      self.shows.sort(function(a, b)
        {
          if(a.date.start.before(b.date.start)) return -1;
          else if(a.date.start.after(b.date.start)) return 1;
          else if(b.isRecurring()) return -1;
          else if(a.isRecurring()) return 1;
          else return 0;
        }      
      );

    }, // parseXML
    
		/**
		* Display the schedule in the designated container (out)
		* @method display
		*/
    display : function(out)
    {
      var html = "";
      switch(this.options.view) {
        case "today":
          html = this.todayHtml();
          break;
        case "now":
          html = this.nowHtml();
          break;
        case "week":
          html = this.weekHtml();
          break;
      }
      $(out).append(html);
    },
    
		/**
		* Print an agenda for the given day as a list of times, show names, and hosts
		* @method todayHtml
		*/
    todayHtml : function(when)
    {
      var self = this, o = this.options, shows = this.shows;
      if(!when) when = o.now;
      
      var html = []; // array joins are much faster than string concatenation
      html.push("<h4 class='showday'>");
      html.push(when.formatDate("l, F jS"));
      html.push("</h4>");
      html.push("<ul class='showlist'>");
      
      var show, nextshow, alt = 0;
      for(var s=0; s < shows.length; s++)
      {
        var date = shows[s].date;
        var conflict = false;
        show = shows[s];
        nextshow = shows[s+1];
        
        if(nextshow != null) {
          // check if this show overlaps with the next show
          if(show.start().equals(nextshow.start()) || // shows start at the same time OR
             show.end().after(nextshow.start()) ) { // show ends after the time when the next show starts
              // non-recurring shows bump recurring shows
              if(!show.isRecurring()) {
                s++; // skip the next show
              // there's a time conflict with a recurring show              
              } else {
                conflict = true;
              }
          }
        }
        
        if(date.isToday(when)) {
          html.push("<li class='clearfix");
          //log( show.name, date.start, date.end );
          if(show.isOnAir(o.now)) html.push(" current");
          html.push((alt%2==1) ? " alt" : ""); alt++;
          html.push((conflict) ? " conflict" : "");
          html.push("'>");
          html.push(show.html());
          html.push("</li>");
        }
        
      }
      
      html.push("</ul>");
      return html.join('');
      
    }, // printToday
    
		/**
		* Find and return html for the show that is currently playing.
		* @method printNow
		*/
    nowHtml : function()
    {
      var self = this, o = this.options, shows = this.shows;
      var html = [];
      
      for(var i=0; i < shows.length; i++)
      {
        if(shows[i].date.isNow(o.now)) {
          html.push(shows[i].html());
					break;
        }
      }
      return html.join('');
    }, // printNow
    
		/**
		* Construct the html for a week-long schedule, which is an agenda for each day of the week
		* @method weekHtml
		* @return {String} html for the weekly schedule
		*/
    weekHtml : function()
    {
      var self = this, o = this.options, shows = this.shows;
      var weekday = o.now.subtract(Date.DAY,o.now.getDay()); // week starts on Sunday
      var html = []; // array joins are much faster than string concatenation
      
      // loop over each weekday and add the current weekday's agenda to the html
      for(var i=0; i<7; i++)
      {
        html.push(self.todayHtml(weekday));
        weekday = weekday.add(Date.DAY,1); // next day
      }
      return html.join('');
    }, // printWeek
    
    log : function() {
      if(!this.options.debug) return;
      if( window.console )
        console.debug.apply( console, arguments );
    }
    
}); // Schedule methods

/** A scheduled show for a single time frame
* @class Show
* @param {String} name 
* @param {ShowDate} date
* @param {String} link 
* @param {String} image 
* @param {Array} hosts
*/
function Show(name, date, link, image, hosts)
{
	this.name = name;
	this.date = date;
	this.link = link;
	this.image = image;
	this.hosts = hosts;
  
	
	/**
	* Compare the ShowDate(s) of two shows, a and b
	* @method sortByDate
	* @param {Show} a 
	* @param {Show} b 
	* @return {Number} 1 if show a runs before show b, 0 if both shows run in the same period, -1 if show b runs before show a
	*/
	this.sortByDate = function(a, b)
	{
    if(a.date.start.before(b.start.date)) return -1;
    else if(a.date.start.after(b.start.date)) return 1;
    else return 0;
	}
    
  /** 
      * Returns true if this show runs today.
      */
	this.isOnAir = function(when)
	{
    return this.date.isNow(when);
	}
  
  /** 
      * Returns true if this show recurs each week
      */
	this.isRecurring = function()
	{
    return this.date.recurring;
	}
    
  /** 
      * Date-time the show starts
      */
	this.start = function()
	{
    return this.date.start;
	}
  
  /** 
      * Date-time the show ends
      */
	this.end = function()
	{
    return this.date.end;
	}
  
  /** 
      * 
      */
	this.html = function()
	{
    var html = [];
    
    if(!empty(this.image)) html.push('<img src="'+this.image+'"/>');
    html.push(this.date.html());
    html.push('<div class="show">');
    html.push('<h4>');
    if(!empty(this.link)) html.push('<a href="'+this.link+'">');
    html.push(this.name);
    if(!empty(this.link)) html.push('</a>');
    html.push('</h4>');     
    html.push(this.hostsHtml());
    html.push('</div>');

		return html.join('');
	}
  
  this.hostsHtml = function()
  {
    var html = [];
    var hostCount = this.hosts.length;
    html.push('<div class="hosts">');
    for(var i=0; i<hostCount; i++) {
      html.push(this.hosts[i].html());
      if(hostCount > 1 && i < hostCount-2) {
        html.push(", ");
      } else if(hostCount > 1 && i == hostCount-2) {
        html.push(" &amp; ");
      }
    }
    html.push('</div>');
    return html.join('');
  }
  
} // Show

/**
* @class ShowHost
* @param {String} name 
* @param {String} link 
* @param {String} image 
*/
function ShowHost(name, link, image)
{
	this.name = name;
	this.link = link;
	this.image = image;
	
	this.html = function()
	{
    var html = [];
		html.push('<span>');
		if(!empty(this.link)) html.push('<a href="'+this.link+'">');
		//if(!empty(this.image)) html.push('<img src="'+this.image+'"/>');
		html.push(this.name);
		if(!empty(this.link)) html.push('</a>');
		html.push('</span>');
		return html.join('');
	}
	
}

/**
* @class ShowDate
* @param {Date} start 
* @param {Date} end 
* @param {Boolean} recurring 
*/
function ShowDate(_start,_end, _recurring)
{
  // Constructor
	this.start = _start;
	this.end = _end;
  this.recurring = _recurring;
    
	/**
	* Determine if this ShowDate runs on the day provided.
	* @method isToday
	* @param {Date} day	The day to test agains
	* @return {Boolean} true if the show runs on the day
	*/
  this.isToday = function(day)
  { 
    if(!day) day = new Date();
    return (this.start.getDay() == day.getDay() &&
           this.start.getMonth() == day.getMonth() &&
           this.start.getDate() == day.getDate() );
  	//var nowDay = day.getDay();
  	//var due = this.start.getDay() == nowDay || this.end.getDay() == nowDay;
//log(nowDay, due, this.start, this.end);
  	//return due;
  }
  
  /** 
      * Determine if this ShowDate is running now
      */
  this.isNow = function(now)
  {
    if(!now) now = new Date();
    //log(now, this.start, this.end, now.between(this.start,this.end));
    return now.between(this.start,this.end);
  }
	
  /**
      *	<div class="time">
      *   <span class="start">00:00</span>
      *   <span class="to"> - </span>
      *   <span class="end">12:00</span>
      * </div>
      */
	this.html = function()
	{
    var f = "g:ia";
    var html = new Array();
		html.push('<div class="time">');
    html.push('<span class="start">');
    html.push(this.start.formatDate(f));
    html.push('</span>');
		if(!empty(this.end)) {
      html.push('<span class="to"> - </span>');
      html.push('<span class="end">');
      html.push(this.end.formatDate(f));
      html.push('</span>');
    }
		html.push('</div>');
		return html.join('');
	}
  
  /**
      *	<div class="time">
      *   <span class="start">00:00</span>
      *   <span class="to"> - </span>
      *   <span class="end">12:00</span>
      * </div>
      */
	this.toString = function()
	{
    var f = "g:i";
    return this.start.formatDate(f)+"-"+this.end.formatDate(f);
	}	
	
} // ShowDate

/**
* 24 hour clock-time object for parsing and storing times without a date component
* @class ClockTime
* @param {String} time
*/
function ClockTime(time)
{
  this.hour;
  this.minute;
  
  /**
    * Cases: 1200, 2300, 0000, 0500, 1am, 2pm, 2300pm, 12:00
    */
  this.parse = function(time)
  {
    var hour = 0, minute = 0;
    var hrShift = 0, hrPos = 0, minPos=2, hrLength=2;
    
    // clean up the time
    time = jQuery.trim(time.toLowerCase());
    if(time.indexOf('p.m.') != -1 || time.indexOf('pm') != -1) {
      hrShift = 12;
    }
    // strip any non-digit characters (Remove colons, ams, pms, etc.)
    time = time.replace(/[^\d.]/g,"");
    
    // time starts with a zero (e.g. morning times in 24hr format - 0100,0200,0300,etc) so skip the first digit
  	if(time.indexOf('0') == 0) { hrPos = 1; }
    // only 3 digits in the time, therefore the leading zero on a morning time was omitted
    if(time.length == 3) { minPos--; hrPos=0; hrLength=1; }
    
    this.hour = parseInt(time.substr(hrPos,hrLength-hrPos));
    if(this.hour < 13) this.hour += hrShift; // case: 20:00pm - don't add the hrShift
    this.minute = parseInt(time.substr(minPos,2));
    
    return this;
  }
  
  this.toString = function(format)
  {
    var out = new Array();
    switch(format) {
    
    default: 
      var minPad = "";
      if(this.minute < 10) minPad = "0";
      out.push(this.hour);
      out.push(":");
      out.push(minPad);
      out.push(this.minute);
    }
    return out.join('');
  }
  
  if(time) {
    this.parse(time);
  } else {
    this.hour = 0;
    this.minute = 0;
  }
  
  
} // Clock Date

/**************************************************
 * Utilities
 **************************************************/
function empty(txt)
{
	return (txt == null || txt == '');
}

var DEBUG = true;
function log() {
  if(!DEBUG) return;
  if( window.console )
    console.debug.apply( console, arguments );
}