const {app, Menu} = require('electron')
const _ = require('underscore-plus')
const MenuHelpers = require('../menu-helpers')

// Used to manage the global application menu.
//
// It's created by {AtomApplication} upon instantiation and used to add, remove
// and maintain the state of all menu items.
module.exports =
class ApplicationMenu {
  constructor (version, autoUpdateManager) {
    this.version = version
    this.autoUpdateManager = autoUpdateManager
    this.windowTemplates = new WeakMap()
    this.setActiveTemplate(this.getDefaultTemplate())
    this.autoUpdateManager.on('state-changed', state => this.showUpdateMenuItem(state))
  }

  // Public: Updates the entire menu with the given keybindings.
  //
  // window - The BrowserWindow this menu template is associated with.
  // template - The Object which describes the menu to display.
  // keystrokesByCommand - An Object where the keys are commands and the values
  //                       are Arrays containing the keystroke.
  update (window, template, keystrokesByCommand) {
    this.translateTemplate(template, keystrokesByCommand)
    this.substituteVersion(template)
    this.windowTemplates.set(window, template)
    if (window === this.lastFocusedWindow) return this.setActiveTemplate(template)
  }

  setActiveTemplate (template) {
    if (!_.isEqual(template, this.activeTemplate)) {
      this.activeTemplate = template
      this.menu = Menu.buildFromTemplate(_.deepClone(template))
      Menu.setApplicationMenu(this.menu)
    }

    return this.showUpdateMenuItem(this.autoUpdateManager.getState())
  }

  // Register a BrowserWindow with this application menu.
  addWindow (window) {
    if (this.lastFocusedWindow == null) this.lastFocusedWindow = window

    const focusHandler = () => {
      this.lastFocusedWindow = window
      const template = this.windowTemplates.get(window)
      if (template) this.setActiveTemplate(template)
    }

    window.on('focus', focusHandler)
    window.once('closed', () => {
      if (window === this.lastFocusedWindow) this.lastFocusedWindow = null
      this.windowTemplates.delete(window)
      window.removeListener('focus', focusHandler)
    })

    this.enableWindowSpecificItems(true)
  }

  // Flattens the given menu and submenu items into an single Array.
  //
  // menu - A complete menu configuration object for atom-shell's menu API.
  //
  // Returns an Array of native menu items.
  flattenMenuItems (menu) {
    const object = menu.items || {}
    let items = []
    for (let index in object) {
      const item = object[index]
      items.push(item)
      if (item.submenu) items = items.concat(this.flattenMenuItems(item.submenu))
    }
    return items
  }

  // Flattens the given menu template into an single Array.
  //
  // template - An object describing the menu item.
  //
  // Returns an Array of native menu items.
  flattenMenuTemplate (template) {
    let items = []
    for (let item of template) {
      items.push(item)
      if (item.submenu) items = items.concat(this.flattenMenuTemplate(item.submenu))
    }
    return items
  }

  // Public: Used to make all window related menu items are active.
  //
  // enable - If true enables all window specific items, if false disables all
  //          window specific items.
  enableWindowSpecificItems (enable) {
    for (let item of this.flattenMenuItems(this.menu)) {
      if (item.metadata && item.metadata.windowSpecific) item.enabled = enable
    }
  }

  // Replaces VERSION with the current version.
  substituteVersion (template) {
    let item = this.flattenMenuTemplate(template).find(({label}) => label === 'VERSION')
    if (item) item.label = `Version ${this.version}`
  }

  // Sets the proper visible state the update menu items
  showUpdateMenuItem (state) {
    const items = this.flattenMenuItems(this.menu)
    const checkForUpdateItem = items.find(({label}) => label === 'Check for Update')
    const checkingForUpdateItem = items.find(({label}) => label === 'Checking for Update')
    const downloadingUpdateItem = items.find(({label}) => label === 'Downloading Update')
    const installUpdateItem = items.find(({label}) => label === 'Restart and Install Update')

    if (!checkForUpdateItem || !checkingForUpdateItem ||
        !downloadingUpdateItem || !installUpdateItem) return

    checkForUpdateItem.visible = false
    checkingForUpdateItem.visible = false
    downloadingUpdateItem.visible = false
    installUpdateItem.visible = false

    switch (state) {
      case 'idle':
      case 'error':
      case 'no-update-available':
        checkForUpdateItem.visible = true
        break
      case 'checking':
        checkingForUpdateItem.visible = true
        break
      case 'downloading':
        downloadingUpdateItem.visible = true
        break
      case 'update-available':
        installUpdateItem.visible = true
        break
    }
  }

  // Default list of menu items.
  //
  // Returns an Array of menu item Objects.
  getDefaultTemplate () {
    return [{
      label: 'Atom',
      submenu: [
        {
          label: 'Check for Update',
          metadata: {autoUpdate: true}
        },
        {
          label: 'Reload',
          accelerator: 'Command+R',
          click: () => {
            const window = this.focusedWindow()
            if (window) window.reload()
          }
        },
        {
          label: 'Close Window',
          accelerator: 'Command+Shift+W',
          click: () => {
            const window = this.focusedWindow()
            if (window) window.close()
          }
        },
        {
          label: 'Toggle Dev Tools',
          accelerator: 'Command+Alt+I',
          click: () => {
            const window = this.focusedWindow()
            if (window) window.toggleDevTools()
          }
        },
        {
          label: 'Quit',
          accelerator: 'Command+Q',
          click: () => app.quit()
        }
      ]
    }]
  }

  focusedWindow () {
    return global.atomApplication.getAllWindows().find(window => window.isFocused())
  }

  // Combines a menu template with the appropriate keystroke.
  //
  // template - An Object conforming to atom-shell's menu api but lacking
  //            accelerator and click properties.
  // keystrokesByCommand - An Object where the keys are commands and the values
  //                       are Arrays containing the keystroke.
  //
  // Returns a complete menu configuration object for atom-shell's menu API.
  translateTemplate (template, keystrokesByCommand) {
    template.forEach(item => {
      if (item.metadata == null) item.metadata = {}
      if (item.command) {
        item.accelerator = this.acceleratorForCommand(item.command, keystrokesByCommand)
        item.click = () => global.atomApplication.sendCommand(item.command, item.commandDetail)
        if (!/^application:/.test(item.command)) {
          item.metadata.windowSpecific = true
        }
      }
      if (item.submenu) this.translateTemplate(item.submenu, keystrokesByCommand)
    })
    return template
  }

  // Determine the accelerator for a given command.
  //
  // command - The name of the command.
  // keystrokesByCommand - An Object where the keys are commands and the values
  //                       are Arrays containing the keystroke.
  //
  // Returns a String containing the keystroke in a format that can be interpreted
  //   by Electron to provide nice icons where available.
  acceleratorForCommand (command, keystrokesByCommand) {
    const firstKeystroke = keystrokesByCommand[command] && keystrokesByCommand[command][0]
    return MenuHelpers.acceleratorForKeystroke(firstKeystroke)
  }
}
