#ifndef RBX_BUILTIN_EXCEPTION_HPP
#define RBX_BUILTIN_EXCEPTION_HPP

#include "builtin/object.hpp"

namespace rubinius {
  class Class;
  class Array;
  class CompiledCode;

  class Exception : public Object {
  public:
    const static object_type type = ExceptionType;

  private:
    Object* reason_message_;  // slot
    Array* locations_; // slot
    Exception* parent_; // slot
    Object* backtrace_; // slot
    Object* custom_backtrace_; // slot

  public:
    /* accessors */

    attr_accessor(reason_message, Object);
    attr_accessor(locations, Array);
    attr_accessor(parent, Exception);
    attr_accessor(backtrace, Object);
    attr_accessor(custom_backtrace, Object);

    /* interface */

    static void init(STATE);
    static Exception* create(STATE);

    void print_locations(STATE);

    const char* message_c_str(STATE);

    static Exception* make_exception(STATE, Class* exc_class, const char* message);
    static Exception* make_type_error(STATE, object_type type, Object* object,
                                      const char* reason = NULL);
    static Exception* make_errno_exception(STATE, Class* exc_class, Object* reason);

    static Exception* make_argument_error(STATE, int expected, int given, Symbol* name=0);
    static Exception* make_encoding_compatibility_error(STATE, Object* a, Object* b);
    static Exception* make_frozen_exception(STATE, Object* obj);

    NORETURN(static void argument_error(STATE, int expected, int given));
    NORETURN(static void argument_error(STATE, const char* reason));
    NORETURN(static void regexp_error(STATE, const char* reason));
    NORETURN(static void type_error(STATE, const char* reason));
    NORETURN(static void type_error(STATE, object_type type, Object* object,
                           const char* reason = NULL));
    static void type_error(STATE, const char* reason, CallFrame* call_frame);
    NORETURN(static void float_domain_error(STATE, const char* reason = NULL));
    NORETURN(static void zero_division_error(STATE, const char* reason = NULL));
    NORETURN(static void io_error(STATE, const char* reason));
    NORETURN(static void range_error(STATE, const char* reason));
    NORETURN(static void runtime_error(STATE, const char* reason));

    NORETURN(static void assertion_error(STATE, const char* reason = NULL));
    NORETURN(static void object_bounds_exceeded_error(STATE, Object* obj, int index));
    NORETURN(static void object_bounds_exceeded_error(STATE, const char* reason));

    /** Raise a SystemCallError with given message. */
    NORETURN(static void system_call_error(STATE, const char* reason));
    NORETURN(static void system_call_error(STATE, const std::string& reason));

    NORETURN(static void thread_error(STATE, const char* reason));
    NORETURN(static void fiber_error(STATE, const char* reason));
    NORETURN(static void memory_error(STATE));

    static Exception* make_lje(STATE, CallFrame* frame);

    static void internal_error(STATE, CallFrame* frame, const char* reason);
    static void bytecode_error(STATE, CallFrame* frame, CompiledCode* code,
                               int ip, const char* reason);
    static void frozen_error(STATE, Object* obj);
    static void frozen_error(STATE, CallFrame* frame, Object* obj);

    static void encoding_compatibility_error(STATE, Object* a, Object* b);
    static void encoding_compatibility_error(STATE, Object* a, Object* b,
                                             CallFrame* frame);

    // Rubinius.primitive :exception_errno_error
    static Object* errno_error(STATE, Object* reason, Fixnum* ern);

    static void errno_error(STATE, const char* reason = NULL, int ern = 0,
                            const char* entity = 0);
    static void errno_eagain_error(STATE, const char* reason);

    /**
     * Convenience predicates for checking the class of an
     * exception instance. These are provided as a way around
     * creating a bunch more C++ builtin classes and globals
     * and because the exception hierarchy (e.g. FloatDomainError)
     * isn't always consistent.
     */
    static bool argument_error_p(STATE, Exception* exc);
    static bool type_error_p(STATE, Exception* exc);
    static bool zero_division_error_p(STATE, Exception* exc);
    static bool float_domain_error_p(STATE, Exception* exc);
    static bool assertion_error_p(STATE, Exception* exc);
    static bool object_bounds_exceeded_error_p(STATE, Exception* exc);
    static bool errno_error_p(STATE, Exception* exc);
    static bool system_call_error_p(STATE, Exception* exc);
    static bool io_error_p(STATE, Exception* exc);
    static bool runtime_error_p(STATE, Exception* exc);

    static Class* get_argument_error(STATE);
    static Class* get_type_error(STATE);
    static Class* get_zero_division_error(STATE);
    static Class* get_float_domain_error(STATE);
    static Class* get_range_error(STATE);
    static Class* get_assertion_error(STATE);
    static Class* get_object_bounds_exceeded_error(STATE);
    static Class* get_io_error(STATE);
    static Class* get_system_call_error(STATE);
    static Class* get_thread_error(STATE);
    static Class* get_fiber_error(STATE);
    static Class* get_errno_error(STATE, Fixnum* ern);
    static Class* get_runtime_error(STATE);
    static Class* get_encoding_compatibility_error(STATE);

    class Info : public TypeInfo {
    public:
      BASIC_TYPEINFO(TypeInfo)
      virtual void show(STATE, Object* self, int level);
    };

    friend class Info;
  };
};

#endif
