#!/usr/bin/env ruby

MSPEC_HOME = File.expand_path(File.dirname(__FILE__) + '/../../..')

require 'mspec/version'
require 'mspec/utils/options'
require 'mspec/utils/script'
require 'mspec/helpers/tmp'
require 'mspec/runner/actions/filter'
require 'mspec/runner/actions/timer'


class MSpecMain < MSpecScript
  def initialize
    config[:includes] = []
    config[:requires] = []
    config[:target]   = ENV['RUBY'] || 'ruby'
    config[:flags]    = []
    config[:command]  = nil
    config[:options]  = []
    config[:launch]   = []
  end

  def options(argv=ARGV)
    config[:command] = argv.shift if ["ci", "run", "tag"].include?(argv[0])

    options = MSpecOptions.new "mspec [COMMAND] [options] (FILE|DIRECTORY|GLOB)+", 30, config

    options.doc " The mspec command sets up and invokes the sub-commands"
    options.doc " (see below) to enable, for instance, running the specs"
    options.doc " with different implementations like ruby, jruby, rbx, etc.\n"

    options.configure do |f|
      load f
      config[:options] << '-B' << f
    end

    options.targets

    options.on("-D", "--gdb", "Run under gdb") do
      config[:use_gdb] = true
    end

    options.on("-A", "--valgrind", "Run under valgrind") do
      config[:use_valgrind] = true
    end

    options.on("--warnings", "Don't supress warnings") do
      config[:flags] << '-w'
      ENV['OUTPUT_WARNINGS'] = '1'
    end

    options.on("-j", "--multi", "Run multiple (possibly parallel) subprocesses") do
      config[:multi] = true
      config[:options] << "-fy"
    end

    options.version MSpec::VERSION do
      if config[:command]
        config[:options] << "-v"
      else
        puts "#{File.basename $0} #{MSpec::VERSION}"
        exit
      end
    end

    options.help do
      if config[:command]
        config[:options] << "-h"
      else
        puts options
        exit 1
      end
    end

    options.doc "\n Custom options"
    custom_options options

    # The rest of the help output
    options.doc "\n where COMMAND is one of:\n"
    options.doc "   run - Run the specified specs (default)"
    options.doc "   ci  - Run the known good specs"
    options.doc "   tag - Add or remove tags\n"
    options.doc " mspec COMMAND -h for more options\n"
    options.doc "   example: $ mspec run -h\n"

    options.on_extra { |o| config[:options] << o }
    config[:options].concat options.parse(argv)
  end

  def register; end

  def parallel
    @parallel ||= !(Object.const_defined?(:JRUBY_VERSION) ||
                  /(mswin|mingw)/ =~ RUBY_PLATFORM)
  end

  def fork(&block)
    parallel ? Kernel.fork(&block) : block.call
  end

  def report(files, timer)
    require 'yaml'

    exceptions = []
    tally = Tally.new

    files.each do |file|
      d = File.open(file, "r") { |f| YAML.load f }
      File.delete file

      exceptions += Array(d['exceptions'])
      tally.files!        d['files']
      tally.examples!     d['examples']
      tally.expectations! d['expectations']
      tally.errors!       d['errors']
      tally.failures!     d['failures']
    end

    print "\n"
    exceptions.each_with_index do |exc, index|
      print "\n#{index+1})\n", exc, "\n"
    end
    print "\n#{timer.format}\n\n#{tally.format}\n"
  end

  def multi_exec(argv)
    timer = TimerAction.new
    timer.start

    files = config[:ci_files].inject([]) do |list, item|
      name = tmp "mspec-ci-multi-#{list.size}"

      rest = argv + ["-o", name, item]
      fork { system [config[:target], *rest].join(" ") }

      list << name
    end

    Process.waitall
    timer.finish
    report files, timer
  end

  def run
    ENV['MSPEC_RUNNER'] = '1'
    ENV['RUBY_EXE']     = config[:target]
    ENV['RUBY_FLAGS']   = config[:flags].join " "

    argv = []

    argv.concat config[:launch]
    argv.concat config[:flags]
    argv.concat config[:includes]
    argv.concat config[:requires]
    argv << "-v"
    argv << "#{MSPEC_HOME}/bin/mspec-#{ config[:command] || "run" }"
    argv.concat config[:options]

    if config[:multi] and config[:command] == "ci"
      multi_exec argv
    else
      if config[:use_valgrind]
        more = ["--db-attach=#{config[:use_gdb] ? 'yes' : 'no'}", config[:target]] + argv
        exec "valgrind", *more
      elsif config[:use_gdb]
        more = ["--args", config[:target]] + argv
        exec "gdb", *more
      else
        cmd, *rest = config[:target].split(/\s+/)
        argv = rest + argv unless rest.empty?
        exec cmd, *argv
      end
    end
  end
end

