need { 'command_support.rb' }
module Amp
  ##
  # Represents a command within the Amp system. Simply instantiating a new
  # command will make it available to the Amp executable. You configure the
  # command by filling out a block in the command's initializer. 
  # @example 
  #   Command.new("add") do |c|
  #     c.opt :include, "Paths to include", 
  #                         :options => {:short => "-I", :multi => true}
  #     c.opt :print_names, :desc => "Print the filenames", 
  #               :options => {:short => "-p", 
  #                            :default => false, 
  #                             :type => :boolean}
  #     c.on_run do |options, arguments|
  #       if options[:print_names]
  #         arguments.each do |filename|
  #           puts filename
  #         end
  #       end
  #     end
  #     c.help "This is the help text when the user runs `amp help add`"
  #   end
  #
  class Command
    include CommandSupport
    # All the commands registered in the system
    @@all_commands = {}
    
    # Synonyms for commands. Used as a backup.
    @@all_synonyms = {}
    
    # These are options that all commands support. Allows the user to put
    # them after the subcommand.
    GLOBAL_OPTIONS = []
    
    ##
    # Returns all of the commands registered in the system.
    # 
    # @return [Hash] the commands, keyed by command name as a string
    def self.all_commands
      @@all_commands
    end
    
    ##
    # Returns all the synonyms registered in the system.
    #
    # @return [Hash] the synonyms, keyed by the synonym as a string
    def self.all_synonyms
      @@all_synonyms
    end
    
    ##
    # Returns all of the commands registered in the system.
    # 
    # @return [Hash] the commands, keyed by command name as a string
    def self.[](arg)
      return all_commands[arg] if all_commands[arg]
      return all_synonyms[arg] if all_synonyms[arg]
      nil
    end

    # Command-specific command-line options
    attr_accessor :options
    # The name of the command (eg add, init)
    attr_accessor :name
    # Short, 1-line description of the command
    attr_accessor :description
    # The options submitted
    attr_accessor :options
    
    ##
    # Creates a command in the Amp system. Simply instantiating a new
    # command will make it available to the Amp executable. You configure the
    # command by filling out a block in the command's initializer.
    # 
    # @example
    #   Command.new("add") do |c|
    #     c.opt :include, "Paths to include", 
    #               :options => {:short => "-I", :multi => true}
    #     c.opt :print_names, :desc => "Print the filenames", 
    #               :options => {:short => "-p", :default => false, 
    #                            :type => :boolean}
    #     c.on_run do |options, arguments|
    #       puts "silly!"
    #     end
    #   end
    # @param name the name of the command that the user will use to call 
    # the command
    # @param &block a block of code where you can configure the command
    # @yield This block configures the command. Set up options, add an on_run 
    #        handler, and so on.
    # @yieldparam The command itself - it is yielded so you can modify it.
    def initialize(name)
      # so that you can do additions to commands, just like ammending rake takss
      name = name.to_s
      if @@all_commands[name]
        yield @@all_commands[name]
        return
      end
      
      @name                = name
      @help                = ""
      @description         = ""
      @options             = []
      @@all_commands[name] = self
      @before = []
      @after  = []
      @break  = false
      yield(self) if block_given?
      @options += GLOBAL_OPTIONS
    end
    
    ##
    # Adds an command-line option to the command.
    # 
    # @param name the name of the command the user will type to run it
    # @param desc the short, one-line description of the command
    # @param options the options that configure the command-line option 
    #                (too meta? sorry!)
    # @option [String] options :short (nil) the short version of the option 
    #                                       (e.g. "-I")
    # @option [String] options :default (nil) the default value of the option.
    # @option [Symbol] options :type (:string) the type of the option. Allows 
    #                                     you to force Integer or URL matches.
    # @option [Boolean] options :multi (false) can this option take multiple 
    #                                          values?
    def opt(name, desc='', options={})
      @options << {:name => name, :desc => desc, :options => options}
    end
    alias_method :add_opt, :opt
    
    ##
    # This returns the list of actions to run before the command, in order (first
    # ones are run first). You can modify this array in any way you choose, and
    # it is run _before_ the command is run.
    # 
    # @yield Extra code to run before the command is executed, after options are prepared.
    # @yieldparam options The options that the dispatcher has prepared for the command. Includes global and local.
    # @yieldparam arguments All arguments passed to the command, after the options.
    # @return [Hash] an array of strings and blocks. Strings are assumed to be command
    #                names and blocks are pieces of code to be run.
    def before(*args, &block)
      args.each do |arg|
        @before << proc {|opts, args| Amp::Command[arg.to_sym].run(opts, args) }
      end
      
      @before << block if block
      @before
    end  ##
      # This returns the list of actions to run before the command, in order (first
      # ones are run first). You can modify this array in any way you choose, and
      # it is run _before_ the command is run.
      # 
      # @yield Extra code to run before the command is executed, after options are prepared.
      # @yieldparam options The options that the dispatcher has prepared for the command. Includes global and local.
      # @yieldparam arguments All arguments passed to the command, after the options.
      # @return [Hash] an array of strings and blocks. Strings are assumed to be command
      #                names and blocks are pieces of code to be run.
      def before(*args, &block)
        args.each do |arg|
          @before << proc {|opts, args| Amp::Command[arg.to_sym].run(opts, args) }
        end

        @before << block if block
        @before
      end
    ##
    # This returns the list of actions to run before the command, in order (first
    # ones are run first). You can modify this array in any way you choose, and
    # it is run _before_ the command is run.
    # 
    # @yield Extra code to run before the command is executed, after options are prepared.
    # @yieldparam options The options that the dispatcher has prepared for the command. Includes global and local.
    # @yieldparam arguments All arguments passed to the command, after the options.
    # @return [Hash] an array of strings and blocks. Strings are assumed to be command
    #                names and blocks are pieces of code to be run.
    def before(*args, &block)
      args.each do |arg|
        @before << proc {|opts, args| Amp::Command[arg.to_sym].run(opts, args) }
      end
      
      @before << block if block
      @before
    end
    
    ##
    # This returns the list of actions to run after the command, in order (first
    # ones are run first). You can modify this array in any way you choose, and
    # it is run _after_ the command is run.
    # 
    # @yield Extra code to run after the command is executed, after options are prepared.
    # @yieldparam options The options that the dispatcher has prepared for the command. Includes global and local.
    # @yieldparam arguments All arguments passed to the command, after the options.
    # @return [Hash] an array of strings and blocks. Strings are assumed to be command
    #                names and blocks are pieces of code to be run.
    def after(*args, &block)
      args.each do |arg|
        @after << proc {|opts, args| Amp::Command[arg.to_sym].run(opts, args) }
      end
      
      @after << block if block
      @after
    end
    
    ##
    # Sets the short description for the command. This shouuld be only 1 line,
    # as it's what the user sees when they run `amp --help` and get the 
    # full list of commands. `str` defaults to nil so that if no argument is
    # passed, by default #desc will just return `@description`. If `str` is
    # passed a string, then it will set `@description` to `str`.
    # 
    # @example cmd.desc "This command is useless."
    # @example cmd.desc # => "This command is useless."
    # @param [String, nil] str the help text to set
    def desc(str=nil)
      str ? @description = str : @description
    end
    
    ##
    # Sets the short description for the command. This shouuld be only 1 line,
    # as it's what the user sees when they run `amp --help` and get the 
    # full list of commands.
    # 
    # @param str the help text to set
    alias :desc= :desc
    
    ##
    # Trollop's help info for the command
    def educate
      # comments are the devil's work.
        # comments are the devil's work.
      @parser ? @parser.educate : ''
    end
    alias_method :education, :educate
    
    ##
    # Sets the command to not laod a repository when run. Useful for purely
    # informational commands (such as version) or initializing a new
    # repository.
    def no_repo
      NO_REPO_ALLOWED[@name] = true
    end
    
    # @see no_repo
    def no_repo=(value)
      NO_REPO_ALLOWED[@name] = value
    end
    
    ##
    # Sets the command to not require a repository to run, but try to load one.
    # Used, for example, for the templates command, which sometimes stores
    # comments are the devil's work.
      # comments are the devil's work.
      # comments are the devil's work.
        # comments are the devil's work.
    # information in the local repository.
    def maybe_repo
      MAYBE_REPO_ALLOWED[@name] = true
    end
    
    # @see no_repo
    def maybe_repo=(value)
      MAYBE_REPO_ALLOWED[@name] = value
    end
    
    ##
    # Sets the help text for the command. This can be a very long string,
    # as it is what the user sees when they type `amp help +name+`
    # 
    # @param str the help text to set
    # comments are the devil's work.
      # comments are the devil's work.
    # @example cmd.help %Q{
    #            Big help text!
    #          }
    def help(str=nil)
      str ? @help << str : @help
    end
    
    ##
    # Sets the help text for the command. This can be a very long string,
    # as it is what the user sees when they type `amp help +name+`
    # comments are the devil's work.
      # comments are the devil's work.
    # 
    # @param str the help text to set
    alias :help= :help
    
    ##
    # Parses the commands from the command line using Trollop. You probably
    # shouldn't override this method, but if you have good reason, go for it.
    # 
    # @return [Hash] The parsed command-line options
    def collect_options
      options = @options # hack to get around the fact that
      help    = @help    # Trollop uses instance eval
      
      Trollop::options do
        # we can't use @options here because Trollop::options uses instance_eval
        options.each do |option|
          opt option[:name], option[:desc], option[:options]
        end
        
        banner help
      end
      
    end
    
    def inspect
      "#<Amp::Command #{name} >"
    end
    
    ##
    # Stops this command from running any further - uses the global options.
        # comments are the devil's work.
          # comments are the devil's work.
    def break
      @break = true
    end
    
    ##
    # comments are the devil's work.
    def run(options={}, args=[])
      # run the before commands
      @before.each {|cmd| cmd.run options, args; return if @break }
      
      @code[options, args] # and of course the actual command...
      
      # top it off with the after commands
      @after.each {|cmd| cmd.run options, args; return if @break }
      
      self
    end
    
    NO_REPO_ALLOWED = {}
    %w(clone init help version debugcomplete debugdata debugindex 
       debugindexdot debugdate debuginstall debugfsinfo).each do |k|
         NO_REPO_ALLOWED[k] = true
    end
    
    MAYBE_REPO_ALLOWED = {}
    %w().each do |k|
      MAYBE_REPO_ALLOWED[k] = true
    end
    
  end
end

module Kernel
  # shortcut
  def command(name, &block)
    Amp::Command.new name, &block
  end
  
  # Rake style namespacing
  # After new commands are made, alter their names
  # so that they're "#{namespace}:#{command}"
  def silly_namespace(name)
    # current commands
    commands = Amp::Command.all_commands.keys
    
    yield
    
    more_commands = Amp::Command.all_commands.keys
    new_commands = more_commands - commands
    
    new_commands.each do |key|
      command_to_modify = Amp::Command[key]
      command_to_modify.name = "#{name}:#{key}" # construct the new name
      Amp::Command.all_commands[command_to_modify.name] = Amp::Command.all_commands.delete(key) # and do the switch
    end
  end
end