require "log4r"

module Vagrant
  module Action
    module Builtin
      # This middleware will remove a box for a given provider.
      class BoxRemove
        def initialize(app, env)
          @app    = app
          @logger = Log4r::Logger.new("vagrant::action::builtin::box_remove")
        end

        def call(env)
          box_name     = env[:box_name]
          box_provider = env[:box_provider]
          box_provider = box_provider.to_sym if box_provider
          box_version  = env[:box_version]
          box_remove_all_versions = env[:box_remove_all_versions]

          boxes = {}
          env[:box_collection].all.each do |n, v, p|
            boxes[n] ||= {}
            boxes[n][p] ||= []
            boxes[n][p] << v
          end

          all_box = boxes[box_name]
          if !all_box
            raise Errors::BoxRemoveNotFound, name: box_name
          end

          all_versions = nil
          if !box_provider
            if all_box.length == 1
              # There is only one provider, just use that.
              all_versions = all_box.values.first
              box_provider = all_box.keys.first
            else
              raise Errors::BoxRemoveMultiProvider,
                name: box_name,
                providers: all_box.keys.map(&:to_s).sort.join(", ")
            end
          else
            all_versions = all_box[box_provider]
            if !all_versions
              raise Errors::BoxRemoveProviderNotFound,
                name: box_name,
                provider: box_provider.to_s,
                providers: all_box.keys.map(&:to_s).sort.join(", ")
            end
          end

          if !box_version
            if all_versions.length == 1
              # There is only one version, just use that.
              box_version = all_versions.first
            elsif not box_remove_all_versions
              # There are multiple versions, we can't choose.
              raise Errors::BoxRemoveMultiVersion,
                name: box_name,
                provider: box_provider.to_s,
                versions: all_versions.sort.map { |k| " * #{k}" }.join("\n")
            end
          elsif !all_versions.include?(box_version)
            raise Errors::BoxRemoveVersionNotFound,
              name: box_name,
              provider: box_provider.to_s,
              version: box_version,
              versions: all_versions.sort.map { |k| " * #{k}" }.join("\n")
          end

          versions_to_remove = [box_version]
          versions_to_remove = all_versions if box_remove_all_versions

          versions_to_remove.sort.each do |version_to_remove|
            box = env[:box_collection].find(
              box_name, box_provider, box_version)

            # Verify that this box is not in use by an active machine,
            # otherwise warn the user.
            users = box.in_use?(env[:machine_index]) || []
            users = users.find_all { |u| u.valid?(env[:home_path]) }
            if !users.empty?
              # Build up the output to show the user.
              users = users.map do |entry|
                "#{entry.name} (ID: #{entry.id})"
              end.join("\n")

              force_key = :force_confirm_box_remove
              message   = I18n.t(
                "vagrant.commands.box.remove_in_use_query",
                name: box.name,
                provider: box.provider,
                version: box.version,
                users: users) + " "

              # Ask the user if we should do this
              stack = Builder.new.tap do |b|
                b.use Confirm, message, force_key
              end

              # Keep used boxes, even if "force" is applied
              keep_used_boxes = env[:keep_used_boxes]

              result = env[:action_runner].run(stack, env)
              if !result[:result] || keep_used_boxes
                # They said "no", so continue with the next box
                next
              end
            end

            env[:ui].info(I18n.t("vagrant.commands.box.removing",
                                name: box.name,
                                provider: box.provider,
                                version: box.version))
            box.destroy!
            env[:box_collection].clean(box.name)

            # Passes on the removed box to the rest of the middleware chain
            env[:box_removed] = box
          end

          @app.call(env)
        end
      end
    end
  end
end
