{remove, last} = require 'underscore-plus'
{join} = require 'path'
{Model} = require 'theorist'
Q = require 'q'
Serializable = require 'serializable'
Delegator = require 'delegato'
PaneContainer = require './pane-container'
Pane = require './pane'

# Public: Represents the view state of the entire window, including the panes at
# the center and panels around the periphery.
#
# An instance of this class is always available as the `atom.workspace` global.
module.exports =
class Workspace extends Model
  atom.deserializers.add(this)
  Serializable.includeInto(this)

  @delegatesProperty 'activePane', 'activePaneItem', toProperty: 'paneContainer'
  @delegatesMethod 'getPanes', 'saveAll', 'activateNextPane', 'activatePreviousPane',
    toProperty: 'paneContainer'

  @properties
    paneContainer: -> new PaneContainer
    fullScreen: false
    destroyedItemUris: -> []

  constructor: ->
    super
    @subscribe @paneContainer, 'item-destroyed', @onPaneItemDestroyed
    @registerOpener (filePath) =>
      switch filePath
        when 'atom://.atom/stylesheet'
          @open(atom.themes.getUserStylesheetPath())
        when 'atom://.atom/keymap'
          @open(atom.keymap.getUserKeymapPath())
        when 'atom://.atom/config'
          @open(atom.config.getUserConfigPath())
        when 'atom://.atom/init-script'
          @open(atom.getUserInitScriptPath())

  # Called by the Serializable mixin during deserialization
  deserializeParams: (params) ->
    params.paneContainer = PaneContainer.deserialize(params.paneContainer)
    params

  # Called by the Serializable mixin during serialization.
  serializeParams: ->
    paneContainer: @paneContainer.serialize()
    fullScreen: atom.isFullScreen()

  # Public: Calls callback for every existing {Editor} and for all new {Editor}s.
  # that are created.
  #
  # callback - A {Function} with an {Editor} as its only argument
  eachEditor: (callback) ->
    atom.project.eachEditor(callback)

  # Public: Returns an {Array} of all open {Editor}s.
  getEditors: ->
    atom.project.getEditors()

  # Public: Asynchronously opens a given a filepath in Atom.
  #
  # uri - A {String} uri.
  # options  - An options {Object} (default: {}).
  #   :initialLine - A {Number} indicating which line number to open to.
  #   :split - A {String} ('left' or 'right') that opens the filePath in a new
  #            pane or an existing one if it exists.
  #   :changeFocus - A {Boolean} that allows the filePath to be opened without
  #                  changing focus.
  #   :searchAllPanes - A {Boolean} that will open existing editors from any pane
  #                     if the uri is already open (default: false)
  #
  # Returns a promise that resolves to the {Editor} for the file URI.
  open: (uri, options={}) ->
    searchAllPanes = options.searchAllPanes
    split = options.split
    uri = atom.project.resolve(uri)

    pane = @paneContainer.paneForUri(uri) if searchAllPanes
    pane ?= switch split
      when 'left'
        @activePane.findLeftmostSibling()
      when 'right'
        @activePane.findOrCreateRightmostSibling()
      else
        @activePane

    @openUriInPane(uri, pane, options)

  openLicense: ->
    @open(join(atom.getLoadSettings().resourcePath, 'LICENSE'))

  # Only used in specs
  openSync: (uri='', options={}) ->
    {initialLine} = options
    # TODO: Remove deprecated changeFocus option
    activatePane = options.activatePane ? options.changeFocus ? true
    uri = atom.project.resolve(uri)

    item = @activePane.itemForUri(uri)
    if uri
      item ?= opener(uri, options) for opener in @getOpeners() when !item
    item ?= atom.project.openSync(uri, {initialLine})

    @activePane.activateItem(item)
    @itemOpened(item)
    @activePane.activate() if activatePane
    item

  openUriInPane: (uri, pane, options={}) ->
    changeFocus = options.changeFocus ? true

    if uri?
      item = pane.itemForUri(uri)
      item ?= opener(atom.project.resolve(uri), options) for opener in @getOpeners() when !item
    item ?= atom.project.open(uri, options)

    Q(item)
      .then (item) =>
        if not pane
          pane = new Pane(items: [item])
          @paneContainer.root = pane
        @itemOpened(item)
        pane.activateItem(item)
        pane.activate() if changeFocus
        @emit "uri-opened"
        item
      .catch (error) ->
        console.error(error.stack ? error)

  # Public: Reopens the last-closed item uri if it hasn't already been reopened.
  reopenItemSync: ->
    if uri = @destroyedItemUris.pop()
      @openSync(uri)

  # Public: Register an opener for a uri.
  #
  # An {Editor} will be used if no openers return a value.
  #
  # ## Example
  # ```coffeescript
  #   atom.project.registerOpener (uri) ->
  #     if path.extname(uri) is '.toml'
  #       return new TomlEditor(uri)
  # ```
  #
  # opener - A {Function} to be called when a path is being opened.
  registerOpener: (opener) ->
    atom.project.registerOpener(opener)

  # Public: Remove a registered opener.
  unregisterOpener: (opener) ->
    atom.project.unregisterOpener(opener)

  getOpeners: ->
    atom.project.openers

  # Public: Returns the active {Pane}.
  getActivePane: ->
    @paneContainer.activePane

  # Public: Returns the first pane {Pane} with an item for the given uri or
  # undefined if none exists.
  paneForUri: (uri) ->
    @paneContainer.paneForUri(uri)

  # Public: save the active item.
  saveActivePaneItem: ->
    @activePane?.saveActiveItem()

  # Public: save the active item as.
  saveActivePaneItemAs: ->
    @activePane?.saveActiveItemAs()

  # Public: destroy/close the active item.
  destroyActivePaneItem: ->
    @activePane?.destroyActiveItem()

  # Public: destroy/close the active pane.
  destroyActivePane: ->
    @activePane?.destroy()

  # Public: Returns an {Editor} if the active pane item is an {Editor},
  # or null otherwise.
  getActiveEditor: ->
    @activePane?.getActiveEditor()

  increaseFontSize: ->
    atom.config.set("editor.fontSize", atom.config.get("editor.fontSize") + 1)

  decreaseFontSize: ->
    fontSize = atom.config.get("editor.fontSize")
    atom.config.set("editor.fontSize", fontSize - 1) if fontSize > 1

  resetFontSize: ->
    atom.config.restoreDefault("editor.fontSize")

  # Removes the item's uri from the list of potential items to reopen.
  itemOpened: (item) ->
    if uri = item.getUri?()
      remove(@destroyedItemUris, uri)

  # Adds the destroyed item's uri to the list of items to reopen.
  onPaneItemDestroyed: (item) =>
    if uri = item.getUri?()
      @destroyedItemUris.push(uri)

  # Called by Model superclass when destroyed
  destroyed: ->
    @paneContainer.destroy()
