/*!
 * viewport-units-buggyfill v0.6.2
 * @web: https://github.com/rodneyrehm/viewport-units-buggyfill/
 * @author: Rodney Rehm - http://rodneyrehm.de/en/
 */

(function() {
  (function(root, factory) {
    'use strict';
    if (typeof define === 'function' && define.amd) {
      // AMD. Register as an anonymous module.
      define([], factory);
    } else if (typeof exports === 'object') {
      // Node. Does not work with strict CommonJS, but
      // only CommonJS-like enviroments that support module.exports,
      // like Node.
      module.exports = factory();
    } else {
      // Browser globals (root is window)
      root.viewportUnitsBuggyfill = factory();
    }
  }(this, function() {
    'use strict';
    /* global document, window, navigator, location, XMLHttpRequest, XDomainRequest, CustomEvent */

    var initialized = false;
    var options;
    var userAgent = window.navigator.userAgent;
    var viewportUnitExpression = /([+-]?[0-9.]+)(vh|vw|vmin|vmax)/g;
    var urlExpression = /(https?:)?\/\//
    var forEach = [].forEach;
    var dimensions;
    var declarations;
    var styleNode;
    var isBuggyIE = /MSIE [0-9]\./i.test(userAgent);
    var isOldIE = /MSIE [0-8]\./i.test(userAgent);
    var isOperaMini = userAgent.indexOf('Opera Mini') > -1;

    var isMobileSafari = /(iPhone|iPod|iPad).+AppleWebKit/i.test(userAgent) && (function() {
      // Regexp for iOS-version tested against the following userAgent strings:
      // Example WebView UserAgents:
      // * iOS Chrome on iOS8: "Mozilla/5.0 (iPad; CPU OS 8_1 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) CriOS/39.0.2171.50 Mobile/12B410 Safari/600.1.4"
      // * iOS Facebook on iOS7: "Mozilla/5.0 (iPhone; CPU iPhone OS 7_1_1 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Mobile/11D201 [FBAN/FBIOS;FBAV/12.1.0.24.20; FBBV/3214247; FBDV/iPhone6,1;FBMD/iPhone; FBSN/iPhone OS;FBSV/7.1.1; FBSS/2; FBCR/AT&T;FBID/phone;FBLC/en_US;FBOP/5]"
      // Example Safari UserAgents:
      // * Safari iOS8: "Mozilla/5.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/600.1.3 (KHTML, like Gecko) Version/8.0 Mobile/12A4345d Safari/600.1.4"
      // * Safari iOS7: "Mozilla/5.0 (iPhone; CPU iPhone OS 7_0 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A4449d Safari/9537.53"
      var iOSversion = userAgent.match(/OS (\d+)/);
      // viewport units work fine in mobile Safari and webView on iOS 8+
      return iOSversion && iOSversion.length > 1 && parseInt(iOSversion[1]) < 10;
    })();

    var isBadStockAndroid = (function() {
      // Android stock browser test derived from
      // http://stackoverflow.com/questions/24926221/distinguish-android-chrome-from-stock-browser-stock-browsers-user-agent-contai
      var isAndroid = userAgent.indexOf(' Android ') > -1;
      if (!isAndroid) {
        return false;
      }

      var isStockAndroid = userAgent.indexOf('Version/') > -1;
      if (!isStockAndroid) {
        return false;
      }

      var versionNumber = parseFloat((userAgent.match('Android ([0-9.]+)') || [])[1]);
      // anything below 4.4 uses WebKit without *any* viewport support,
      // 4.4 has issues with viewport units within calc()
      return versionNumber <= 4.4;
    })();

    // added check for IE10, IE11 and Edge < 20, since it *still* doesn't understand vmax
    // http://caniuse.com/#feat=viewport-units
    if (!isBuggyIE) {
      isBuggyIE = !!navigator.userAgent.match(/MSIE 10\.|Trident.*rv[ :]*1[01]\.| Edge\/1\d\./);
    }

    // Polyfill for creating CustomEvents on IE9/10/11
    // from https://github.com/krambuhl/custom-event-polyfill
    try {
      // eslint-disable-next-line no-new, no-use-before-define
      new CustomEvent('test');
    } catch (e) {
      var CustomEvent = function(event, params) {
        var evt;
        params = params || {
          bubbles: false,
          cancelable: false,
          detail: undefined,
        };

        evt = document.createEvent('CustomEvent');
        evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
        return evt;
      };
      CustomEvent.prototype = window.Event.prototype;
      window.CustomEvent = CustomEvent; // expose definition to window
    }

    function debounce(func, wait) {
      var timeout;
      return function() {
        var context = this;
        var args = arguments;
        var callback = function() {
          func.apply(context, args);
        };

        clearTimeout(timeout);
        timeout = setTimeout(callback, wait);
      };
    }

    // from http://stackoverflow.com/questions/326069/how-to-identify-if-a-webpage-is-being-loaded-inside-an-iframe-or-directly-into-t
    function inIframe() {
      try {
        return window.self !== window.top;
      } catch (e) {
        return true;
      }
    }

    function initialize(initOptions) {
      if (initialized) {
        return;
      }

      if (initOptions === true) {
        initOptions = {
          force: true,
        };
      }

      options = initOptions || {};
      options.isMobileSafari = isMobileSafari;
      options.isBadStockAndroid = isBadStockAndroid;

      if (options.ignoreVmax && !options.force && !isOldIE) {
        // modern IE (10 and up) do not support vmin/vmax,
        // but chances are this unit is not even used, so
        // allow overwriting the "hacktivation"
        // https://github.com/rodneyrehm/viewport-units-buggyfill/issues/56
        isBuggyIE = false;
      }

      if (isOldIE || (!options.force && !isMobileSafari && !isBuggyIE && !isBadStockAndroid && !isOperaMini && (!options.hacks || !options.hacks.required(options)))) {
        // this buggyfill only applies to mobile safari, IE9-10 and the Stock Android Browser.
        if (window.console && isOldIE) {
          console.info('viewport-units-buggyfill requires a proper CSSOM and basic viewport unit support, which are not available in IE8 and below');
        }

        return {
          init: function() {},
        };
      }

      // fire a custom event that buggyfill was initialize
      window.dispatchEvent(new CustomEvent('viewport-units-buggyfill-init'));

      options.hacks && options.hacks.initialize(options);

      initialized = true;
      styleNode = document.createElement('style');
      styleNode.id = 'patched-viewport';
      document[options.appendToBody ? 'body' : 'head'].appendChild(styleNode);

      // Issue #6: Cross Origin Stylesheets are not accessible through CSSOM,
      // therefore download and inject them as <style> to circumvent SOP.
      importCrossOriginLinks(function() {
        var _refresh = debounce(refresh, options.refreshDebounceWait || 100);
        // doing a full refresh rather than updateStyles because an orientationchange
        // could activate different stylesheets
        window.addEventListener('orientationchange', _refresh, true);
        // orientationchange might have happened while in a different window
        window.addEventListener('pageshow', _refresh, true);

        if (options.force || isBuggyIE || inIframe()) {
          window.addEventListener('resize', _refresh, true);
          options._listeningToResize = true;
        }

        options.hacks && options.hacks.initializeEvents(options, refresh, _refresh);

        refresh();
      });
    }

    function updateStyles() {
      styleNode.textContent = getReplacedViewportUnits();
      // move to the end in case inline <style>s were added dynamically
      styleNode.parentNode.appendChild(styleNode);
      // fire a custom event that styles were updated
      window.dispatchEvent(new CustomEvent('viewport-units-buggyfill-style'));
    }

    function refresh() {
      if (!initialized) {
        return;
      }

      findProperties();

      // iOS Safari will report window.innerWidth and .innerHeight as 0 unless a timeout is used here.
      // TODO: figure out WHY innerWidth === 0
      setTimeout(function() {
        updateStyles();
      }, 1);
    }

    // http://stackoverflow.com/a/23613052
    function processStylesheet(ss) {
      // cssRules respects same-origin policy, as per
      // https://code.google.com/p/chromium/issues/detail?id=49001#c10.
      try {
        if (!ss.cssRules) { return; }
      } catch (e) {
        if (e.name !== 'SecurityError') { throw e; }
        return;
      }
      // ss.cssRules is available, so proceed with desired operations.
      var rules = [];
      for (var i = 0; i < ss.cssRules.length; i++) {
        var rule = ss.cssRules[i];
        rules.push(rule);
      }
      return rules;
    }

    function findProperties() {
      declarations = [];
      forEach.call(document.styleSheets, function(sheet) {
        var cssRules = processStylesheet(sheet);

        if (!cssRules || sheet.ownerNode.id === 'patched-viewport' || sheet.ownerNode.getAttribute('data-viewport-units-buggyfill') === 'ignore') {
          // skip entire sheet because no rules are present, it's supposed to be ignored or it's the target-element of the buggyfill
          return;
        }

        if (sheet.media && sheet.media.mediaText && window.matchMedia && !window.matchMedia(sheet.media.mediaText).matches) {
          // skip entire sheet because media attribute doesn't match
          return;
        }

        forEach.call(cssRules, findDeclarations);
      });

      return declarations;
    }

    function findDeclarations(rule) {
      if (rule.type === 7) {
        var value;

        // there may be a case where accessing cssText throws an error.
        // I could not reproduce this issue, but the worst that can happen
        // this way is an animation not running properly.
        // not awesome, but probably better than a script error
        // see https://github.com/rodneyrehm/viewport-units-buggyfill/issues/21
        try {
          value = rule.cssText;
        } catch (e) {
          return;
        }

        viewportUnitExpression.lastIndex = 0;
        if (viewportUnitExpression.test(value) && !urlExpression.test(value)) {
          // KeyframesRule does not have a CSS-PropertyName
          declarations.push([rule, null, value]);
          options.hacks && options.hacks.findDeclarations(declarations, rule, null, value);
        }

        return;
      }

      if (!rule.style) {
        if (!rule.cssRules) {
          return;
        }

        forEach.call(rule.cssRules, function(_rule) {
          findDeclarations(_rule);
        });

        return;
      }

      forEach.call(rule.style, function(name) {
        var value = rule.style.getPropertyValue(name);
        // preserve those !important rules
        if (rule.style.getPropertyPriority(name)) {
          value += ' !important';
        }

        viewportUnitExpression.lastIndex = 0;
        if (viewportUnitExpression.test(value)) {
          declarations.push([rule, name, value]);
          options.hacks && options.hacks.findDeclarations(declarations, rule, name, value);
        }
      });
    }

    function getReplacedViewportUnits() {
      dimensions = getViewport();

      var css = [];
      var buffer = [];
      var open;
      var close;

      declarations.forEach(function(item) {
        var _item = overwriteDeclaration.apply(null, item);
        var _open = _item.selector.length ? (_item.selector.join(' {\n') + ' {\n') : '';
        var _close = new Array(_item.selector.length + 1).join('\n}');

        if (!_open || _open !== open) {
          if (buffer.length) {
            css.push(open + buffer.join('\n') + close);
            buffer.length = 0;
          }

          if (_open) {
            open = _open;
            close = _close;
            buffer.push(_item.content);
          } else {
            css.push(_item.content);
            open = null;
            close = null;
          }

          return;
        }

        if (_open && !open) {
          open = _open;
          close = _close;
        }

        buffer.push(_item.content);
      });

      if (buffer.length) {
        css.push(open + buffer.join('\n') + close);
      }

      // Opera Mini messes up on the content hack (it replaces the DOM node's innerHTML with the value).
      // This fixes it. We test for Opera Mini only since it is the most expensive CSS selector
      // see https://developer.mozilla.org/en-US/docs/Web/CSS/Universal_selectors
      if (isOperaMini) {
        css.push('* { content: normal !important; }');
      }

      return css.join('\n\n');
    }

    function overwriteDeclaration(rule, name, value) {
      var _value;
      var _selectors = [];

      _value = value.replace(viewportUnitExpression, replaceValues);

      if (options.hacks) {
        _value = options.hacks.overwriteDeclaration(rule, name, _value);
      }

      if (name) {
        // skipping KeyframesRule
        _selectors.push(rule.selectorText);
        _value = name + ': ' + _value + ';';
      }

      var _rule = rule.parentRule;
      while (_rule) {
        if (_rule.media) {
          _selectors.unshift('@media ' + _rule.media.mediaText);
        } else if (_rule.conditionText) {
          _selectors.unshift('@supports ' + _rule.conditionText);
        }

        _rule = _rule.parentRule;
      }

      return {
        selector: _selectors,
        content: _value,
      };
    }

    function replaceValues(match, number, unit) {
      var _base = dimensions[unit];
      var _number = parseFloat(number) / 100;
      return (_number * _base) + 'px';
    }

    function getViewport() {
      var vh = window.innerHeight;
      var vw = window.innerWidth;

      return {
        vh: vh,
        vw: vw,
        vmax: Math.max(vw, vh),
        vmin: Math.min(vw, vh),
      };
    }

    function importCrossOriginLinks(next) {
      var _waiting = 0;
      var decrease = function() {
        _waiting--;
        if (!_waiting) {
          next();
        }
      };

      forEach.call(document.styleSheets, function(sheet) {
        if (!sheet.href || origin(sheet.href) === origin(location.href) || sheet.ownerNode.getAttribute('data-viewport-units-buggyfill') === 'ignore') {
          // skip <style> and <link> from same origin or explicitly declared to ignore
          return;
        }

        _waiting++;
        convertLinkToStyle(sheet.ownerNode, decrease);
      });

      if (!_waiting) {
        next();
      }
    }

    function origin(url) {
      return url.slice(0, url.indexOf('/', url.indexOf('://') + 3));
    }

    function convertLinkToStyle(link, next) {
      getCors(link.href, function() {
        var style = document.createElement('style');
        style.media = link.media;
        style.setAttribute('data-href', link.href);
        style.textContent = this.responseText;
        link.parentNode.replaceChild(style, link);
        next();
      }, next);
    }

    function getCors(url, success, error) {
      var xhr = new XMLHttpRequest();
      if ('withCredentials' in xhr) {
        // XHR for Chrome/Firefox/Opera/Safari.
        xhr.open('GET', url, true);
      } else if (typeof XDomainRequest !== 'undefined') {
        // XDomainRequest for IE.
        xhr = new XDomainRequest();
        xhr.open('GET', url);
      } else {
        throw new Error('cross-domain XHR not supported');
      }

      xhr.onload = success;
      xhr.onerror = error;
      xhr.send();
      return xhr;
    }

    return {
      version: '0.6.1',
      findProperties: findProperties,
      getCss: getReplacedViewportUnits,
      init: initialize,
      refresh: refresh,
    };
  }));
})();
