/* Here's the jargon. Stick to it. Stations have Schedules which
 * are lists of Shows. Stations are always refered to by their Slug.
 * Stations have a Stream Playlist. We have a Popup Player and a
 * Whats On Now module, together refered to as the Player. Users
 * can add Audio Files to their My Playlist. We also have a
 * Most Listened Playlist. */
var inPopupPlayer = false;
var animateTime = 500;
var stationStreamSelected = true;
var currentAudioFile = null;

var clientMinusServerHours;
/* We want to share code between the Whats On Now module and the Popup Player.
 * Each calls their own init function, which sets some function pointers
 * for places where we need tailored functionality. For example, when you click
 * a station tab the current tab should always highlight. However, the Pop
 * Up Player begins playing, and the Whats On Now module shows some options. */
function initSchedule() {
  // These hrefs are meant to make the player work in a very minimal way if the
  // client disabled javascript.
  $("#help").attr("href", audioHelpPath());
  // If we broadcast a show at 5 in NY, but the user is in L.A., we should show
  // it all in L.A.'s time zone. The server time, stored in the server_now
  // input, won't line up exactly with the client, so take our best guess with
  // rounding.
  clientD = new Date();
  clientH = clientD.getHours() + clientD.getMinutes() / 60;
  serverD = dateFromString(server_now);
  serverH = serverD.getHours() + serverD.getMinutes() / 60;
  // We need to round both because we want to display whole numbers for times,
  // and because we may cache the Popup Player. Notice that this means we
  // can't cache the popup player for more than 20 minutes or so.
  clientMinusServerHours = Math.round(clientH - serverH);
  setStationsDates(stations);
  setupStationsRefresh();
}

function setStationsDates(stationList) {
  /* json pulls dates in as strings, so we need to convert them to dates so we
   * can compare dates and figure out when shows occur. We'll store all times
   * in  the client's time zone. */
  for (var station in stationList) {
    var schedule = stationList[station].schedule;
    for (var i = 0; i < schedule.length; i++) {
      schedule[i].start = dateFromString(schedule[i].start,
          clientMinusServerHours);
      schedule[i].end = dateFromString(schedule[i].end,
          clientMinusServerHours);
    }
  }
}

/* All state should reflect this station. Also play and display this station. */
function playStation(slug) {
  loadStation(slug);
  onStationUpdate(slug); // whatsOnNowStationUpdate or popupPlayerStationUpdate
}

var stationLoaded = false;
var currentStationSlug = "";
/* Update all state to reflect this station. Don't, however, update the player
 * or display. */
function loadStation(slug) {
  currentStationSlug = slug;
  stationLoaded = true;
  $("#station_tabs li").each(function(i) {
    var clss = "ui-tabs-selected";
    if ("station_" + slug == $(this).attr("id")) {
      $(this).addClass(clss);
    } else {
      $(this).removeClass(clss);
    }
  });
  setupScheduleShowUpdate();
  return false; // Necessary because the tabs call this function directly.
}

function unselectStations() {
  $("#station_tabs li").each(function(i) {
    $(this).removeClass("ui-tabs-selected");
  });
}

var streamPlaylistItem = null;
var streamPlaylistUpdates = [];
var streamPlaylistRefreshRate = 60 * 1000;
var checkStreamPlaylistMessage = "";
function checkStreamPlaylist() {
  // We want to avoid multiple chains of updates if the user mashes buttons.
  while (streamPlaylistUpdates.length) {
    window.clearTimeout(streamPlaylistUpdates.pop());
  }
  if (currentStationSlug && !currentAudioFile) {
    if (stations[currentStationSlug].hasPlaylists) {
      var fromSlug = currentStationSlug;
      $.getJSON("/api/playlists/" + currentStationSlug + "/on_now/",
          function (json, requestStatus) {
            var refreshIn = streamPlaylistRefreshRate;
            checkStreamPlaylistMessage = "Just do a standard refresh in " +
                streamPlaylistRefreshRate;
            if ("success" == requestStatus) {
              // If the user mashes buttons and we happen to get these requests
              // back out of order, we could accidentally end up showing the
              // wrong playlist item. Hence, do a sanity check that this
              // catalog entry is for the current station.
              if (currentStationSlug == fromSlug) {
                streamPlaylistItem = json.count ?
                    json.results[0].catalog_entry : null;
                setInfoHtml();
                // We don't want a thundering herd every time a song ends, so
                // randomly stagger everyone over 30 seconds.
                var randomDelay = Math.round(Math.random() * 30 * 1000);
                // clientMinusServerHours is acurate only to an hour. However,
                // new songs typically start 0 to 4 minutes after the last one,
                // so percision isn't really an issue.
                var cacheExpires = dateFromString(json.expires,
                    clientMinusServerHours).getTime() - (new Date()).getTime();
                refreshIn = Math.max(randomDelay + cacheExpires,
                    streamPlaylistRefreshRate);
                checkStreamPlaylistMessage = "Checking in " + refreshIn +
                    " based on random delay " + randomDelay +
                    ", cache expiring in " + cacheExpires + ", refresh rate " +
                    streamPlaylistRefreshRate + ", now " + (new Date());
              }
            }
            streamPlaylistUpdates.push(
                window.setTimeout(checkStreamPlaylist, refreshIn));
          });
    } else {
      // We're here if we just switched to a stream playlist-less station. If
      // the user is mashing buttons, we're going to want to clear this out.
      streamPlaylistUpdates.push(window.setTimeout(function() {
        streamPlaylistItem = null;
        setInfoHtml();
      }, animateTime / 2));
    }
  }
}

var displayShow = null;
var hideShowIfStreamPlaylist = false;
function setShowHtml(show) {
  displayShow = show;
  setInfoHtml();
}

/* Decide what to display in the information window below the station tabs, and
 * load it up. This function is a bit of a monster, but I just found it less
 * confusing to have all this logic in one place. Then you can safely call
 * setInfoHtml() whenever you need to, and if you get the wrong results, you
 * know where to fix it. */
function setInfoHtml() {
  var composer = "";
  var piece_title = "";
  var description = "";
  var on_air_wrapper = $("#on_air_wrapper");
  var show = displayShow || currentShow() || nextShow();
  var moreInfoLink = "";
  var image = "";
  var on_air_name = "";
  var on_air_when = "";
  var showOnAirWrapper = true;
  if (currentAudioFile) {
    if (currentAudioFile.info) {
      on_air_name = $("<a/>").strippedHtml(currentAudioFile.title);
      setLink(on_air_name, currentAudioFile.info);
    } else {
      on_air_name = currentAudioFile.title;
    }
    moreInfoLink = currentAudioFile.info;
    description = currentAudioFile.description;
    image = currentAudioFile.detailImage;
  } else {
    if (show) {
      moreInfoLink = show.url;
      on_air_name = show.title;
      // Don't display "Special Programming" if we have a stream playlist item.
      hideShowIfStreamPlaylist = !on_air_name;
      if (!on_air_name) {
        on_air_name = "Special Programming";
      }
      // If this is a one show site, then there's not much of a point in
      // linking to the home page. Also, then we'll load up the show info even
      // though we aren't necessarily playing it, so a link to play it makes
      // sense.
      if ((onlyShow && currentShow() == show) || (!onlyShow && show.url)) {
        on_air_name = $("<a/>").strippedHtml(on_air_name);
        if (onlyShow) {
          on_air_name.click(function() {
            playStation(currentStationSlug);
          });
        } else {
          setLink(on_air_name, show.url);
        }
      }
      var on_air_when = "On Air Now: ";
      if (show && show.start > new Date()) {
        if (inPopupPlayer) {
          on_air_when = "Coming Up at " + formatTime(show.start) + ": ";
        } else {
          on_air_when = "Coming Up: ";
        }
      } else if (show && show.end < new Date()) {
        on_when = "Earlier Today: ";
      }
      description = show.description;
    }
    if (streamPlaylistItem && currentShow() == show) {
      var ple = streamPlaylistItem;
      var soloist = "";
      if (ple.soloists.length) {
        soloist = ple.soloists[0].musician.name;
        if (ple.soloists[0].role) {
          soloist += ", " + ple.soloists[0].role;
        } else if (ple.soloists[0].instruments.length) {
          soloist += ", " + ple.soloists[0].instruments[0];
        }
        soloist += ".";
      }
      var details = [
        ple.conductor ? ple.conductor.name + ", conductor." : "",
        ple.ensemble ? ple.ensemble.name + "." : "",
        soloist
      ];
      var printDetails = [];
      for (var i in details) {
        if (details[i]) {
          printDetails.push(details[i]);
        }
      }
      description = printDetails.join(" ");
      composer = ple.composer ? ple.composer.name : "";
      if (ple.url) {
        piece_title = $("<a/>").text(ple.title);
        setLink(piece_title, ple.url);
      } else {
        piece_title = ple.title;
      }
      showOnAirWrapper = !hideShowIfStreamPlaylist;
    }
  }
  if (showOnAirWrapper && on_air_name) {
    on_air_wrapper.show();
  } else {
    on_air_wrapper.hide();
  }
  var img = $("#current_show .image img").attr("src", image);
  img.removeClass("hide");
  if (image) {
    img.show();
  } else {
    img.hide();
  }
  setTitleHelper($("#on_air_name"), on_air_name);
  $("#on_air_when").strippedHtml(on_air_when);
  setMoreInfoHref(moreInfoLink);
  setInfoHtmlHelper($("#composer"), composer);
  setTitleHelper($("#piece_title"), piece_title);
  setInfoHtmlHelper($("#piece_description"), description);
}

function setTitleHelper(element, title) {
  element.empty();
  var display = false;
  if ("string" == typeof title) {
    setInfoHtmlHelper(element, title);
    display = title;
  } else {
    element.append(title);
    display = title.html();
    setInfoHtmlHelper(title, display);
  }
  if (display) {
    element.show();
  } else {
    element.hide();
  }
}

function setInfoHtmlHelper(element, text) {
  /* The popup player is a fixed height, so in that case just truncate to
   * whatever space is left between the title and player. The Whats On Now
   * module is more flexible. */
  if (!text) {
    element.hide();
  } else {
    element.show();
    if (inPopupPlayer) {
      var newHeight = $(".bottom_player").offset().top - element.offset().top;
      element.truncateToHeight(text, newHeight);
    } else {
      element.strippedHtml(text, 140);
    }
  }
}

function setMoreInfoHref(url) {
  if (!url) {
    $("#more_info").hide();
  } else {
    setLink($("#more_info"), url);
    $("#more_info").show();
  }
}

function formatTime(dt) {
  var hours = dt.getHours();
  var minutes = dt.getMinutes();
  var amPm = "AM";
  if (hours >= 12) {
    if (hours > 12) {
      hours -= 12;
    }
    amPm = "PM";
  } else if (0 == hours) {
    hours = 12;
  }
  minutes = minutes < 10 ? "0" + minutes : minutes;
  return hours + ":" + minutes + amPm;
}

function setLink(a, url) {
  if (inPopupPlayer) {
    a.removeAttr("href").removeAttr("target").css({cursor: "pointer"})
    a.click(function() {
      popupPlayerOpenUrl(url);
    });
  } else {
    a.attr("href", url);
    if (isExternal(url)) {
      a.attr("target", "_blank");
    } else {
      a.removeAttr("target");
    }
  }
}

var scheduleShowUpdateEvent = 0;
var onShowUpdate = null;
function setupScheduleShowUpdate() {
  window.clearTimeout(scheduleShowUpdateEvent);
  var newI = currentShowIndex();
  var newShow = currentShow();
  var next = nextShow();
  var updateTime = null;
  if (newShow) {
    updateTime = newShow.end;
  } else if (next) {
    updateTime = next.start;
  }
  if (updateTime) {
    scheduleShowUpdateEvent = window.setTimeout(setupScheduleShowUpdate,
        updateTime.getTime() - (new Date()).getTime() + 1000);
  }
  if (currentStationSlug) {
    checkStreamPlaylist();
  }
  if (newShow) {
    $(".listen_live").addClass("live").removeClass("last_episode");
  } else if (onlyShow) {
    var onlySchedule = null;
    if (currentStationSlug && stations[currentStationSlug].schedule.length) {
      onlySchedule = stations[currentStationSlug].schedule[0];
    }
    if (onlySchedule && onlySchedule.latest_xspf) {
      $(".listen_live").addClass("last_episode").removeClass("live");
      latestEpisodeXspf = onlySchedule.latest_xspf;
    } else {
      $(".listen_live").removeClass("last_episode").removeClass("live");
    }
  }
  // either popupPlayerShowUpdate or whatsOnNowShowUpdate
  onShowUpdate(newI, newShow);
}

function currentShow() {
  return showAtIndex(currentShowIndex());
}

function nextShow() {
  return showAtIndex(nextShowIndex());
}

function showAtIndex(index) {
  return currentStationSlug && index >= 0 ?
      stations[currentStationSlug].schedule[index] : null;
}

function currentShowIndex() {
  return nowNextShowIndexHelper(true);
}

function nextShowIndex() {
  return nowNextShowIndexHelper(false);
}

function nowNextShowIndexHelper(nowNotNext) {
  var now = new Date();
  if (currentStationSlug) {
    var schedule = stations[currentStationSlug].schedule;
    for (var i = 0; i < schedule.length; i++) {
      var show = schedule[i];
      if (nowNotNext && show.start <= now && now < show.end) {
        return i;
      } else if (!nowNotNext && now < show.start) {
        return i;
      }
    }
  }
  return -1;
}

function setupStationsRefresh() {
  window.setTimeout(refreshStations, 15 * 60 * 1000);
}

function refreshStations() {
  $.getJSON("/schedule/whats_on_today/", function (json) {
    setStationsDates(json);
    var stationsDifferent = false;
    for (var slug in stations) {
      stationsDifferent = stationsDifferent || !json[slug];
    }
    for (var slug in json) {
      stationsDifferent = stationsDifferent || !stations[slug];
    }
    if (stationsDifferent) {
      stations = json;
      var tabs = $("#station_tabs .schedule-tabs");
      tabs.empty();
      for (var slug in stations) {
        var li = $("<li/>").attr("id", "station_" + slug).addClass(slug);
        if (slug == currentStationSlug) {
          li.addClass("ui-tabs-selected");
          loadStation(slug);
        }
        var a = $("<a/>").text(stations[slug].name);
        a.attr("onclick", "playStation('" + slug + "'); return false;").appendTo(li);
        tabs.append(li);
      }
    } else {
      for (var slug in stations) {
        if (!schedulesEqual(json[slug].schedule, stations[slug].schedule)) {
          stations[slug] = json[slug];
          if (slug == currentStationSlug) {
            loadStation(slug);
          }
        }
      }
    }
  });
  setupStationsRefresh();
}

function schedulesEqual(s1, s2) {
  if (s1.length != s2.length) {
    return false;
  }
  for (var i = 0; i < s1.length; i++) {
    if (!simpleObjectsEqual(s1[i], s2[i])) {
      return false;
    }
  }
  return true;
}

function simpleObjectsEqual(o1, o2) {
  for (var key in o1) {
    if (("" + o1[key]) != ("" + o2[key])) {
      return false;
    }
  }
  return true;
}
