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

    // http://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(.1, Math.min(Math.abs(scrollY - scrollTargetY) / speed, .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
    }
}));
