class Validator

  # in the absence of digestion, does the spec_id type requires pephits for
  # validation?
  def self.requires_pephits?(spec_id_obj)
    case spec_id_obj
    when Proph::ProtSummary : true
    when Proph::PepSummary : true
    when SQTGroup : true
    else ; false
    end
  end

  Validator_to_string = {
    'Validator::AA' => 'badAA',
    'Validator::AAEst' => 'badAAEst',
    'Validator::Decoy' => 'decoy',
    'Validator::Transmem::Protein' => 'tmm',
    'Validator::TruePos' => 'tps',
    'Validator::Bias' => 'bias',
    'Validator::Probability' => 'prob',
    'Validator::QValue' => 'qval',
    :bad_aa => 'badAA',
    :bad_aa_est => 'badAAEst',
    :decoy => 'decoy',
    :tmm => 'tmm',
    :tps => 'tps',
    :bias => 'bias',
    :prob => 'prob',
    :qval => 'qval',
  }

  def initialize_increment
    @increment_tps = 0
    @increment_fps = 0
    @increment_total_submitted = 0
    @increment_initialized = true
  end

  # if adding pephits in groups at a time, the entire group does not need to be
  # queried, just the individual hit.  Use this OR pephits_precision (NOT
  # both).  The initial query to this method will begin a running tally that
  # is saved by the validator.
  # takes either an array or a single pephit (determined by if it is a
  # SpecID::Pep)
  def increment_pephits_precision(peps)
    tmp = $VERBOSE; $VERBOSE = nil
    initialize_increment unless @increment_initialized
    $VERBOSE = tmp

    to_submit = 
      if peps.is_a? SpecID::Pep
        [peps]
      else
        peps
      end
    @increment_total_submitted += to_submit.size
    (tps, fps) = partition(to_submit)
    @increment_tps += tps.size
    @increment_fps += fps.size
    (num_tps, num_fps) = 
      if self.respond_to?(:calc_precision_prep)  # for digestion based validators
        (num_tps, num_fps) = calc_precision_prep(@increment_tps, @increment_fps)
        [num_tps, num_fps]
      else
        [@increment_tps, @increment_fps]
      end
    calc_precision(num_tps, num_fps)
  end


  # returns an adjusted false positive rate (a float not to drop below 0.0)
  # based on a background of 'false'-false positive hits to total hits.  Also
  # sets the @calculated_background attribute.  Accepts floats or ints
  def adjust_fps_for_background(num_tps, num_fps, background)
    num_fps = num_fps.to_f
    total_peps = num_tps + num_fps
    @calculated_background = num_fps / total_peps
    num_fps -= (total_peps.to_f * background)
    num_fps = 0.0 if num_fps < 0.0
    num_fps
  end

  # copied from libjtp: vec
  # returns the mean and std_dev
  def sample_stats(array)
    _len = array.size
    _sum = 0.0
    _sum_sq = 0.0
    array.each do |val|
      _sum += val
      _sum_sq += val * val
    end
    std_dev = _sum_sq - ((_sum * _sum)/_len)
    std_dev /= ( (_len > 1) ? (_len-1) : 1 )
    # on occasion, a very small negative number occurs
    if std_dev < 0.0    
      std_dev = 0.0 
    else
      std_dev = Math.sqrt(std_dev)
    end
    mean = _sum.to_f/_len
    [mean, std_dev]
  end

  # takes an array of validators and returns a fresh array where each has been
  # turned into a sensible hash (with symbols as the keys!)
  def self.sensible_validator_hashes(validators)
    validators.map do |val|
      hash = {}
      case val
      when Validator::TruePos
        hash.merge( {:correct_wins => val.correct_wins, :file => val.fasta.filename } )
      when Validator::AAEst
        %w(frequency background calculated_background).each do |cat|
          hash[cat.to_sym] = val.send(cat.to_sym)
        end
      when Validator::AA
        %w(false_to_total_ratio background calculated_background).each do |cat|
          hash[cat.to_sym] = val.send(cat.to_sym)
        end
      when Validator::Decoy
        %w(pi_zero correct_wins decoy_on_match).each do |cat|
          hash[cat.to_sym] = val.send(cat.to_sym)
        end
        hash[:constraint] = val.constraint.inspect if val.constraint
      when Validator::Bias
        %w(correct_wins proteins_expected background calculated_background false_to_total_ratio).each do |cat|
          hash[cat.to_sym] = val.send(cat.to_sym)
        end
        hash[:file] = val.fasta.filename
      when Validator::Transmem::Protein
        %w(false_to_total_ratio min_num_tms soluble_fraction correct_wins no_include_tm_peps background calculated_background transmem_file).each do |cat|
          hash[cat.to_sym] = val.send(cat.to_sym)
        end
      when Validator::Probability
        %w(prob_method).each do |cat|
          hash[cat.to_sym] = val.send(cat.to_sym)
        end
      when Validator::QValue
        # no params to add
      else ; raise ArgumentError, "Don't know the validator class #{val}"
      end
      klass_as_s = val.class.to_s
      hash[:type] = Validator_to_string[klass_as_s]
      hash[:class] = klass_as_s
      hash
    end
  end
end

module Precision::Calculator
  # calculates precision by the assumption that the first group are all true
  # hits and the second are all false hits
  # (0,0) is returned as 1.0
  def calc_precision(num_true_hits, num_false_hits)
    if ((num_true_hits.to_f == 0.0) && (num_false_hits.to_f == 0.0))
      1.0
    else
      num_true_hits.to_f / (num_true_hits.to_f + num_false_hits.to_f)
    end
  end
end

# will calculate precision for groups of proteins where the first group are
# normal hits (which may be true or false) and the second are decoy hits.
# edge case:  if num_normal.to_f == 0.0 then if num_decoy.to_f > 0 ; 0, else 1
module Precision::Calculator::Decoy
  def calc_precision(num_normal, num_decoy, frit=1.0)
    # will calculate as floats in case fractional amounts passed in for
    # whatever reason
    num_normal_f = num_normal.to_f
    num_true_pos = num_normal_f - (num_decoy.to_f * frit)
    precision =
      if num_normal_f == 0.0
        if num_decoy.to_f > 0.0
          0.0
        else
          1.0
        end
      else
        num_true_pos/num_normal_f
      end
  end
end

#require 'validator/true_pos'
#require 'validator/aa'
#require 'validator/aa_est'
#require 'validator/bias'
#require 'validator/decoy'
#require 'validator/transmem'
#require 'validator/probability'
#require 'validator/q_value'
#require 'validator/prot_from_pep'