require "rubygems"
require "rubygems/dependency_installer"

begin
  require "rubygems/format"
rescue LoadError
  # rubygems 2.x
end

require "log4r"

module VagrantPlugins
  module CommandPlugin
    module Action
      # This action takes the `:plugin_name` variable in the environment
      # and installs it using the RubyGems API.
      class InstallGem
        def initialize(app, env)
          @app    = app
          @logger = Log4r::Logger.new("vagrant::plugins::plugincommand::installgem")
        end

        def call(env)
          plugin_name = env[:plugin_name]
          prerelease  = env[:plugin_prerelease]
          version     = env[:plugin_version]

          # Determine the plugin name we'll look for in the installed set
          # in order to determine the version and all that.
          find_plugin_name = plugin_name
          if plugin_name =~ /\.gem$/
            # If we're installing from a gem file, determine the name
            # based on the spec in the file.
            pkg = if defined?(Gem::Format)
              # RubyGems 1.x
              Gem::Format.from_file_by_path(plugin_name)
            else
              # RubyGems 2.x
              Gem::Package.new(plugin_name)
            end

            find_plugin_name = pkg.spec.name
            version = pkg.spec.version
          end

          # Install the gem
          plugin_name_label = plugin_name
          plugin_name_label += ' --prerelease' if prerelease
          plugin_name_label += " --version '#{version}'" if version
          env[:ui].info(I18n.t("vagrant.commands.plugin.installing",
                               :name => plugin_name_label))
          installed_gems = env[:gem_helper].with_environment do
            # Override the list of sources by the ones set as a parameter if given
            if env[:plugin_sources]
              @logger.info("Custom plugin sources: #{env[:plugin_sources]}")
              Gem.sources = env[:plugin_sources]
            end

            installer = Gem::DependencyInstaller.new(:document => [], :prerelease => prerelease)

            # If we don't have a version, use the default version
            version ||= Gem::Requirement.default

            begin
              installer.install(plugin_name, version)
            rescue Gem::GemNotFoundException
              raise Vagrant::Errors::PluginInstallNotFound,
                :name => plugin_name
            end
          end

          # The plugin spec is the last installed gem since RubyGems
          # currently always installed the requested gem last.
          @logger.debug("Installed #{installed_gems.length} gems.")
          plugin_spec = installed_gems.find do |gem|
            gem.name.downcase == find_plugin_name.downcase
          end

          # Store the installed name so we can uninstall it if things go
          # wrong.
          @installed_plugin_name = plugin_spec.name

          # Mark that we installed the gem
          @logger.info("Adding the plugin to the state file...")
          env[:plugin_state_file].add_plugin(plugin_spec.name)

          # Tell the user
          env[:ui].success(I18n.t("vagrant.commands.plugin.installed",
                                  :name => plugin_spec.name,
                                  :version => plugin_spec.version.to_s))

          # If the plugin's spec includes a post-install message display it
          post_install_message = plugin_spec.post_install_message
          if post_install_message
            if post_install_message.is_a?(Array)
              post_install_message = post_install_message.join(" ")
            end

            env[:ui].info(I18n.t("vagrant.commands.plugin.post_install",
                                 :name => plugin_spec.name,
                                 :message => post_install_message.to_s))
          end

          # Continue
          @app.call(env)
        end

        def recover(env)
          # If any error happens, we uninstall it and remove it from
          # the state file. We can only do this if we successfully installed
          # the gem in the first place.
          if @installed_plugin_name
            new_env = env.dup
            new_env.delete(:interrupted)
            new_env[:plugin_name] = @installed_plugin_name
            new_env[:action_runner].run(Action.action_uninstall, new_env)
          end
        end
      end
    end
  end
end
