// refsCallback: (optional)
//   Will be called when the player enters a marker that has a `data-ref` attribute. The value of `data-ref` will be passed to the function.
//   When leaving a marker that a `data-ref` attribute, and entering a marker without one (or not entering a new marker at all), the function will be called with `null`.
function Player(htmlContainer, refsCallback) {
    this.container = htmlContainer;
    this.markersContainer = this.container.querySelector(".markers_container");
    this.videoContainer = this.container.querySelector(".video_container");
    this.refsCallback = refsCallback || function() {};

    if (!this.videoContainer.getAttribute("data-videoId")) {
        console.error("Expected to find data-videoId attribute on", this.videoContainer, "for player initialized on", this.container);
        throw new Error("Missing data-videoId attribute.");
    }
    this.markers = [];
    var markerEls = this.markersContainer.querySelectorAll(".marker");
    if (markerEls.length == 0) {
        console.error("No markers found in", this.markersContainer, "for player initialized on", this.container);
        throw new Error("Missing markers.");
    }
    for (var i = 0; i < markerEls.length; ++i) {
        var marker = {
            timestamp: parseInt(markerEls[i].getAttribute("data-timestamp"), 10),
            ref: markerEls[i].getAttribute("data-ref"),
            endTime: (i < markerEls.length - 1 ? parseInt(markerEls[i+1].getAttribute("data-timestamp"), 10) : null),
            el: markerEls[i],
            fadedProgress: markerEls[i].querySelector(".progress.faded"),
            progress: markerEls[i].querySelector(".progress.main"),
            hoverx: null
        };
        marker.el.addEventListener("click", this.onMarkerClick.bind(this, marker));
        marker.el.addEventListener("mousemove", this.onMarkerMouseMove.bind(this, marker));
        marker.el.addEventListener("mouseleave", this.onMarkerMouseLeave.bind(this, marker));
        this.markers.push(marker);
    }

    this.currentMarker = null;
    this.currentMarkerIdx = null;
    this.youtubePlayer = null;
    this.youtubePlayerReady = false;
    this.playing = false;
    this.shouldPlay = false;
    this.buffering = false;
    this.pauseAfterBuffer = false;
    this.speed = 1;
    this.currentTime = 0;
    this.lastFrameTime = 0;
    this.scrollTo = -1;
    this.scrollPosition = 0;
    this.nextFrame = null;
    this.looping = false;


    this.markersContainer.addEventListener("wheel", function(ev) {
        this.scrollTo = -1;
    }.bind(this));

    Player.initializeYoutube(this.onYoutubeReady.bind(this));
    this.updateSize();

    this.resume();
}

// Start playing the video from the current position.
// If the player hasn't loaded yet, it will autoplay when ready.
Player.prototype.play = function() {
    if (this.youtubePlayerReady) {
        if (!this.playing) {
            this.youtubePlayer.playVideo();
        }
        this.pauseAfterBuffer = false;
    } else {
        this.shouldPlay = true;
    }
};

// Pause the video at the current position.
// If the player hasn't loaded yet, it will not autoplay when ready. (This is the default)
Player.prototype.pause = function() {
    if (this.youtubePlayerReady) {
        if (this.playing) {
            this.youtubePlayer.pauseVideo();
        } else if (this.buffering) {
            this.pauseAfterBuffer = true;
        }
    } else {
        this.shouldPlay = false;
    }
};

// Sets the current time. Does not affect play status.
// If the player hasn't loaded yet, it will seek to this time when ready.
Player.prototype.setTime = function(time) {
    this.currentTime = time;
    if (this.youtubePlayerReady) {
        this.currentTime = Math.max(0, Math.min(this.currentTime, this.youtubePlayer.getDuration()));
        this.youtubePlayer.seekTo(this.currentTime);
    }
    this.updateProgress();
};

Player.prototype.jumpToNextMarker = function() {
    var targetMarkerIdx = Math.min((this.currentMarkerIdx === null ? 0 : this.currentMarkerIdx + 1), this.markers.length-1);
    var targetTime = this.markers[targetMarkerIdx].timestamp;
    this.setTime(targetTime);
    this.play();
};

Player.prototype.jumpToPrevMarker = function() {
    var targetMarkerIdx = Math.max(0, (this.currentMarkerIdx === null ? 0 : this.currentMarkerIdx - 1));
    var targetTime = this.markers[targetMarkerIdx].timestamp;
    this.setTime(targetTime);
    this.play();
};

// Call this after changing the size of the video container in order to update the youtube player.
Player.prototype.updateSize = function() {
    var width = this.videoContainer.offsetWidth;
    var height = width / 16 * 9;
    this.markersContainer.style.height = height;
    if (this.youtubePlayerReady) {
        this.youtubePlayer.setSize(Math.floor(width), Math.floor(height));
    }
}

// Stops the per-frame work that the player does. Call when you want to hide or get rid of the player.
Player.prototype.halt = function() {
    this.pause();
    this.looping = false;
    if (this.nextFrame) {
        cancelAnimationFrame(this.nextFrame);
        this.nextFrame = null;
    }
}

// Resumes the per-frame work that the player does. Call when you want to show the player again after hiding.
Player.prototype.resume = function() {
    this.looping = true;
    if (!this.nextFrame) {
        this.doFrame();
    }
}

Player.initializeYoutube = function(callback) {
    if (window.APYoutubeAPIReady === undefined) {
        window.APYoutubeAPIReady = false;
        window.APCallbacks = (callback ? [callback] : []);
        window.onYouTubeIframeAPIReady = function() {
            window.APYoutubeAPIReady = true;
            for (var i = 0; i < APCallbacks.length; ++i) {
                APCallbacks[i]();
            }
        };
        var scriptTag = document.createElement("SCRIPT");
        scriptTag.setAttribute("type", "text/javascript");
        scriptTag.setAttribute("src", "https://www.youtube.com/iframe_api");
        document.body.appendChild(scriptTag);
    } else if (window.APYoutubeAPIReady === false) {
        window.APCallbacks.push(callback);
    } else if (window.APYoutubeAPIReady === true) {
        callback();
    }
}

// END PUBLIC INTERFACE

Player.prototype.onMarkerClick = function(marker, ev) {
    var time = marker.timestamp;
    if (this.currentMarker == marker && marker.hoverx !== null) {
        time += (marker.endTime - marker.timestamp) * marker.hoverx;
    }
    this.setTime(time);
    this.play();
};

function getElementXOffsetFromPage(el) {
    var left = 0;
    do {
        left += el.offsetLeft;
    } while (el = el.offsetParent);
    return left;
}

Player.prototype.onMarkerMouseMove = function(marker, ev) {
    if (this.currentMarker == marker) {
        marker.hoverx = (ev.pageX - getElementXOffsetFromPage(marker.el)) / marker.el.offsetWidth;
    }
};

Player.prototype.onMarkerMouseLeave = function(marker, ev) {
    marker.hoverx = null;
};

Player.prototype.updateProgress = function() {
    var prevMarker = this.currentMarker;
    this.currentMarker = null;
    this.currentMarkerIdx = null;

    for (var i = 0; i < this.markers.length; ++i) {
        var marker = this.markers[i];
        if (marker.timestamp <= this.currentTime && this.currentTime < marker.endTime) {
            this.currentMarker = marker;
            this.currentMarkerIdx = i;
            break;
        }
    }

    if (this.currentMarker) {
        var totalWidth = this.currentMarker.el.offsetWidth;
        var progress = (this.currentTime - this.currentMarker.timestamp) / (this.currentMarker.endTime - this.currentMarker.timestamp);
        if (this.currentMarker.hoverx === null) {
            var pixelWidth = progress * totalWidth;
            this.currentMarker.fadedProgress.style.width = Math.ceil(pixelWidth) + "px";
            this.currentMarker.fadedProgress.style.opacity = pixelWidth - Math.floor(pixelWidth);
            this.currentMarker.progress.style.width = Math.floor(pixelWidth) + "px";
        } else {
            this.currentMarker.fadedProgress.style.opacity = 1;
            this.currentMarker.progress.style.width = Math.floor(Math.min(this.currentMarker.hoverx, progress) * totalWidth) + "px";
            this.currentMarker.fadedProgress.style.width = Math.floor(Math.max(this.currentMarker.hoverx, progress) * totalWidth) + "px";
        }

    }

    if (this.currentMarker != prevMarker) {
        if (prevMarker) {
            prevMarker.el.classList.remove("current");
            prevMarker.fadedProgress.style.width = "0px";
            prevMarker.progress.style.width = "0px";
            prevMarker.hoverx = null;
        }

        if (this.currentMarker) {
            this.currentMarker.el.classList.add("current");
            this.scrollTo = this.currentMarker.el.offsetTop + this.currentMarker.el.offsetHeight/2.0;
            this.scrollPosition = this.markersContainer.scrollTop;
        }

        if (this.currentMarker) {
            this.refsCallback(this.currentMarker.ref, this.currentMarker.el);
        } else if (prevMarker && prevMarker.ref) {
            this.refsCallback(null);
        }
    }
};

Player.prototype.doFrame = function() {
    var now = performance.now();
    var delta = (now - this.lastFrameTime) / 1000.0;
    this.lastFrameTime = now;
    if (this.playing) {
        this.currentTime += delta * this.speed;
    }
    this.updateProgress();

    if (this.scrollTo >= 0) {
        var targetPosition = this.scrollTo - this.markersContainer.offsetHeight/2.0;
        targetPosition = Math.max(0, Math.min(targetPosition, this.markersContainer.scrollHeight - this.markersContainer.offsetHeight));
        this.scrollPosition += (targetPosition - this.scrollPosition) * 0.1;
        if (Math.abs(this.scrollPosition - targetPosition) < 1.0) {
            this.markersContainer.scrollTop = targetPosition;
            this.scrollTo = -1;
        } else {
            this.markersContainer.scrollTop = this.scrollPosition;
        }
    }

    this.nextFrame = requestAnimationFrame(this.doFrame.bind(this));
};

Player.prototype.onYoutubePlayerReady = function() {
    this.youtubePlayerReady = true;
    this.markers[this.markers.length-1].endTime = this.youtubePlayer.getDuration();
    this.updateSize();
    this.youtubePlayer.setPlaybackQuality("hd1080");
    if (this.currentTime > 0) {
        this.currentTime = Math.max(0, Math.min(this.currentTime, this.youtubePlayer.getDuration()));
        this.youtubePlayer.seekTo(this.currentTime, true);
    }
    if (this.shouldPlay) {
        this.youtubePlayer.playVideo();
    }
};

Player.prototype.onYoutubePlayerStateChange = function(ev) {
    if (ev.data == YT.PlayerState.PLAYING) {
        this.playing = true;
        this.currentTime = this.youtubePlayer.getCurrentTime();
    } else if (ev.data == YT.PlayerState.PAUSED || ev.data == YT.PlayerState.BUFFERING) {
        this.playing = false;
        this.currentTime = this.youtubePlayer.getCurrentTime();
        this.updateProgress();
    } else {
        this.playing = false;
    }

    this.buffering = ev.data == YT.PlayerState.BUFFERING;
    if (this.playing && this.pauseAfterBuffer) {
        this.pauseAfterBuffering = false;
        this.pause();
    }
};

Player.prototype.onYoutubePlayerPlaybackRateChange = function(ev) {
    this.speed = ev.data;
};

Player.prototype.onYoutubeReady = function() {
    var youtubePlayerDiv = document.createElement("DIV");
    youtubePlayerDiv.id = "youtube_player_" + Player.youtubePlayerCount++;
    this.videoContainer.appendChild(youtubePlayerDiv);
    this.youtubePlayer = new YT.Player(youtubePlayerDiv.id, {
        videoId: this.videoContainer.getAttribute("data-videoId"),
        width: this.videoContainer.offsetWidth,
        height: this.videoContainer.offsetWidth / 16 * 9,
        //playerVars: { disablekb: 1 },
        events: {
            "onReady": this.onYoutubePlayerReady.bind(this),
            "onStateChange": this.onYoutubePlayerStateChange.bind(this),
            "onPlaybackRateChange": this.onYoutubePlayerPlaybackRateChange.bind(this)
        }
    });
};

Player.youtubePlayerCount = 0;

// NOTE(matt): Hereafter is my stuff. Beware!

function toggleMenuVisibility(element) {
    if(element.classList.contains("visible"))
    {
        element.classList.remove("visible");
        element.parentNode.classList.remove("visible");
        focusedElement.classList.remove("focused");
        focusedElement = null;
        if(focusedIdentifier)
        {
            focusedIdentifier.classList.remove("focused");
            focusedIdentifier = null;
        }
    }
    else
    {
        for(menuIndex in menuState)
        {
            menuState[menuIndex].classList.remove("visible");
            menuState[menuIndex].parentNode.classList.remove("visible");
            if(focusedElement)
            {
                focusedElement.classList.remove("focused");
            }
            if(focusedIdentifier)
            {
                focusedIdentifier.classList.remove("focused");
            }
        }
        element.classList.add("visible");
        element.parentNode.classList.add("visible");

        if(element.classList.contains("quotes_container"))
        {
            if(!lastFocusedQuote)
            {
                lastFocusedQuote = element.querySelectorAll(".ref")[0];
            }
            focusedElement = lastFocusedQuote;
            focusedElement.classList.add("focused");
        }
        else if(element.classList.contains("references_container"))
        {
            if(!lastFocusedReference || !lastFocusedIdentifier)
            {
                lastFocusedReference = element.querySelectorAll(".ref")[0];
                lastFocusedIdentifier = lastFocusedReference.querySelector(".ref_indices").firstElementChild;
            }
            focusedElement = lastFocusedReference;
            focusedElement.classList.add("focused");
            focusedIdentifier = lastFocusedIdentifier;
            focusedIdentifier.classList.add("focused");
        }
        else if(element.classList.contains("filter_container"))
        {
            if(!lastFocusedCategory)
            {
                lastFocusedCategory = element.querySelectorAll(".filter_content")[0];
            }
            focusedElement = lastFocusedCategory;
            focusedElement.classList.add("focused");
        }
        else if(element.classList.contains("credits_container"))
        {
            if(!lastFocusedCreditItem)
            {
                if(element.querySelectorAll(".credit .support")[0])
                {
                    lastFocusedCreditItem = element.querySelectorAll(".credit .support")[0];
                }
                else
                {
                    lastFocusedCreditItem = element.querySelectorAll(".credit .person")[0];
                }
            }
            focusedElement = lastFocusedCreditItem;
            focusedElement.classList.add("focused");
        }
    }
}

function handleKey(key) {
    var gotKey = true;
    switch (key) {
        case "q": {
            if(quotesMenu)
            {
                toggleMenuVisibility(quotesMenu)
            }
        } break;
        case "r": {
            if(referencesMenu)
            {
                toggleMenuVisibility(referencesMenu)
            }
        } break;
        case "f": {
            if(filterMenu)
            {
                toggleMenuVisibility(filterMenu)
            }
        } break;
        case "c": {
            if(creditsMenu)
            {
                toggleMenuVisibility(creditsMenu)
            }
        } break;

        case "Enter": {
            if(focusedElement)
            {
                if(focusedElement.parentNode.classList.contains("quotes_container"))
                {
                    var time = focusedElement.querySelector(".timecode").getAttribute("data-timestamp");
                    player.setTime(parseInt(time, 10));
                    player.play();
                }
                else if(focusedElement.parentNode.classList.contains("references_container"))
                {
                    var time = focusedIdentifier.getAttribute("data-timestamp");
                    player.setTime(parseInt(time, 10));
                    player.play();
                }
                else if(focusedElement.parentNode.classList.contains("credit"))
                {
                    if(focusedElement.hasAttribute)
                    {
                        var url = focusedElement.getAttribute("href");
                        window.open(url, "_blank");
                    }
                }
            }
            else
            {
                console.log("TODO(matt): Implement me, perhaps?\n");
            }
        } break;

        case "o": {
            if(focusedElement)
            {
                if(focusedElement.parentNode.classList.contains("references_container"))
                {
                    var url = focusedElement.getAttribute("href");
                    window.open(url, "_blank");
                }
                else if(focusedElement.parentNode.classList.contains("credit"))
                {
                    if(focusedElement.hasAttribute("href"))
                    {
                        var url = focusedElement.getAttribute("href");
                        window.open(url, "_blank");
                    }
                }
            }
        } break;

        case "w": case "k": case "ArrowUp": {
            if(focusedElement)
            {
                if(focusedElement.parentNode.classList.contains("quotes_container"))
                {
                    if(focusedElement.previousElementSibling)
                    {
                        focusedElement.classList.remove("focused");

                        lastFocusedQuote = focusedElement.previousElementSibling;
                        focusedElement = lastFocusedQuote;
                        focusedElement.classList.add("focused");
                    }
                }
                else if(focusedElement.parentNode.classList.contains("references_container"))
                {
                    if(focusedElement.previousElementSibling)
                    {
                        focusedElement.classList.remove("focused");
                        focusedIdentifier.classList.remove("focused");

                        lastFocusedReference = focusedElement.previousElementSibling;
                        focusedElement = lastFocusedReference;
                        focusedElement.classList.add("focused");

                        lastFocusedIdentifier = focusedElement.querySelector(".ref_indices").firstElementChild;
                        focusedIdentifier = lastFocusedIdentifier;
                        focusedIdentifier.classList.add("focused");
                    }
                }
                else if(focusedElement.parentNode.parentNode.classList.contains("filters"))
                {
                    if(focusedElement.previousElementSibling &&
                        focusedElement.previousElementSibling.classList.contains("filter_content"))
                    {
                        focusedElement.classList.remove("focused");

                        lastFocusedCategory = focusedElement.previousElementSibling;
                        focusedElement = lastFocusedCategory;
                        focusedElement.classList.add("focused");
                    }
                }
                else if(focusedElement.parentNode.classList.contains("credit"))
                {
                    if(focusedElement.parentNode.previousElementSibling)
                    {
                        focusedElement.classList.remove("focused");
                        if(focusedElement.parentNode.previousElementSibling.querySelector(".support") &&
                            focusedElement.classList.contains("support"))
                        {
                            lastFocusedCreditItem = focusedElement.parentNode.previousElementSibling.querySelector(".support");
                            focusedElement = lastFocusedCreditItem;
                        }
                        else
                        {
                            lastFocusedCreditItem = focusedElement.parentNode.previousElementSibling.querySelector(".person");
                            focusedElement = lastFocusedCreditItem;
                        }
                        focusedElement.classList.add("focused");
                    }
                }
            }
        } break;

        case "s": case "j": case "ArrowDown": {
            if(focusedElement)
            {
                if(focusedElement.parentNode.classList.contains("quotes_container"))
                {
                    if(focusedElement.nextElementSibling)
                    {
                        focusedElement.classList.remove("focused");

                        lastFocusedQuote = focusedElement.nextElementSibling;
                        focusedElement = lastFocusedQuote;
                        focusedElement.classList.add("focused");
                    }
                }
                else if(focusedElement.parentNode.classList.contains("references_container"))
                {
                    if(focusedElement.nextElementSibling)
                    {
                        focusedElement.classList.remove("focused");
                        focusedIdentifier.classList.remove("focused");

                        lastFocusedReference = focusedElement.nextElementSibling;
                        focusedElement = lastFocusedReference;
                        focusedElement.classList.add("focused");

                        lastFocusedIdentifier = focusedElement.querySelector(".ref_indices").firstElementChild;
                        focusedIdentifier = lastFocusedIdentifier;
                        focusedIdentifier.classList.add("focused");
                    }
                }
                else if(focusedElement.parentNode.parentNode.classList.contains("filters"))
                {
                    if(focusedElement.nextElementSibling &&
                        focusedElement.nextElementSibling.classList.contains("filter_content"))
                    {
                        focusedElement.classList.remove("focused");

                        lastFocusedCategory = focusedElement.nextElementSibling;
                        focusedElement = lastFocusedCategory;
                        focusedElement.classList.add("focused");
                    }
                }
                else if(focusedElement.parentNode.classList.contains("credit"))
                {
                    if(focusedElement.parentNode.nextElementSibling)
                    {
                        focusedElement.classList.remove("focused");
                        if(focusedElement.parentNode.nextElementSibling.querySelector(".support") &&
                            focusedElement.classList.contains("support"))
                        {
                            lastFocusedCreditItem = focusedElement.parentNode.nextElementSibling.querySelector(".support");
                            focusedElement = lastFocusedCreditItem;
                        }
                        else
                        {
                            lastFocusedCreditItem = focusedElement.parentNode.nextElementSibling.querySelector(".person");
                            focusedElement = lastFocusedCreditItem;
                        }
                        focusedElement.classList.add("focused");
                    }
                }
            }
        } break;

        case "a": case "h": case "ArrowLeft": {
            if(focusedElement)
            {
                if(focusedElement.parentNode.classList.contains("references_container"))
                {
                    if(focusedIdentifier.previousElementSibling)
                    {
                        focusedIdentifier.classList.remove("focused");

                        lastFocusedIdentifier = focusedIdentifier.previousElementSibling;
                        focusedIdentifier = lastFocusedIdentifier;
                        focusedIdentifier.classList.add("focused");
                    }
                }
                else if(focusedElement.classList.contains("filter_content"))
                {
                    if(focusedElement.parentNode.classList.contains("filter_media") &&
                        focusedElement.parentNode.previousElementSibling)
                    {
                        focusedElement.classList.remove("focused");
                        lastFocusedMedium = focusedElement;

                        if(!lastFocusedTopic)
                        {
                            lastFocusedTopic = focusedElement.parentNode.previousElementSibling.children[1];
                        }
                        lastFocusedCategory = lastFocusedTopic;
                        focusedElement = lastFocusedCategory;
                        focusedElement.classList.add("focused");
                    }
                }
                else if(focusedElement.parentNode.classList.contains("credit"))
                {
                    if(focusedElement.classList.contains("support"))
                    {
                        focusedElement.classList.remove("focused");

                        lastFocusedCreditItem = focusedElement.previousElementSibling;
                        focusedElement = lastFocusedCreditItem;
                        focusedElement.classList.add("focused");
                    }
                }
            }
        } break;

        case "d": case "l": case "ArrowRight": {
            if(focusedElement)
            {
                if(focusedElement.parentNode.classList.contains("references_container"))
                {
                    if(focusedIdentifier.nextElementSibling)
                    {
                        focusedIdentifier.classList.remove("focused");

                        lastFocusedIdentifier = focusedIdentifier.nextElementSibling;
                        focusedIdentifier = lastFocusedIdentifier;
                        focusedIdentifier.classList.add("focused");
                    }
                }
                else if(focusedElement.classList.contains("filter_content"))
                {
                    if(focusedElement.parentNode.classList.contains("filter_topics") &&
                        focusedElement.parentNode.nextElementSibling)
                    {
                        focusedElement.classList.remove("focused");
                        lastFocusedTopic = focusedElement;

                        if(!lastFocusedMedium)
                        {
                            lastFocusedMedium = focusedElement.parentNode.nextElementSibling.children[1];
                        }
                        lastFocusedCategory = lastFocusedMedium;
                        focusedElement = lastFocusedCategory;
                        focusedElement.classList.add("focused");
                    }
                }
                else if(focusedElement.parentNode.classList.contains("credit"))
                {
                    if(focusedElement.classList.contains("person") &&
                        focusedElement.nextElementSibling)
                    {
                        focusedElement.classList.remove("focused");

                        lastFocusedCreditItem = focusedElement.nextElementSibling;
                        focusedElement = lastFocusedCreditItem;
                        focusedElement.classList.add("focused");
                    }
                }
            }
        } break;

        case "x": case " ": {
            if(focusedElement && focusedElement.classList.contains("filter_content"))
            {
                filterItemToggle(focusedElement);
                if(focusedElement.nextElementSibling &&
                    focusedElement.nextElementSibling.classList.contains("filter_content"))
                {
                    focusedElement.classList.remove("focused");
                    if(focusedElement.parentNode.classList.contains("filter_topics"))
                    {
                        lastFocusedTopic = focusedElement.nextElementSibling;
                        lastFocusedCategory = lastFocusedTopic;
                    }
                    else
                    {
                        lastFocusedMedium = focusedElement.nextElementSibling;
                        lastFocusedCategory = lastFocusedMedium;
                    }
                    lastFocusedElement = lastFocusedCategory;
                    focusedElement = lastFocusedElement;
                    focusedElement.classList.add("focused");
                }
            }
        } break;

        case "X": case "capitalSpace": {
            if(focusedElement && focusedElement.classList.contains("filter_content"))
            {
                filterItemToggle(focusedElement);
                if(focusedElement.previousElementSibling &&
                    focusedElement.previousElementSibling.classList.contains("filter_content"))
                {
                    focusedElement.classList.remove("focused");
                    if(focusedElement.parentNode.classList.contains("filter_topics"))
                    {
                        lastFocusedTopic = focusedElement.previousElementSibling;
                        lastFocusedCategory = lastFocusedTopic;
                    }
                    else
                    {
                        lastFocusedMedium = focusedElement.previousElementSibling;
                        lastFocusedCategory = lastFocusedMedium;
                    }
                    lastFocusedElement = lastFocusedCategory;
                    focusedElement = lastFocusedElement;
                    focusedElement.classList.add("focused");
                }
            }
        } break;

        case "z": {
            toggleFilterMode();
        } break;

        case "v": {
            if(focusedElement && focusedElement.classList.contains("filter_content"))
            {
                invertFilter(focusedElement)
            }
        } break;

        case "V": {
            resetFilter();
        } break;

        case "?": {
            helpDocumentation.classList.toggle("visible");
        } break;

        case 'N':
        case 'D':
        case 'S': {
            player.jumpToNextMarker();
        } break;

        case 'P':
        case 'A':
        case 'W': {
            player.jumpToPrevMarker();
        } break;
        default: {
            gotKey = false;
        } break;
    }
    return gotKey;
}

function applyFilter() {
    if(filterMode == "exclusive")
    {
        for(var i = 0; i < testMarkers.length; ++i)
        {
            var testCategories = testMarkers[i].classList;
            for(var j = 0; j < testCategories.length; ++j)
            {
                if((testCategories[j].startsWith("off_")) && !testMarkers[i].classList.contains("skip"))
                {
                    testMarkers[i].classList.add("skip");
                }
            }
        }
    }
    else
    {
        for(var i = 0; i < testMarkers.length; ++i)
        {
            var testCategories = testMarkers[i].classList;
            for(var j = 0; j < testCategories.length; ++j)
            {
                if((testCategories[j] in filterState || testCategories[j].startsWith("cat_")) && testMarkers[i].classList.contains("skip"))
                {
                    testMarkers[i].classList.remove("skip");
                }
            }
        }
    }
}

function toggleFilterMode() {
    if(filterMode == "inclusive")
    {
        filterModeElement.classList.remove("inclusive");
        filterModeElement.classList.add("exclusive");
        filterMode = "exclusive";
    }
    else
    {
        filterModeElement.classList.remove("exclusive");
        filterModeElement.classList.add("inclusive");
        filterMode = "inclusive";
    }
    applyFilter();
}

function filterItemToggle(filterItem) {
    var selectedCategory = filterItem.classList[1];
    filterState[selectedCategory].off = !filterState[selectedCategory].off;

    if(filterState[selectedCategory].off)
    {
        filterItem.classList.add("off");
        if(!filterItem.parentNode.classList.contains("filter_media"))
        {
            filterItem.querySelector(".icon").style.backgroundColor = "transparent";
        }
        var testMarkers = document.querySelectorAll(".marker." + selectedCategory + ", .marker.cat_" + selectedCategory);
        for(var j = 0; j < testMarkers.length; ++j)
        {
            if(filterState[selectedCategory].type == "topic")
            {
                testMarkers[j].classList.remove("cat_" + selectedCategory);
                testMarkers[j].classList.add("off_" + selectedCategory);
                var markerCategories = testMarkers[j].querySelectorAll(".category." + selectedCategory);
                for(var k = 0; k < markerCategories.length; ++k)
                {
                    if(markerCategories[k].classList.contains(selectedCategory))
                    {
                        markerCategories[k].classList.add("off");
                        markerCategories[k].style.backgroundColor = "transparent";
                    }
                }
            }
            else
            {
                testMarkers[j].classList.remove(selectedCategory);
                testMarkers[j].classList.add("off_" + selectedCategory);
            }

            Skipping = 1;
            if(filterMode == "exclusive")
            {
                testMarkers[j].classList.add("skip");
            }
            else
            {
                var markerClasses = testMarkers[j].classList;
                for(var k = 0; k < markerClasses.length; ++k)
                {
                    if(markerClasses[k] in filterState || markerClasses[k].replace(/^cat_/, "") in filterState)
                    {
                        Skipping = 0;
                    }
                }
                if(Skipping)
                {
                    testMarkers[j].classList.add("skip");
                }
            }

        }
    }
    else
    {
        filterItem.classList.remove("off");
        if(!filterItem.parentNode.classList.contains("filter_media"))
        {
            filterItem.querySelector(".icon").style.backgroundColor = getComputedStyle(filterItem.querySelector(".icon")).getPropertyValue("border-color");
        }
        setDotLightness(filterItem.querySelector(".icon"));
        var testMarkers = document.querySelectorAll(".marker.off_" + selectedCategory);
        for(var j = 0; j < testMarkers.length; ++j)
        {
            if(filterState[selectedCategory].type == "topic")
            {
                testMarkers[j].classList.remove("off_" + selectedCategory);
                testMarkers[j].classList.add("cat_" + selectedCategory);
                var markerCategories = testMarkers[j].querySelectorAll(".category." + selectedCategory);
                for(var k = 0; k < markerCategories.length; ++k)
                {
                    if(markerCategories[k].classList.contains(selectedCategory))
                    {
                        markerCategories[k].classList.remove("off");
                        markerCategories[k].style.backgroundColor = getComputedStyle(markerCategories[k]).getPropertyValue("border-color");
                        setDotLightness(markerCategories[k]);
                    }
                }
            }
            else
            {
                testMarkers[j].classList.remove("off_" + selectedCategory);
                testMarkers[j].classList.add(selectedCategory);
            }

            Skipping = 0;
            if(filterMode == "inclusive")
            {
                testMarkers[j].classList.remove("skip");
            }
            else
            {
                var markerClasses = testMarkers[j].classList;
                for(var k = 0; k < markerClasses.length; ++k)
                {
                    if(markerClasses[k].startsWith("off_"))
                    {
                        Skipping = 1;
                    }
                }
                if(!Skipping)
                {
                    testMarkers[j].classList.remove("skip");
                }
            }
        }
    }
}

function resetFilter() {
    for(i in filterItems)
    {
        if(filterItems[i].classList && filterItems[i].classList.contains("off"))
        {
            filterItemToggle(filterItems[i]);
        }
    }
    if(filterMode == "exclusive")
    {
        toggleFilterMode();
    }
}

function invertFilter(focusedElement) {
    var siblings = focusedElement.parentNode.querySelectorAll(".filter_content");
    for(i in siblings)
    {
        if(siblings[i].classList)
        {
            filterItemToggle(siblings[i]);
        }
    }
}

function resetFade() {
    filter.classList.remove("responsible");
    filter.querySelector(".filter_mode").classList.remove("responsible");
    var responsibleCategories = filter.querySelectorAll(".filter_content.responsible");
    for(var i = 0; i < responsibleCategories.length; ++i)
    {
        responsibleCategories[i].classList.remove("responsible");
    }
}

function onRefChanged(ref, element) {
    if(element.classList.contains("skip"))
    {
        if(!filter.classList.contains("responsible"))
        {
            filter.classList.add("responsible");
        }

        for(var selector = 0; selector < element.classList.length; ++selector)
        {
            if(element.classList[selector].startsWith("off_"))
            {
                if(!filter.querySelector(".filter_content." + element.classList[selector].replace(/^off_/, "")).classList.contains("responsible"))
                {
                    filter.querySelector(".filter_content." + element.classList[selector].replace(/^off_/, "")).classList.add("responsible");
                }
            }
            if((element.classList[selector].startsWith("cat_") || element.classList[selector] in filterState))
            {
                if(!filter.querySelector(".filter_mode").classList.add("responsible"))
                {
                    filter.querySelector(".filter_mode").classList.add("responsible");
                }
            }
            setTimeout(resetFade, 8000);
        }
        player.jumpToNextMarker();
        return;
    }

    for (var MenuIndex = 0; MenuIndex < sourceMenus.length; ++MenuIndex)
    {
        var SetMenu = 0;
        if (ref !== undefined && ref !== null) {
            var refElements = sourceMenus[MenuIndex].querySelectorAll(".refs .ref");
            var refs = ref.split(",");

            for (var i = 0; i < refElements.length; ++i) {
                if (refs.includes(refElements[i].getAttribute("data-id"))) {
                    refElements[i].classList.add("current");
                    SetMenu = 1;
                } else {
                    refElements[i].classList.remove("current");
                }
            }
            if(SetMenu) {
                sourceMenus[MenuIndex].classList.add("current");
            } else {
                sourceMenus[MenuIndex].classList.remove("current");
            }

        } else {
            sourceMenus[MenuIndex].classList.remove("current");
            var refs = sourceMenus[MenuIndex].querySelectorAll(".refs .ref");
            for (var i = 0; i < refs.length; ++i) {
                refs[i].classList.remove("current");
            }
        }
    }
}

function navigateFilter(filterItem) {
    if(filterItem != lastFocusedCategory)
    {
        lastFocusedCategory.classList.remove("focused");
        if(filterItem.parentNode.classList.contains("filter_topics"))
        {
            lastFocusedTopic = filterItem;
            lastFocusedCategory = lastFocusedTopic;
        }
        else
        {
            lastFocusedMedium = filterItem;
            lastFocusedCategory = lastFocusedMedium;
        }
        focusedElement = lastFocusedCategory;
        focusedElement.classList.add("focused");
    }
}

function mouseOverQuotes(quote) {
    if(focusedElement && quote != lastFocusedQuote)
    {
        focusedElement.classList.remove("focused");
        lastFocusedQuote = quote;
        focusedElement = lastFocusedQuote;
        focusedElement.classList.add("focused");
    }
}

function mouseOverReferences(reference) {
    if(focusedElement && reference != lastFocusedReference)
    {
        focusedElement.classList.remove("focused")
        lastFocusedReference = reference;
    }
    focusedElement = lastFocusedReference;
    focusedElement.classList.add("focused");

    var ourIdentifiers = reference.querySelector(".ref_indices").children;
    weWereLastFocused = false;
    for(var k = 0; k < ourIdentifiers.length; ++k)
    {
        if(ourIdentifiers[k] == lastFocusedIdentifier)
        {
            weWereLastFocused = true;
        }
    }
    if(!weWereLastFocused)
    {
        lastFocusedIdentifier.classList.remove("focused");
        lastFocusedIdentifier = reference.querySelector(".ref_indices").firstElementChild;
    }
    focusedIdentifer = lastFocusedIdentifier;
    focusedIdentifer.classList.add("focused");

    for(var l = 0; l < ourIdentifiers.length; ++l)
    {
        ourIdentifiers[l].addEventListener("mouseenter", function(ev) {
            if(this != lastFocusedIdentifier)
            {
                lastFocusedIdentifier.classList.remove("focused");
                lastFocusedIdentifier = this;
                lastFocusedIdentifier.classList.add("focused");
            }
        })
    }
}

function mouseSkipToTimecode(player, time, ev)
{
    player.setTime(parseInt(time, 10));
    player.play();
    ev.preventDefault();
    ev.stopPropagation();
    return false;
}

function handleMouseOverMenu(menu, eventType)
{
    if(!(menu.classList.contains("visible")) && eventType == "mouseenter" ||
        menu.classList.contains("visible") && eventType == "mouseleave")
    {
        if(menu.classList.contains("quotes"))
        {
            toggleMenuVisibility(quotesMenu);
        }
        else if(menu.classList.contains("references"))
        {
            toggleMenuVisibility(referencesMenu);
        }
        else if(menu.classList.contains("filter"))
        {
            toggleMenuVisibility(filterMenu);
        }
        else if(menu.classList.contains("credits"))
        {
            toggleMenuVisibility(creditsMenu);
        }
    }
    if(eventType == "click" && menu.classList.contains("help"))
    {
        helpDocumentation.classList.toggle("visible");
    }
}

function RGBtoHSL(colour)
{
	var rgb = colour.slice(4, -1).split(", ");
    var red = rgb[0];
    var green = rgb[1];
    var blue = rgb[2];
    var min = Math.min(red, green, blue);
    var max = Math.max(red, green, blue);
    var chroma = max - min;
    var hue = 0;
    if(max == red)
    {
        hue = ((green - blue) / chroma) % 6;
    }
    else if(max == green)
    {
        hue = ((blue - red) / chroma) + 2;
    }
    else if(max == blue)
    {
        hue = ((red - green) / chroma) + 4;
    }

    var saturation = chroma / 255 * 100;
    hue = (hue * 60) < 0 ? 360 + (hue * 60) : (hue * 60);

    return [hue, saturation]
}

function getBackgroundBrightness(element) {
    var colour = getComputedStyle(element).getPropertyValue("background-color");
    var depth = 0;
    while((colour == "transparent" || colour == "rgba(0, 0, 0, 0)") && depth <= 4)
    {
        element = element.parentNode;
        colour = getComputedStyle(element).getPropertyValue("background-color");
        ++depth;
    }
	var rgb = colour.slice(4, -1).split(", ");
	var result = Math.sqrt(rgb[0] * rgb[0] * .241 +
	rgb[1] * rgb[1] * .691 +
	rgb[2] * rgb[2] * .068);
    return result;
}

function setTextLightness(textElement)
{
    var textHue = textElement.getAttribute("data-hue");
    var textSaturation = textElement.getAttribute("data-saturation");
    if(getBackgroundBrightness(textElement.parentNode) < 127)
    {
        textElement.style.color = ("hsl(" + textHue + ", " + textSaturation + ", 76%)");
    }
    else
    {
        textElement.style.color = ("hsl(" + textHue + ", " + textSaturation + ", 24%)");
    }
}

function setDotLightness(topicDot)
{
    var Hue = RGBtoHSL(getComputedStyle(topicDot).getPropertyValue("background-color"))[0];
    var Saturation = RGBtoHSL(getComputedStyle(topicDot).getPropertyValue("background-color"))[1];
    if(getBackgroundBrightness(topicDot.parentNode) < 127)
    {
        topicDot.style.backgroundColor = ("hsl(" + Hue + ", " + Saturation + "%, 76%)");
        topicDot.style.borderColor = ("hsl(" + Hue + ", " + Saturation + "%, 76%)");
    }
    else
    {
        topicDot.style.backgroundColor = ("hsl(" + Hue + ", " + Saturation + "%, 24%)");
        topicDot.style.borderColor = ("hsl(" + Hue + ", " + Saturation + "%, 24%)");
    }
}