// @license  magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3.0
/* eslint-disable no-var, semi, prefer-arrow-callback, prefer-template */

/**
 * Collection of methods for sending analytics events to Archive.org's analytics server.
 *
 * These events are used for internal stats and sent (in anonymized form) to Google Analytics.
 *
 * @see analytics.md
 *
 * @type {Object}
 */
window.archive_analytics = (function defineArchiveAnalytics() {
  // keep orignal Date object so as not to be affected by wayback's
  // hijacking global Date object
  var Date = window.Date;
  var ARCHIVE_ANALYTICS_VERSION = 2;
  var DEFAULT_SERVICE = 'ao_2';
  var NO_SAMPLING_SERVICE = 'ao_no_sampling'; // sends every event instead of a percentage

  var startTime = new Date();

  /**
   * @return {Boolean}
   */
  function isPerformanceTimingApiSupported() {
    return 'performance' in window && 'timing' in window.performance;
  }

  /**
   * Determines how many milliseconds elapsed between the browser starting to parse the DOM and
   * the current time.
   *
   * Uses the Performance API or a fallback value if it's not available.
   *
   * @see https://developer.mozilla.org/en-US/docs/Web/API/Performance_API
   *
   * @return {Number}
   */
  function getLoadTime() {
    var start;

    if (isPerformanceTimingApiSupported())
      start = window.performance.timing.domLoading;
    else
      start = startTime.getTime();

    return new Date().getTime() - start;
  }

  /**
   * Determines how many milliseconds elapsed between the user navigating to the page and
   * the current time.
   *
   * @see https://developer.mozilla.org/en-US/docs/Web/API/Performance_API
   *
   * @return {Number|null} null if the browser doesn't support the Performance API
   */
  function getNavToDoneTime() {
    if (!isPerformanceTimingApiSupported())
      return null;

    return new Date().getTime() - window.performance.timing.navigationStart;
  }

  /**
   * Performs an arithmetic calculation on a string with a number and unit, while maintaining
   * the unit.
   *
   * @param {String} original value to modify, with a unit
   * @param {Function} doOperation accepts one Number parameter, returns a Number
   * @returns {String}
   */
  function computeWithUnit(original, doOperation) {
    var number = parseFloat(original, 10);
    var unit = original.replace(/(\d*\.\d+)|\d+/, '');

    return doOperation(number) + unit;
  }

  /**
   * Computes the default font size of the browser.
   *
   * @returns {String|null} computed font-size with units (typically pixels), null if it cannot be computed
   */
  function getDefaultFontSize() {
    var fontSizeStr;

    if (!('getComputedStyle' in window))
      return null;

    var style = window.getComputedStyle(document.documentElement);
    if (!style)
      return null;

    fontSizeStr = style.fontSize;

    // Don't modify the value if tracking book reader.
    if (document.querySelector('#BookReader'))
      return fontSizeStr;

    return computeWithUnit(fontSizeStr, function reverseBootstrapFontSize(number) {
      // Undo the 62.5% size applied in the Bootstrap CSS.
      return number * 1.6;
    });
  }

  /**
   * Get the URL parameters for a given Location
   * @param  {Location}
   * @return {Object} The URL parameters
   */
  function getParams(location) {
    if (!location) location = window.location;
    var vars;
    var i;
    var pair;
    var params = {};
    var query = location.search;
    if (!query) return params;
    vars = query.substring(1).split('&');
    for (i = 0; i < vars.length; i++) {
      pair = vars[i].split('=');
      params[pair[0]] = decodeURIComponent(pair[1]);
    }
    return params;
  }

  function getMetaProp(name) {
    var metaTag = document.querySelector('meta[property=' + name + ']');
    return metaTag ? metaTag.getAttribute('content') || null : null;
  }

  var ArchiveAnalytics = {
    /**
     * @type {String|null}
     */
    service: getMetaProp('service'),
    mediaType: getMetaProp('mediatype'),
    primaryCollection: getMetaProp('primary_collection'),

    /**
     * Key-value pairs to send in pageviews (you can read this after a pageview to see what was
     * sent).
     *
     * @type {Object}
     */
    values: {},

    /**
     * Sends an analytics ping, preferably using navigator.sendBeacon()
     * @param {Object}   values
     * @param {Function} [onload_callback]      (deprecated) callback to invoke once ping to analytics server is done
     * @param {Boolean}  [augment_for_ao_site]  (deprecated) if true, add some archive.org site-specific values
     */
    send_ping: function send_ping(values, onload_callback, augment_for_ao_site) {
      if (typeof window.navigator !== 'undefined' && typeof window.navigator.sendBeacon !== 'undefined')
        this.send_ping_via_beacon(values);
      else
        this.send_ping_via_image(values);
    },

    /**
     * Sends a ping via Beacon API
     * NOTE: Assumes window.navigator.sendBeacon exists
     * @param {Object} values Tracking parameters to pass
     */
    send_ping_via_beacon: function send_ping_via_beacon(values) {
      var url = this.generate_tracking_url(values || {});
      window.navigator.sendBeacon(url);
    },

    /**
     * Sends a ping via Image object
     * @param {Object} values Tracking parameters to pass
     */
    send_ping_via_image: function send_ping_via_image(values) {
      var url = this.generate_tracking_url(values || {});
      var loadtime_img = new Image(1, 1);
      loadtime_img.src = url;
      loadtime_img.alt = '';
    },

    /**
     * Construct complete tracking URL containing payload
     * @param {Object} params Tracking parameters to pass
     * @return {String} URL to use for tracking call
     */
    generate_tracking_url: function generate_tracking_url(params) {
      var baseUrl = '//analytics.archive.org/0.gif';
      var keys;
      var outputParams = params;
      var outputParamsArray = [];

      outputParams.service = outputParams.service || this.service || DEFAULT_SERVICE;

      // Build array of querystring parameters
      keys = Object.keys(outputParams);
      keys.forEach(function keyIteration(key) {
        outputParamsArray.push(encodeURIComponent(key) + '=' + encodeURIComponent(outputParams[key]));
      });
      outputParamsArray.push('version=' + ARCHIVE_ANALYTICS_VERSION);
      outputParamsArray.push('count=' + (keys.length + 2)); // Include `version` and `count` in count

      return baseUrl + '?' + outputParamsArray.join('&');
    },

    /**
     * @param {int} page Page number
     */
    send_scroll_fetch_event: function send_scroll_fetch_event(page) {
      var additionalValues = { ev: page };
      var loadTime = getLoadTime();
      var navToDoneTime = getNavToDoneTime();
      if (loadTime) additionalValues.loadtime = loadTime;
      if (navToDoneTime) additionalValues.nav_to_done_ms = navToDoneTime;
      this.send_event('page_action', 'scroll_fetch', location.pathname, additionalValues);
    },

    send_scroll_fetch_base_event: function send_scroll_fetch_base_event() {
      var additionalValues = {};
      var loadTime = getLoadTime();
      var navToDoneTime = getNavToDoneTime();
      if (loadTime) additionalValues.loadtime = loadTime;
      if (navToDoneTime) additionalValues.nav_to_done_ms = navToDoneTime;
      this.send_event('page_action', 'scroll_fetch_base', location.pathname, additionalValues);
    },

    /**
     * @param {Object} [options]
     * @param {String} [options.mediaType]
     * @param {String} [options.mediaLanguage]
     * @param {String} [options.page] The path portion of the page URL
     */
    send_pageview: function send_pageview(options) {
      var settings = options || {};

      var defaultFontSize;
      var loadTime = getLoadTime();
      var mediaType = settings.mediaType;
      var primaryCollection = settings.primaryCollection;
      var page = settings.page;
      var navToDoneTime = getNavToDoneTime();

      /**
       * @return {String}
       */
      function get_locale() {
        if (navigator) {
          if (navigator.language)
            return navigator.language;

          else if (navigator.browserLanguage)
            return navigator.browserLanguage;

          else if (navigator.systemLanguage)
            return navigator.systemLanguage;

          else if (navigator.userLanguage)
            return navigator.userLanguage;
        }
        return '';
      }

      defaultFontSize = getDefaultFontSize();

      // Set field values
      this.values.kind     = 'pageview';
      this.values.timediff = (new Date().getTimezoneOffset()/60)*(-1); // *timezone* diff from UTC
      this.values.locale   = get_locale();
      this.values.referrer = (document.referrer == '' ? '-' : document.referrer);

      if (loadTime)
        this.values.loadtime = loadTime;

      if (navToDoneTime)
        this.values.nav_to_done_ms = navToDoneTime;

      if (settings.trackingId) {
        this.values.ga_tid = settings.trackingId;
      }

      /* START CUSTOM DIMENSIONS */
      if (defaultFontSize)
        this.values.ga_cd1 = defaultFontSize;

      if ('devicePixelRatio' in window)
        this.values.ga_cd2 = window.devicePixelRatio;

      if (mediaType)
        this.values.ga_cd3 = mediaType;

      if (settings.mediaLanguage) {
        this.values.ga_cd4 = settings.mediaLanguage;
      }

      if (primaryCollection) {
        this.values.ga_cd5 = primaryCollection;
      }
      /* END CUSTOM DIMENSIONS */

      if (page)
        this.values.page = page;

      this.send_ping(this.values);
    },

    /**
     * Sends a tracking "Event".
     * @param {string} category
     * @param {string} action
     * @param {string} label
     * @param {Object} additionalEventParams
     */
    send_event: function send_event(
        category,
        action,
        label,
        additionalEventParams
    ) {
      if (!label) label = window.location.pathname;
      if (!additionalEventParams) additionalEventParams = {};
      if (additionalEventParams.mediaLanguage) {
        additionalEventParams.ga_cd4 = additionalEventParams.mediaLanguage;
        delete additionalEventParams.mediaLanguage;
      }
      var eventParams = Object.assign(
        {
          kind: 'event',
          ec: category,
          ea: action,
          el: label,
          cache_bust: Math.random(),
        },
        additionalEventParams
      );
      this.send_ping(eventParams);
    },

    /**
     * Sends every event instead of a small percentage.
     *
     * Use this sparingly as it can generate a lot of events.
     *
     * @param {string} category
     * @param {string} action
     * @param {string} label
     * @param {Object} additionalEventParams
     */
    send_event_no_sampling: function send_event_no_sampling(
      category,
      action,
      label,
      additionalEventParams
    ) {
      var extraParams = additionalEventParams || {};
      extraParams.service = NO_SAMPLING_SERVICE;
      this.send_event(category, action, label, extraParams);
    },

    /**
     * @param {Object} options see this.send_pageview options
     */
    send_pageview_on_load: function send_pageview_on_load(options) {
      var self = this;
      window.addEventListener('load', function send_pageview_with_options() {
        self.send_pageview(options);
      });
    },

    /**
     * Handles tracking events passed in URL.
     * Assumes category and action values are separated by a "|" character.
     * NOTE: Uses the unsampled analytics property. Watch out for future high click links!
     * @param {Location}
     */
    process_url_events: function process_url_events(location) {
      var eventValues;
      var actionValue;
      var eventValue = getParams(location).iax;
      if (!eventValue) return;
      eventValues = eventValue.split('|');
      actionValue = eventValues.length >= 1 ? eventValues[1] : '';
      this.send_event_no_sampling(
        eventValues[0],
        actionValue,
        window.location.pathname
      );
    },

    /**
     * Attaches handlers for event tracking.
     *
     * To enable click tracking for a link, add a `data-event-click-tracking`
     * attribute containing the Google Analytics Event Category and Action, separated
     * by a vertical pipe (|).
     * e.g. `<a href="foobar" data-event-click-tracking="TopNav|FooBar">`
     *
     * To enable form submit tracking, add a `data-event-form-tracking` attribute
     * to the `form` tag.
     * e.g. `<form data-event-form-tracking="TopNav|SearchForm" method="GET">`
     *
     * Additional tracking options can be added via a `data-event-tracking-options`
     * parameter. This parameter, if included, should be a JSON string of the parameters.
     * Valid parameters are:
     * - service {string}: Corresponds to the Google Analytics property data values flow into
     */
    set_up_event_tracking: function set_up_event_tracking() {
      var self = this;
      var clickTrackingAttributeName = 'event-click-tracking';
      var formTrackingAttributeName = 'event-form-tracking';
      var trackingOptionsAttributeName = 'event-tracking-options';

      function handleAction(event, attributeName) {
        var selector = '[data-' + attributeName + ']';
        var eventTarget = event.target;
        if (!eventTarget) return;
        var target = eventTarget.closest(selector);
        if (!target) return;
        var categoryAction;
        var categoryActionParts;
        var options;
        categoryAction = target.dataset[toCamelCase(attributeName)];
        if (!categoryAction) return;
        categoryActionParts = categoryAction.split('|');
        options = target.dataset[toCamelCase(trackingOptionsAttributeName)];
        options = options ? JSON.parse(options) : {};
        self.send_event(
          categoryActionParts[0],
          categoryActionParts[1],
          categoryActionParts[2] || window.location.pathname,
          options.service ? { service: options.service } : {}
        );
      }

      function toCamelCase(str) {
        return str.replace(/\W+(.)/g, function (match, chr) {
          return chr.toUpperCase();
        });
      };

      document.addEventListener('click', function(e) {
        handleAction(e, clickTrackingAttributeName);
      });

      document.addEventListener('submit', function(e) {
        handleAction(e, formTrackingAttributeName);
      });
    },

    /**
     * @returns {Object[]}
     */
    get_data_packets: function get_data_packets() {
      return [this.values];
    },

    /**
     * Creates a tracking image for tracking JS compatibility.
     *
     * @param {string} type The type value for track_js_case in query params for 0.gif
     */
    create_tracking_image: function create_tracking_image(type) {
      this.send_ping_via_image({
        cache_bust: Math.random(),
        kind: 'track_js',
        track_js_case: type,
      });
    }
  };

  return ArchiveAnalytics;
}());
// @license-end
