/* 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;

function openStation(slug) {
    openPopupPlayer({station: slug});
}

function listenAudioFromUrl(url) {
    handleAudioFromUrl(url, "listen");
}

function addAudioFromUrl(url) {
    handleAudioFromUrl(url, "add");
}

/* Assume no extension, or some of the common playlist extensions are playlists,
 * and everything else is direct audio such as mp3. */
function handleAudioFromUrl(url, action) {
    var extension = url.match(/\.([\w]+)($|\?)/);
    extension = extension ? extension[1].toLowerCase() : "";
    if (extension.match(/^(|pls|xspf|m3u)$/)) {
        var parameters = {};
        parameters[action] = url;
        openPopupPlayer(parameters);
    } else {
        url = action + "mp3=" + url.replace("?", "&");
        openPopupPlayer(parametersFromQuery(url));
    }
}

function audioUrlForPopup(audio, title, thumbnail, download) {
    popArgs = [];
    var push = function(key, value) {
            if (value) {
                popArgs.push(key + "=" + value);
            }
        }
    push("title", title);
    push("thumbnail", thumbnail);
    push("download", download);
    return audio + (popArgs.length ? "?" + popArgs.join("&") : "");
}

function embed_audio(audio, id, width, title, thumbnail, download) {
    // IE6 is sensitive to modifying the dom before it's done loading. Here, we add
    // a div to a parent element before we hit the closing tag of the parent. Hence,
    // $(document).ready
    $(document).ready(function() {
            var flashvars = false;
            var params = {
                wmode: "transparent",
                allowscriptaccess: "always",
                quality: "high",
                flashvars: "file=" + audio + "&autostart=false&popurl=" + escape(audioUrlForPopup(audio, title, thumbnail, download))
            };
            var new_id = "audioplayer_"+id;
            var attributes = {
                id: new_id,
                name: new_id
            };
            var obj = $("#" + new_id);
            var buttons_id = new_id + "_buttons";
            var buttons = $("<div/>").attr("id", buttons_id).css({width: width}).insertAfter(obj);
            embed_audio_buttons(audio, buttons_id, title, thumbnail, download)
            swfobject.embedSWF(inlinePlayerPath(), new_id, width, 34, "9.0.0","", flashvars, params, attributes);
        });
}

/* The flash player calls openPlayerFromFlash when you click the pop out link. */
openPlayerFromFlash = listenAudioFromUrl;

function embed_audio_buttons(audio, id, title, thumbnail, download) {
    var url = audioUrlForPopup(audio, title, thumbnail, download);
    var addButton = function(label, action) {
            $("<a/>").text(label).addClass(label).css({cursor: "pointer"}).click(function() { action(url); }).appendTo($("#" + id));
        }
    addButton("Listen", listenAudioFromUrl);
    addButton("Add", addAudioFromUrl);
}

function openPopupPlayer(parameters) {
    var paramList = [];
    for (key in parameters) {
        paramList.push(escape(key) + "=" + escape(parameters[key]));
    }
    var link = '/popup_player/#' + paramList.join("&");
    var options = "'toolbar=no,location=no,status=no,menubar=no,scrollbars=no," +
            "resizable=yes,left=0,top=0,width=620,height=310'";
    var newWindow = window.open(link, "popup_player", options);
    if (newWindow.focus) {
        newWindow.focus();
    }
}

function parametersFromQuery(url) {
    url = url.replace("#", "");
    var hashParts = url.split("&");
    var variables = {};
    for (var i in hashParts) {
        var subParts = hashParts[i].split("=");
        if (subParts.length > 1) {
            // Notice that we store unsafe values, so we must avoid XSS during display.
            variables[unescape(subParts[0])] = unescape(subParts[1]);
        }
    }
    return variables;
}

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.
    $("#station_tabs A").removeAttr("href").css({cursor: "pointer"});
    $("#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 dateFromString(str, addHours) {
    addHours = addHours ? addHours : 0;
    var d = new Date();
    // dumps turns dates into yyyy-mm-ddThh:mm:ss which is great for sorting dates while they're still strings.
    // We need to add time to dates though, so we need to parse it out.
    var match = str.match(/(\d{4})-(\d\d)-(\d\d)T(\d\d:\d\d:\d\d)/);
    str = match ? [match[2], match[3], match[1]].join("/") + " " + match[4] : str;
    d.setTime(Date.parse(str) + addHours * 1000 * 60 * 60);
    return d;
}

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);
        }
    }
}

var currentStationSlug = "";
/* Update to this station, including displaying the current show, and doing whatever
 * the Popup Player or Whats On Now need. */
function loadStation(slug) {
    currentStationSlug = slug;
    $("#station_tabs li").each(function(i) {
            var clss = "ui-tabs-selected";
            if ("station_" + slug == $(this).attr("id")) {
                $(this).addClass(clss);
            } else {
                $(this).removeClass(clss);
            }
        });
    onStationUpdate(slug); // whatsOnNowStationUpdate or popupPlayerStationUpdate
    setupScheduleShowUpdate();
}

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 (stations[currentStationSlug].hasPlaylists) {
        $.getJSON("/api/playlists/" + currentStationSlug + "/on_now/", function (json, requestStatus) {
                var refreshIn = streamPlaylistRefreshRate;
                checkStreamPlaylistMessage = "Just do a standard refresh in " + streamPlaylistRefreshRate;
                if ("success" == requestStatus) {
                    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));
    }
}

/* Decide what to display in the information window below the station tabs, and load it up. 
 * It's much less confusing to have just one function manipulate the info html, so please
 * keep that logic here. */
function setInfoHtml() {
    var composer = "";
    var title = "";
    var description = "";
    var showTitle = $("#on_air_now");
    var show = displayShow ? displayShow : currentShow();
    var link = show ? show.url : "";
    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 : "";
        title = ple.title;
        if (hideShowIfStreamPlaylist) {
            showTitle.hide();
        } else {
            showTitle.show();
        }
    } else if (stationStreamSelected) {
        showTitle.show();
        if (show) {
            description = show.description;
        }
    } else if (currentAudioFile) {
        link = currentAudioFile.info;
    }
    $("#composer").strippedHtml(composer);
    $("#piece_title").strippedHtml(title);
    // The popup player is a fixed height, so in that case just truncate to
    // a specific height. The Whats On Now module is more flexible.
    if (inPopupPlayer) {
        $("#piece_description").truncateToHeight(description, 55);
    } else {
        $("#piece_description").strippedHtml(description, 140);
    }
    setMoreInfoHref(link);
}

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

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

var displayShow = null;
// Don't bother to display "Special Programming" if we have a stream playlist item.
var hideShowIfStreamPlaylist = false;
function setShowHtml(show) {
    displayShow = show;
    var title = show ? show.title : "";
    hideShowIfStreamPlaylist = !title;
    if (!title) {
        title = "Special Programming";
    }
    if (show && show.url) {
        var a = $("<a/>").strippedHtml(title);
        setLink(a, show.url);
        $("#on_air_name").empty().append(a);
    } else {
        $("#on_air_name").strippedHtml(title);
    }
    var on_when = "On Air Now: ";
    if (show && show.start > new Date()) {
        on_when = "Coming Up: ";
    } else if (show && show.end < new Date()) {
        on_when = "Earlier Today: ";
    }
    $("#on_air_when").text(on_when);
    setInfoHtml();
}

var scheduleShowUpdateEvent = 0;
var currentScheduleShow = null;
var onShowUpdate = null;
function setupScheduleShowUpdate() {
    window.clearTimeout(scheduleShowUpdateEvent);
    if (currentStationSlug) {
        var newI = currentShowIndex();
        var newShow = currentShow();
        currentScheduleShow = newShow;
        onShowUpdate(newI, newShow); // either popupPlayerShowUpdate or whatsOnNowShowUpdate
        if (newShow) {
            scheduleShowUpdateEvent = window.setTimeout(setupScheduleShowUpdate, newShow.end.getTime() - (new Date()).getTime() + 1000);
        }
        checkStreamPlaylist();
    }
}

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

function currentShowIndex() {
    var now = new Date();
    if (currentStationSlug) {
        var schedule = stations[currentStationSlug].schedule;
        for (var i = 0; i < schedule.length; i++) {
            var show = schedule[i];
            if (show.start <= now && now < show.end) {
                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);
                    }
                    $("<a/>").text(stations[slug].name).css({cursor: "pointer"}).click(function() {
                            loadStation(slug);
                        }).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;
}
