class Thor
  class Task < Struct.new(:name, :description, :usage, :options)

    # Creates a dynamic task. Dynamic tasks are created on demand to allow method
    # missing calls (since a method missing does not have a task object for it).
    #
    def self.dynamic(name)
      new(name, "A dynamically-generated task", name.to_s)
    end

    def initialize(name, description, usage, options=nil)
      super(name.to_s, description, usage, options || {})
    end

    def initialize_copy(other)
      super(other)
      self.options = other.options.dup if other.options
    end

    def short_description
      description.split("\n").first if description
    end

    # By default, a task invokes a method in the thor class. You can change this
    # implementation to create custom tasks.
    #
    def run(instance, args=[])
      raise UndefinedTaskError, "the '#{name}' task of #{instance.class} is private" unless public_method?(instance)
      instance.send(name, *args)
    rescue ArgumentError => e
      parse_argument_error(instance, e, caller)
    rescue NoMethodError => e
      parse_no_method_error(instance, e)
    end

    # Returns the formatted usage. If a class is given, the class arguments are
    # injected in the usage.
    #
    def formatted_usage(klass=nil, namespace=false)
      formatted = ''
      formatted << "#{klass.namespace.gsub(/^default/,'')}:" if klass && namespace
      formatted << formatted_arguments(klass)
      formatted << " #{formatted_options}"
      formatted.strip!
      formatted
    end

    # Injects the class arguments into the task usage.
    #
    def formatted_arguments(klass)
      if klass && !klass.arguments.empty?
        usage.to_s.gsub(/^#{name}/) do |match|
          match << " " << klass.arguments.map{ |a| a.usage }.join(' ')
        end
      else
        usage.to_s
      end
    end

    # Returns the options usage for this task.
    #
    def formatted_options
      @formatted_options ||= options.map{ |_, o| o.usage }.sort.join(" ")
    end

    protected

      # Given a target, checks if this class name is not a private/protected method.
      #
      def public_method?(instance)
        collection = instance.private_methods + instance.protected_methods
        !(collection).include?(name.to_s) && !(collection).include?(name.to_sym) # For Ruby 1.9
      end

      # Clean everything that comes from the Thor gempath and remove the caller.
      #
      def sans_backtrace(backtrace, caller)
        dirname = /^#{Regexp.escape(File.dirname(__FILE__))}/
        saned  = backtrace.reject { |frame| frame =~ dirname }
        saned -= caller
      end

      def parse_argument_error(instance, e, caller)
        backtrace = sans_backtrace(e.backtrace, caller)

        if backtrace.empty? && e.message =~ /wrong number of arguments/
          if instance.is_a?(Thor::Group)
            raise e, "'#{name}' was called incorrectly. Are you sure it has arity equals to 0?"
          else
            raise InvocationError, "'#{name}' was called incorrectly. Call as " <<
                                   "'#{formatted_usage(instance.class, true)}'"
          end
        else
          raise e
        end
      end

      def parse_no_method_error(instance, e)
        if e.message =~ /^undefined method `#{name}' for #{Regexp.escape(instance.to_s)}$/
          raise UndefinedTaskError, "The #{instance.class.namespace} namespace " <<
                                    "doesn't have a '#{name}' task"
        else
          raise e
        end
      end

  end
end
