path = require 'path'
{Disposable, CompositeDisposable} = require 'event-kit'
fs = require 'fs-plus'
listen = require './delegated-listener'

# Handles low-level events related to the @window.
module.exports =
class WindowEventHandler
  constructor: ({@atomEnvironment, @applicationDelegate, @window, @document}) ->
    @reloadRequested = false
    @subscriptions = new CompositeDisposable

    @previousOnbeforeunloadHandler = @window.onbeforeunload
    @window.onbeforeunload = @handleWindowBeforeunload
    @addEventListener(@window, 'focus', @handleWindowFocus)
    @addEventListener(@window, 'blur', @handleWindowBlur)

    @addEventListener(@document, 'keydown', @handleDocumentKeydown)
    @addEventListener(@document, 'drop', @handleDocumentDrop)
    @addEventListener(@document, 'dragover', @handleDocumentDragover)
    @addEventListener(@document, 'contextmenu', @handleDocumentContextmenu)
    @subscriptions.add listen(@document, 'click', 'a', @handleLinkClick)
    @subscriptions.add listen(@document, 'submit', 'form', @handleFormSubmit)

    @subscriptions.add @atomEnvironment.commands.add @window,
      'window:toggle-full-screen': @handleWindowToggleFullScreen
      'window:close': @handleWindowClose
      'window:reload': @handleWindowReload
      'window:toggle-dev-tools': @handleWindowToggleDevTools

    if process.platform in ['win32', 'linux']
      @subscriptions.add @atomEnvironment.commands.add @window,
        'window:toggle-menu-bar': @handleWindowToggleMenuBar

    @subscriptions.add @atomEnvironment.commands.add @document,
      'core:focus-next': @handleFocusNext
      'core:focus-previous': @handleFocusPrevious

    @handleNativeKeybindings()

  # Wire commands that should be handled by Chromium for elements with the
  # `.native-key-bindings` class.
  handleNativeKeybindings: ->
    bindCommandToAction = (command, action) =>
      @addEventListener @document, command, (event) =>
        if event.target.webkitMatchesSelector('.native-key-bindings')
          @applicationDelegate.getCurrentWindow().webContents[action]()

    bindCommandToAction('core:copy', 'copy')
    bindCommandToAction('core:paste', 'paste')
    bindCommandToAction('core:undo', 'undo')
    bindCommandToAction('core:redo', 'redo')
    bindCommandToAction('core:select-all', 'selectAll')
    bindCommandToAction('core:cut', 'cut')

  unsubscribe: ->
    @window.onbeforeunload = @previousOnbeforeunloadHandler
    @subscriptions.dispose()

  on: (target, eventName, handler) ->
    target.on(eventName, handler)
    @subscriptions.add(new Disposable ->
      target.removeListener(eventName, handler)
    )

  addEventListener: (target, eventName, handler) ->
    target.addEventListener(eventName, handler)
    @subscriptions.add(new Disposable(-> target.removeEventListener(eventName, handler)))

  handleDocumentKeydown: (event) =>
    @atomEnvironment.keymaps.handleKeyboardEvent(event)
    event.stopImmediatePropagation()

  handleDrop: (event) ->
    event.preventDefault()
    event.stopPropagation()

  handleDragover: (event) ->
    event.preventDefault()
    event.stopPropagation()
    event.dataTransfer.dropEffect = 'none'

  eachTabIndexedElement: (callback) ->
    for element in @document.querySelectorAll('[tabindex]')
      continue if element.disabled
      continue unless element.tabIndex >= 0
      callback(element, element.tabIndex)
    return

  handleFocusNext: =>
    focusedTabIndex = @document.activeElement.tabIndex ? -Infinity

    nextElement = null
    nextTabIndex = Infinity
    lowestElement = null
    lowestTabIndex = Infinity
    @eachTabIndexedElement (element, tabIndex) ->
      if tabIndex < lowestTabIndex
        lowestTabIndex = tabIndex
        lowestElement = element

      if focusedTabIndex < tabIndex < nextTabIndex
        nextTabIndex = tabIndex
        nextElement = element

    if nextElement?
      nextElement.focus()
    else if lowestElement?
      lowestElement.focus()

  handleFocusPrevious: =>
    focusedTabIndex = @document.activeElement.tabIndex ? Infinity

    previousElement = null
    previousTabIndex = -Infinity
    highestElement = null
    highestTabIndex = -Infinity
    @eachTabIndexedElement (element, tabIndex) ->
      if tabIndex > highestTabIndex
        highestTabIndex = tabIndex
        highestElement = element

      if focusedTabIndex > tabIndex > previousTabIndex
        previousTabIndex = tabIndex
        previousElement = element

    if previousElement?
      previousElement.focus()
    else if highestElement?
      highestElement.focus()

  handleWindowFocus: ->
    @document.body.classList.remove('is-blurred')

  handleWindowBlur: =>
    @document.body.classList.add('is-blurred')
    @atomEnvironment.storeDefaultWindowDimensions()

  handleWindowBeforeunload: =>
    confirmed = @atomEnvironment.workspace?.confirmClose(windowCloseRequested: true)
    if confirmed and not @reloadRequested and not @atomEnvironment.inSpecMode() and @atomEnvironment.getCurrentWindow().isWebViewFocused()
      @atomEnvironment.hide()
    @reloadRequested = false

    @atomEnvironment.storeDefaultWindowDimensions()
    @atomEnvironment.storeWindowDimensions()
    if confirmed
      @atomEnvironment.unloadEditorWindow()
    else
      @applicationDelegate.didCancelWindowUnload()

    confirmed

  handleWindowUnload: =>
    @atomEnvironment.destroy()

  handleWindowToggleFullScreen: =>
    @atomEnvironment.toggleFullScreen()

  handleWindowClose: =>
    @atomEnvironment.close()

  handleWindowReload: =>
    @reloadRequested = true
    @atomEnvironment.reload()

  handleWindowToggleDevTools: =>
    @atomEnvironment.toggleDevTools()

  handleWindowToggleMenuBar: =>
    @atomEnvironment.config.set('core.autoHideMenuBar', not @atomEnvironment.config.get('core.autoHideMenuBar'))

    if @atomEnvironment.config.get('core.autoHideMenuBar')
      detail = "To toggle, press the Alt key or execute the window:toggle-menu-bar command"
      @atomEnvironment.notifications.addInfo('Menu bar hidden', {detail})

  handleLinkClick: (event) =>
    event.preventDefault()
    uri = event.currentTarget?.getAttribute('href')
    if uri and uri[0] isnt '#' and /^https?:\/\//.test(uri)
      @applicationDelegate.openExternal(uri)

  handleFormSubmit: (event) ->
    # Prevent form submits from changing the current window's URL
    event.preventDefault()

  handleDocumentContextmenu: (event) =>
    event.preventDefault()
    @atomEnvironment.contextMenu.showForEvent(event)
