text: import observable from 'riot-observable'
  /**
   * Simple client-side router
   * @module riot-route
   */
  
  
  var RE_ORIGIN = /^.+?\/\/+[^\/]+/,
    EVENT_LISTENER = 'EventListener',
    REMOVE_EVENT_LISTENER = 'remove' + EVENT_LISTENER,
    ADD_EVENT_LISTENER = 'add' + EVENT_LISTENER,
    HAS_ATTRIBUTE = 'hasAttribute',
    REPLACE = 'replace',
    POPSTATE = 'popstate',
    HASHCHANGE = 'hashchange',
    TRIGGER = 'trigger',
    MAX_EMIT_STACK_LEVEL = 3,
    win = typeof window != 'undefined' && window,
    doc = typeof document != 'undefined' && document,
    hist = win && history,
    loc = win && (hist.location || win.location), // see html5-history-api
    prot = Router.prototype, // to minify more
    clickEvent = doc && doc.ontouchstart ? 'touchstart' : 'click',
    started = false,
    central = observable(),
    routeFound = false,
    debouncedEmit,
    base, current, parser, secondParser, emitStack = [], emitStackLevel = 0
  
  /**
   * Default parser. You can replace it via router.parser method.
   * @param {string} path - current path (normalized)
   * @returns {array} array
   */
  function DEFAULT_PARSER(path) {
    return path.split(/[/?#]/)
  }
  
  /**
   * Default parser (second). You can replace it via router.parser method.
   * @param {string} path - current path (normalized)
   * @param {string} filter - filter string (normalized)
   * @returns {array} array
   */
  function DEFAULT_SECOND_PARSER(path, filter) {
    var re = new RegExp('^' + filter[REPLACE](/\*/g, '([^/?#]+?)')[REPLACE](/\.\./, '.*') + '$'),
      args = path.match(re)
  
    if (args) return args.slice(1)
  }
  
  /**
   * Simple/cheap debounce implementation
   * @param   {function} fn - callback
   * @param   {number} delay - delay in seconds
   * @returns {function} debounced function
   */
  function debounce(fn, delay) {
    var t
    return function () {
      clearTimeout(t)
      t = setTimeout(fn, delay)
    }
  }
  
  /**
   * Set the window listeners to trigger the routes
   * @param {boolean} autoExec - see route.start
   */
  function start(autoExec) {
    debouncedEmit = debounce(emit, 1)
    win[ADD_EVENT_LISTENER](POPSTATE, debouncedEmit)
    win[ADD_EVENT_LISTENER](HASHCHANGE, debouncedEmit)
    doc[ADD_EVENT_LISTENER](clickEvent, click)
    if (autoExec) emit(true)
  }
  
  /**
   * Router class
   */
  function Router() {
    this.$ = []
    observable(this) // make it observable
    central.on('stop', this.s.bind(this))
    central.on('emit', this.e.bind(this))
  }
  
  function normalize(path) {
    return path[REPLACE](/^\/|\/$/, '')
  }
  
  function isString(str) {
    return typeof str == 'string'
  }
  
  /**
   * Get the part after domain name
   * @param {string} href - fullpath
   * @returns {string} path from root
   */
  function getPathFromRoot(href) {
    return (href || loc.href)[REPLACE](RE_ORIGIN, '')
  }
  
  /**
   * Get the part after base
   * @param {string} href - fullpath
   * @returns {string} path from base
   */
  function getPathFromBase(href) {
    return base[0] == '#'
      ? (href || loc.href || '').split(base)[1] || ''
      : (loc ? getPathFromRoot(href) : href || '')[REPLACE](base, '')
  }
  
  function emit(force) {
    // the stack is needed for redirections
    var isRoot = emitStackLevel == 0, first
    if (MAX_EMIT_STACK_LEVEL <= emitStackLevel) return
  
    emitStackLevel++
    emitStack.push(function() {
      var path = getPathFromBase()
      if (force || path != current) {
        central[TRIGGER]('emit', path)
        current = path
      }
    })
    if (isRoot) {
      while (first = emitStack.shift()) first() // stack increses within this call
      emitStackLevel = 0
    }
  }
  
  function click(e) {
    if (
      e.which != 1 // not left click
      || e.metaKey || e.ctrlKey || e.shiftKey // or meta keys
      || e.defaultPrevented // or default prevented
    ) return
  
    var el = e.target
    while (el && el.nodeName != 'A') el = el.parentNode
  
    if (
      !el || el.nodeName != 'A' // not A tag
      || el[HAS_ATTRIBUTE]('download') // has download attr
      || !el[HAS_ATTRIBUTE]('href') // has no href attr
      || el.target && el.target != '_self' // another window or frame
      || el.href.indexOf(loc.href.match(RE_ORIGIN)[0]) == -1 // cross origin
    ) return
  
    if (el.href != loc.href
      && (
        el.href.split('#')[0] == loc.href.split('#')[0] // internal jump
        || base[0] != '#' && getPathFromRoot(el.href).indexOf(base) !== 0 // outside of base
        || base[0] == '#' && el.href.split(base)[0] != loc.href.split(base)[0] // outside of #base
        || !go(getPathFromBase(el.href), el.title || doc.title) // route not found
      )) return
  
    e.preventDefault()
  }
  
  /**
   * Go to the path
   * @param {string} path - destination path
   * @param {string} title - page title
   * @param {boolean} shouldReplace - use replaceState or pushState
   * @returns {boolean} - route not found flag
   */
  function go(path, title, shouldReplace) {
    // Server-side usage: directly execute handlers for the path
    if (!hist) return central[TRIGGER]('emit', getPathFromBase(path))
  
    path = base + normalize(path)
    title = title || doc.title
    // browsers ignores the second parameter `title`
    shouldReplace
      ? hist.replaceState(null, title, path)
      : hist.pushState(null, title, path)
    // so we need to set it manually
    doc.title = title
    routeFound = false
    emit()
    return routeFound
  }
  
  /**
   * Go to path or set action
   * a single string:                go there
   * two strings:                    go there with setting a title
   * two strings and boolean:        replace history with setting a title
   * a single function:              set an action on the default route
   * a string/RegExp and a function: set an action on the route
   * @param {(string|function)} first - path / action / filter
   * @param {(string|RegExp|function)} second - title / action
   * @param {boolean} third - replace flag
   */
  prot.m = function(first, second, third) {
    if (isString(first) && (!second || isString(second))) go(first, second, third || false)
    else if (second) this.r(first, second)
    else this.r('@', first)
  }
  
  /**
   * Stop routing
   */
  prot.s = function() {
    this.off('*')
    this.$ = []
  }
  
  /**
   * Emit
   * @param {string} path - path
   */
  prot.e = function(path) {
    this.$.concat('@').some(function(filter) {
      var args = (filter == '@' ? parser : secondParser)(normalize(path), normalize(filter))
      if (typeof args != 'undefined') {
        this[TRIGGER].apply(null, [filter].concat(args))
        return routeFound = true // exit from loop
      }
    }, this)
  }
  
  /**
   * Register route
   * @param {string} filter - filter for matching to url
   * @param {function} action - action to register
   */
  prot.r = function(filter, action) {
    if (filter != '@') {
      filter = '/' + normalize(filter)
      this.$.push(filter)
    }
    this.on(filter, action)
  }
  
  var mainRouter = new Router()
  var route = mainRouter.m.bind(mainRouter)
  
  /**
   * Create a sub router
   * @returns {function} the method of a new Router object
   */
  route.create = function() {
    var newSubRouter = new Router()
    // assign sub-router's main method
    var router = newSubRouter.m.bind(newSubRouter)
    // stop only this sub-router
    router.stop = newSubRouter.s.bind(newSubRouter)
    return router
  }
  
  /**
   * Set the base of url
   * @param {(str|RegExp)} arg - a new base or '#' or '#!'
   */
  route.base = function(arg) {
    base = arg || '#'
    current = getPathFromBase() // recalculate current path
  }
  
  /** Exec routing right now **/
  route.exec = function() {
    emit(true)
  }
  
  /**
   * Replace the default router to yours
   * @param {function} fn - your parser function
   * @param {function} fn2 - your secondParser function
   */
  route.parser = function(fn, fn2) {
    if (!fn && !fn2) {
      // reset parser for testing...
      parser = DEFAULT_PARSER
      secondParser = DEFAULT_SECOND_PARSER
    }
    if (fn) parser = fn
    if (fn2) secondParser = fn2
  }
  
  /**
   * Helper function to get url query as an object
   * @returns {object} parsed query
   */
  route.query = function() {
    var q = {}
    var href = loc.href || current
    href[REPLACE](/[?&](.+?)=([^&]*)/g, function(_, k, v) { q[k] = v })
    return q
  }
  
  /** Stop routing **/
  route.stop = function () {
    if (started) {
      if (win) {
        win[REMOVE_EVENT_LISTENER](POPSTATE, debouncedEmit)
        win[REMOVE_EVENT_LISTENER](HASHCHANGE, debouncedEmit)
        doc[REMOVE_EVENT_LISTENER](clickEvent, click)
      }
      central[TRIGGER]('stop')
      started = false
    }
  }
  
  /**
   * Start routing
   * @param {boolean} autoExec - automatically exec after starting if true
   */
  route.start = function (autoExec) {
    if (!started) {
      if (win) {
        if (document.readyState == 'complete') start(autoExec)
        // the timeout is needed to solve
        // a weird safari bug https://github.com/riot/route/issues/33
        else win[ADD_EVENT_LISTENER]('load', function() {
          setTimeout(function() { start(autoExec) }, 1)
        })
      }
      started = true
    }
  }
  
  /** Prepare the router **/
  route.base()
  route.parser()
  
  export default route
  