require 'fileutils'
#
# Bundles the configuration options used for SimpleCov. All methods
# defined here are usable from SimpleCov directly. Please check out
# SimpleCov documentation for further info.
#
module SimpleCov::Configuration
  attr_writer :filters, :groups, :formatter

  #
  # The root for the project. This defaults to the
  # current working directory.
  #
  # Configure with SimpleCov.root('/my/project/path')
  #
  def root(root=nil)
    return @root if defined? @root and root.nil?
    @root = File.expand_path(root || Dir.getwd)
  end

  #
  # The name of the output and cache directory. Defaults to 'coverage'
  #
  # Configure with SimpleCov.coverage_dir('cov')
  #
  def coverage_dir(dir=nil)
    return @coverage_dir if defined? @coverage_dir and dir.nil?
    @coverage_dir = (dir || 'coverage')
  end

  #
  # Returns the full path to the output directory using SimpleCov.root
  # and SimpleCov.coverage_dir, so you can adjust this by configuring those
  # values. Will create the directory if it's missing
  #
  def coverage_path
    coverage_path = File.join(root, coverage_dir)
    FileUtils.mkdir_p coverage_path
    coverage_path
  end

  #
  # Returns the list of configured filters. Add filters using SimpleCov.add_filter.
  #
  def filters
    @filters ||= []
  end

  # The name of the command (a.k.a. Test Suite) currently running. Used for result
  # merging and caching. It first tries to make a guess based upon the command line
  # arguments the current test suite is running on and should automatically detect
  # unit tests, functional tests, integration tests, rpsec and cucumber and label
  # them properly. If it fails to recognize the current command, the command name
  # is set to the shell command that the current suite is running on.
  #
  # You can specify it manually with SimpleCov.command_name("test:units") - please
  # also check out the corresponding section in README.rdoc
  def command_name(name=nil)
    @name = name unless name.nil?
    @name ||= SimpleCov::CommandGuesser.guess
    @name
  end

  #
  # Gets or sets the configured formatter.
  #
  # Configure with: SimpleCov.formatter(SimpleCov::Formatter::SimpleFormatter)
  #
  def formatter(formatter=nil)
    return @formatter if defined? @formatter and formatter.nil?
    @formatter = formatter
    raise "No formatter configured. Please specify a formatter using SimpleCov.formatter = SimpleCov::Formatter::SimpleFormatter" unless @formatter
    @formatter
  end

  #
  # Certain code blocks (i.e. Ruby-implementation specific code) can be excluded from
  # the coverage metrics by wrapping it inside # :nocov: comment blocks. The nocov token
  # can be configured to be any other string using this.
  #
  # Configure with SimpleCov.nocov_token('skip') or it's alias SimpleCov.skip_token('skip')
  #
  def nocov_token(nocov_token=nil)
    return @nocov_token if defined? @nocov_token and nocov_token.nil?
    @nocov_token = (nocov_token || 'nocov')
  end
  alias_method :skip_token, :nocov_token

  #
  # Returns the configured groups. Add groups using SimpleCov.add_group
  #
  def groups
    @groups ||= {}
  end

  #
  # Returns the hash of available adapters
  #
  def adapters
    @adapters ||= SimpleCov::Adapters.new
  end

  #
  # Allows you to configure simplecov in a block instead of prepending SimpleCov to all config methods
  # you're calling.
  #
  # SimpleCov.configure do
  #   add_filter 'foobar'
  # end
  #
  # This is equivalent to SimpleCov.add_filter 'foobar' and thus makes it easier to set a bunch of configure
  # options at once.
  #
  def configure(&block)
    return false unless SimpleCov.usable?
    instance_exec(&block)
  end

  #
  # Gets or sets the behavior to process coverage results.
  #
  # By default, it will call SimpleCov.result.format!
  #
  # Configure with:
  #   SimpleCov.at_exit do
  #     puts "Coverage done"
  #     SimpleCov.result.format!
  #   end
  #
  def at_exit(&block)
    return Proc.new {} unless running or block_given?
    @at_exit = block if block_given?
    @at_exit ||= Proc.new { SimpleCov.result.format! }
  end

  #
  # Returns the project name - currently assuming the last dirname in
  # the SimpleCov.root is this.
  #
  def project_name(new_name=nil)
    return @project_name if defined? @project_name and @project_name and new_name.nil?
    @project_name = new_name if new_name.kind_of?(String)
    @project_name ||= File.basename(root.split('/').last).capitalize.gsub('_', ' ')
  end

  #
  # Defines whether to use result merging so all your test suites (test:units, test:functionals, cucumber, ...)
  # are joined and combined into a single coverage report
  #
  def use_merging(use=nil)
    @use_merging = use unless use.nil?
    @use_merging = true unless defined? @use_merging and @use_merging == false
  end

  #
  # Defines them maximum age (in seconds) of a resultset to still be included in merged results.
  # i.e. If you run cucumber features, then later rake test, if the stored cucumber resultset is
  # more seconds ago than specified here, it won't be taken into account when merging (and is also
  # purged from the resultset cache)
  #
  # Of course, this only applies when merging is active (e.g. SimpleCov.use_merging is not false!)
  #
  # Default is 600 seconds (10 minutes)
  #
  # Configure with SimpleCov.merge_timeout(3600) # 1hr
  #
  def merge_timeout(seconds=nil)
    @merge_timeout = seconds if seconds.kind_of?(Fixnum)
    @merge_timeout ||= 600
  end

  #
  # Defines the minimum overall coverage required for the testsuite to pass.
  # SimpleCov will return non-zero if the current coverage is below this threshold.
  #
  # Default is 0% (disabled)
  #
  def minimum_coverage(coverage=nil)
    @minimum_coverage ||= (coverage || 0).to_f.round(2)
  end

  #
  # Defines the maximum coverage drop at once allowed for the testsuite to pass.
  # SimpleCov will return non-zero if the coverage decreases by more than this threshold.
  #
  # Default is 100% (disabled)
  #
  def maximum_coverage_drop(coverage_drop=nil)
    @maximum_coverage_drop ||= (coverage_drop || 100).to_f.round(2)
  end

  #
  # Refuses any coverage drop. That is, coverage is only allowed to increase.
  # SimpleCov will return non-zero if the coverage decreases.
  #
  def refuse_coverage_drop
    maximum_coverage_drop 0
  end

  #
  # Add a filter to the processing chain.
  # There are three ways to define a filter:
  #
  # * as a String that will then be matched against all source files' file paths,
  #   SimpleCov.add_filter 'app/models' # will reject all your models
  # * as a block which will be passed the source file in question and should either
  #   return a true or false value, depending on whether the file should be removed
  #   SimpleCov.add_filter do |src_file|
  #     File.basename(src_file.filename) == 'environment.rb'
  #   end # Will exclude environment.rb files from the results
  # * as an instance of a subclass of SimpleCov::Filter. See the documentation there
  #   on how to define your own filter classes
  #
  def add_filter(filter_argument=nil, &filter_proc)
    filters << parse_filter(filter_argument, &filter_proc)
  end

  #
  # Define a group for files. Works similar to add_filter, only that the first
  # argument is the desired group name and files PASSING the filter end up in the group
  # (while filters exclude when the filter is applicable).
  #
  def add_group(group_name, filter_argument=nil, &filter_proc)
    groups[group_name] = parse_filter(filter_argument, &filter_proc)
  end

  private

  #
  # The actal filter processor. Not meant for direct use
  #
  def parse_filter(filter_argument=nil, &filter_proc)
    if filter_argument.kind_of?(SimpleCov::Filter)
      filter_argument
    elsif filter_argument.kind_of?(String)
      SimpleCov::StringFilter.new(filter_argument)
    elsif filter_proc
      SimpleCov::BlockFilter.new(filter_proc)
    else
      raise ArgumentError, "Please specify either a string or a block to filter with"
    end
  end
end
