###*
Browser history
===============
  
\#\#\# Incomplete documentation!
  
We need to work on this page:

- Explain how the other modules manipulate history
- Decide whether we want to expose these methods as public API
- Document methods and parameters

@class up.history
###
up.history = (($) ->
  
  u = up.util

  ###*
  @property up.history.config
  @param {Array} [config.popTargets=['body']]
    An array of CSS selectors to replace when the user goes
    back in history.
  @param {Boolean} [config.restoreScroll=true]
    Whether to restore the known scroll positions
    when the user goes back or forward in history.
  @stable
  ###
  config = u.config
    popTargets: ['body']
    restoreScroll: true

  ###*
  Returns the previous URL in the browser history.

  Note that this will only work reliably for history changes that
  were applied by [`up.history.push`](/up.history.replace) or
  [`up.history.replace`](/up.history.replace).

  @function up.history.previousUrl
  @internal
  ###
  previousUrl = undefined
  nextPreviousUrl = undefined

  reset = ->
    config.reset()
    previousUrl = undefined
    nextPreviousUrl = undefined

  normalizeUrl = (url) ->
    u.normalizeUrl(url, hash: true)

  ###*
  Returns a normalized URL for the current history entry.

  @function up.history.url
  @experimental
  ###
  currentUrl = ->
    normalizeUrl(up.browser.url())
  
  isCurrentUrl = (url) ->
    normalizeUrl(url) == currentUrl()

  observeNewUrl = (url) ->
    if nextPreviousUrl
      previousUrl = nextPreviousUrl
      nextPreviousUrl = undefined
    nextPreviousUrl = url

  ###*
  Replaces the current history entry and updates the
  browser's location bar with the given URL.

  When the user navigates to the replaced history entry at a later time,
  Unpoly will [`replace`](/up.replace) the document body with
  the body from that URL.

  Note that functions like [`up.replace`](/up.replace) or
  [`up.submit`](/up.submit) will automatically update the
  browser's location bar for you.

  @function up.history.replace
  @param {String} url
  @experimental
  ###
  replace = (url) ->
    manipulate('replaceState', url)

  ###*
  Adds a new history entry and updates the browser's
  address bar with the given URL.

  When the user navigates to the added  history entry at a later time,
  Unpoly will [`replace`](/up.replace) the document body with
  the body from that URL.

  Note that functions like [`up.replace`](/up.replace) or
  [`up.submit`](/up.submit) will automatically update the
  browser's location bar for you.

  Emits events [`up:history:push`](/up:history:push) and [`up:history:pushed`](/up:history:pushed).

  @function up.history.push
  @param {String} url
    The URL for the history entry to be added.
  @experimental
  ###
  push = (url, options) ->
    options = u.options(options, force: false)
    url = normalizeUrl(url)
    if (options.force || !isCurrentUrl(url)) && up.bus.nobodyPrevents('up:history:push', url: url, message: "Adding history entry for #{url}")
      manipulate('pushState', url)
      up.emit('up:history:pushed', url: url, message: "Advanced to location #{url}")

  ###*
  This event is [emitted](/up.emit) before a new history entry is added.

  @event up:history:push
  @param {String} event.url
    The URL for the history entry that is going to be added.
  @param event.preventDefault()
    Event listeners may call this method to prevent the history entry from being added.
  @experimental
  ###

  ###*
  This event is [emitted](/up.emit) after a new history entry has been added.

  @event up:history:pushed
  @param {String} event.url
    The URL for the history entry that has been added.
  @experimental
  ###

  manipulate = (method, url) ->
    if up.browser.canPushState()
      state = buildState()
      window.history[method](state, '', url)
      observeNewUrl(currentUrl())
    else
      up.fail "This browser doesn't support history.#{method}"

  buildState = ->
    fromUp: true

  restoreStateOnPop = (state) ->
    if state?.fromUp
      url = currentUrl()
      up.log.group "Restoring URL %s", url, ->
        popSelector = config.popTargets.join(', ')
        up.replace popSelector, url,
          history: false,
          title: true,
          reveal: false,
          transition: 'none',
          saveScroll: false # since the URL was already changed by the browser, don't save scroll state
          restoreScroll: config.restoreScroll
    else
      up.puts 'Ignoring a state not pushed by Unpoly (%o)', state

  pop = (event) ->
    observeNewUrl(currentUrl())
    up.layout.saveScroll(url: previousUrl)
    state = event.originalEvent.state
    restoreStateOnPop(state)
    url = currentUrl()
    up.emit('up:history:restored', url: url, message: "Restored location #{url}")

  ###*
  This event is [emitted](/up.emit) after a history entry has been restored.

  History entries are restored when the user uses the *Back* or *Forward* button.

  @event up:history:restored
  @param {String} event.url
    The URL for the history entry that has been restored.
  @experimental
  ###

  if up.browser.canPushState()
    register = ->
      $(window).on "popstate", pop
      replace(currentUrl(), force: true)

    if jasmine?
      # Can't delay this in tests.
      register()
    else
      # Defeat an unnecessary popstate that some browsers trigger
      # on pageload (Safari, Chrome < 34).
      # We should check in 2023 if we can remove this.
      setTimeout register, 100

  ###*
  Changes the link's destination so it points to the previous URL.

  Note that this will *not* call `location.back()`, but will set
  the link's `up-href` attribute to the actual, previous URL.

  \#\#\# Under the hood

  This link ...

      <a href="/default" up-back>
        Go back
      </a>

  ... will be transformed to:

      <a href="/default" up-href="/previous-page" up-restore-scroll up-follow>
        Goback
      </a>

  @selector [up-back]
  @stable
  ###
  up.compiler '[up-back]', ($link) ->
    if u.isPresent(previousUrl)
      u.setMissingAttrs $link,
        'up-href': previousUrl,
        'up-restore-scroll': ''
      $link.removeAttr 'up-back'
      up.link.makeFollowable($link)

  up.on 'up:framework:reset', reset

  config: config
  defaults: -> up.fail('up.history.defaults(...) no longer exists. Set values on he up.history.config property instead.')
  push: push
  replace: replace
  url: currentUrl
  previousUrl: -> previousUrl
  normalizeUrl: normalizeUrl

)(jQuery)