module Validatable
  def self.included(klass) #:nodoc:
    klass.extend Validatable::ClassMethods
    klass.extend Validatable::Macros
  end
  
  # call-seq: valid?
  #
  # Returns true if no errors were added otherwise false. Only executes validations that have no :groups option specified
  def valid?
    valid_for_group?(nil)
  end
  
  # call-seq: errors
  #
  # Returns the Errors object that holds all information about attribute error messages.
  def errors
    @errors ||= Validatable::Errors.new
  end
  
  def valid_for_group?(group) #:nodoc:
    run_before_validations
    errors.clear
    self.class.validate_children(self, group)
    self.validate(group)
    errors.empty?
  end
  
  def times_validated(key) #:nodoc:
    times_validated_hash[key] || 0
  end
  
  def increment_times_validated_for(validation) #:nodoc:
    if validation.key != nil
      if times_validated_hash[validation.key].nil?
        times_validated_hash[validation.key] = 1
      else
        times_validated_hash[validation.key] += 1
      end
    end
  end

  protected
  def times_validated_hash #:nodoc:
    @times_validated_hash ||= {}
  end

  def validate(group) #:nodoc:
    validation_levels.each do |level|
      validations_for_level_and_group(level, group).each do |validation|
        run_validation(validation) if validation.should_validate?(self)
      end
      return unless self.errors.empty?
    end
  end

  def run_validation(validation) #:nodoc:
    validation_result = validation.valid?(self)
    add_error(validation.attribute, validation.message(self)) unless validation_result
    increment_times_validated_for(validation)
    validation.run_after_validate(validation_result, self, validation.attribute)
  end
  
  def run_before_validations
    self.class.all_before_validations.each do |block|
      instance_eval &block
    end
  end
  
  def add_error(attribute, message) #:nodoc:
    self.class.add_error(self, attribute, message)
  end
  
  def validations_for_level_and_group(level, group) #:nodoc:
    validations_for_level = self.class.all_validations.select { |validation| validation.level == level }
    return validations_for_level.select { |validation| validation.groups.empty? } if group.nil?
    validations_for_level.select { |validation| validation.groups.include?(group) }
  end
  
  def validation_levels #:nodoc:
    self.class.all_validations.inject([1]) { |result, validation| result << validation.level }.uniq.sort
  end
end