require 'rubygems/user_interaction'

module Gem
  class Command
    include UserInteraction
    
    Option = Struct.new(:short, :long, :description, :handler)
    
    attr_reader :command, :options
    attr_accessor :summary, :defaults, :program_name
    
    def initialize(command, summary=nil, defaults={})
      @command = command
      @summary = summary
      @program_name = "gem #{command}"
      @defaults = defaults
      @options = defaults.dup
      @option_list = []
      @parser = nil
    end
    
    def show_help
      parser.program_name = usage
      say parser
    end

    def usage
      "#{program_name}"
    end

    # Override this method to provide details of the arguments a command takes.  It should
    # return a left-justified string, one argument per line.
    def arguments
      ""
    end

    # This is just like the 'arguments' method, except it describes the default options used.
    def defaults_str
      ""
    end

    def invoke(*args)
      handle_options(args)
      if options[:help]
        show_help
      elsif @when_invoked
        @when_invoked.call(options)
      else
        execute
      end
    end
    
    def when_invoked(&block)
      @when_invoked = block
    end
    
    def add_option(*args, &handler)
      @option_list << [args, handler]
    end

    def remove_option(name)
      @option_list.reject! { |args, handler| args.any? { |x| x =~ /^#{name}/ } }
    end
    
    def merge_options(new_options)
      @options = @defaults.clone
      new_options.each do |k,v| @options[k] = v end
    end

    def handles?(args)
      begin
        parser.parse!(args.dup)
        return true
      rescue
        return false
      end
    end

    private

    def handle_options(args)
      args = add_extra_args(args)
      @options = @defaults.clone
      parser.parse!(args)
      @options[:args] = args
    end
    
    def add_extra_args(args)
      result = []
      extra = Command.extra_args.dup
      while ! extra.empty?
        ex = []
        ex << extra.shift
        ex << extra.shift if extra.first.to_s =~ /^[^-]/
        result << ex if handles?(ex)
      end
      result.flatten!
      result.concat(args)
      result
    end
    
    # Create on demand parser.
    def parser
      create_option_parser if @parser.nil?
      @parser
    end

    def create_option_parser
      require 'optparse'
      @parser = OptionParser.new
      option_names = {}
      @parser.separator("")
      unless @option_list.empty?
        @parser.separator("  Options:")
        configure_options(@option_list, option_names)
        @parser.separator("")
      end
      @parser.separator("  Common Options:")
      configure_options(Command.common_options, option_names)
      @parser.separator("")
      unless arguments.empty?
        @parser.separator("  Arguments:")
        arguments.split(/\n/).each do |arg_desc|
          @parser.separator("    #{arg_desc}")
        end
        @parser.separator("")
      end
      @parser.separator("  Summary:")
      @parser.separator("    #@summary")
      unless defaults_str.empty?
        @parser.separator("")
        @parser.separator("  Defaults:")
        defaults_str.split(/\n/).each do |line|
          @parser.separator("    #{line}")
        end
      end
    end

    def configure_options(option_list, option_names)
      option_list.each do |args, handler|
        dashes = args.select { |arg| arg =~ /^-/ }
        next if dashes.any? { |arg| option_names[arg] }
        @parser.on(*args) do |value|
          handler.call(value, @options)
        end
        dashes.each do |arg| option_names[arg] = true end
      end
    end

    class << self
      def common_options
        @common_options ||= []
      end
    
      def add_common_option(*args, &handler)
        Gem::Command.common_options << [args, handler]
      end

      def extra_args
        @extra_args ||= []
      end

      def extra_args=(value)
        case value
        when Array
          @extra_args = value
        when String
          @extra_args = value.split
        end
      end
    end

    add_common_option('--source URL', 'Use URL as the remote source for gems') do |value, options|
      require_gem("sources")
      Gem.sources.clear
      Gem.sources << value
    end
    add_common_option('-p', '--[no-]http-proxy [URL]', 'Use HTTP proxy for remote operations') do |value, options|
      options[:http_proxy] = (value == false) ? :no_proxy : value
    end
    add_common_option('-h', '--help', 'Get help on this command') do |value, options|
      options[:help] = true
    end
    # Backtrace and config-file are added so they show up in the help
    # commands.  Both options are actually handled before the other
    # options get parsed.
    add_common_option('--config-file FILE', "Use this config file instead of default") do end
    add_common_option('--backtrace', 'Show stack backtrace on errors') do end
    add_common_option('--debug', 'Turn on Ruby debugging') do end
  end # class
end # module
