module Rapporteur
  # The center of the Rapporteur library, Checker manages holding and running
  # the custom checks, holding any application error messages, and provides the
  # controller with that data for rendering.
  #
  class Checker
    extend CheckerDeprecations


    def initialize
      @messages = MessageList.new(:messages)
      @errors = MessageList.new(:errors)
      @check_list = CheckList.new
      reset
    end


    # Public: Add a pre-built or custom check to your status endpoint. These
    # checks are used to test the state of the world of the application, and
    # need only respond to `#call`.
    #
    # Once added, the given check will be called and passed an instance of this
    # checker. If everything is good, do nothing! If there is a problem, use
    # `add_error` to add an error message to the checker.
    #
    # Examples
    #
    #   Rapporteur.add_check { |checker|
    #     checker.add_error(:luck, :bad) if rand(2) == 1
    #   }
    #
    # Returns self.
    # Raises ArgumentError if the given check does not respond to call.
    #
    def add_check(object_or_nil_with_block=nil, &block)
      if block_given?
        @check_list.add(block)
      elsif object_or_nil_with_block.respond_to?(:call)
        @check_list.add(object_or_nil_with_block)
      else
        raise ArgumentError, "A check must respond to #call."
      end
      self
    end

    # Public: Empties all configured checks from the checker. This may be
    # useful for testing and for cases where you might've built up some basic
    # checks but for one reason or another (environment constraint) need to
    # start from scratch.
    #
    # Returns self.
    #
    def clear
      @check_list.clear
      self
    end

    ##
    # Public: Checks can call this method to halt any further processing. This
    # is useful for critical or fatal check failures.
    #
    # For example, if load is too high on a machine you may not want to run any
    # other checks.
    #
    # Returns true.
    #
    def halt!
      @halted = true
    end

    # Public: This is the primary execution point for this class. Use run to
    # exercise the configured checker and collect any application errors or
    # data for rendering.
    #
    # Returns self.
    #
    def run
      reset
      @check_list.each do |object|
        object.call(self)
        break if @halted
      end
      self
    end

    # Public: Add an error message to the checker in order to have it rendered
    # in the status request.
    #
    # It is suggested that you use I18n and locale files for these messages, as
    # is done with the pre-built checks. If you're using I18n, you'll need to
    # define `rapporteur.errors.<your key>.<your message>`.
    #
    # Examples
    #
    #   checker.add_error(:you, "failed.")
    #   checker.add_error(:using, :i18n_key_is_better)
    #
    #   en:
    #     rapporteur:
    #       errors:
    #         using:
    #           i18n_key_is_better: 'Look, localization!'
    #
    # Returns self.
    #
    def add_error(key, message, options={})
      @errors.add(key, message)
      self
    end

    ##
    # Public: Adds a status message for inclusion in the success response.
    #
    # name - A String containing the name or identifier for your message. This
    #        is unique and may be overriden by other checks using the name
    #        message name key.
    # message - A String or Numeric for the value of the message.
    #
    # Examples
    #
    #   checker.add_message(:repository, 'git@github.com/user/repo.git')
    #   checker.add_message(:load, 0.934)
    #
    # Returns self.
    #
    def add_message(name, message)
      @messages.add(name, message)
      self
    end

    ##
    # Internal: Returns a hash of messages suitable for conversion into JSON.
    #
    def as_json(args={})
      @messages.to_hash
    end

    ##
    # Internal: Used by Rails' JSON serialization to render error messages.
    #
    def errors
      @errors
    end

    ##
    # Internal: Used by Rails' JSON serialization, specifically in
    # ActionController::Responder.
    #
    def read_attribute_for_serialization(key)
      @messages[key]
    end

    alias read_attribute_for_validation read_attribute_for_serialization


    private


    def reset
      @halted = false
      @messages.clear
      @errors.clear
    end
  end
end