;(function(root, factory) {
    if (typeof define === "function" && define.amd) {
        // AMD. Register as an anonymous module.
        define([], factory)
    } else if (typeof module === "object" && module.exports) {
        // Node. Does not work with strict CommonJS, but
        // only CommonJS-like environments that support module.exports,
        // like Node.
        module.exports = factory()
    } else {
        // Browser globals (root is window)
        root.diagrams_behavior = factory()
    }
})(this, function() {
    /**
     * @param [scrollingEnabled=true] {boolean} - Is the scrolling from a non-terminal usage to it's definition
     * enabled. it is enabled by default, but this flow is not relevant in all use cases (playground) and thus
     * it is parametrized.
     */
    function initDiagramsBehavior(scrollingEnabled) {
        if (scrollingEnabled === undefined) {
            scrollingEnabled = true
        }

        var diagramHeaders = toArr(
            document.getElementsByClassName("diagramHeader")
        )
        diagramHeaders.forEach(function(header) {
            header.addEventListener(
                "mouseover",
                toggleNonTerminalUsageAndDef_fromHeader
            )
            header.addEventListener(
                "mouseout",
                toggleNonTerminalUsageAndDef_fromHeader
            )
        })

        var noneTerminals = toArr(
            document.getElementsByClassName("non-terminal")
        )
        var noneTerminalsText = findDomChildrenByTagName(noneTerminals, "text")
        noneTerminalsText.forEach(function(nonTerminal) {
            nonTerminal.addEventListener(
                "mouseover",
                toggleNonTerminalUsageAndDef_fromNoneTerminal
            )
            nonTerminal.addEventListener(
                "mouseout",
                toggleNonTerminalUsageAndDef_fromNoneTerminal
            )

            if (scrollingEnabled) {
                nonTerminal.addEventListener("click", jumpToNoneTerminalDef)
            }
        })

        var terminals = toArr(document.getElementsByClassName("terminal"))
        var terminalsText = findDomChildrenByTagName(terminals, "text")
        terminalsText.forEach(function(terminal) {
            terminal.addEventListener("mouseover", toggleTerminalUsage)
            terminal.addEventListener("mouseout", toggleTerminalUsage)
        })
    }

    function toggleTerminalUsage(mouseEvent) {
        var terminalName = mouseEvent.target.getAttribute("label")
        var rects = getUsageSvgRect(terminalName, "terminal", "label")
        toggleClassForNodes(rects, "diagramRectUsage")
    }

    function toggleNonTerminalUsageAndDef_fromNoneTerminal(mouseEvent) {
        var rectsHeaderAndRuleName = getUsageRectAndDefHeader(mouseEvent.target)
        toggleClassForNodes(rectsHeaderAndRuleName.rects, "diagramRectUsage")
        toggleClass(rectsHeaderAndRuleName.header, "diagramHeaderDef")
    }

    function jumpToNoneTerminalDef(mouseEvent) {
        var header = findHeader(mouseEvent.target.getAttribute("rulename"))
        scrollToY(header.offsetTop, 666, "easeInOutQuint")
    }

    function toggleNonTerminalUsageAndDef_fromHeader(mouseEvent) {
        toggleClass(mouseEvent.target, "diagramHeaderDef")
        // this does not work on an svg DOM element so its ok to use innerHTML.
        var definitionName = mouseEvent.target.innerHTML
        var rects = getUsageSvgRect(definitionName, "non-terminal", "rulename")
        toggleClassForNodes(rects, "diagramRectUsage")
    }

    function getUsageSvgRect(definitionName, className, attributeName) {
        var classDomElements = toArr(document.getElementsByClassName(className))
        var rects = findDomChildrenByTagName(classDomElements, "rect")
        return rects.filter(function(currRect) {
            var textNode = currRect.parentNode.getElementsByTagName("text")[0]
            return textNode.getAttribute(attributeName) === definitionName
        })
    }

    function findHeader(headerName) {
        var headers = toArr(document.getElementsByClassName("diagramHeader"))
        var header = headers.find(function(currHeader) {
            // this works on H2 dom elements and not SVG elements so innerHTML usage is safe.
            return currHeader.innerHTML === headerName
        })
        return header
    }

    function getUsageRectAndDefHeader(target) {
        var headerName = target.getAttribute("rulename")
        var rects = getUsageSvgRect(headerName, "non-terminal", "rulename")
        var header = findHeader(headerName)
        return {
            rects: rects,
            header: header,
            ruleName: headerName
        }
    }

    // utils

    // IE 10/11 does not support this on svg elements.
    // I'm uncertain I really care... :)
    // https://developer.mozilla.org/en-US/docs/Web/API/Element/classList
    function toggleClass(domNode, className) {
        if (domNode.classList.contains(className)) {
            domNode.classList.remove(className)
        } else {
            domNode.classList.add(className)
        }
    }

    function toggleClassForNodes(domNodes, className) {
        domNodes.forEach(function(currDomNode) {
            toggleClass(currDomNode, className)
        })
    }

    function toArr(htmlCollection) {
        return Array.prototype.slice.call(htmlCollection)
    }

    // first add raf shim
    // http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
    var requestAnimFrame = (function() {
        return (
            window.requestAnimationFrame ||
            window.webkitRequestAnimationFrame ||
            window.mozRequestAnimationFrame ||
            function(callback) {
                window.setTimeout(callback, 1000 / 60)
            }
        )
    })()

    // https://stackoverflow.com/questions/8917921/cross-browser-javascript-not-jquery-scroll-to-top-animation
    function scrollToY(scrollTargetY, speed, easing) {
        // scrollTargetY: the target scrollY property of the window
        // speed: time in pixels per second
        // easing: easing equation to use

        var scrollY = window.scrollY,
            scrollTargetY = scrollTargetY || 0,
            speed = speed || 2000,
            easing = easing || "easeOutSine",
            currentTime = 0

        // min time .1, max time .8 seconds
        var time = Math.max(
            0.1,
            Math.min(Math.abs(scrollY - scrollTargetY) / speed, 0.8)
        )

        // easing equations from https://github.com/danro/easing-js/blob/master/easing.js
        var PI_D2 = Math.PI / 2,
            easingEquations = {
                easeOutSine: function(pos) {
                    return Math.sin(pos * (Math.PI / 2))
                },
                easeInOutSine: function(pos) {
                    return -0.5 * (Math.cos(Math.PI * pos) - 1)
                },
                easeInOutQuint: function(pos) {
                    if ((pos /= 0.5) < 1) {
                        return 0.5 * Math.pow(pos, 5)
                    }
                    return 0.5 * (Math.pow(pos - 2, 5) + 2)
                }
            }

        // add animation loop
        function tick() {
            currentTime += 1 / 60

            var p = currentTime / time
            var t = easingEquations[easing](p)

            if (p < 1) {
                requestAnimFrame(tick)

                window.scrollTo(0, scrollY + (scrollTargetY - scrollY) * t)
            } else {
                window.scrollTo(0, scrollTargetY)
            }
        }

        // call it once to get started
        tick()
    }

    function findDomChildrenByTagName(domElements, tagName) {
        var elemsFound = []
        domElements.forEach(function(currDomNode) {
            toArr(currDomNode.children).forEach(function(currChild) {
                if (currChild.tagName === tagName) {
                    elemsFound.push(currChild)
                }
            })
        })
        return elemsFound
    }
    return {
        initDiagramsBehavior: initDiagramsBehavior
    }
})
