const {find} = require('underscore-plus')
const {Emitter, CompositeDisposable} = require('event-kit')
const Pane = require('./pane')
const ItemRegistry = require('./item-registry')
const PaneContainerElement = require('./pane-container-element')

const SERIALIZATION_VERSION = 1
const STOPPED_CHANGING_ACTIVE_PANE_ITEM_DELAY = 100

module.exports =
class PaneContainer {
  constructor (params) {
    let applicationDelegate, deserializerManager, notificationManager;
    ({config: this.config, applicationDelegate, notificationManager, deserializerManager, viewRegistry: this.viewRegistry, location: this.location} = params)
    this.emitter = new Emitter()
    this.subscriptions = new CompositeDisposable()
    this.itemRegistry = new ItemRegistry()
    this.alive = true
    this.stoppedChangingActivePaneItemTimeout = null

    this.setRoot(new Pane({container: this, config: this.config, applicationDelegate, notificationManager, deserializerManager, viewRegistry: this.viewRegistry}))
    this.didActivatePane(this.getRoot())
  }

  getLocation () { return this.location }

  getElement () {
    return this.element != null ? this.element : (this.element = new PaneContainerElement().initialize(this, {views: this.viewRegistry}))
  }

  destroy () {
    this.alive = false
    for (let pane of this.getRoot().getPanes()) { pane.destroy() }
    this.cancelStoppedChangingActivePaneItemTimeout()
    this.subscriptions.dispose()
    this.emitter.dispose()
  }

  isAlive () { return this.alive }

  isDestroyed () { return !this.isAlive() }

  serialize (params) {
    return {
      deserializer: 'PaneContainer',
      version: SERIALIZATION_VERSION,
      root: this.root ? this.root.serialize() : null,
      activePaneId: this.activePane.id
    }
  }

  deserialize (state, deserializerManager) {
    if (state.version !== SERIALIZATION_VERSION) return
    this.itemRegistry = new ItemRegistry()
    this.setRoot(deserializerManager.deserialize(state.root))
    this.activePane = find(this.getRoot().getPanes(), pane => pane.id === state.activePaneId) || this.getPanes()[0]
    if (this.config.get('core.destroyEmptyPanes')) this.destroyEmptyPanes()
  }

  onDidChangeRoot (fn) {
    return this.emitter.on('did-change-root', fn)
  }

  observeRoot (fn) {
    fn(this.getRoot())
    return this.onDidChangeRoot(fn)
  }

  onDidAddPane (fn) {
    return this.emitter.on('did-add-pane', fn)
  }

  observePanes (fn) {
    for (let pane of this.getPanes()) { fn(pane) }
    return this.onDidAddPane(({pane}) => fn(pane))
  }

  onDidDestroyPane (fn) {
    return this.emitter.on('did-destroy-pane', fn)
  }

  onWillDestroyPane (fn) {
    return this.emitter.on('will-destroy-pane', fn)
  }

  onDidChangeActivePane (fn) {
    return this.emitter.on('did-change-active-pane', fn)
  }

  onDidActivatePane (fn) {
    return this.emitter.on('did-activate-pane', fn)
  }

  observeActivePane (fn) {
    fn(this.getActivePane())
    return this.onDidChangeActivePane(fn)
  }

  onDidAddPaneItem (fn) {
    return this.emitter.on('did-add-pane-item', fn)
  }

  observePaneItems (fn) {
    for (let item of this.getPaneItems()) { fn(item) }
    return this.onDidAddPaneItem(({item}) => fn(item))
  }

  onDidChangeActivePaneItem (fn) {
    return this.emitter.on('did-change-active-pane-item', fn)
  }

  onDidStopChangingActivePaneItem (fn) {
    return this.emitter.on('did-stop-changing-active-pane-item', fn)
  }

  observeActivePaneItem (fn) {
    fn(this.getActivePaneItem())
    return this.onDidChangeActivePaneItem(fn)
  }

  onWillDestroyPaneItem (fn) {
    return this.emitter.on('will-destroy-pane-item', fn)
  }

  onDidDestroyPaneItem (fn) {
    return this.emitter.on('did-destroy-pane-item', fn)
  }

  getRoot () { return this.root }

  setRoot (root) {
    this.root = root
    this.root.setParent(this)
    this.root.setContainer(this)
    this.emitter.emit('did-change-root', this.root)
    if ((this.getActivePane() == null) && this.root instanceof Pane) {
      this.didActivatePane(this.root)
    }
  }

  replaceChild (oldChild, newChild) {
    if (oldChild !== this.root) { throw new Error('Replacing non-existent child') }
    this.setRoot(newChild)
  }

  getPanes () {
    if (this.alive) {
      return this.getRoot().getPanes()
    } else {
      return []
    }
  }

  getPaneItems () {
    return this.getRoot().getItems()
  }

  getActivePane () {
    return this.activePane
  }

  getActivePaneItem () {
    return this.getActivePane().getActiveItem()
  }

  paneForURI (uri) {
    return find(this.getPanes(), pane => pane.itemForURI(uri) != null)
  }

  paneForItem (item) {
    return find(this.getPanes(), pane => pane.getItems().includes(item))
  }

  saveAll () {
    for (let pane of this.getPanes()) { pane.saveItems() }
  }

  confirmClose (options) {
    const promises = []
    for (const pane of this.getPanes()) {
      for (const item of pane.getItems()) {
        promises.push(pane.promptToSaveItem(item, options))
      }
    }
    return Promise.all(promises).then((results) => !results.includes(false))
  }

  activateNextPane () {
    const panes = this.getPanes()
    if (panes.length > 1) {
      const currentIndex = panes.indexOf(this.activePane)
      const nextIndex = (currentIndex + 1) % panes.length
      panes[nextIndex].activate()
      return true
    } else {
      return false
    }
  }

  activatePreviousPane () {
    const panes = this.getPanes()
    if (panes.length > 1) {
      const currentIndex = panes.indexOf(this.activePane)
      let previousIndex = currentIndex - 1
      if (previousIndex < 0) { previousIndex = panes.length - 1 }
      panes[previousIndex].activate()
      return true
    } else {
      return false
    }
  }

  moveActiveItemToPane (destPane) {
    const item = this.activePane.getActiveItem()

    if (!destPane.isItemAllowed(item)) { return }

    this.activePane.moveItemToPane(item, destPane)
    destPane.setActiveItem(item)
  }

  copyActiveItemToPane (destPane) {
    const item = this.activePane.copyActiveItem()

    if (item && destPane.isItemAllowed(item)) {
      destPane.activateItem(item)
    }
  }

  destroyEmptyPanes () {
    for (let pane of this.getPanes()) { if (pane.items.length === 0) { pane.destroy() } }
  }

  didAddPane (event) {
    this.emitter.emit('did-add-pane', event)
    const items = event.pane.getItems()
    for (let i = 0, length = items.length; i < length; i++) {
      const item = items[i]
      this.didAddPaneItem(item, event.pane, i)
    }
  }

  willDestroyPane (event) {
    this.emitter.emit('will-destroy-pane', event)
  }

  didDestroyPane (event) {
    this.emitter.emit('did-destroy-pane', event)
  }

  didActivatePane (activePane) {
    if (activePane !== this.activePane) {
      if (!this.getPanes().includes(activePane)) {
        throw new Error('Setting active pane that is not present in pane container')
      }

      this.activePane = activePane
      this.emitter.emit('did-change-active-pane', this.activePane)
      this.didChangeActiveItemOnPane(this.activePane, this.activePane.getActiveItem())
    }
    this.emitter.emit('did-activate-pane', this.activePane)
    return this.activePane
  }

  didAddPaneItem (item, pane, index) {
    this.itemRegistry.addItem(item)
    this.emitter.emit('did-add-pane-item', {item, pane, index})
  }

  willDestroyPaneItem (event) {
    return this.emitter.emitAsync('will-destroy-pane-item', event)
  }

  didDestroyPaneItem (event) {
    this.itemRegistry.removeItem(event.item)
    this.emitter.emit('did-destroy-pane-item', event)
  }

  didChangeActiveItemOnPane (pane, activeItem) {
    if (pane === this.getActivePane()) {
      this.emitter.emit('did-change-active-pane-item', activeItem)

      this.cancelStoppedChangingActivePaneItemTimeout()
      // `setTimeout()` isn't available during the snapshotting phase, but that's okay.
      if (!global.isGeneratingSnapshot) {
        this.stoppedChangingActivePaneItemTimeout = setTimeout(() => {
          this.stoppedChangingActivePaneItemTimeout = null
          this.emitter.emit('did-stop-changing-active-pane-item', activeItem)
        }, STOPPED_CHANGING_ACTIVE_PANE_ITEM_DELAY)
      }
    }
  }

  cancelStoppedChangingActivePaneItemTimeout () {
    if (this.stoppedChangingActivePaneItemTimeout != null) {
      clearTimeout(this.stoppedChangingActivePaneItemTimeout)
    }
  }
}
