require 'slop'

module Commander
  module CLI
    ##
    # Wrapper run command with error handling
    def run!(*args)
      Commander.traceable_error_handler(*args) do |new_args|
        run(*new_args)
      end
    end

    def run(*args)
      instance = Runner.new(
        @program, commands, default_command,
        global_slop, aliases, args
      )
      instance.run
    end

    ##
    # Assign program information.
    #
    # === Examples
    #
    #   # Set data
    #   program :name, 'Commander'
    #   program :version, Commander::VERSION
    #   program :description, 'Commander utility program.'
    #   program :help, 'Copyright', '2008 TJ Holowaychuk'
    #   program :help, 'Anything', 'You want'
    #
    #   # Get data
    #   program :name # => 'Commander'
    #
    # === Keys
    #
    #   :version         (required) Program version triple, ex: '0.0.1'
    #   :description     (required) Program description
    #   :name            Program name, defaults to basename of executable
    #   :config          An optional argument to be passed into the action
    #   :help_formatter  Defaults to Commander::HelpFormatter::Terminal
    #   :help            Allows addition of arbitrary global help blocks
    #   :help_paging     Flag for toggling help paging
    #

    def program(key, *args, &block)
      @program ||= {
        help_formatter: HelpFormatter::Terminal,
        name: File.basename($PROGRAM_NAME),
        help_paging: true,
      }

      if key == :help && !args.empty?
        @program[:help] ||= {}
        @program[:help][args.first] = args.at(1)
      elsif key == :help_formatter && !args.empty?
        @program[key] = (@help_formatter_aliases[args.first] || args.first)
      elsif block
        @program[key] = block
      else
        unless args.empty?
          @program[key] = args.count == 1 ? args[0] : args
        end
        @program[key]
      end
    end

    ##
    # Default command _name_ to be used when no other
    # command is found in the arguments.

    def default_command(name = nil)
      @default_command = name unless name.nil?
      @default_command
    end


    ##
    # Hash of Command objects
    def commands
      @commands ||= {}
    end

    ##
    # Hash of Global Options
    #
    def global_slop
      @global_slop ||= Slop::Options.new.tap do |slop|
        slop.bool '-h', '--help', 'Display help documentation'
        slop.bool '--version', 'Display version information'
      end
    end

    ##
    # Define and get a command by name
    #
    def command(name)
      name = name.to_s
      (commands[name] ||= Command.new(name)).tap do |cmd|
        yield cmd if block_given?
      end
    end

    # A hash of known aliases
    def aliases
      @aliases ||= {}
    end

    ##
    # Alias command _name_ with _alias_name_. Optionally _args_ may be passed
    # as if they were being passed straight to the original command via the command-line.

    def alias_command(alias_name, name, *args)
      commands[alias_name.to_s] = command name
      aliases[alias_name.to_s] = args
    end
  end
end