#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++

require 'timeout'
require 'rubygems/command'
require 'rubygems/user_interaction'

##
# The command manager registers and installs all the individual sub-commands
# supported by the gem command.
#
# Extra commands can be provided by writing a rubygems_plugin.rb
# file in an installed gem.  You should register your command against the
# Gem::CommandManager instance, like this:
#
#   # file rubygems_plugin.rb
#   require 'rubygems/command_manager'
#
#   class Gem::Commands::EditCommand < Gem::Command
#     # ...
#   end
#
#   Gem::CommandManager.instance.register_command :edit
#
# See Gem::Command for instructions on writing gem commands.

class Gem::CommandManager

  include Gem::UserInteraction

  ##
  # Return the authoritative instance of the command manager.

  def self.instance
    @command_manager ||= new
  end

  ##
  # Register all the subcommands supported by the gem command.

  def initialize
    @commands = {}
    register_command :build
    register_command :cert
    register_command :check
    register_command :cleanup
    register_command :contents
    register_command :dependency
    register_command :environment
    register_command :fetch
    register_command :generate_index
    register_command :help
    register_command :install
    register_command :list
    register_command :lock
    register_command :mirror
    register_command :outdated
    register_command :pristine
    register_command :query
    register_command :rdoc
    register_command :search
    register_command :server
    register_command :sources
    register_command :specification
    register_command :stale
    register_command :uninstall
    register_command :unpack
    register_command :update
    register_command :which
  end

  ##
  # Register the command object.

  def register_command(command_obj)
    @commands[command_obj] = false
  end

  ##
  # Return the registered command from the command name.

  def [](command_name)
    command_name = command_name.intern
    return nil if @commands[command_name].nil?
    @commands[command_name] ||= load_and_instantiate(command_name)
  end

  ##
  # Return a sorted list of all command names (as strings).

  def command_names
    @commands.keys.collect {|key| key.to_s}.sort
  end

  ##
  # Run the config specified by +args+.

  def run(args)
    process_args(args)
  rescue StandardError, Timeout::Error => ex
    alert_error "While executing gem ... (#{ex.class})\n    #{ex.to_s}"
    ui.errs.puts "\t#{ex.backtrace.join "\n\t"}" if
      Gem.configuration.backtrace
    terminate_interaction(1)
  rescue Interrupt
    alert_error "Interrupted"
    terminate_interaction(1)
  end

  def process_args(args)
    args = args.to_str.split(/\s+/) if args.respond_to?(:to_str)
    if args.size == 0
      say Gem::Command::HELP
      terminate_interaction(1)
    end
    case args[0]
    when '-h', '--help'
      say Gem::Command::HELP
      terminate_interaction(0)
    when '-v', '--version'
      say Gem::RubyGemsVersion
      terminate_interaction(0)
    when /^-/
      alert_error "Invalid option: #{args[0]}.  See 'gem --help'."
      terminate_interaction(1)
    else
      cmd_name = args.shift.downcase
      cmd = find_command(cmd_name)
      cmd.invoke(*args)
    end
  end

  def find_command(cmd_name)
    possibilities = find_command_possibilities cmd_name
    if possibilities.size > 1 then
      raise "Ambiguous command #{cmd_name} matches [#{possibilities.join(', ')}]"
    elsif possibilities.size < 1 then
      raise "Unknown command #{cmd_name}"
    end

    self[possibilities.first]
  end

  def find_command_possibilities(cmd_name)
    len = cmd_name.length

    command_names.select { |n| cmd_name == n[0, len] }
  end

  private

  def load_and_instantiate(command_name)
    command_name = command_name.to_s
    retried = false

    begin
      const_name = command_name.capitalize.gsub(/_(.)/) { $1.upcase }
      Gem::Commands.const_get("#{const_name}Command").new
    rescue NameError
      if retried then
        raise
      else
        retried = true
        require "rubygems/commands/#{command_name}_command"
        retry
      end
    end
  end

end

