_ = require 'underscore'
ipc = require 'ipc'
Reflux = require 'reflux'
path = require 'path'
fs = require 'fs-plus'
shell = require 'shell'
PluginsActions = require './plugins-actions'
{APMWrapper} = require 'nylas-exports'
dialog = require('remote').require('dialog')

module.exports =
PackagesStore = Reflux.createStore

  init: ->
    @_apm = new APMWrapper()

    @_globalSearch = ""
    @_installedSearch = ""
    @_installing = {}
    @_featured = {themes: [], packages: []}
    @_newerVersions = []
    @_searchResults = null
    @_refreshFeatured()

    @listenTo PluginsActions.refreshFeaturedPackages, @_refreshFeatured
    @listenTo PluginsActions.refreshInstalledPackages, @_refreshInstalled

    atom.commands.add 'body',
      'application:create-package': => @_onCreatePackage()

    atom.commands.add 'body',
      'application:install-package': => @_onInstallPackage()

    @listenTo PluginsActions.createPackage, @_onCreatePackage
    @listenTo PluginsActions.updatePackage, @_onUpdatePackage
    @listenTo PluginsActions.setGlobalSearchValue, @_onGlobalSearchChange
    @listenTo PluginsActions.setInstalledSearchValue, @_onInstalledSearchChange

    @listenTo PluginsActions.showPackage, (pkg) =>
      dir = atom.packages.resolvePackagePath(pkg.name)
      shell.showItemInFolder(dir) if dir

    @listenTo PluginsActions.installPackage, (pkg) =>
      @_installing[pkg.name] = true
      @trigger(@)
      @_apm.install pkg, (err) =>
        if err
          delete @_installing[pkg.name]
          @_displayMessage("Sorry, an error occurred", err.toString())
        else
          if atom.packages.isPackageDisabled(pkg.name)
            atom.packages.enablePackage(pkg.name)
        @_onPackagesChanged()

    @listenTo PluginsActions.uninstallPackage, (pkg) =>
      if atom.packages.isPackageLoaded(pkg.name)
        atom.packages.disablePackage(pkg.name)
        atom.packages.unloadPackage(pkg.name)
      @_apm.uninstall pkg, (err) =>
        @_displayMessage("Sorry, an error occurred", err.toString()) if err
        @_onPackagesChanged()

    @listenTo PluginsActions.enablePackage, (pkg) ->
      if atom.packages.isPackageDisabled(pkg.name)
        atom.packages.enablePackage(pkg.name)

    @listenTo PluginsActions.disablePackage, (pkg) ->
      unless atom.packages.isPackageDisabled(pkg.name)
        atom.packages.disablePackage(pkg.name)

    @_hasPrepared = false

  # Getters

  installed: ->
    @_prepareIfFresh()
    @_addPackageStates(@_filter(@_installed, @_installedSearch))

  installedSearchValue: ->
    @_installedSearch

  featured: ->
    @_prepareIfFresh()
    @_addPackageStates(@_featured)

  searchResults: ->
    @_addPackageStates(@_searchResults)

  globalSearchValue: ->
    @_globalSearch

  # Action Handlers

  _prepareIfFresh: ->
    return if @_hasPrepared
    atom.packages.onDidActivatePackage(=> @_onPackagesChangedDebounced())
    atom.packages.onDidDeactivatePackage(=> @_onPackagesChangedDebounced())
    atom.packages.onDidLoadPackage(=> @_onPackagesChangedDebounced())
    atom.packages.onDidUnloadPackage(=> @_onPackagesChangedDebounced())
    @_onPackagesChanged()
    @_hasPrepared = true

  _filter: (hash, search) ->
    result = {}
    search = search.toLowerCase()
    for key, pkgs of hash
      result[key] = _.filter pkgs, (p) =>
        search.length is 0 or p.name.toLowerCase().indexOf(search) isnt -1
    result

  _refreshFeatured: ->
    @_apm.getFeatured({themes: false}).then (results) =>
      @_featured.packages = results
      @trigger()
    .catch (err) =>
      # We may be offline
    @_apm.getFeatured({themes: true}).then (results) =>
      @_featured.themes = results
      @trigger()
    .catch (err) =>
      # We may be offline

  _refreshInstalled: ->
    @_onPackagesChanged()

  _refreshSearch: ->
    return unless @_globalSearch?.length > 0

    @_apm.search(@_globalSearch).then (results) =>
      @_searchResults =
        packages: results.filter ({theme}) -> not theme
        themes: results.filter ({theme}) -> theme
      @trigger()
    .catch (err) =>
      # We may be offline

  _refreshSearchThrottled: _.debounce((-> @_refreshSearch()), 400)

  _onPackagesChanged: ->
    @_apm.getInstalled().then (packages) =>
      for category in ['dev', 'user', 'core']
        packages[category] = packages[category].filter ({theme}) -> not theme
        packages[category].forEach (pkg) =>
          pkg.category = category
          delete @_installing[pkg.name]
      @_installed = packages
      @trigger()

    @_apm.getOutdated().then (packages) =>
      @_newerVersions = {}
      for pkg in packages
        @_newerVersions[pkg.name] = pkg.latestVersion
      @trigger()

  _onPackagesChangedDebounced: _.debounce((-> @_onPackagesChanged()), 200)

  _onInstalledSearchChange: (val) ->
    @_installedSearch = val
    @trigger()

  _onUpdatePackage: (pkg) ->
    @_apm.update(pkg, pkg.newerVersion)

  _onInstallPackage: ->
    {resourcePath} = atom.getLoadSettings()
    if resourcePath.indexOf('app.asar') != -1
      starterPackagesPath = path.join(resourcePath,'..', 'app.asar.unpacked', 'examples')
    else
      starterPackagesPath = path.join(resourcePath, "examples")

    dialog.showOpenDialog
      title: "Choose a Package Directory"
      defaultPath: starterPackagesPath
      properties: ['openDirectory']
    , (filenames) =>
      return if not filenames or filenames.length is 0
      atom.packages.installPackageFromPath filenames[0], (err, packageTargetDir) =>
        return if err
        packageName = path.basename(filenames[0])
        msg = "#{packageName} has been installed and enabled. No need to \
               restart! If you don't see the package loaded, check the \
               console for errors."
        @_displayMessage("Package installed", msg)
        if packageTargetDir
          shell.showItemInFolder(packageTargetDir)

  _onCreatePackage: ->
    if not atom.inDevMode()
      btn = dialog.showMessageBox
        type: 'warning'
        message: "Run with debug flags?"
        detail: "To develop plugins, you should run N1 with debug flags.
                This gives you better error messages, the debug version of
                React, and more. You can disable it at any time from the
                Developer menu."
        buttons: ["OK", "Cancel"]
      if btn is 0
        ipc.send('command', 'application:toggle-dev')
      return

    packagesDir = path.join(atom.getConfigDirPath(), 'dev', 'packages')
    fs.makeTreeSync(packagesDir)

    dialog.showSaveDialog
      title: "Save New Package"
      defaultPath: packagesDir
      properties: ['createDirectory']
    , (packageDir) =>
      return unless packageDir

      packageName = path.basename(packageDir)

      if not packageDir.startsWith(packagesDir)
        return @_displayMessage('Invalid package location', 'Sorry, you must
                                    create packages in the packages folder.')

      if atom.packages.resolvePackagePath(packageName)
        return @_displayMessage('Invalid package name', 'Sorry, you must
                                    give your package a unqiue name.')

      if packageName.indexOf(' ') isnt -1
        return @_displayMessage('Invalid package name', 'Sorry, package names
                                 cannot contain spaces.')

      fs.mkdir packageDir, (err) =>
        return @_displayMessage('Could not create package', err.toString()) if err

        {resourcePath} = atom.getLoadSettings()
        packageTemplatePath = path.join(resourcePath, 'static', 'package-template')
        packageJSON =
          name: packageName
          main: "./lib/main"
          version: '0.1.0'
          repository:
            type: 'git'
            url: ''
          engines:
            atom: ">=#{atom.getVersion()}"
          description: "Enter a description of your package!"
          dependencies: {}
          license: "MIT"

        fs.copySync(packageTemplatePath, packageDir)
        fs.writeFileSync(path.join(packageDir, 'package.json'), JSON.stringify(packageJSON, null, 2))
        shell.showItemInFolder(packageDir)
        _.defer ->
          atom.packages.enablePackage(packageDir)
          atom.packages.activatePackage(packageName)

  _onGlobalSearchChange: (val) ->
    # Clear previous search results data if this is a new
    # search beginning from "".
    if @_globalSearch.length is 0 and val.length > 0
      @_searchResults = null

    @_globalSearch = val
    @_refreshSearchThrottled()
    @trigger()

  _addPackageStates: (pkgs) ->
    installedNames = _.flatten(_.values(@_installed)).map (pkg) -> pkg.name

    _.flatten(_.values(pkgs)).forEach (pkg) =>
      pkg.enabled = !atom.packages.isPackageDisabled(pkg.name)
      pkg.installed = pkg.name in installedNames
      pkg.installing = @_installing[pkg.name]?
      pkg.newerVersionAvailable = @_newerVersions[pkg.name]?
      pkg.newerVersion = @_newerVersions[pkg.name]

    pkgs

  _displayMessage: (title, message) ->
    dialog.showMessageBox
      type: 'warning'
      message: title
      detail: message
      buttons: ["OK"]
