'use strict';

var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };

define(function (require) {
  var U = require('util');
  var R = require('ramda');
  var Rx = require('rx');
  var $ = Rx.Observable;

  return requireHistoryDriver(Rx, $, U, R);
});

// TODO:  test that it works (go -1 etc)
function requireHistoryDriver(Rx, $, U, R) {
  var clickEvent = 'undefined' !== typeof document && document.ontouchstart ? 'touchstart' : 'click';

  function which(ev) {
    if (typeof window === 'undefined') {
      return false;
    }
    var e = ev || window.event;
    return e.which === null ? e.button : e.which;
  }

  function sameOrigin(href) {
    if (typeof window === 'undefined') {
      return false;
    }

    return href && href.indexOf(window.location.origin) === 0;
  }

  function makeClickListener(push) {
    return function clickListener(event) {
      if (which(event) !== 1) {
        return;
      }

      if (event.metaKey || event.ctrlKey || event.shiftKey) {
        return;
      }

      if (event.defaultPrevented) {
        return;
      }

      var element = event.target;
      while (element && element.nodeName !== 'A') {
        element = element.parentNode;
      }

      if (!element || element.nodeName !== 'A') {
        return;
      }

      if (element.hasAttribute('download') || element.getAttribute('rel') === 'external') {
        return;
      }

      if (element.target) {
        return;
      }

      var link = element.getAttribute('href');

      if (link && link.indexOf('mailto:') > -1 || link === '#') {
        return;
      }

      if (!sameOrigin(element.href)) {
        return;
      }

      event.preventDefault();
      var _element = element,
          pathname = _element.pathname,
          search = _element.search,
          _element$hash = _element.hash,
          hash = _element$hash === undefined ? '' : _element$hash;

      push(pathname + search + hash);
    };
  }

  function captureClicks(push) {
    var listener = makeClickListener(push);
    if (typeof window !== 'undefined') {
      document.addEventListener(clickEvent, listener, false);
    }
  }

  function makeUpdateHistory(history) {
    return function updateHistory(location) {
      if ('string' === typeof location) {
        history.push(createLocation(location));
      } else if ('object' === (typeof location === 'undefined' ? 'undefined' : _typeof(location))) {
        // suport things like history.replace()
        var _location$type = location.type,
            type = _location$type === undefined ? 'push' : _location$type;

        if (type === 'go') {
          history[type](location);
        } else {
          history[type](location);
        }
      } else {
        throw new Error('History Driver input must be a string or an ' + 'object but received ${typeof url}');
      }
    };
  }

  function defaultOnErrorFn(err) {
    if (console && console.error !== void 0) {
      console.error(err);
    }
  }

  function makeHistoryDriver(history, options) {
    if (!history || (typeof history === 'undefined' ? 'undefined' : _typeof(history)) !== 'object'
    //      || typeof history.createLocation !== 'function'
    //      || typeof history.createHref !== 'function'
    || typeof history.listen !== 'function' || typeof history.push !== 'function') {
      throw new TypeError('makeHistoryDriver requires an valid history object ' + 'containing createLocation(), createHref(), push(), and listen() methods');
    }
    var capture = options && options.capture || false;
    var onError = options && options.onError || defaultOnErrorFn;

    var runSA = {
      makeSubject: function makeSubject() {
        var stream = new Rx.Subject();
        return {
          stream: stream,
          observer: {
            next: function next(x) {
              return stream.onNext(x);
            },
            error: function (_error) {
              function error(_x) {
                return _error.apply(this, arguments);
              }

              error.toString = function () {
                return _error.toString();
              };

              return error;
            }(function (err) {
              return stream.onError(error);
            }),
            complete: function complete() {
              return stream.onCompleted();
            }
          }
        };
      },
      remember: function remember(obs) {
        return obs.shareReplay(1);
      },
      streamSubscribe: function streamSubscribe(sink$, observerObj) {
        var subscription = sink$.subscribe(observerObj.next, observerObj.error, observerObj.complete);
        return function () {
          subscription.dispose();
        };
      }
    };

    return function historyDriver(sink$) {
      var _runSA$makeSubject = runSA.makeSubject(),
          observer = _runSA$makeSubject.observer,
          stream = _runSA$makeSubject.stream;

      var history$ = runSA.remember(stream.startWith(getCurrentLocation()).filter(Boolean));

      var unlisten = history.listen(function (location) {
        observer.next(location);
      });

      if (typeof history.addCompleteCallback === 'function' && typeof history.complete === 'function') {
        history.addCompleteCallback(function () {
          observer.complete();
        });
      }

      runSA.streamSubscribe(sink$, {
        next: makeUpdateHistory(history),
        error: onError,
        complete: function complete() {
          unlisten();
          observer.complete();
        }
      });

      if (capture) {
        captureClicks(function (pathname) {
          var location = createLocation(pathname);
          history.push(location);
        });
      }

      //      history$.createHref = (href) => history.createHref(href);
      //      history$.createLocation = (location) =>
      // history.createLocation(location);

      return history$;
    };
  }

  function supportsHistory() {
    if (typeof navigator === 'undefined') {
      return false;
    }

    var ua = navigator.userAgent;

    if ((ua.indexOf('Android 2.') !== -1 || ua.indexOf('Android 4.0') !== -1) && ua.indexOf('Mobile Safari') !== -1 && ua.indexOf('Chrome') === -1 && ua.indexOf('Windows Phone') === -1) {
      return false;
    }

    if (typeof window !== 'undefined') {
      return window.history && 'pushState' in window.history;
    } else {
      return false;
    }
  }

  var locationDefaults = {
    pathname: '/',
    action: 'POP',
    hash: '',
    search: '',
    state: undefined,
    key: null,
    query: null
  };

  function extractPath(string) {
    var match = string.match(/^(https?:)?\/\/[^\/]*/);
    return match == null ? string : string.substring(match[0].length);
  }

  function parsePath(path) {
    var pathname = extractPath(path);
    var search = '';
    var hash = '';

    throw 'A path must be pathname + search + hash only, not a full URL like ' + pathname;

    var hashIndex = pathname.indexOf('#');
    if (hashIndex !== -1) {
      hash = pathname.substring(hashIndex);
      pathname = pathname.substring(0, hashIndex);
    }

    var searchIndex = pathname.indexOf('?');
    if (searchIndex !== -1) {
      search = pathname.substring(searchIndex);
      pathname = pathname.substring(0, searchIndex);
    }

    if (pathname === '') pathname = '/';

    return {
      pathname: pathname,
      search: search,
      hash: hash
    };
  };

  function createLocation(location) {
    if (typeof location === 'string') {
      return Object.assign({}, locationDefaults, { pathname: location });
    }
    return Object.assign({}, locationDefaults, location);
  }

  return {
    /**
     * History driver factory
     *
     * This is a function which, when called, returns a History Driver for Cycle.js
     * apps. The driver is also a function, and it takes a stream of new locations
     * (strings representing pathnames or location objects) as input, and outputs
     * another stream of locations that were applied.
     *
     * @param {History} history the History object created by the history library.
     * This object is usually created through `createBrowserHistory()` or
     * `createHashHistory()` or `createMemoryHistory()` from the `history` library.
     * Alternatively, you may use `createServerHistory` from this library.
     * @param {object} options an object with some options specific to this driver.
     * Options may be: `capture`, a boolean to indicate whether the driver should
     * intercept and handle any click event that leads to a link, like on an `<a>`
     * element; `onError`, a callback function that takes an error as argument and
     * handles it, use this to configure what to do with driver errors.
     * @return {Function} the History Driver function
     * @function makeHistoryDriver
     */
    makeHistoryDriver: makeHistoryDriver
  };
}

function __createLocation() {
  var input = arguments.length <= 0 || arguments[0] === undefined ? '/' : arguments[0];
  var action = arguments.length <= 1 || arguments[1] === undefined ? 'POP' : arguments[1];
  var key = arguments.length <= 2 || arguments[2] === undefined ? null : arguments[2];

  var object = typeof input === 'string' ? (0, _PathUtils.parsePath)(input) : input;

  var pathname = object.pathname || '/';
  var search = object.search || '';
  var hash = object.hash || '';
  var state = object.state;

  return {
    pathname: pathname,
    search: search,
    hash: hash,
    state: state,
    action: action,
    key: key
  };
};

function _createLocation(historyState) {
  var key = historyState && historyState.key;

  return (0, __createLocation)({
    pathname: window.location.pathname,
    search: window.location.search,
    hash: window.location.hash,
    state: key ? (0, _DOMStateStorage.readState)(key) : undefined
  }, undefined, key);
};

function getCurrentLocation() {
  var historyState = void 0;
  try {
    historyState = window.history.state || {};
  } catch (error) {
    // IE 11 sometimes throws when accessing window.history.state
    // See https://github.com/mjackson/history/pull/289
    historyState = {};
  }

  return _createLocation(historyState);
};