module SimpleCLI #:nodoc:

  #
  # Command represents in individual command.
  #
  # For example, in <tt>gem install foo</tt>
  # - <tt>gem</tt> is the script (a Class that includes SimpleCLI)
  # - <tt>install</tt> is the Command
  # - <tt>foo</tt> is an argument being passed to the <tt>install</tt> Command
  #
  class Command

    SummaryMatcher = /Summary:(.*)/m

    # Command class methods
    class << self

      # Gets Command objects for a given Class or instance with commands
      #
      # IMPORTANT: Command methods can NOT be #call'd without an instance 
      #            of the class.  #documentation won't be available either!
      #
      # ==== Parameters
      # Object::
      #   Any Class or instance with commands.  It is *highly* 
      #   recommended that an instance be used, otherwise the 
      #   Command returned won't have #documentation and it 
      #   won't be #call'able.
      #
      #   SimpleCLI.command_names(object) is used to find the 
      #   names of commands to initialize.  Returned Command 
      #   objects will only be #call'able if an instance 
      #   if passed (instead of a Class)
      #
      # ==== Returns
      # Array(Command)::
      #   An Array of Command objects.  
      #   These are only #call'able if an instance was used 
      #   to find the command objects (instead of a Class).
      #   
      #   The same goes for documentation - it won't be available
      #   unless an instance was used!
      #
      # ==== Usage
      #
      #   >> Command.commands MyClassWithCommands.new
      #   >> [ ... command objects ... ]
      #
      #   >> Command.commands( MyClassWithCommands.new ).first.call 1, 2, 3
      #   => "Called a command, passed in [1, 2, 3]"
      #
      #   >> Command.commands MyClassWithCommands
      #   >> [ ... command objects ... ]
      #
      #   >> Command.commands( MyClassWithCommands ).first.call 1, 2, 3
      #   => # EXCEPTION!  Commands need an *instance* to be #call'd
      #
      def commands object_with_commands
        command_names = SimpleCLI.command_names object_with_commands
        command_names.map do |name|
          command object_with_commands, name
        end
      end

      # Returns a single Command by name, for a particular object with commands
      def command object_with_commands, name
        name = name.to_s
        if SimpleCLI.command_names(object_with_commands).include?(name)
          if object_with_commands.is_a?(Class)
            Command.new :name => name, :proc => Command::RaiseCalledOnClassException, 
                         :documentation_proc => Command::RaiseCalledOnClassException
          else
            Command.new :name => name, :proc => object_with_commands.method(name),
                         :documentation_proc => object_with_commands.method(SimpleCLI.help_method_name(name))

          end  
        end
      end

    end

    # :api: public
    attr_accessor :name, :documentation, :summary

    # not sure if these should be public or private ... these very well might change!
    #
    # making these private (for now)
    #
    # :api: private
    attr_accessor :proc, :documentation_proc

    def initialize options = {}
      options.each {|k,v| instance_variable_set "@#{k}", v }
    end

    # Returns the documentation for this Command
    #
    # Typically generated from a help method call, but it can also be set manually
    #
    # :api: public
    def documentation
      if @documentation.nil?
        documentation_proc.call if documentation_proc and documentation_proc.respond_to?(:call)
      else
        @documentation
      end
    end

    # Returns a short summary for this Command
    #
    # Typically generated by parsing #documentation for 'Summary: something',
    # but it can also be set manually
    #
    # :api: public
    def summary
      if @summary.nil?
        match = Command::SummaryMatcher.match documentation
        match.captures.first.strip if match
      else
        @summary
      end
    end

    # Calls this Command's #proc, typically a method
    #
    # ==== Parameters
    # Array:: 
    #   an array of arbitrary arguments, 
    #   typically arguments passed from the command line
    #
    # ==== Returns
    # Whatever the #proc returns
    #
    # :api: public
    def call *args
      @proc.call *args if @proc and @proc.respond_to?(:call)
    end

  end

end
