require 'rack/utils'
require 'active_support/core_ext/class/attribute_accessors'
require 'active_support/inflector'
require 'action_pack'
require 'action_controller'

# The HTTPStatus module is the core of the http_status_exceptions gem and
# contains all functionality.
#
# The module contains <tt>HTTPStatus::Base</tt> class, which is used as a
# superclass for every HTTPStatus exception. Subclasses, like
# <tt>HTTPStatus::Forbidden</tt> or <tt>HTTPStatus::NotFound</tt> will be
# generated on demand by the <tt>HTTPStatus.const_missing</tt> method.
module HTTPStatus

  # The current gem release version. Do not set this value by hand, it will
  # be done automatically by them gem release script.
  VERSION = "0.3.0"

  # The Base HTTP status exception class is used as superclass for every
  # exception class that is constructed. It implements some shared
  # functionality for finding the status code.
  #
  # Subclasses of this class will be generated on demand when a non-exisiting
  # constant of the <tt>HTTPStatus</tt> module is requested. This is
  # implemented in the <tt>HTTPStatus.const_missing</tt> method.
  class Base < StandardError

    attr_reader :details

    # Initializes the exception instance.
    # <tt>message</tt>:: The exception message.
    # <tt>details</tt>:: An object with details about the exception.
    def initialize(message = nil, details = nil)
      @details = details
      super(message)
    end

    # Returns the HTTP status symbol corresponding to this class. This is one
    # of the symbols that can be found in the map that can be found in
    # <tt>ActionController::StatusCodes</tt>.
    #
    # This method should be overridden by subclasses, as it returns
    # <tt>:internal_server_error</tt> by default. This is done automatically
    # when a new exception class is being generated by
    # <tt>HTTPStatus.const_missing</tt>.
    def self.status
      :internal_server_error
    end

    # Returns the HTTP status symbol (as defined by Rails) corresponding to
    # this instance. By default, it calls the class method of the same name.
    def status
      self.class.status
    end

    # The numeric status code corresponding to this exception class. Uses the
    # status symbol to code map in <tt>Rack::Utils::SYMBOL_TO_STATUS_CODE</tt>.
    def self.status_code
      Rack::Utils::SYMBOL_TO_STATUS_CODE[self.status]
    end

    # The numeric status code corresponding to this exception. By default, it
    # calls the class method of the same name.
    def status_code
      self.class.status_code
    end
  end

  # Generates a <tt>HTTPStatus::Base</tt> subclass on demand based on the
  # constant name. The constant name should correspond to one of the status
  # symbols defined in <tt>ActionController::StatusCodes</tt>. The function
  # will raise an exception if the constant name cannot be mapped onto one of
  # the status symbols.
  #
  # This method will create a new subclass of <tt>HTTPStatus::Base</tt> and
  # overrides the status class method of the class to return the correct
  # status symbol.
  #
  # <tt>const</tt>:: The name of the missing constant, for which an exception
  # class should be generated.
  def self.const_missing(const)
    status_symbol = const.to_s.underscore.to_sym
    raise "Unrecognized HTTP Status name!" unless Rack::Utils::SYMBOL_TO_STATUS_CODE.has_key?(status_symbol)
    klass = Class.new(HTTPStatus::Base)
    klass.cattr_accessor(:status)
    klass.status = status_symbol
    const_set(const, klass)

    # Inject the newly created exception into ActionController's look up table
    if defined?(ActionPack)
      if ActionPack::VERSION::MAJOR == 2
        ActionController::Base.rescue_responses.update("HTTPStatus::#{const}" => status_symbol) if defined?(ActionController)
      elsif ActionPack::VERSION::MAJOR == 3
        ActionDispatch::ShowExceptions.rescue_responses.update("HTTPStatus::#{const}" => status_symbol) if defined?(ActionDispatch)
      end
    end

    return const_get(const)
  end

end

# Include the HTTPStatus module into <tt>ActionController::Base</tt> to enable
# the <tt>http_status_exception</tt> exception handler.
ActionController::Base.send(:include, HTTPStatus) if defined?(ActionController)
