/* -*- c++ -*-
 * error.h: define classes used to throw exceptions out of spigot
 * functions.
 */

#include <string>
#include <sstream>
#include <exception>
#include <utility>

using std::string;
using std::ostringstream;
using std::exception;
using std::forward;

class spigot_error_base : public exception {
  protected:
    string errmsg;
    spigot_error_base(string msg);
  public:
    const char *text() const;
};

/*
 * Derive distinguishable classes of error from the above base class.
 */
class spigot_error : public spigot_error_base {
    /*
     * spigot_error is the base class for any fatal error (no matter
     * at parse time, spigot setup time, or during main evaluation).
     *
     * Instead of a single constructor, it has a variadic-template
     * family of them which construct a string in the same style as
     * the dprint mechanism in spigot.h.
     */
  public:
    spigot_error(const spigot_error &) = default;
    spigot_error(spigot_error &) = default;
    spigot_error(spigot_error &&) = default;
    template<typename... Args> spigot_error(Args&&... args);
};

template<typename... Args>
string format_to_string(Args&&... args)
{
    ostringstream oss;
    struct Empty {};
    Empty dummy[] = {
        ((oss) << (forward<Args>(args)), Empty())...
    };
    ignore(dummy);
    return oss.str();
}

template<typename... Args>
spigot_error::spigot_error(Args&&... args)
    : spigot_error_base(format_to_string(forward<Args>(args)...))
{
}

/*
 * Now we define several identical subclasses of spigot_error,
 * differing only in their formal type (so that they can be caught
 * separately if necessary).
 *
 * The breakdown of error categories is:
 *
 *  - config_error: the options used to configure a particular spigot
 *    evaluation were combined in an invalid way (like an out-of-range
 *    digit limit for IEEE output mode, or a digit limit at all for
 *    rational output mode).
 *
 *  - expr_error: a textual expression passed to spigot was invalid in
 *    some way. (Lex errors, parse errors, semantic errors like wrong
 *    parameter count, and a few informal 'type errors' such as the
 *    parameters to algebraic() being complicated subexpressions
 *    instead of rationals.)
 *
 *  - domain_error: a function was evaluated at an input value where
 *    it isn't defined
 *
 *  - io_error: an attempt to open or read a file containing input
 *    numeric data failed, or was prohibited, or encountered bogus
 *    data inside the file.
 */

class domain_error : public spigot_error {
    using spigot_error::spigot_error;
  public:
    domain_error(domain_error &) = default;
    domain_error(domain_error &&) = default;
};

class expr_error : public spigot_error {
    using spigot_error::spigot_error;
  public:
    expr_error(expr_error &) = default;
    expr_error(expr_error &&) = default;
};

class io_error : public spigot_error {
    using spigot_error::spigot_error;
  public:
    io_error(io_error &) = default;
    io_error(io_error &&) = default;
};

class config_error : public spigot_error {
    using spigot_error::spigot_error;
  public:
    config_error(config_error &) = default;
    config_error(config_error &&) = default;
};

class internal_error : public spigot_error {
  public:
    internal_error(const internal_error &) = default;
    internal_error(internal_error &) = default;
    internal_error(internal_error &&) = default;
    template<typename... Args> internal_error(Args&&... args);
};

template<typename... Args>
internal_error::internal_error(Args&&... args)
    : spigot_error("INTERNAL FAULT: ", forward<Args>(args)...) { }

template<typename... Args>
void Debuggable::internal_fault(Args&&... args)
{
    throw internal_error("[", debuggable_id_string(debuggable_object_id),
                         "] ", forward<Args>(args)...);
}

class eof_event : public spigot_error_base {
    /*
     * eof_event is used to indicate that a file we were reading a
     * real number from has ended, and hence we can't generate any
     * further output. Its constructor takes a plain filename string,
     * and handlers for it will cope accordingly.
     *
     * This exception class is _not_ descended from spigot_error,
     * because it isn't an _error_ condition in quite the same sense -
     * you could imagine it having been the intended course of a
     * spigot evaluation that it terminated for this reason. Also,
     * it's unlike spigot_error in that its string field doesn't
     * represent a human-readable error message, but rather the name
     * of the file on which EOF occurred.
     */
  public:
    eof_event(const char *filename);
};

/*
 * This utility spigot function is defined in enforce.cpp, and
 * declared in this header file because it's dependent on having
 * spigot_error defined.
 */
#define ENFORCE_RELATIONS(X) \
    X(ENFORCE_GT, ">"), X(ENFORCE_GE, ">="), \
    X(ENFORCE_LT, "<"), X(ENFORCE_LE, "<=")
#define ENUMERATE(a,b) a
enum EnforceRelation { ENFORCE_RELATIONS(ENUMERATE) };
#undef ENUMERATE
Spigot spigot_enforce(Spigot x, EnforceRelation rel, Spigot bound,
                      domain_error err);
