/*! responsive-nav.js 1.0.34
* https://github.com/viljamis/responsive-nav.js
* http://responsive-nav.com
*
* Copyright (c) 2014 @viljamis
* Available under the MIT license
*/

(function (document, window, index) {
  // Index is used to keep multiple navs on the same page namespaced

  "use strict";

  var responsiveNav = function (el, options) {

    var computed = !!window.getComputedStyle;

    /**
    * getComputedStyle polyfill for old browsers
    */
    if (!computed) {
      window.getComputedStyle = function(el) {
        this.el = el;
        this.getPropertyValue = function(prop) {
          var re = /(\-([a-z]){1})/g;
          if (prop === "float") {
            prop = "styleFloat";
          }
          if (re.test(prop)) {
            prop = prop.replace(re, function () {
              return arguments[2].toUpperCase();
            });
          }
          return el.currentStyle[prop] ? el.currentStyle[prop] : null;
        };
        return this;
      };
    }
    /* exported addEvent, removeEvent, getChildren, setAttributes, addClass, removeClass, forEach */

    /**
    * Add Event
    * fn arg can be an object or a function, thanks to handleEvent
    * read more at: http://www.thecssninja.com/javascript/handleevent
    *
    * @param  {element}  element
    * @param  {event}    event
    * @param  {Function} fn
    * @param  {boolean}  bubbling
    */
    var addEvent = function (el, evt, fn, bubble) {
      if ("addEventListener" in el) {
        // BBOS6 doesn't support handleEvent, catch and polyfill
        try {
          el.addEventListener(evt, fn, bubble);
        } catch (e) {
          if (typeof fn === "object" && fn.handleEvent) {
            el.addEventListener(evt, function (e) {
              // Bind fn as this and set first arg as event object
              fn.handleEvent.call(fn, e);
            }, bubble);
          } else {
            throw e;
          }
        }
      } else if ("attachEvent" in el) {
        // check if the callback is an object and contains handleEvent
        if (typeof fn === "object" && fn.handleEvent) {
          el.attachEvent("on" + evt, function () {
            // Bind fn as this
            fn.handleEvent.call(fn);
          });
        } else {
          el.attachEvent("on" + evt, fn);
        }
      }
    },

    /**
    * Remove Event
    *
    * @param  {element}  element
    * @param  {event}    event
    * @param  {Function} fn
    * @param  {boolean}  bubbling
    */
    removeEvent = function (el, evt, fn, bubble) {
      if ("removeEventListener" in el) {
        try {
          el.removeEventListener(evt, fn, bubble);
        } catch (e) {
          if (typeof fn === "object" && fn.handleEvent) {
            el.removeEventListener(evt, function (e) {
              fn.handleEvent.call(fn, e);
            }, bubble);
          } else {
            throw e;
          }
        }
      } else if ("detachEvent" in el) {
        if (typeof fn === "object" && fn.handleEvent) {
          el.detachEvent("on" + evt, function () {
            fn.handleEvent.call(fn);
          });
        } else {
          el.detachEvent("on" + evt, fn);
        }
      }
    },

    /**
    * Get the children of any element
    *
    * @param  {element}
    * @return {array} Returns matching elements in an array
    */
    getChildren = function (e) {
      if (e.children.length < 1) {
        throw new Error("The Nav container has no containing elements");
      }
      // Store all children in array
      var children = [];
      // Loop through children and store in array if child != TextNode
      for (var i = 0; i < e.children.length; i++) {
        if (e.children[i].nodeType === 1) {
          children.push(e.children[i]);
        }
      }
      return children;
    },

    /**
    * Sets multiple attributes at once
    *
    * @param {element} element
    * @param {attrs}   attrs
    */
    setAttributes = function (el, attrs) {
      for (var key in attrs) {
        el.setAttribute(key, attrs[key]);
      }
    },

    /**
    * Adds a class to any element
    *
    * @param {element} element
    * @param {string}  class
    */
    addClass = function (el, cls) {
      if (el.className.indexOf(cls) !== 0) {
        el.className += " " + cls;
        el.className = el.className.replace(/(^\s*)|(\s*$)/g,"");
      }
    },

    /**
    * Remove a class from any element
    *
    * @param  {element} element
    * @param  {string}  class
    */
    removeClass = function (el, cls) {
      var reg = new RegExp("(\\s|^)" + cls + "(\\s|$)");
      el.className = el.className.replace(reg, " ").replace(/(^\s*)|(\s*$)/g,"");
    },

    /**
    * forEach method that passes back the stuff we need
    *
    * @param  {array}    array
    * @param  {Function} callback
    * @param  {scope}    scope
    */
    forEach = function (array, callback, scope) {
      for (var i = 0; i < array.length; i++) {
        callback.call(scope, i, array[i]);
      }
    };

    var nav,
    opts,
    navToggle,
    styleElement = document.createElement("style"),
    htmlEl = document.documentElement,
    hasAnimFinished,
    isMobile,
    navOpen;

    var ResponsiveNav = function (el, options) {
      var i;

      /**
      * Default options
      * @type {Object}
      */
      this.options = {
        animate: true,                    // Boolean: Use CSS3 transitions, true or false
        transition: 284,                  // Integer: Speed of the transition, in milliseconds
        label: "Menu",                    // String: Label for the navigation toggle
        insert: "before",                 // String: Insert the toggle before or after the navigation
        customToggle: "",                 // Selector: Specify the ID of a custom toggle
        closeOnNavClick: false,           // Boolean: Close the navigation when one of the links are clicked
        openPos: "relative",              // String: Position of the opened nav, relative or static
        navClass: "nav-collapse",         // String: Default CSS class. If changed, you need to edit the CSS too!
        navActiveClass: "js-nav-active",  // String: Class that is added to <html> element when nav is active
        jsClass: "js",                    // String: 'JS enabled' class which is added to <html> element
        init: function(){},               // Function: Init callback
        open: function(){},               // Function: Open callback
        close: function(){}               // Function: Close callback
      };

      // User defined options
      for (i in options) {
        this.options[i] = options[i];
      }

      // Adds "js" class for <html>
      addClass(htmlEl, this.options.jsClass);

      // Wrapper
      this.wrapperEl = el.replace("#", "");

      // Try selecting ID first
      if (document.getElementById(this.wrapperEl)) {
        this.wrapper = document.getElementById(this.wrapperEl);

        // If element with an ID doesn't exist, use querySelector
      } else if (document.querySelector(this.wrapperEl)) {
        this.wrapper = document.querySelector(this.wrapperEl);

        // If element doesn't exists, stop here.
      } else {
        throw new Error("The nav element you are trying to select doesn't exist");
      }

      // Inner wrapper
      this.wrapper.inner = getChildren(this.wrapper);

      // For minification
      opts = this.options;
      nav = this.wrapper;

      // Init
      this._init(this);
    };

    ResponsiveNav.prototype = {

      /**
      * Unattaches events and removes any classes that were added
      */
      destroy: function () {
        this._removeStyles();
        removeClass(nav, "closed");
        removeClass(nav, "opened");
        removeClass(nav, opts.navClass);
        removeClass(nav, opts.navClass + "-" + this.index);
        removeClass(htmlEl, opts.navActiveClass);
        nav.removeAttribute("style");
        nav.removeAttribute("aria-hidden");

        removeEvent(window, "resize", this, false);
        removeEvent(window, "focus", this, false);
        removeEvent(document.body, "touchmove", this, false);
        removeEvent(navToggle, "touchstart", this, false);
        removeEvent(navToggle, "touchend", this, false);
        removeEvent(navToggle, "mouseup", this, false);
        removeEvent(navToggle, "keyup", this, false);
        removeEvent(navToggle, "click", this, false);

        if (!opts.customToggle) {
          navToggle.parentNode.removeChild(navToggle);
        } else {
          navToggle.removeAttribute("aria-hidden");
        }
      },

      /**
      * Toggles the navigation open/close
      */
      toggle: function () {
        if (hasAnimFinished === true) {
          if (!navOpen) {
            this.open();
          } else {
            this.close();
          }

          // Enable pointer events again
          this._enablePointerEvents();
        }
      },

      /**
      * Opens the navigation
      */
      open: function () {
        if (!navOpen) {
          removeClass(nav, "closed");
          addClass(nav, "opened");
          addClass(htmlEl, opts.navActiveClass);
          addClass(navToggle, "active");
          nav.style.position = opts.openPos;
          setAttributes(nav, {"aria-hidden": "false"});
          navOpen = true;
          opts.open();
        }
      },

      /**
      * Closes the navigation
      */
      close: function () {
        if (navOpen) {
          addClass(nav, "closed");
          removeClass(nav, "opened");
          removeClass(htmlEl, opts.navActiveClass);
          removeClass(navToggle, "active");
          setAttributes(nav, {"aria-hidden": "true"});

          // If animations are enabled, wait until they finish
          if (opts.animate) {
            hasAnimFinished = false;
            setTimeout(function () {
              nav.style.position = "absolute";
              hasAnimFinished = true;
            }, opts.transition + 10);

            // Animations aren't enabled, we can do these immediately
          } else {
            nav.style.position = "absolute";
          }

          navOpen = false;
          opts.close();
        }
      },

      /**
      * Resize is called on window resize and orientation change.
      * It initializes the CSS styles and height calculations.
      */
      resize: function () {

        // Resize watches navigation toggle's display state
        if (window.getComputedStyle(navToggle, null).getPropertyValue("display") !== "none") {

          isMobile = true;
          setAttributes(navToggle, {"aria-hidden": "false"});

          // If the navigation is hidden
          if (nav.className.match(/(^|\s)closed(\s|$)/)) {
            setAttributes(nav, {"aria-hidden": "true"});
            nav.style.position = "absolute";
          }

          this._createStyles();
          this._calcHeight();
        } else {

          isMobile = false;
          setAttributes(navToggle, {"aria-hidden": "true"});
          setAttributes(nav, {"aria-hidden": "false"});
          nav.style.position = opts.openPos;
          this._removeStyles();
        }
      },

      /**
      * Takes care of all even handling
      *
      * @param  {event} event
      * @return {type} returns the type of event that should be used
      */
      handleEvent: function (e) {
        var evt = e || window.event;

        switch (evt.type) {
          case "touchstart":
            this._onTouchStart(evt);
            break;
            case "touchmove":
              this._onTouchMove(evt);
              break;
              case "touchend":
                case "mouseup":
                  this._onTouchEnd(evt);
                  break;
                  case "click":
                    this._preventDefault(evt);
                    break;
                    case "keyup":
                      this._onKeyUp(evt);
                      break;
                      case "focus":
                        case "resize":
                          this.resize(evt);
                          break;
                        }
                      },

                      /**
                      * Initializes the widget
                      */
                      _init: function () {
                        this.index = index++;

                        addClass(nav, opts.navClass);
                        addClass(nav, opts.navClass + "-" + this.index);
                        addClass(nav, "closed");
                        hasAnimFinished = true;
                        navOpen = false;

                        this._closeOnNavClick();
                        this._createToggle();
                        this._transitions();
                        this.resize();

                        /**
                        * On IE8 the resize event triggers too early for some reason
                        * so it's called here again on init to make sure all the
                        * calculated styles are correct.
                        */
                        var self = this;
                        setTimeout(function () {
                          self.resize();
                        }, 20);

                        addEvent(window, "resize", this, false);
                        addEvent(window, "focus", this, false);
                        addEvent(document.body, "touchmove", this, false);
                        addEvent(navToggle, "touchstart", this, false);
                        addEvent(navToggle, "touchend", this, false);
                        addEvent(navToggle, "mouseup", this, false);
                        addEvent(navToggle, "keyup", this, false);
                        addEvent(navToggle, "click", this, false);

                        /**
                        * Init callback here
                        */
                        opts.init();
                      },

                      /**
                      * Creates Styles to the <head>
                      */
                      _createStyles: function () {
                        if (!styleElement.parentNode) {
                          styleElement.type = "text/css";
                          document.getElementsByTagName("head")[0].appendChild(styleElement);
                        }
                      },

                      /**
                      * Removes styles from the <head>
                      */
                      _removeStyles: function () {
                        if (styleElement.parentNode) {
                          styleElement.parentNode.removeChild(styleElement);
                        }
                      },

                      /**
                      * Creates Navigation Toggle
                      */
                      _createToggle: function () {

                        // If there's no toggle, let's create one
                        if (!opts.customToggle) {
                          var toggle = document.createElement("a");
                          toggle.innerHTML = opts.label;
                          setAttributes(toggle, {
                            "href": "#",
                            "class": "nav-toggle"
                          });

                          // Determine where to insert the toggle
                          if (opts.insert === "after") {
                            nav.parentNode.insertBefore(toggle, nav.nextSibling);
                          } else {
                            nav.parentNode.insertBefore(toggle, nav);
                          }

                          navToggle = toggle;

                          // There is a toggle already, let's use that one
                        } else {
                          var toggleEl = opts.customToggle.replace("#", "");

                          if (document.getElementById(toggleEl)) {
                            navToggle = document.getElementById(toggleEl);
                          } else if (document.querySelector(toggleEl)) {
                            navToggle = document.querySelector(toggleEl);
                          } else {
                            throw new Error("The custom nav toggle you are trying to select doesn't exist");
                          }
                        }
                      },

                      /**
                      * Closes the navigation when a link inside is clicked
                      */
                      _closeOnNavClick: function () {
                        if (opts.closeOnNavClick) {
                          var links = nav.getElementsByTagName("a"),
                          self = this;
                          forEach(links, function (i, el) {
                            addEvent(links[i], "click", function () {
                              if (isMobile) {
                                self.toggle();
                              }
                            }, false);
                          });
                        }
                      },

                      /**
                      * Prevents the default tap functionality
                      *
                      * @param  {event} event
                      */
                      _preventDefault: function(e) {
                        if (e.preventDefault) {
                          if (e.stopImmediatePropagation) {
                            e.stopImmediatePropagation();
                          }
                          e.preventDefault();
                          e.stopPropagation();
                          return false;

                          // This is strictly for old IE
                        } else {
                          e.returnValue = false;
                        }
                      },

                      /**
                      * On touch start get the location of the touch
                      * and disable pointer events on the body.
                      *
                      * @param  {event} event
                      */
                      _onTouchStart: function (e) {
                        this._preventDefault(e);
                        addClass(document.body, "disable-pointer-events");
                        this.startX = e.touches[0].clientX;
                        this.startY = e.touches[0].clientY;
                        this.touchHasMoved = false;

                        /**
                        * We remove mouseup event completely here to avoid
                        * double triggering of events.
                        */
                        removeEvent(navToggle, "mouseup", this, false);
                      },

                      /**
                      * Check if the user is scrolling instead of tapping and
                      * re-enable pointer events if movement happed.
                      *
                      * @param  {event} event
                      */
                      _onTouchMove: function (e) {
                        if (Math.abs(e.touches[0].clientX - this.startX) > 10 ||
                          Math.abs(e.touches[0].clientY - this.startY) > 10) {
                            this._enablePointerEvents();
                            this.touchHasMoved = true;
                          }
                        },

                        /**
                        * On touch end toggle either the whole navigation or
                        * a sub-navigation depending on which one was tapped.
                        *
                        * @param  {event} event
                        */
                        _onTouchEnd: function (e) {
                          this._preventDefault(e);
                          if (!isMobile) {
                            return;
                          }

                          // If the user isn't scrolling
                          if (!this.touchHasMoved) {

                            // If the event type is touch
                            if (e.type === "touchend") {
                              this.toggle();
                              if (opts.insert === "after") {
                                setTimeout(function () {
                                  removeClass(document.body, "disable-pointer-events");
                                }, opts.transition + 300);
                              }
                              return;

                              // Event type was click, not touch
                            } else {
                              var evt = e || window.event;

                              // If it isn't a right click, do toggling
                              if (!(evt.which === 3 || evt.button === 2)) {
                                this.toggle();
                              }
                            }
                          }
                        },

                        /**
                        * For keyboard accessibility, toggle the navigation on Enter
                        * keypress too (also sub-navigation is keyboard accessible
                        * which explains the complexity here)
                        *
                        * @param  {event} event
                        */
                        _onKeyUp: function (e) {
                          var evt = e || window.event;
                          if (evt.keyCode === 13) {
                            this.toggle();
                          }
                        },

                        /**
                        * Enable pointer events
                        */
                        _enablePointerEvents: function () {
                          removeClass(document.body, "disable-pointer-events");
                        },

                        /**
                        * Adds the needed CSS transitions if animations are enabled
                        */
                        _transitions: function () {
                          if (opts.animate) {
                            var objStyle = nav.style,
                            transition = "max-height " + opts.transition + "ms";

                            objStyle.WebkitTransition = transition;
                            objStyle.MozTransition = transition;
                            objStyle.OTransition = transition;
                            objStyle.transition = transition;
                          }
                        },

                        /**
                        * Calculates the height of the navigation and then creates
                        * styles which are later added to the page <head>
                        */
                        _calcHeight: function () {
                          var savedHeight = 0;
                          for (var i = 0; i < nav.inner.length; i++) {
                            savedHeight += nav.inner[i].offsetHeight;
                          }

                          // Pointer event styles are also here since they might only be confusing inside the stylesheet
                          var innerStyles = "." + opts.jsClass + " ." + opts.navClass + "-" + this.index + ".opened{max-height:" + savedHeight + "px !important} ." + opts.jsClass + " .disable-pointer-events{pointer-events:none !important} ." + opts.jsClass + " ." + opts.navClass + "-" + this.index + ".opened.dropdown-active {max-height:9999px !important}";


                          if (styleElement.styleSheet) {
                            styleElement.styleSheet.cssText = innerStyles;
                          } else {
                            styleElement.innerHTML = innerStyles;
                          }

                          innerStyles = "";
                        }

                      };

                      /**
                      * Return new Responsive Nav
                      */
                      return new ResponsiveNav(el, options);

                    };

                    window.responsiveNav = responsiveNav;

                  }(document, window, 0));
