/**
 * gcal.js
 * Fetches Google Calendar data from a given source.
 * @author Crystabelle Lopez
 * @version 1.2
 */

/* Acquires the Google Data APIs JavaScript client library after fetching the loader. */
google.load("gdata", "2");

/* Tells the loader to wait until the page finishes loading before calling code. */
google.setOnLoadCallback(init);


/**
 * Initializes both the Google data JS client library with an error handler and the calendar(s).
 */
function init() {
  google.gdata.client.init(handleGDError);
  loadGoogDevCalendar();
}


/**
 * Loads the Google Developer Events Calendar, with a given source email address and 
 * a name you choose for the resulting Calendar Service.
 */
function loadGoogDevCalendar() {
  loadCalendarByAddress('developer-calendar@google.com', 'DevCalendar');
}


/**
 * Determines the full calendarUrl based upon a given calendarAddress
 * argument and calls loadCalendar with the calendarUrl value.
 *
 * @param {string} calendarAddress is the email-style address for the calendar
 * @param {string] calendarSvcName is the name you choose for the Calendar Service.
 */
function loadCalendarByAddress(calendarAddress, calendarSvcName) {
  var calendarUrl = 'https://www.google.com/calendar/feeds/'
                    + calendarAddress
                    + '/public/full';
  loadCalendar(calendarUrl, calendarSvcName);
}


/**
 * Uses Google data JS client library to retrieve a calendar feed from the specified
 * URL. The feed is controlled by several query parameters and a callback 
 * function is called to process the feed results.
 *
 * @param {string} calendarUrl is the URL for a specific calendar feed
 * @param {string] calendarSvcName is the name you choose for the Calendar Service.
 */
function loadCalendar(calendarUrl, calendarSvcName) {
  var service = new google.gdata.calendar.CalendarService(calendarSvcName);
  var query = new google.gdata.calendar.CalendarEventQuery(calendarUrl);

  query.setOrderBy('starttime');
  query.setSortOrder('ascending');
  query.setFutureEvents(true);
  query.setSingleEvents(true);
  query.setMaxResults(10);

  service.getEventsFeed(query, listEvents, handleGDError);
}


/**
 * Callback function for the Google data JS client library to call when an error
 * occurs during the retrieval of the feed.
 *
 * If the user's browser is Opera, skip error handling and load the calendar. Google's code
 * throws an error that states that Opera is not a supported browser. So far, calendar loads fine.
 *
 * @param {Error} e is an instance of an Error 
 */
function handleGDError(e) {
  if (window.opera) {
    // Bypass the "unsupported error" that is thrown for Opera. Continue loading the calendar.
  } else {
    // Error found, show an alert instead of the calendar.
    document.getElementById('gcal').setAttribute('style', 'display: none;');
    if (e instanceof Error) {
      // Show alert with the error line number, file and message.
      alert('Error at line ' + e.lineNumber +
      ' in ' + e.fileName + '\n' +
      'Message: ' + e.message);

      // If available, output HTTP error code and status text.
      if (e.cause) {
        var status = e.cause.status;
        var statusText = e.cause.statusText;
        alert('Root cause: HTTP error ' + status + ' with status text of: ' + 
        statusText);
      }
    } else {
      alert(e.toString())
    }
  }
}



/****************************************
 * Variables to be used in listEvents().
 * Placed outside of listEvents() so that variable values can be manipulated as needed 
 * by associated helper functions. Only the variables that are reused multiple times 
 * are placed here.
 ****************************************/

  // These variables help keep track of the dates shown in the completed calendar table 
  // on the web page. They must be instantiated outside of the for-loop!
  var lastStartJSDate = null;
  var lastEndJSDate = null;
  
  // Event's start time information.
  var startDateTimeObj = null;
  var startJSDate = null;
  var startTimeArray = new Array();
  var startHour;
  var startMinutes;
  
  // Event's end time information.
  var endDateTimeObj = null;
  var endJSDate = null;
  var endTimeArray = new Array();
  var endHour;
  var endMinutes;
  
  // Href that goes to the event's Google Calendar detail view, if available.
  var eventEntryLinkHref = null;


/**
 * Callback function for the Google data JS client library to call with a feed 
 * of events retrieved.
 *
 * Creates a table of events in a human-readable form. This table of
 * events is added into a div called 'events'. The title for the calendar is
 * placed in a div called 'calendarTitle'.
 *
 * @param {json} feedRoot is the root of the feed, containing all entries.
 */ 
function listEvents(feedRoot) {
  var entries = feedRoot.feed.getEntries();
  var eventDiv = document.getElementById('events');

  if (eventDiv.childNodes.length > 0) {
    eventDiv.removeChild(eventDiv.childNodes[0]);
  }

  // Create a new table.
  var table = document.createElement('table');

  // Set the calendarTitle div with the name of the calendar.
  document.getElementById('calendarTitle').innerHTML = feedRoot.feed.title.$t;

  // Loop through each event in the feed.
  for (var i = 0; i < entries.length; i++) {
    var entry = entries[i];
    var eventTitle = entry.getTitle().getText();
    
    // Get the event's date/time object. Then get the date.
    getEventDateTimes(entry);

    // Get the event's full date: day of the week, date, month, and year.
    // Note that you can change how the date is shown by re-ordering the strings that are
    // combined to create the dateString var.
    var dateString = dayToText(startJSDate.getDay()) + " "
                     + startJSDate.getDate() + " "
                     + monthToText(startJSDate.getMonth()) + " "
                     + startJSDate.getFullYear();
    
    // Get the event's start time, and split that time into hours and minutes.
    startTimeArray = formatTime(startDateTimeObj, startJSDate, startTimeArray);
    startHour = startTimeArray[0];
    startMinutes = startTimeArray[1];
    startTimeArray.length = 0; // reset the array so it can be reused.
    
    // Get the event's end time, and split that time into hours and minutes.
    endTimeArray = formatTime(endDateTimeObj, endJSDate, endTimeArray);
    endHour = endTimeArray[0];
    endMinutes = endTimeArray[1];
    endTimeArray.length = 0; // reset the array so it can be reused.
    
    // Add a new row that contains a date, only when either of these conditions is met:
    // (1) This is the first event to be pulled from the feed, or
    // (2) The current event's date is different than the previous event's date.
    if ((lastStartJSDate == null) && (lastEndJSDate == null)) {
      addDateRow(table, dateString);
    } else if ((startJSDate.getMonth() > lastStartJSDate.getMonth())
              || (startJSDate.getDate() > lastStartJSDate.getDate())) {
      addDateRow(table, dateString);
    }
    
    // Get the event's Href that goes to the event's Google Calendar detail view, if available.
    if (entry.getHtmlLink() != null) {
      eventEntryLinkHref = entry.getHtmlLink().getHref();
    }
    
    // Add a new row that contain's the event's information.
    addEventRow(table, eventTitle, eventEntryLinkHref);
    
    // Set the two variables to keep track of the last start date.
    // This helps keep track of when to append a new row with a date.
    lastStartJSDate = startJSDate;
    lastEndJSDate = endJSDate;
  }

  // Once all events have been parsed, add the completed table to the page.
  eventDiv.appendChild(table);
}

/**
 * Gets an event's starting and ending date/time object. Then, gets the date.
 * Assigns these two items to the appropriate variables.
 *
 * @param {EventEntry} entry is the given calendar entry that we want to get information about.
 */
function getEventDateTimes(entry) {
  var times = entry.getTimes();
  if (times.length > 0) {
    startDateTimeObj = times[0].getStartTime();
    startJSDate = startDateTimeObj.getDate();
    
    endDateTimeObj = times[0].getEndTime();
    endJSDate = endDateTimeObj.getDate();
  }
}


/**
 * Adds a new row that contains a given date (e.g., Thursday 13 May) to a specified table.
 *
 * @param {element} table is the specified table element that this new row should be placed in.
 * @param {string} dateString is the date to be inserted into the table.
 * @return table
 */
function addDateRow(table, dateString) {
  // Create the table elements necessary for the event's date to show up.
  var trDate = document.createElement('tr');
  var thDate = document.createElement('th');
  thDate.colSpan='2';

  // Populate the sole <th> cell.
  table.appendChild(trDate);
  trDate.appendChild(thDate);
  thDate.appendChild(document.createTextNode(dateString));

  return table;
}


/**
 * Adds a new row that contains a given single event's time and title to a specified table.
 *
 * @param {element} table is the specified table element that this new row should be placed in.
 * @param {String} eventTitle is the title of the event.
 * @param {String} eventEntryLinkHref is the Href for that event's Google Calendar details.
 */
function addEventRow(table, eventTitle) {
  // Create the elements necessary for an event to show up.
  var tr = document.createElement('tr');
  var tdEventTime = document.createElement('td');
  tdEventTime.setAttribute('class', 'eventTime');
  var tdEventTitle = document.createElement('td');
  
  // Populate the cell that contain the event's start and end time
  if (startHour != 'All day') {
    tdEventTime.appendChild(document.createTextNode(startHour + startMinutes + ' - ' + endHour + endMinutes));
  } else {
    tdEventTime.appendChild(document.createTextNode(startHour));
  }
  
  // Create the tdEventTitle cell and populate it accordingly:
  // If the eventEntryLinkHref exists, create an HTML anchor element that'll contain the event's title.
  // Otherwise, populate the cell with the event's title
  if (eventEntryLinkHref != null) {
    var eventEntryLink = document.createElement('a');
    eventEntryLink.setAttribute('href', eventEntryLinkHref);
    eventEntryLink.appendChild(document.createTextNode(eventTitle));
    tdEventTitle.appendChild(eventEntryLink);
  } else {
    tdEventTitle.appendChild(document.createTextNode(eventTitle));
  }
  
  // Append the event row, tdEventTime and tdEventTitle cells onto the table.
  table.appendChild(tr);
  tr.appendChild(tdEventTime);
  tr.appendChild(tdEventTitle);
}


/**
 * Formats a given time, in the form of hours and minutes (e.g., 11:30 or 15:00).
 * If there is no start time, show the text 'All Day'.
 * Otherwise, gets the event's starting or ending hour and minutes
 * 1. Adds a leading zero to minutes less than 10, thereby returning a time in 24-hour format.
 * 2. Adds a colon just before the minutes
 *
 * @param {date} dateTime is the Date object for a given event.
 * @param {date} jsDate is the Date data obtained from the dateTime object, (month, day, hours, minutes).
 * @param {Array} formattedTimeArray is the array that this function should put the formatted time in.
 * @return {Array} the specified formattedTimeArray that should contain a properly-formatted time.
 */
function formatTime(dateTime, jsDate, formattedTimeArray) {
  if (dateTime.isDateOnly()) {
    formattedTimeArray[0] = 'All day';
    formattedTimeArray[1] = '';
  } else {
    formattedTimeArray[0] = padNumber(jsDate.getHours());               // Hour
    formattedTimeArray[1] = ':' + padNumber(startJSDate.getMinutes());  // Minutes
  }
  
  return formattedTimeArray;
}


/**
 * Adds a leading zero to a single-digit number. Used for displaying dates.
 *
 * @param {integer} num is the number to be padded.
 * @return {string} the padded number.
 */
function padNumber(num) {
  if (num <= 9) {
    num = '0' + num;
  }

  return num;
}


/**
 * Returns a month's written name.
 *
 * @param {integer} m is the number of the month.
 * @return {string} the month's written-out name.
 */
function monthToText(m) {
  var month = null;
  switch(m) {
  case 0:
    month = "January";
    break;
  case 1:
    month = "February";
    break;
  case 2:
    month = "March";
    break;
  case 3:
    month = "April";
    break;
  case 4:
    month = "May";
    break;
  case 5:
    month = "June";
    break;
  case 6:
    month = "July";
    break;
  case 7:
    month = "August";
    break;
  case 8:
    month = "September";
    break;
  case 9:
    month = "October";
    break;
  case 10:
    month = "November";
    break;	
  case 11:
    month = "December";
    break;
  default:
    month = "No month found";
  }

  return month;
}


/**
 * Returns a day's written name.
 *
 * @param {integer} d is the day of the week.
 * @return {string} the day of the week's written-out name.
 */
function dayToText(d) {
  var day = null;
  switch(d) {
  case 0:
    day = "Sun";
    break;
  case 1:
    day = "Mon";
    break;
  case 2:
    day = "Tue";
    break;
  case 3:
    day = "Wed";
    break;
  case 4:
    day = "Thu";
    break;
  case 5:
    day = "Fri";
    break;
  case 6:
    day = "Sat";
    break;
  default:
    day = "No day found";
  }

  return day;
}
