/**
 * Rocketship UI JS.
 *
 * Helper functions:
 * - checkScreenSize
 * - getBreakpoint
 * - optimizedResize
 * - scrollTo
 * - getScrollTop
 * - imgLoaded
 * - round
 **/

(function(Drupal, window, document) {
  "use strict";

  // Set namespace for UI javascript
  if (typeof window.rocketshipUI == 'undefined') {
    window.rocketshipUI = {};
  }

  const self = window.rocketshipUI;

  ///////////////////////////////////////////////////////////////////////
  // Cache variables available across the namespace
  ///////////////////////////////////////////////////////////////////////

  self.html = document.querySelector('html');
  self.body = document.querySelector('body');
  self.page = document.scrollingElement || document.documentElement;
  self.touch = false;
  self.screen = '';
  self.scrollStop = false;

  ///////////////////////////////////////////////////////////////////////
  // Behavior for Base: triggers
  ///////////////////////////////////////////////////////////////////////

  Drupal.behaviors.rocketshipUIHelpers = {
    attach: (context, settings) => {
      // Find out our current breakpoint.
      // saves it in a variable 'screen'.
      self.checkScreenSize();

      window.rocketshipUI.optimizedResize().add(() => {
        self.checkScreenSize();
      });

      // Add passiveSupported check for use with adding events
      self.checkPassiveSupported();
    }
  };

  ///////////////////////////////////////////////////////////////////////
  // Helper functions
  ///////////////////////////////////////////////////////////////////////

  /**
   * Add passiveSupported check for use with adding events.
   */
  self.checkPassiveSupported = () => {
    self.passiveSupported = false;

    try {
      var options = {
        get passive() {
          // This function will be called when the browser
          // attempts to access the passive property.
          self.passiveSupported = true;
        }
      };

      window.addEventListener("test", options, options);
      window.removeEventListener("test", options, options);
    } catch (err) {
      self.passiveSupported = false;
    }
  };

  /**
   * Find out if we're on a small device (phone).
   */
  self.checkScreenSize = () => {
    var currentBreakpoint = self.getBreakpoint();

    switch (currentBreakpoint) {
      case 'bp-xs':
        self.screen = 'xs';
        break;

      case 'bp-sm':
        self.screen = 'sm';
        break;

      case 'bp-md':
        self.screen = 'md';
        break;

      case 'bp-lg':
        self.screen = 'lg';
        break;
    }
  };

  /**
   * Get the current breakpoint.
   * Refers to the content of the body::after pseudo-element (set in set-breakpoints.scss)
   * call with window.rocketshipUI.getBreakpoint().
   */
  self.getBreakpoint = () => {
    var tag = window.getComputedStyle(document.body, '::after').getPropertyValue('content');
    // Firefox bugfix
    tag = tag.replace(/"/g, '');

    return tag.replace(/'/g, '');
  };

  /**
   * Debounce function so event handlers don't get called too many times
   * when fired in quick succession
   *
   * https://davidwalsh.name/javascript-debounce-function
   *
   * @param func
   * @param wait
   * @param immediate
   * @returns {Function}
   */
  // Example usage:
  //
  // var mouseupHandler = self.debounce(function(e) {
  //   // do stuff
  // }, 250);
  // document.body.addEventListener('mouseup', mouseupHandler, self.passiveSupported ? {capture: false, once: false, passive: true} : false);
  self.debounce = (func, wait, immediate) => {
    var timeout;
    return () => {
      var context = this,
        args = arguments;
      var later = () => {
        timeout = null;
        if (!immediate) func.apply(context, args);
      };
      var callNow = immediate && !timeout;
      clearTimeout(timeout);
      timeout = setTimeout(later, wait);
      if (callNow) func.apply(context, args);
    };
  };

  /**
   * Since resize events can fire at a high rate,
   * the event handler shouldn't execute computationally expensive operations
   * such as DOM modifications.
   * Instead, it is recommended to throttle the event using requestAnimationFrame,
   * setTimeout or customEvent
   *
   * Src: https://developer.mozilla.org/en-US/docs/Web/Events/resize
   *
   * Example:
   *
   * window.rocketshipUI.optimizedResize().add(() => {
   *   // do something
   * });
   */
  self.optimizedResize = () => {
    var callbacks = [],
      running = false;

    // Fired on resize event.
    function resize() {
      if (!running) {
        running = true;
        if (window.requestAnimationFrame) {
          window.requestAnimationFrame(runCallbacks);
        }
        else {
          setTimeout(runCallbacks, 250);
        }
      }
    }

    // Run the actual callbacks.
    function runCallbacks() {
      callbacks.forEach((callback) => {
        callback();
      });
      running = false;
    }

    // Adds callback to loop.
    function addCallback(callback) {
      if (callback) {
        callbacks.push(callback);
      }
    }
    return {
      // Public method to add additional callback.
      add: (callback) => {
        if (!callbacks.length) {
          window.addEventListener('resize', resize);
        }
        addCallback(callback);
      }
    };
  };

  /**
   * Function to scroll smoothly to an anchor in the page.
   *
   * @el = required!, element to scroll to.
   * @offset = not required, offset the landing position or set to 'bottom' to scroll to bottom of the element.
   * @speed = not required, speed with which to scroll.
   * @callback = callback function that can be invoked after scroll to is done.
   */
  self.scrollTo = (params) => {
    const scrollEvents = ['scroll', 'mousedown', 'wheel', 'DOMMouseScroll', 'mousewheel', 'keyup', 'touchmove'];
    params.pos = params.el.offsetTop;

    if (typeof params.offset === 'undefined') {
      params.offset = 0;
    }
    if (params.offset === 'bottom') {
      params.pos = params.el.offsetTop + params.el.offsetHeight;
    }
    if (typeof params.speed === 'undefined') {
      params.speed = 1000;
    }
    if (typeof params.callback === 'undefined') {
      params.callback = () => {};
    }

    // When user does any of these events, cancel all running animated scrolls.
    for (const event of scrollEvents) {
      self.page.addEventListener(event, self.scrollStop);
    }

    function animateScroll() {
      const start = self.page.scrollTop;
      const startTime = 'now' in window.performance ? performance.now() : new Date().getTime();

      function scroll() {
        const currentTime = 'now' in window.performance ? performance.now() : new Date().getTime();
        const time = Math.min(1, ((currentTime - startTime) / params.speed));

        self.page.scrollTop = Math.ceil((time * (params.pos + params.offset - start)) + start);

        if (self.page.scrollTop === params.pos + params.offset) {
          cancelAnimationFrame(animation);
          params.callback();
          for (const event of scrollEvents) {
            self.page.removeEventListener(event, self.scrollStop);
          }
        } else {
          requestAnimationFrame(scroll);
        }
      }

      requestAnimationFrame(scroll);
    }

    animateScroll();
  };

  /**
   * Cancels a running scrollTo call.
   *
   * https://stackoverflow.com/questions/18445590/jquery-animate-stop-scrolling-when-user-scrolls-manually#18445654
   */
  self.scrollStop = () => {
    // remove queued animation + don't complete current animation =>
    // abrupt end of the scroll.
    self.page.scrollTop = self.page.scrollTop;
  };

  /**
   * Get the top scroll position.
   */
  self.getScrollTop = () => {
    return window.pageYOffset || document.documentElement.scrollTop;
  };

  /**
   * Detect if all the images withing your object are loaded.
   *
   * No longer needs imagesLoaded plugin to work.
   */
  self.imgLoaded = (el, callback) => {
    var img = el.querySelectorAll('img'),
      iLength = img.length,
      iCount = 0;

    if (iLength) {
      img.forEach((image) => {
        // fires after images are loaded (if not cached)
        image.addEventListener('load', () => {
          iCount = iCount + 1;

          if (iCount === iLength) {
            // all images loaded so proceed
            callback();
          }
        });

        // in case images are cached
        // re-enter the load function in order to get to the callback.
        if (image.complete) {
          image.addEventListener('load', () => {
            iCount = iCount + 1;

            if (iCount === iLength) {
              // all images loaded so proceed
              callback();
            }
          });
          image.dispatchEvent(new Event('load'));
        }
      });
    }
    else {
      // no images, so we can proceed
      return callback();
    }
  };

  /**
   * Round numbers to x decimals.
   *
   * http://www.jacklmoore.com/notes/rounding-in-javascript/
   */
  self.round = (value, decimals) => {
    return Number(Math.round(value + 'e' + decimals) + 'e-' + decimals);
  };
})(Drupal, window, document);
