/**
 * Vanilla JS Smooth Scroll
 * Author: Bastian Fießinger
 * Version: 1.0.1
 */

'use strict';

HTMLAnchorElement.prototype.scrollToSmooth = function (settings) {

  if (typeof (settings) == 'undefined') {
    settings = {};
  }

  // Setup Default Settings Object
  let defaults = {
    speed: 400,
    easing: 'linear',
    callback: null,
    fixedHeader: null
  };

	/**
	 * Merge two or more objects together.
	 * @param   {Object}   objects  The objects to merge together
	 * @returns {Object}            Merged values of defaults and settings
	 */
  let extend = function () {
    var merged = {};
    Array.prototype.forEach.call(arguments, (obj) => {
      for (var key in obj) {
        if (!obj.hasOwnProperty(key)) return;
        merged[key] = obj[key];
      }
    });
    return merged;
  };

  // Available Easing Functions
  const easings = {
    linear(t) { return t; },
    easeInQuad(t) { return t * t; },
    easeOutQuad(t) { return t * (2 - t); },
    easeInOutQuad(t) { return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t; },
    easeInCubic(t) { return t * t * t; },
    easeOutCubic(t) { return (--t) * t * t + 1; },
    easeInOutCubic(t) { return t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1; },
    easeInQuart(t) { return t * t * t * t; },
    easeOutQuart(t) { return 1 - (--t) * t * t * t; },
    easeInOutQuart(t) { return t < 0.5 ? 8 * t * t * t * t : 1 - 8 * (--t) * t * t * t; },
    easeInQuint(t) { return t * t * t * t * t; },
    easeOutQuint(t) { return 1 + (--t) * t * t * t * t; },
    easeInOutQuint(t) { return t < 0.5 ? 16 * t * t * t * t * t : 1 + 16 * (--t) * t * t * t * t; },
    easeInElastic: function (t) { return (.04 - .04 / t) * Math.sin(25 * t) + 1 },
    easeOutElastic: function (t) { return .04 * t / (--t) * Math.sin(25 * t) },
    easeInOutElastic: function (t) { return (t -= .5) < 0 ? (.02 + .01 / t) * Math.sin(50 * t) : (.02 - .01 / t) * Math.sin(50 * t) + 1 }
  };

  // Build the Settings Object from Defaults and user defined settings
  settings = extend(defaults, settings || {});

  const reqAnimFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
  const cancelAnimFrame = window.cancelAnimationFrame || window.mozCancelAnimationFrame;
  
  // Get All Links with hashes based on the current URI
  const findHashLinks = () => {

    // Make sure to only apply to hash links
    if (this.href.indexOf(this.baseURI.replace(/\/+$/, '')) != -1 && this.href.indexOf('#') != -1 && this.hash != '') {
      this.addEventListener('click', clickHandler);
    }

  }

  const clickHandler = (e) => {

    // Prevent Default Behaviour of how the browser would treat the click event
    e.preventDefault();

    // Evaluate the current Target Element
    const currentTargetIdSliced = this.hash.slice(1);
    let currentTarget = document.getElementById(currentTargetIdSliced);

    // If no ID for the current target is present try to find an element with it's name attribute.
    currentTarget = currentTarget ? currentTarget : document.querySelector('[name="' + currentTargetIdSliced + '"]');
    if (!currentTarget) return;

    const windowStartPos = window.pageYOffset;
    const startTime = 'now' in window.performance ? performance.now() : new Date().getTime();

    const docHeight = Math.max(document.body.scrollHeight, document.body.offsetHeight, document.documentElement.clientHeight, document.documentElement.scrollHeight, document.documentElement.offsetHeight);
    const winHeight = window.innerHeight || document.documentElement.clientHeight || document.getElementsByTagName('body')[0].clientHeight;
    const targetOffset = currentTarget.offsetTop;
    let distToScroll = Math.round(docHeight - targetOffset < winHeight ? docHeight - winHeight : targetOffset);

    if (settings.fixedHeader !== null) {

      const fixedHeader = document.querySelector(settings.fixedHeader);
      if (fixedHeader.tagName) {
        distToScroll -= Math.round(fixedHeader.getBoundingClientRect().height);
      }

    }

    // Distance can't be negative
    distToScroll = (distToScroll < 0) ? 0 : distToScroll;

    scrollToTarget(0, distToScroll, windowStartPos, startTime);

  }
  
  // Animate the ScrollTop
  const scrollToTarget = (timestamp, distToScroll, startPos, startTime) => {

    const now = 'now' in window.performance ? performance.now() : new Date().getTime();
    const time = Math.min(1, ((now - startTime) / settings.speed));
    const timeFunction = easings[settings.easing](time);
    let curScrollPosition = window.pageYOffset || document.body.scrollTop || document.documentElement.scrollTop;
    
    window.scroll(0, Math.ceil((timeFunction * (distToScroll - startPos)) + startPos));
    
    if (Math.ceil(curScrollPosition) === distToScroll) {
      if (settings.callback) {
        settings.callback();
      }

      // Stop when the element is reached
      return;
    }

    let scrollAnimationFrame = reqAnimFrame((timestamp) => {
      scrollToTarget(timestamp, distToScroll, startPos, startTime);
    });

    // Cancel Animation on User Scroll Interaction
    let cancelAnimationOnEvents = ['mousewheel', 'wheel', 'touchstart'];
    cancelAnimationOnEvents.forEach((ev) => {
      window.addEventListener(ev, () => {
        cancelAnimationFrame( scrollAnimationFrame );
      });
    });
    
  }
  
  const BindEvents = () => {

    window.addEventListener('load', findHashLinks);
    
  }

  this.init = () => {
    // Bind Events
    BindEvents.call(this);
  };

  this.init();

}

document.addEventListener('DOMContentLoaded', function () {

  let links = document.getElementsByTagName('a');

  [].forEach.call(links, (el) => {
    el.scrollToSmooth({
      speed: 500,
      easing: 'easeInOutQuint',
      callback: function () { console.log('we reached it!'); },
      fixedHeader: null
    });
  });
  
  // Init Highlight JS
  hljs.initHighlightingOnLoad();

});