require 'mspec/runner/mspec'
require 'mspec/runner/actions/tally'

require 'rbconfig'

class SpecGuard
  def self.report
    @report ||= Hash.new { |h,k| h[k] = [] }
  end

  def self.clear
    @report = nil
  end

  def self.finish
    report.keys.sort.each do |key|
      desc = report[key]
      size = desc.size
      spec = size == 1 ? "spec" : "specs"
      print "\n\n#{size} #{spec} omitted by guard: #{key}:\n"
      desc.each { |description| print "\n", description; }
    end

    print "\n\n"
  end

  def self.guards
    @guards ||= []
  end

  def self.clear_guards
    @guards = []
  end

  @@ruby_version_override = nil

  def self.ruby_version_override=(version)
    @@ruby_version_override = version
  end

  def self.ruby_version_override
    @@ruby_version_override
  end

  # Returns a partial Ruby version string based on +which+. For example,
  # if RUBY_VERSION = 8.2.3 and RUBY_PATCHLEVEL = 71:
  #
  #  :major  => "8"
  #  :minor  => "8.2"
  #  :tiny   => "8.2.3"
  #  :teeny  => "8.2.3"
  #  :full   => "8.2.3.71"
  def self.ruby_version(which = :minor)
    case which
    when :major
      n = 1
    when :minor
      n = 2
    when :tiny, :teeny
      n = 3
    else
      n = 4
    end

    patch = RUBY_PATCHLEVEL.to_i
    patch = 0 if patch < 0
    version = "#{ruby_version_override || RUBY_VERSION}.#{patch}"
    version.split('.')[0,n].join('.')
  end

  attr_accessor :name, :parameters

  def initialize(*args)
    self.parameters = @args = args
  end

  def yield?(invert=false)
    return true if MSpec.mode? :unguarded

    allow = match? ^ invert

    if not allow and reporting?
      MSpec.guard
      MSpec.register :finish, SpecGuard
      MSpec.register :add,    self
      return true
    elsif MSpec.mode? :verify
      return true
    end

    allow
  end

  def ===(other)
    true
  end

  def reporting?
    MSpec.mode?(:report) or
      (MSpec.mode?(:report_on) and SpecGuard.guards.include?(name))
  end

  def report_key
    "#{name} #{parameters.join(", ")}"
  end

  def record(description)
    SpecGuard.report[report_key] << description
  end

  def add(example)
    record example.description
    MSpec.retrieve(:formatter).tally.counter.guards!
  end

  def unregister
    MSpec.unguard
    MSpec.unregister :add, self
  end

  def implementation?(*args)
    args.any? do |name|
      !!case name
      when :rubinius
        RUBY_NAME =~ /^rbx/
      when :ruby
        RUBY_NAME =~ /^ruby/
      when :jruby
        RUBY_NAME =~ /^jruby/
      when :ironruby
        RUBY_NAME =~ /^ironruby/
      when :macruby
        RUBY_NAME =~ /^macruby/
      when :maglev
        RUBY_NAME =~ /^maglev/
      when :topaz
        RUBY_NAME =~ /^topaz/
      when :opal
        RUBY_NAME =~ /^opal/
      else
        false
      end
    end
  end

  def standard?
    implementation? :ruby
  end

  def windows?(sym, key)
    sym == :windows && !key.match(/(mswin|mingw)/).nil?
  end

  def platform?(*args)
    args.any? do |platform|
      if platform != :java && RUBY_PLATFORM.match('java') && os?(platform)
        true
      else
        RUBY_PLATFORM.match(platform.to_s) || windows?(platform, RUBY_PLATFORM)
      end
    end
  end

  def wordsize?(size)
    size == 8 * 1.size
  end

  def os?(*oses)
    oses.any? do |os|
      host_os = RbConfig::CONFIG['host_os'] || RUBY_PLATFORM
      host_os.downcase!
      host_os.match(os.to_s) || windows?(os, host_os)
    end
  end

  def match?
    implementation?(*@args) or platform?(*@args)
  end
end
