module Respec
  class App
    def initialize(*args)
      if (i = args.index('--'))
        @args = args.slice!(0...i)
        @raw_args = args[1..-1]
      else
        @args = args
        @raw_args = []
      end
      @formatter = 'progress'
      @selected_failures = false
      @update_failures = true
      process_args
    end

    def command
      @command ||= bundler_args + ['rspec'] + formatter_args + generated_args + raw_args
    end

    def bundler_args
      if File.exist?(ENV['BUNDLE_GEMFILE'] || 'Gemfile')
        ['bundle', 'exec']
      else
        []
      end
    end

    def formatter_args
      if @update_failures
        formatter_path = File.expand_path('formatter.rb', File.dirname(__FILE__))
        ['--require', formatter_path, '--format', 'Respec::Formatter', '--out', failures_path, '--format', @formatter]
      else
        []
      end
    end

    attr_reader :generated_args, :raw_args

    class << self
      attr_accessor :failures_path
    end
    self.failures_path = ENV['RESPEC_FAILURES'] || File.expand_path("~/.respec_failures")

    def help_only?
      @help_only
    end

    def help
      <<-EOS.gsub(/^ *\|/, '')
        |USAGE: respec RESPEC-ARGS ... [ -- RSPEC-ARGS ... ]
        |
        |Run rspec recording failed examples for easy rerunning later.
        |
        |RESPEC-ARGS may consist of:
        |
        |  f            Rerun all failed examples
        |  <integer>    Rerun only the n-th failure
        |  s            Output specdoc format, instead of progress
        |  c            Output context diffs
        |  <file name>  Run specs in these files
        |  <other>      Run only examples matching this pattern
        |  --help       This!  (Also 'help'.)
        |
        |RSPEC-ARGS may follow a '--' argument, and are passed
        |directly to rspec.
        |
        |More info: http://github.com/oggy/respec
      EOS
    end

    private

    def process_args
      args = []
      files = []
      @args.each do |arg|
        if File.exist?(arg)
          files << arg
        elsif arg =~ /\A(--)?help\z/
          @help_only = true
        elsif arg == 'f'
          if File.exist?(failures_path)
            if failures.empty?
              abort "No specs failed!"
            else
              failures.each do |line|
                args << line.strip
              end
              @selected_failures = true
            end
          else
            warn "no fail file - ignoring 'f' argument"
          end
        elsif arg == 's'
          @formatter = 'specdoc'
        elsif arg == 'd'
          args << '--debugger'
        elsif arg =~ /\A[xX]\z/
          args << '--drb'
        elsif arg == 'c'
          args << '--diff' << 'context'
        elsif arg =~ /\A\d+\z/
          i = Integer(arg)
          if (failure = failures[i - 1])
            args << failure
            @selected_failures = true
            @update_failures = false
          else
            warn "invalid failure: #{i} for (1..#{failures.size})"
          end
        else
          args << '--example' << arg.gsub(/[$]/, '\\\\\\0')
        end
      end
      # If we selected individual failures to rerun, don't give the
      # files to rspec, as those files will be run in entirety.
      @generated_args = args
      @generated_args.concat(files) unless @selected_failures
    end

    def failures_path
      self.class.failures_path
    end

    def failures
      @failures ||=
        if File.exist?(failures_path)
          File.read(failures_path).split(/\n/)
        else
          []
        end
    end
  end
end