const {EventEmitter} = require('events')
const path = require('path')

const IdleState = 'idle'
const CheckingState = 'checking'
const DownloadingState = 'downloading'
const UpdateAvailableState = 'update-available'
const NoUpdateAvailableState = 'no-update-available'
const UnsupportedState = 'unsupported'
const ErrorState = 'error'

let autoUpdater = null

module.exports =
class AutoUpdateManager extends EventEmitter {
  constructor (version, testMode, config) {
    super()
    this.onUpdateNotAvailable = this.onUpdateNotAvailable.bind(this)
    this.onUpdateError = this.onUpdateError.bind(this)
    this.version = version
    this.testMode = testMode
    this.config = config
    this.state = IdleState
    this.iconPath = path.resolve(__dirname, '..', '..', 'resources', 'atom.png')
  }

  initialize () {
    if (process.platform === 'win32') {
      const archSuffix = process.arch === 'ia32' ? '' : `-${process.arch}`
      this.feedUrl = `https://atom.io/api/updates${archSuffix}?version=${this.version}`
      autoUpdater = require('./auto-updater-win32')
    } else {
      this.feedUrl = `https://atom.io/api/updates?version=${this.version}`;
      ({autoUpdater} = require('electron'))
    }

    autoUpdater.on('error', (event, message) => {
      this.setState(ErrorState, message)
      this.emitWindowEvent('update-error')
      console.error(`Error Downloading Update: ${message}`)
    })

    autoUpdater.setFeedURL(this.feedUrl)

    autoUpdater.on('checking-for-update', () => {
      this.setState(CheckingState)
      this.emitWindowEvent('checking-for-update')
    })

    autoUpdater.on('update-not-available', () => {
      this.setState(NoUpdateAvailableState)
      this.emitWindowEvent('update-not-available')
    })

    autoUpdater.on('update-available', () => {
      this.setState(DownloadingState)
      // We use sendMessage to send an event called 'update-available' in 'update-downloaded'
      // once the update download is complete. This mismatch between the electron
      // autoUpdater events is unfortunate but in the interest of not changing the
      // one existing event handled by applicationDelegate
      this.emitWindowEvent('did-begin-downloading-update')
      this.emit('did-begin-download')
    })

    autoUpdater.on('update-downloaded', (event, releaseNotes, releaseVersion) => {
      this.releaseVersion = releaseVersion
      this.setState(UpdateAvailableState)
      this.emitUpdateAvailableEvent()
    })

    this.config.onDidChange('core.automaticallyUpdate', ({newValue}) => {
      if (newValue) {
        this.scheduleUpdateCheck()
      } else {
        this.cancelScheduledUpdateCheck()
      }
    })

    if (this.config.get('core.automaticallyUpdate')) this.scheduleUpdateCheck()

    switch (process.platform) {
      case 'win32':
        if (!autoUpdater.supportsUpdates()) {
          this.setState(UnsupportedState)
        }
        break
      case 'linux':
        this.setState(UnsupportedState)
    }
  }

  emitUpdateAvailableEvent () {
    if (this.releaseVersion == null) return
    this.emitWindowEvent('update-available', {releaseVersion: this.releaseVersion})
  }

  emitWindowEvent (eventName, payload) {
    for (let atomWindow of this.getWindows()) {
      atomWindow.sendMessage(eventName, payload)
    }
  }

  setState (state, errorMessage) {
    if (this.state === state) return
    this.state = state
    this.errorMessage = errorMessage
    this.emit('state-changed', this.state)
  }

  getState () {
    return this.state
  }

  getErrorMessage () {
    return this.errorMessage
  }

  scheduleUpdateCheck () {
    // Only schedule update check periodically if running in release version and
    // and there is no existing scheduled update check.
    if (!/-dev/.test(this.version) && !this.checkForUpdatesIntervalID) {
      const checkForUpdates = () => this.check({hidePopups: true})
      const fourHours = 1000 * 60 * 60 * 4
      this.checkForUpdatesIntervalID = setInterval(checkForUpdates, fourHours)
      checkForUpdates()
    }
  }

  cancelScheduledUpdateCheck () {
    if (this.checkForUpdatesIntervalID) {
      clearInterval(this.checkForUpdatesIntervalID)
      this.checkForUpdatesIntervalID = null
    }
  }

  check ({hidePopups} = {}) {
    if (!hidePopups) {
      autoUpdater.once('update-not-available', this.onUpdateNotAvailable)
      autoUpdater.once('error', this.onUpdateError)
    }

    if (!this.testMode) autoUpdater.checkForUpdates()
  }

  install () {
    if (!this.testMode) autoUpdater.quitAndInstall()
  }

  onUpdateNotAvailable () {
    autoUpdater.removeListener('error', this.onUpdateError)
    const {dialog} = require('electron')
    dialog.showMessageBox({
      type: 'info',
      buttons: ['OK'],
      icon: this.iconPath,
      message: 'No update available.',
      title: 'No Update Available',
      detail: `Version ${this.version} is the latest version.`
    }, () => {}) // noop callback to get async behavior
  }

  onUpdateError (event, message) {
    autoUpdater.removeListener('update-not-available', this.onUpdateNotAvailable)
    const {dialog} = require('electron')
    dialog.showMessageBox({
      type: 'warning',
      buttons: ['OK'],
      icon: this.iconPath,
      message: 'There was an error checking for updates.',
      title: 'Update Error',
      detail: message
    }, () => {}) // noop callback to get async behavior
  }

  getWindows () {
    return global.atomApplication.getAllWindows()
  }
}
